├── .gitignore ├── images ├── Stash Stats │ └── stats-page.png ├── Stash Batch Query Edit │ ├── config.png │ └── scenes-tagger.png ├── Stash Batch Save │ └── scenes-tagger.png ├── Stash Markdown │ └── tag-description.png ├── Stash StashID Icon │ ├── scene-page.png │ ├── studio-page.png │ └── performer-page.png ├── Stash Batch Result Toggle │ ├── config.png │ └── scenes-tagger.png ├── Stash Batch Search │ └── scenes-tagger.png ├── Stash StashID Input │ ├── studio-page.png │ └── performer-page.png ├── Stash Scene Tagger Additions │ ├── config.png │ └── scenes-tagger.png ├── Stash Scene Tagger Colorizer │ ├── config.png │ ├── tag-colors.png │ └── scenes-tagger.png ├── Stash Markers Autoscroll │ └── scroll-settings.png ├── Stash Open Media Player │ └── system-settings.png ├── Userscript Functions Plugin │ ├── plugin-tasks.png │ └── system-settings.png ├── Stash Performer Markers Tab │ └── performer-page.png ├── Stash Tag Image Cropper │ └── tag-image-cropper.png ├── Stash Performer URL Searchbox │ └── performers-page.png ├── Stash Scene Tagger Draft Submit │ └── scenes-tagger.png ├── Stash Performer Audit Task Button │ ├── plugin-tasks.png │ ├── performers-page.png │ └── system-settings.png ├── Stash New Performer Filter Button │ └── performers-page.png ├── Stash Performer Tagger Additions │ └── performer-tagger.png ├── Stash Set Stashbox Favorite Performers │ ├── plugin-tasks.png │ ├── performers-page.png │ └── system-settings.png └── Stash Performer Image Cropper │ └── performer-image-cropper.png ├── plugins └── userscript_functions │ ├── config.ini │ ├── performer_url_regexes.txt │ ├── log.py │ ├── config_manager.py │ ├── userscript_functions.yml │ ├── audit_performer_urls.py │ ├── userscript_functions.py │ ├── studiodownloader.py │ └── favorite_performers_sync.py ├── config.py ├── src ├── header │ ├── Stash Stats.user.js │ ├── Stash Batch Save.user.js │ ├── Stash Markers Autoscroll.user.js │ ├── Stash Performer Markers Tab.user.js │ ├── Stash Scene Tagger Draft Submit.user.js │ ├── Stash Performer URL Searchbox.user.js │ ├── Stash New Performer Filter Button.user.js │ ├── Stash StashID Icon.user.js │ ├── Stash Batch Query Edit.user.js │ ├── Stash StashID Input.user.js │ ├── Stash Batch Search.user.js │ ├── Stash Batch Result Toggle.user.js │ ├── Stash Markdown.user.js │ ├── Stash Scene Tagger Additions.user.js │ ├── Stash Performer Tagger Additions.user.js │ ├── Stash Performer Audit Task Button.user.js │ ├── Stash Scene Tagger Colorizer.user.js │ ├── Stash Open Media Player.user.js │ ├── Stash Set Stashbox Favorite Performers.user.js │ ├── Stash Tag Image Cropper.user.js │ └── Stash Performer Image Cropper.user.js └── body │ ├── Stash Markdown.user.js │ ├── Stash New Performer Filter Button.user.js │ ├── Stash Performer URL Searchbox.user.js │ ├── Stash Performer Markers Tab.user.js │ ├── Stash Performer Audit Task Button.user.js │ ├── Stash Open Media Player.user.js │ ├── Stash Performer Tagger Additions.user.js │ ├── Stash Scene Tagger Draft Submit.user.js │ ├── Stash StashID Icon.user.js │ ├── Stash Batch Search.user.js │ ├── Stash Tag Image Cropper.user.js │ ├── Stash Performer Image Cropper.user.js │ ├── Stash Set Stashbox Favorite Performers.user.js │ ├── Stash Batch Save.user.js │ ├── Stash Stats.user.js │ ├── Stash Scene Tagger Additions.user.js │ ├── Stash Batch Query Edit.user.js │ └── Stash Markers Autoscroll.user.js └── dist └── public ├── Stash Markdown.user.js ├── Stash New Performer Filter Button.user.js ├── Stash Performer URL Searchbox.user.js ├── Stash Studio Image And Parent On Create.user.js ├── Stash Performer Markers Tab.user.js ├── Stash Performer Audit Task Button.user.js ├── Stash Open Media Player.user.js ├── Stash Userscripts Bundle.user.js ├── Stash Performer Tagger Additions.user.js ├── Stash Scene Tagger Draft Submit.user.js ├── Stash StashID Icon.user.js ├── Stash Batch Search.user.js ├── Stash Set Stashbox Favorite Performers.user.js ├── Stash Batch Save.user.js ├── Stash Tag Image Cropper.user.js ├── Stash Performer Image Cropper.user.js ├── Stash Stats.user.js └── Stash Scene Tagger Additions.user.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist/local 2 | __pycache__ 3 | stash-userscripts.code-workspace -------------------------------------------------------------------------------- /images/Stash Stats/stats-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Stats/stats-page.png -------------------------------------------------------------------------------- /images/Stash Batch Query Edit/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Batch Query Edit/config.png -------------------------------------------------------------------------------- /images/Stash Batch Save/scenes-tagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Batch Save/scenes-tagger.png -------------------------------------------------------------------------------- /images/Stash Markdown/tag-description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Markdown/tag-description.png -------------------------------------------------------------------------------- /images/Stash StashID Icon/scene-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash StashID Icon/scene-page.png -------------------------------------------------------------------------------- /images/Stash StashID Icon/studio-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash StashID Icon/studio-page.png -------------------------------------------------------------------------------- /images/Stash Batch Result Toggle/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Batch Result Toggle/config.png -------------------------------------------------------------------------------- /images/Stash Batch Search/scenes-tagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Batch Search/scenes-tagger.png -------------------------------------------------------------------------------- /images/Stash StashID Input/studio-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash StashID Input/studio-page.png -------------------------------------------------------------------------------- /images/Stash Scene Tagger Additions/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Scene Tagger Additions/config.png -------------------------------------------------------------------------------- /images/Stash Scene Tagger Colorizer/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Scene Tagger Colorizer/config.png -------------------------------------------------------------------------------- /images/Stash StashID Icon/performer-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash StashID Icon/performer-page.png -------------------------------------------------------------------------------- /images/Stash StashID Input/performer-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash StashID Input/performer-page.png -------------------------------------------------------------------------------- /images/Stash Batch Query Edit/scenes-tagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Batch Query Edit/scenes-tagger.png -------------------------------------------------------------------------------- /images/Stash Batch Result Toggle/scenes-tagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Batch Result Toggle/scenes-tagger.png -------------------------------------------------------------------------------- /images/Stash Markers Autoscroll/scroll-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Markers Autoscroll/scroll-settings.png -------------------------------------------------------------------------------- /images/Stash Open Media Player/system-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Open Media Player/system-settings.png -------------------------------------------------------------------------------- /images/Stash Scene Tagger Colorizer/tag-colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Scene Tagger Colorizer/tag-colors.png -------------------------------------------------------------------------------- /images/Userscript Functions Plugin/plugin-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Userscript Functions Plugin/plugin-tasks.png -------------------------------------------------------------------------------- /images/Stash Performer Markers Tab/performer-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Performer Markers Tab/performer-page.png -------------------------------------------------------------------------------- /images/Stash Scene Tagger Additions/scenes-tagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Scene Tagger Additions/scenes-tagger.png -------------------------------------------------------------------------------- /images/Stash Scene Tagger Colorizer/scenes-tagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Scene Tagger Colorizer/scenes-tagger.png -------------------------------------------------------------------------------- /images/Stash Tag Image Cropper/tag-image-cropper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Tag Image Cropper/tag-image-cropper.png -------------------------------------------------------------------------------- /images/Stash Performer URL Searchbox/performers-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Performer URL Searchbox/performers-page.png -------------------------------------------------------------------------------- /images/Stash Scene Tagger Draft Submit/scenes-tagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Scene Tagger Draft Submit/scenes-tagger.png -------------------------------------------------------------------------------- /images/Userscript Functions Plugin/system-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Userscript Functions Plugin/system-settings.png -------------------------------------------------------------------------------- /images/Stash Performer Audit Task Button/plugin-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Performer Audit Task Button/plugin-tasks.png -------------------------------------------------------------------------------- /plugins/userscript_functions/config.ini: -------------------------------------------------------------------------------- 1 | [STASH] 2 | url = http://localhost:9999 3 | api_key = 4 | 5 | [MEDIAPLAYER] 6 | path = C:/Program Files/VideoLAN/VLC/vlc.exe 7 | 8 | -------------------------------------------------------------------------------- /images/Stash New Performer Filter Button/performers-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash New Performer Filter Button/performers-page.png -------------------------------------------------------------------------------- /images/Stash Performer Audit Task Button/performers-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Performer Audit Task Button/performers-page.png -------------------------------------------------------------------------------- /images/Stash Performer Audit Task Button/system-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Performer Audit Task Button/system-settings.png -------------------------------------------------------------------------------- /images/Stash Performer Tagger Additions/performer-tagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Performer Tagger Additions/performer-tagger.png -------------------------------------------------------------------------------- /images/Stash Set Stashbox Favorite Performers/plugin-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Set Stashbox Favorite Performers/plugin-tasks.png -------------------------------------------------------------------------------- /images/Stash Performer Image Cropper/performer-image-cropper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Performer Image Cropper/performer-image-cropper.png -------------------------------------------------------------------------------- /images/Stash Set Stashbox Favorite Performers/performers-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Set Stashbox Favorite Performers/performers-page.png -------------------------------------------------------------------------------- /images/Stash Set Stashbox Favorite Performers/system-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/HEAD/images/Stash Set Stashbox Favorite Performers/system-settings.png -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | GITHUB_ROOT_URL = r"https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/%%BRANCH%%/" 2 | BUNDLE_VERSION = "0.24.2" 3 | SERVER_URL = "http://localhost:9999" 4 | NAMESPACE = "https://github.com/7dJx1qP/stash-userscripts" -------------------------------------------------------------------------------- /src/header/Stash Stats.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Stats 3 | // @namespace %NAMESPACE% 4 | // @description Add stats to stats page 5 | // @version 0.3.1 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @require %LIBRARYPATH% 10 | // @require %FILEPATH% 11 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Batch Save.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Batch Save 3 | // @namespace %NAMESPACE% 4 | // @description Adds a batch save button to scenes tagger 5 | // @version 0.5.3 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @require %LIBRARYPATH% 10 | // @require %FILEPATH% 11 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Markers Autoscroll.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Markers Autoscroll 3 | // @namespace %NAMESPACE% 4 | // @description Automatically scrolls markers page 5 | // @version 0.1.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @require %LIBRARYPATH% 10 | // @require %FILEPATH% 11 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Performer Markers Tab.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Performer Markers Tab 3 | // @namespace %NAMESPACE% 4 | // @description Adds a Markers link to performer pages 5 | // @version 0.1.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @require %LIBRARYPATH% 10 | // @require %FILEPATH% 11 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Scene Tagger Draft Submit.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Scene Tagger Draft Submit 3 | // @namespace %NAMESPACE% 4 | // @description Adds button to Scene Tagger to submit draft to stashdb 5 | // @version 0.1.1 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @require %LIBRARYPATH% 10 | // @require %FILEPATH% 11 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Performer URL Searchbox.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Performer URL Searchbox 3 | // @namespace %NAMESPACE% 4 | // @description Adds a search by performer url textbox to the performers page 5 | // @version 0.2.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @require %LIBRARYPATH% 10 | // @require %FILEPATH% 11 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash New Performer Filter Button.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash New Performer Filter Button 3 | // @namespace %NAMESPACE% 4 | // @description Adds a button to the performers page to switch to a new performers filter 5 | // @version 0.3.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @require %LIBRARYPATH% 10 | // @require %FILEPATH% 11 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash StashID Icon.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash StashID Icon 3 | // @namespace %NAMESPACE% 4 | // @description Adds checkmark icon to performer and studio cards that have a stashid 5 | // @version 0.2.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @grant GM_addStyle 10 | // @require %LIBRARYPATH% 11 | // @require %FILEPATH% 12 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Batch Query Edit.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Batch Query Edit 3 | // @namespace %NAMESPACE% 4 | // @description Batch modify scene tagger search query 5 | // @version 0.6.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @grant GM.getValue 10 | // @grant GM.setValue 11 | // @require %LIBRARYPATH% 12 | // @require %FILEPATH% 13 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash StashID Input.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash StashID Input 3 | // @namespace %NAMESPACE% 4 | // @description Adds input for entering new stash id to performer details page and studio page 5 | // @version 0.5.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @grant GM_setClipboard 10 | // @require %LIBRARYPATH% 11 | // @require %FILEPATH% 12 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Batch Search.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Batch Search 3 | // @namespace %NAMESPACE% 4 | // @description Adds a batch search button to scenes and performers tagger 5 | // @version 0.4.2 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @grant GM.getValue 10 | // @grant GM.setValue 11 | // @require %LIBRARYPATH% 12 | // @require %FILEPATH% 13 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Batch Result Toggle.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Batch Result Toggle 3 | // @namespace %NAMESPACE% 4 | // @description Batch toggle scene tagger search result fields 5 | // @version 0.6.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @grant GM.getValue 10 | // @grant GM.setValue 11 | // @require %LIBRARYPATH% 12 | // @require %FILEPATH% 13 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Markdown.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Markdown 3 | // @namespace %NAMESPACE% 4 | // @description Adds markdown parsing to tag description fields 5 | // @version 0.2.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @require %LIBRARYPATH% 10 | // @require https://cdnjs.cloudflare.com/ajax/libs/marked/4.2.2/marked.min.js 11 | // @require %FILEPATH% 12 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Scene Tagger Additions.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Scene Tagger Additions 3 | // @namespace %NAMESPACE% 4 | // @description Adds scene duration and filepath to tagger view. 5 | // @version 0.3.1 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @grant GM.getValue 10 | // @grant GM.setValue 11 | // @require %LIBRARYPATH% 12 | // @require %FILEPATH% 13 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Performer Tagger Additions.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Performer Tagger Additions 3 | // @namespace %NAMESPACE% 4 | // @description Adds performer birthdate and url to tagger view. Makes clicking performer name open stash profile in new tab instead of current tab. 5 | // @version 0.2.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @require %LIBRARYPATH% 10 | // @require %FILEPATH% 11 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Performer Audit Task Button.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Performer Audit Task Button 3 | // @namespace %NAMESPACE% 4 | // @description Adds a button to the performers page to run the audit plugin task 5 | // @version 0.3.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @grant GM.getValue 10 | // @grant GM.setValue 11 | // @require %LIBRARYPATH% 12 | // @require %FILEPATH% 13 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Scene Tagger Colorizer.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Scene Tagger Colorizer 3 | // @namespace %NAMESPACE% 4 | // @description Colorize scene tagger match results to show matching and mismatching scene data. 5 | // @version 0.7.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @grant GM.getValue 10 | // @grant GM.setValue 11 | // @require %LIBRARYPATH% 12 | // @require %FILEPATH% 13 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Open Media Player.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Open Media Player 3 | // @namespace %NAMESPACE% 4 | // @description Open scene filepath links in an external media player. Requires userscript_functions stash plugin 5 | // @version 0.2.1 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @grant GM.getValue 10 | // @grant GM.setValue 11 | // @require %LIBRARYPATH% 12 | // @require %FILEPATH% 13 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Set Stashbox Favorite Performers.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Set Stashbox Favorite Performers 3 | // @namespace %NAMESPACE% 4 | // @description Set Stashbox favorite performers according to stash favorites. Requires userscript_functions stash plugin 5 | // @version 0.3.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @grant unsafeWindow 9 | // @grant GM.getValue 10 | // @grant GM.setValue 11 | // @require %LIBRARYPATH% 12 | // @require %FILEPATH% 13 | // ==/UserScript== -------------------------------------------------------------------------------- /plugins/userscript_functions/performer_url_regexes.txt: -------------------------------------------------------------------------------- 1 | (babepedia).*?\/babe\/(.*?)$ 2 | (bgafd).*?\/(?:gallery|details)\.php\/id\/(.*?)(?:\/.*?)?$ 3 | (boobpedia).*?\/boobs\/(.*?)$ 4 | (egafd).*?\/(?:gallery|details)\.php\/id\/(.*?)(?:\/.*?)?$ 5 | (eurobabeindex).*?\/sbandoindex\/(.*?)\.html$ 6 | (freeones).*?\/(.*?)(?:\/.*?)?$ 7 | (iafd).*?\/perfid=(.*?\/gender=.)\/ 8 | (imdb).*?\/(?:name|title)\/(.*?)(?:\/.*?)?$ 9 | (indexxx).*?\/m\/(.*?)$ 10 | (instagram).*?\/(.*?)\/?$ 11 | (onlyfans).*?\/(.*?)\/?$ 12 | (pornhub).*?\/(?:model|pornstar)\/(.*?)(?:\/.*?)?$ 13 | (pornteengirl).*?\/model\/(.*?)\.html$ 14 | (thenude).*?\/(?:.*?)_(.*?)\.html?$ 15 | (twitter).*?\/(.*?)\/?$ -------------------------------------------------------------------------------- /src/header/Stash Tag Image Cropper.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Tag Image Cropper 3 | // @namespace %NAMESPACE% 4 | // @description Adds an image cropper to tag page 5 | // @version 0.2.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @resource IMPORTED_CSS https://raw.githubusercontent.com/fengyuanchen/cropperjs/main/dist/cropper.min.css 9 | // @grant unsafeWindow 10 | // @grant GM_getResourceText 11 | // @grant GM_addStyle 12 | // @require %LIBRARYPATH% 13 | // @require https://raw.githubusercontent.com/fengyuanchen/cropperjs/main/dist/cropper.min.js 14 | // @require %FILEPATH% 15 | // ==/UserScript== -------------------------------------------------------------------------------- /src/header/Stash Performer Image Cropper.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Performer Image Cropper 3 | // @namespace %NAMESPACE% 4 | // @description Adds an image cropper to performer page 5 | // @version 0.3.0 6 | // @author 7dJx1qP 7 | // @match %MATCHURL% 8 | // @resource IMPORTED_CSS https://raw.githubusercontent.com/fengyuanchen/cropperjs/main/dist/cropper.min.css 9 | // @grant unsafeWindow 10 | // @grant GM_getResourceText 11 | // @grant GM_addStyle 12 | // @require %LIBRARYPATH% 13 | // @require https://raw.githubusercontent.com/fengyuanchen/cropperjs/main/dist/cropper.min.js 14 | // @require %FILEPATH% 15 | // ==/UserScript== -------------------------------------------------------------------------------- /plugins/userscript_functions/log.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | # Log messages sent from a script scraper instance are transmitted via stderr and are 4 | # encoded with a prefix consisting of special character SOH, then the log 5 | # level (one of t, d, i, w or e - corresponding to trace, debug, info, 6 | # warning and error levels respectively), then special character 7 | # STX. 8 | # 9 | # The log.trace, log.debug, log.info, log.warning, and log.error methods, and their equivalent 10 | # formatted methods are intended for use by script scraper instances to transmit log 11 | # messages. 12 | # 13 | 14 | def __log(level_char: bytes, s): 15 | if level_char: 16 | lvl_char = "\x01{}\x02".format(level_char.decode()) 17 | s = re.sub(r"data:image.+?;base64(.+?')","[...]",str(s)) 18 | for x in s.split("\n"): 19 | print(lvl_char, x, file=sys.stderr, flush=True) 20 | 21 | 22 | def trace(s): 23 | __log(b't', s) 24 | 25 | 26 | def debug(s): 27 | __log(b'd', s) 28 | 29 | 30 | def info(s): 31 | __log(b'i', s) 32 | 33 | 34 | def warning(s): 35 | __log(b'w', s) 36 | 37 | 38 | def error(s): 39 | __log(b'e', s) -------------------------------------------------------------------------------- /src/body/Stash Markdown.user.js: -------------------------------------------------------------------------------- 1 | /* global marked */ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | const { 7 | stash, 8 | Stash, 9 | waitForElementId, 10 | waitForElementClass, 11 | waitForElementByXpath, 12 | getElementByXpath, 13 | insertAfter, 14 | reloadImg, 15 | } = unsafeWindow.stash; 16 | 17 | function processMarkdown(el) { 18 | el.innerHTML = marked.parse(el.innerHTML); 19 | } 20 | 21 | stash.addEventListener('page:tag:any', function () { 22 | waitForElementByXpath("//span[contains(@class, 'detail-item-value') and contains(@class, 'description')]", function (xpath, el) { 23 | el.style.display = 'block'; 24 | el.style.whiteSpace = 'initial'; 25 | processMarkdown(el); 26 | }); 27 | }); 28 | 29 | stash.addEventListener('page:tags', function () { 30 | waitForElementByXpath("//div[contains(@class, 'tag-description')]", function (xpath, el) { 31 | for (const node of document.querySelectorAll('.tag-description')) { 32 | processMarkdown(node); 33 | } 34 | }); 35 | }); 36 | })(); -------------------------------------------------------------------------------- /plugins/userscript_functions/config_manager.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import os 3 | from configparser import ConfigParser 4 | 5 | def init_config(configpath): 6 | config_object = ConfigParser() 7 | 8 | config_object["STASH"] = { 9 | "url": "http://localhost:9999", 10 | "api_key": "" 11 | } 12 | 13 | config_object["MEDIAPLAYER"] = { 14 | "path": "C:/Program Files/VideoLAN/VLC/vlc.exe" 15 | } 16 | 17 | #Write the above sections to config.ini file 18 | with open(configpath, 'w') as conf: 19 | config_object.write(conf) 20 | 21 | def get_config_value(configpath, section_key, prop_name): 22 | config_object = ConfigParser() 23 | config_object.read(configpath) 24 | 25 | return config_object[section_key][prop_name] 26 | 27 | def update_config_value(configpath, section_key, prop_name, new_value): 28 | config_object = ConfigParser() 29 | config_object.read(configpath) 30 | 31 | config_object[section_key][prop_name] = new_value 32 | 33 | with open(configpath, 'w') as conf: 34 | config_object.write(conf) 35 | 36 | if __name__ == "__main__": 37 | init_config(os.path.join(pathlib.Path(__file__).parent.resolve(), 'config.ini')) -------------------------------------------------------------------------------- /src/body/Stash New Performer Filter Button.user.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | const { 5 | stash, 6 | Stash, 7 | waitForElementId, 8 | waitForElementClass, 9 | waitForElementByXpath, 10 | getElementByXpath, 11 | } = unsafeWindow.stash; 12 | 13 | stash.addEventListener('page:performers', function () { 14 | waitForElementClass("btn-toolbar", function () { 15 | if (!document.getElementById('new-performer-filter')) { 16 | const toolbar = document.querySelector(".btn-toolbar"); 17 | 18 | const newGroup = document.createElement('div'); 19 | newGroup.classList.add('mx-2', 'mb-2', 'd-flex'); 20 | toolbar.appendChild(newGroup); 21 | 22 | const newButton = document.createElement("a"); 23 | newButton.setAttribute("id", "new-performer-filter"); 24 | newButton.classList.add('btn', 'btn-secondary'); 25 | newButton.innerHTML = 'New Performers'; 26 | newButton.href = `${stash.serverUrl}/performers?disp=3&sortby=created_at&sortdir=desc`; 27 | newGroup.appendChild(newButton); 28 | } 29 | }); 30 | }); 31 | })(); -------------------------------------------------------------------------------- /dist/public/Stash Markdown.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Markdown 3 | // @namespace https://github.com/7dJx1qP/stash-userscripts 4 | // @description Adds markdown parsing to tag description fields 5 | // @version 0.2.0 6 | // @author 7dJx1qP 7 | // @match http://localhost:9999/* 8 | // @grant unsafeWindow 9 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js 10 | // @require https://cdnjs.cloudflare.com/ajax/libs/marked/4.2.2/marked.min.js 11 | // ==/UserScript== 12 | 13 | /* global marked */ 14 | 15 | (function () { 16 | 'use strict'; 17 | 18 | const { 19 | stash, 20 | Stash, 21 | waitForElementId, 22 | waitForElementClass, 23 | waitForElementByXpath, 24 | getElementByXpath, 25 | insertAfter, 26 | reloadImg, 27 | } = unsafeWindow.stash; 28 | 29 | function processMarkdown(el) { 30 | el.innerHTML = marked.parse(el.innerHTML); 31 | } 32 | 33 | stash.addEventListener('page:tag:any', function () { 34 | waitForElementByXpath("//span[contains(@class, 'detail-item-value') and contains(@class, 'description')]", function (xpath, el) { 35 | el.style.display = 'block'; 36 | el.style.whiteSpace = 'initial'; 37 | processMarkdown(el); 38 | }); 39 | }); 40 | 41 | stash.addEventListener('page:tags', function () { 42 | waitForElementByXpath("//div[contains(@class, 'tag-description')]", function (xpath, el) { 43 | for (const node of document.querySelectorAll('.tag-description')) { 44 | processMarkdown(node); 45 | } 46 | }); 47 | }); 48 | })(); -------------------------------------------------------------------------------- /src/body/Stash Performer URL Searchbox.user.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | const { 5 | stash, 6 | Stash, 7 | waitForElementId, 8 | waitForElementClass, 9 | waitForElementByXpath, 10 | getElementByXpath, 11 | } = unsafeWindow.stash; 12 | 13 | stash.addEventListener('page:performers', function () { 14 | waitForElementClass("btn-toolbar", function () { 15 | if (!document.getElementById('performer-url-search-input')) { 16 | const toolbar = document.querySelector(".btn-toolbar"); 17 | 18 | const newGroup = document.createElement('div'); 19 | newGroup.classList.add('mx-2', 'mb-2', 'd-flex'); 20 | toolbar.appendChild(newGroup); 21 | 22 | const perfUrlGroup = document.createElement('div'); 23 | perfUrlGroup.classList.add('flex-grow-1', 'query-text-field-group'); 24 | newGroup.appendChild(perfUrlGroup); 25 | 26 | const perfUrlTextbox = document.createElement('input'); 27 | perfUrlTextbox.setAttribute('id', 'performer-url-search-input'); 28 | perfUrlTextbox.classList.add('query-text-field', 'bg-secondary', 'text-white', 'border-secondary', 'form-control'); 29 | perfUrlTextbox.setAttribute('placeholder', 'URL…'); 30 | perfUrlTextbox.addEventListener('change', () => { 31 | const url = `${window.location.origin}/performers?c={"type":"url","value":"${perfUrlTextbox.value}","modifier":"EQUALS"}` 32 | window.location = url; 33 | }); 34 | perfUrlGroup.appendChild(perfUrlTextbox); 35 | } 36 | }); 37 | }); 38 | })(); -------------------------------------------------------------------------------- /dist/public/Stash New Performer Filter Button.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash New Performer Filter Button 3 | // @namespace https://github.com/7dJx1qP/stash-userscripts 4 | // @description Adds a button to the performers page to switch to a new performers filter 5 | // @version 0.3.0 6 | // @author 7dJx1qP 7 | // @match http://localhost:9999/* 8 | // @grant unsafeWindow 9 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js 10 | // ==/UserScript== 11 | 12 | (function () { 13 | 'use strict'; 14 | 15 | const { 16 | stash, 17 | Stash, 18 | waitForElementId, 19 | waitForElementClass, 20 | waitForElementByXpath, 21 | getElementByXpath, 22 | } = unsafeWindow.stash; 23 | 24 | stash.addEventListener('page:performers', function () { 25 | waitForElementClass("btn-toolbar", function () { 26 | if (!document.getElementById('new-performer-filter')) { 27 | const toolbar = document.querySelector(".btn-toolbar"); 28 | 29 | const newGroup = document.createElement('div'); 30 | newGroup.classList.add('mx-2', 'mb-2', 'd-flex'); 31 | toolbar.appendChild(newGroup); 32 | 33 | const newButton = document.createElement("a"); 34 | newButton.setAttribute("id", "new-performer-filter"); 35 | newButton.classList.add('btn', 'btn-secondary'); 36 | newButton.innerHTML = 'New Performers'; 37 | newButton.href = `${stash.serverUrl}/performers?disp=3&sortby=created_at&sortdir=desc`; 38 | newGroup.appendChild(newButton); 39 | } 40 | }); 41 | }); 42 | })(); -------------------------------------------------------------------------------- /plugins/userscript_functions/userscript_functions.yml: -------------------------------------------------------------------------------- 1 | name: Userscript Functions 2 | description: Tasks for userscripts 3 | url: https://github.com/7dJx1qP/stash-userscripts 4 | version: 0.6.0 5 | exec: 6 | - python 7 | - "{pluginDir}/userscript_functions.py" 8 | interface: raw 9 | tasks: 10 | - name: Open in File Explorer 11 | description: Open folder 12 | defaultArgs: 13 | name: explorer 14 | path: null 15 | - name: Open in Media Player 16 | description: Open video 17 | defaultArgs: 18 | name: mediaplayer 19 | path: null 20 | - name: Update Studio 21 | description: Update studio 22 | defaultArgs: 23 | name: update_studio 24 | studio_id: null 25 | endpoint: null 26 | remote_site_id: null 27 | - name: Audit performer urls 28 | description: Audit performer IAFD urls for dupes 29 | defaultArgs: 30 | name: audit_performer_urls 31 | - name: Update Config Value 32 | description: Update value in config.ini 33 | defaultArgs: 34 | name: update_config_value 35 | section_key: null 36 | prop_name: null 37 | value: null 38 | - name: Get Config Value 39 | description: Get value in config.ini 40 | defaultArgs: 41 | name: get_config_value 42 | section_key: null 43 | prop_name: null 44 | - name: Set Stashbox Favorite Performers 45 | description: Set Stashbox favorite performers according to stash favorites 46 | defaultArgs: 47 | name: favorite_performers_sync 48 | endpoint: null 49 | - name: Set Stashbox Favorite Performer 50 | description: Update Stashbox performer favorite status 51 | defaultArgs: 52 | name: favorite_performer_sync 53 | endpoint: null 54 | stash_id: null 55 | favorite: null -------------------------------------------------------------------------------- /dist/public/Stash Performer URL Searchbox.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Performer URL Searchbox 3 | // @namespace https://github.com/7dJx1qP/stash-userscripts 4 | // @description Adds a search by performer url textbox to the performers page 5 | // @version 0.2.0 6 | // @author 7dJx1qP 7 | // @match http://localhost:9999/* 8 | // @grant unsafeWindow 9 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js 10 | // ==/UserScript== 11 | 12 | (function () { 13 | 'use strict'; 14 | 15 | const { 16 | stash, 17 | Stash, 18 | waitForElementId, 19 | waitForElementClass, 20 | waitForElementByXpath, 21 | getElementByXpath, 22 | } = unsafeWindow.stash; 23 | 24 | stash.addEventListener('page:performers', function () { 25 | waitForElementClass("btn-toolbar", function () { 26 | if (!document.getElementById('performer-url-search-input')) { 27 | const toolbar = document.querySelector(".btn-toolbar"); 28 | 29 | const newGroup = document.createElement('div'); 30 | newGroup.classList.add('mx-2', 'mb-2', 'd-flex'); 31 | toolbar.appendChild(newGroup); 32 | 33 | const perfUrlGroup = document.createElement('div'); 34 | perfUrlGroup.classList.add('flex-grow-1', 'query-text-field-group'); 35 | newGroup.appendChild(perfUrlGroup); 36 | 37 | const perfUrlTextbox = document.createElement('input'); 38 | perfUrlTextbox.setAttribute('id', 'performer-url-search-input'); 39 | perfUrlTextbox.classList.add('query-text-field', 'bg-secondary', 'text-white', 'border-secondary', 'form-control'); 40 | perfUrlTextbox.setAttribute('placeholder', 'URL…'); 41 | perfUrlTextbox.addEventListener('change', () => { 42 | const url = `${window.location.origin}/performers?c={"type":"url","value":"${perfUrlTextbox.value}","modifier":"EQUALS"}` 43 | window.location = url; 44 | }); 45 | perfUrlGroup.appendChild(perfUrlTextbox); 46 | } 47 | }); 48 | }); 49 | })(); -------------------------------------------------------------------------------- /plugins/userscript_functions/audit_performer_urls.py: -------------------------------------------------------------------------------- 1 | import log 2 | import os 3 | import pathlib 4 | import re 5 | import sys 6 | from urllib.parse import unquote 7 | try: 8 | from stashlib.stash_database import StashDatabase 9 | from stashlib.stash_models import PerformersRow 10 | except ModuleNotFoundError: 11 | print("If you have pip (normally installed with python), run this command in a terminal (cmd): pip install pystashlib)", file=sys.stderr) 12 | sys.exit() 13 | 14 | def to_iafd_fragment(url): 15 | performer_prefix = 'https://www.iafd.com/person.rme/perfid=' 16 | decoded_url = unquote(url) 17 | fragment = decoded_url.removeprefix(performer_prefix) 18 | return '/'.join(fragment.split('/')[:-1]) 19 | 20 | def audit_performer_urls(db: StashDatabase): 21 | """Check for valid iafd url format and duplicate urls""" 22 | 23 | regexpath = os.path.join(pathlib.Path(__file__).parent.resolve(), 'performer_url_regexes.txt') 24 | patterns = [re.compile(s.strip()) for s in open(regexpath, 'r').readlines()] 25 | 26 | rows = db.fetchall("""SELECT * FROM performers WHERE url IS NOT NULL AND url <> ''""") 27 | performers = [PerformersRow().from_sqliterow(row) for row in rows] 28 | log.info(f'Checking {str(len(rows))} performers with urls...') 29 | site_performer_fragments = {} 30 | for performer in performers: 31 | if 'iafd.com' in performer.url and not performer.url.startswith('https://www.iafd.com/person.rme/perfid='): 32 | log.info(f'malformed url {performer.id} {performer.name} {performer.url}') 33 | site_id = 'OTHER' 34 | url = performer.url.lower().strip() 35 | performer_id = url 36 | for pattern in patterns: 37 | m = pattern.search(url) 38 | if m: 39 | site_id = m.group(1) 40 | performer_id = m.group(2) 41 | break 42 | if site_id not in site_performer_fragments: 43 | site_performer_fragments[site_id] = {} 44 | if performer_id not in site_performer_fragments[site_id]: 45 | site_performer_fragments[site_id][performer_id] = performer 46 | else: 47 | log.info(f'Duplicate performer url: {performer.id} {performer.name}, {site_performer_fragments[site_id][performer_id].id} {site_performer_fragments[site_id][performer_id].name}') 48 | log.info('Done.') -------------------------------------------------------------------------------- /dist/public/Stash Studio Image And Parent On Create.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Studio Image And Parent On Create 3 | // @namespace https://github.com/7dJx1qP/stash-userscripts 4 | // @description Set studio image and parent when creating from StashDB. Requires userscript_functions stash plugin 5 | // @version 0.3.0 6 | // @author 7dJx1qP 7 | // @match http://localhost:9999/* 8 | // @grant unsafeWindow 9 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/develop/src\StashUserscriptLibrary.js 10 | // ==/UserScript== 11 | 12 | (function() { 13 | 'use strict'; 14 | 15 | const { 16 | stash, 17 | Stash, 18 | waitForElementId, 19 | waitForElementClass, 20 | waitForElementByXpath, 21 | getElementByXpath, 22 | getClosestAncestor, 23 | updateTextInput, 24 | } = unsafeWindow.stash; 25 | 26 | stash.userscripts.push('Stash Studio Image And Parent On Create'); 27 | 28 | async function runStudioUpdateTask(studioId, endpoint, remoteSiteId) { 29 | return stash.runPluginTask("userscript_functions", "Update Studio", [{"key":"studio_id", "value":{"str": studioId}}, {"key":"endpoint", "value":{"str": endpoint}}, {"key":"remote_site_id", "value":{"str": remoteSiteId}}]); 30 | } 31 | 32 | stash.addEventListener('stash:response', function (evt) { 33 | const data = evt.detail; 34 | if (data.data?.studioCreate) { 35 | const studioId = data.data?.studioCreate.id; 36 | const endpoint = data.data?.studioCreate.stash_ids[0].endpoint; 37 | const remoteSiteId = data.data?.studioCreate.stash_ids[0].stash_id; 38 | runStudioUpdateTask(studioId, endpoint, remoteSiteId); 39 | } 40 | }); 41 | 42 | stash.addEventListener('userscript_functions:update_studio', async function (evt) { 43 | const { studioId, endpoint, remoteSiteId, callback, errCallback } = evt.detail; 44 | await runStudioUpdateTask(studioId, endpoint, remoteSiteId); 45 | const prefix = `[Plugin / Userscript Functions] update_studio: Done.`; 46 | try { 47 | await this.pollLogsForMessage(prefix); 48 | if (callback) callback(); 49 | } 50 | catch (err) { 51 | if (errCallback) errCallback(err); 52 | } 53 | }); 54 | 55 | })(); -------------------------------------------------------------------------------- /src/body/Stash Performer Markers Tab.user.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | const { 5 | stash, 6 | Stash, 7 | waitForElementId, 8 | waitForElementClass, 9 | waitForElementByXpath, 10 | getElementByXpath, 11 | createElementFromHTML, 12 | } = unsafeWindow.stash; 13 | 14 | async function getPerformerMarkersCount(performerId) { 15 | const reqData = { 16 | "operationName": "FindSceneMarkers", 17 | "variables": { 18 | "scene_marker_filter": { 19 | "performers": { 20 | "value": [ 21 | performerId 22 | ], 23 | "modifier": "INCLUDES_ALL" 24 | } 25 | } 26 | }, 27 | "query": `query FindSceneMarkers($filter: FindFilterType, $scene_marker_filter: SceneMarkerFilterType) { 28 | findSceneMarkers(filter: $filter, scene_marker_filter: $scene_marker_filter) { 29 | count 30 | } 31 | }` 32 | } 33 | return stash.callGQL(reqData); 34 | } 35 | 36 | const markersTabId = 'performer-details-tab-markers'; 37 | 38 | stash.addEventListener('page:performer:details', function () { 39 | waitForElementClass("nav-tabs", async function (className, el) { 40 | const navTabs = el.item(0); 41 | if (!document.getElementById(markersTabId)) { 42 | const performerId = window.location.pathname.replace('/performers/', ''); 43 | const markersCount = (await getPerformerMarkersCount(performerId)).data.findSceneMarkers.count; 44 | const markerTab = createElementFromHTML(`Markers${markersCount}`) 45 | navTabs.appendChild(markerTab); 46 | const performerName = document.querySelector('.performer-head h2').innerText; 47 | const markersUrl = `${window.location.origin}/scenes/markers?c=${JSON.stringify({"type":"performers","value":[{"id":performerId,"label":performerName}],"modifier":"INCLUDES_ALL"})}` 48 | markerTab.href = markersUrl; 49 | } 50 | }); 51 | }); 52 | })(); -------------------------------------------------------------------------------- /src/body/Stash Performer Audit Task Button.user.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | const { 5 | stash, 6 | Stash, 7 | waitForElementId, 8 | waitForElementClass, 9 | waitForElementByXpath, 10 | getElementByXpath, 11 | } = unsafeWindow.stash; 12 | 13 | stash.visiblePluginTasks.push('Audit performer urls'); 14 | 15 | const settingsId = 'userscript-settings-audit-task'; 16 | const inputId = 'userscript-settings-audit-task-button-visible'; 17 | 18 | stash.addEventListener('page:performers', function () { 19 | waitForElementClass("btn-toolbar", async () => { 20 | if (!document.getElementById('audit-task')) { 21 | const toolbar = document.querySelector(".btn-toolbar"); 22 | 23 | const newGroup = document.createElement('div'); 24 | newGroup.classList.add('mx-2', 'mb-2', await GM.getValue(inputId, false) ? 'd-flex' : 'd-none'); 25 | toolbar.appendChild(newGroup); 26 | 27 | const auditButton = document.createElement("button"); 28 | auditButton.setAttribute("id", "audit-task"); 29 | auditButton.classList.add('btn', 'btn-secondary'); 30 | auditButton.innerHTML = 'Audit URLs'; 31 | auditButton.onclick = () => { 32 | stash.runPluginTask("userscript_functions", "Audit performer urls"); 33 | }; 34 | newGroup.appendChild(auditButton); 35 | } 36 | }); 37 | }); 38 | 39 | stash.addSystemSetting(async (elementId, el) => { 40 | if (document.getElementById(inputId)) return; 41 | const settingsHeader = 'Show Audit Performer URLs Button'; 42 | const settingsSubheader = 'Display audit performer urls button on performers page.'; 43 | const checkbox = await stash.createSystemSettingCheckbox(el, settingsId, inputId, settingsHeader, settingsSubheader); 44 | checkbox.checked = await GM.getValue(inputId, false); 45 | checkbox.addEventListener('change', async () => { 46 | const value = checkbox.checked; 47 | await GM.setValue(inputId, value); 48 | }); 49 | }); 50 | 51 | stash.addEventListener('stash:pluginVersion', async function () { 52 | waitForElementId(settingsId, async (elementId, el) => { 53 | el.style.display = stash.pluginVersion != null ? 'flex' : 'none'; 54 | }); 55 | }); 56 | })(); -------------------------------------------------------------------------------- /dist/public/Stash Performer Markers Tab.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Performer Markers Tab 3 | // @namespace https://github.com/7dJx1qP/stash-userscripts 4 | // @description Adds a Markers link to performer pages 5 | // @version 0.1.0 6 | // @author 7dJx1qP 7 | // @match http://localhost:9999/* 8 | // @grant unsafeWindow 9 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js 10 | // ==/UserScript== 11 | 12 | (function () { 13 | 'use strict'; 14 | 15 | const { 16 | stash, 17 | Stash, 18 | waitForElementId, 19 | waitForElementClass, 20 | waitForElementByXpath, 21 | getElementByXpath, 22 | createElementFromHTML, 23 | } = unsafeWindow.stash; 24 | 25 | async function getPerformerMarkersCount(performerId) { 26 | const reqData = { 27 | "operationName": "FindSceneMarkers", 28 | "variables": { 29 | "scene_marker_filter": { 30 | "performers": { 31 | "value": [ 32 | performerId 33 | ], 34 | "modifier": "INCLUDES_ALL" 35 | } 36 | } 37 | }, 38 | "query": `query FindSceneMarkers($filter: FindFilterType, $scene_marker_filter: SceneMarkerFilterType) { 39 | findSceneMarkers(filter: $filter, scene_marker_filter: $scene_marker_filter) { 40 | count 41 | } 42 | }` 43 | } 44 | return stash.callGQL(reqData); 45 | } 46 | 47 | const markersTabId = 'performer-details-tab-markers'; 48 | 49 | stash.addEventListener('page:performer:details', function () { 50 | waitForElementClass("nav-tabs", async function (className, el) { 51 | const navTabs = el.item(0); 52 | if (!document.getElementById(markersTabId)) { 53 | const performerId = window.location.pathname.replace('/performers/', ''); 54 | const markersCount = (await getPerformerMarkersCount(performerId)).data.findSceneMarkers.count; 55 | const markerTab = createElementFromHTML(`Markers${markersCount}`) 56 | navTabs.appendChild(markerTab); 57 | const performerName = document.querySelector('.performer-head h2').innerText; 58 | const markersUrl = `${window.location.origin}/scenes/markers?c=${JSON.stringify({"type":"performers","value":[{"id":performerId,"label":performerName}],"modifier":"INCLUDES_ALL"})}` 59 | markerTab.href = markersUrl; 60 | } 61 | }); 62 | }); 63 | })(); -------------------------------------------------------------------------------- /dist/public/Stash Performer Audit Task Button.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Performer Audit Task Button 3 | // @namespace https://github.com/7dJx1qP/stash-userscripts 4 | // @description Adds a button to the performers page to run the audit plugin task 5 | // @version 0.3.0 6 | // @author 7dJx1qP 7 | // @match http://localhost:9999/* 8 | // @grant unsafeWindow 9 | // @grant GM.getValue 10 | // @grant GM.setValue 11 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js 12 | // ==/UserScript== 13 | 14 | (function () { 15 | 'use strict'; 16 | 17 | const { 18 | stash, 19 | Stash, 20 | waitForElementId, 21 | waitForElementClass, 22 | waitForElementByXpath, 23 | getElementByXpath, 24 | } = unsafeWindow.stash; 25 | 26 | stash.visiblePluginTasks.push('Audit performer urls'); 27 | 28 | const settingsId = 'userscript-settings-audit-task'; 29 | const inputId = 'userscript-settings-audit-task-button-visible'; 30 | 31 | stash.addEventListener('page:performers', function () { 32 | waitForElementClass("btn-toolbar", async () => { 33 | if (!document.getElementById('audit-task')) { 34 | const toolbar = document.querySelector(".btn-toolbar"); 35 | 36 | const newGroup = document.createElement('div'); 37 | newGroup.classList.add('mx-2', 'mb-2', await GM.getValue(inputId, false) ? 'd-flex' : 'd-none'); 38 | toolbar.appendChild(newGroup); 39 | 40 | const auditButton = document.createElement("button"); 41 | auditButton.setAttribute("id", "audit-task"); 42 | auditButton.classList.add('btn', 'btn-secondary'); 43 | auditButton.innerHTML = 'Audit URLs'; 44 | auditButton.onclick = () => { 45 | stash.runPluginTask("userscript_functions", "Audit performer urls"); 46 | }; 47 | newGroup.appendChild(auditButton); 48 | } 49 | }); 50 | }); 51 | 52 | stash.addSystemSetting(async (elementId, el) => { 53 | if (document.getElementById(inputId)) return; 54 | const settingsHeader = 'Show Audit Performer URLs Button'; 55 | const settingsSubheader = 'Display audit performer urls button on performers page.'; 56 | const checkbox = await stash.createSystemSettingCheckbox(el, settingsId, inputId, settingsHeader, settingsSubheader); 57 | checkbox.checked = await GM.getValue(inputId, false); 58 | checkbox.addEventListener('change', async () => { 59 | const value = checkbox.checked; 60 | await GM.setValue(inputId, value); 61 | }); 62 | }); 63 | 64 | stash.addEventListener('stash:pluginVersion', async function () { 65 | waitForElementId(settingsId, async (elementId, el) => { 66 | el.style.display = stash.pluginVersion != null ? 'flex' : 'none'; 67 | }); 68 | }); 69 | })(); -------------------------------------------------------------------------------- /src/body/Stash Open Media Player.user.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | const { 5 | stash, 6 | Stash, 7 | waitForElementId, 8 | waitForElementClass, 9 | waitForElementByXpath, 10 | getElementByXpath, 11 | } = unsafeWindow.stash; 12 | 13 | const MIN_REQUIRED_PLUGIN_VERSION = '0.4.0'; 14 | 15 | function openMediaPlayerTask(path) { 16 | // fixes decodeURI breaking on %'s because they are not encoded 17 | const encodedPctPath = path.replace(/%([^\d].)/, "%25$1"); 18 | // decode encoded path but then encode % and # otherwise VLC breaks 19 | const encodedPath = decodeURI(encodedPctPath).replaceAll('%', '%25').replaceAll('#', '%23'); 20 | 21 | stash.runPluginTask("userscript_functions", "Open in Media Player", {"key":"path", "value":{"str": encodedPath}}); 22 | } 23 | 24 | // scene filepath open with Media Player 25 | stash.addEventListener('page:scene', function () { 26 | waitForElementClass('scene-file-info', function () { 27 | const a = getElementByXpath("//dt[text()='Path']/following-sibling::dd/a"); 28 | if (a) { 29 | a.addEventListener('click', function () { 30 | openMediaPlayerTask(a.href); 31 | }); 32 | } 33 | }); 34 | }); 35 | 36 | const settingsId = 'userscript-settings-mediaplayer'; 37 | 38 | stash.addSystemSetting(async (elementId, el) => { 39 | const inputId = 'userscript-settings-mediaplayer-input'; 40 | if (document.getElementById(inputId)) return; 41 | const settingsHeader = 'Media Player Path'; 42 | const settingsSubheader = 'Path to external media player.'; 43 | const placeholder = 'Media Player Path…'; 44 | const textbox = await stash.createSystemSettingTextbox(el, settingsId, inputId, settingsHeader, settingsSubheader, placeholder, false); 45 | textbox.addEventListener('change', () => { 46 | const value = textbox.value; 47 | if (value) { 48 | stash.updateConfigValueTask('MEDIAPLAYER', 'path', value); 49 | alert(`Media player path set to ${value}`); 50 | } 51 | else { 52 | stash.getConfigValueTask('MEDIAPLAYER', 'path').then(value => { 53 | textbox.value = value; 54 | }); 55 | } 56 | }); 57 | textbox.disabled = true; 58 | stash.getConfigValueTask('MEDIAPLAYER', 'path').then(value => { 59 | textbox.value = value; 60 | textbox.disabled = false; 61 | }); 62 | }); 63 | 64 | stash.addEventListener('stash:pluginVersion', async function () { 65 | waitForElementId(settingsId, async (elementId, el) => { 66 | el.style.display = stash.pluginVersion != null ? 'flex' : 'none'; 67 | }); 68 | if (stash.comparePluginVersion(MIN_REQUIRED_PLUGIN_VERSION) < 0) { 69 | const alertedPluginVersion = await GM.getValue('alerted_plugin_version'); 70 | if (alertedPluginVersion !== stash.pluginVersion) { 71 | await GM.setValue('alerted_plugin_version', stash.pluginVersion); 72 | alert(`User functions plugin version is ${stash.pluginVersion}. Stash Open Media Player userscript requires version ${MIN_REQUIRED_PLUGIN_VERSION} or higher.`); 73 | } 74 | } 75 | }); 76 | })(); -------------------------------------------------------------------------------- /src/body/Stash Performer Tagger Additions.user.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | const { 5 | stash, 6 | Stash, 7 | waitForElementId, 8 | waitForElementClass, 9 | waitForElementByXpath, 10 | getElementByXpath, 11 | insertAfter, 12 | createElementFromHTML, 13 | } = unsafeWindow.stash; 14 | 15 | stash.addEventListener('page:performers', function () { 16 | waitForElementClass("tagger-container", function () { 17 | const performerElements = document.querySelectorAll('.PerformerTagger-details'); 18 | for (const performerElement of performerElements) { 19 | let birthdateElement = performerElement.querySelector('.PerformerTagger-birthdate'); 20 | if (!birthdateElement) { 21 | birthdateElement = document.createElement('h5'); 22 | birthdateElement.classList.add('PerformerTagger-birthdate'); 23 | const headerElement = performerElement.querySelector('.PerformerTagger-header'); 24 | headerElement.classList.add('d-inline-block', 'mr-2'); 25 | headerElement.addEventListener("click", (event) => { 26 | event.preventDefault(); 27 | window.open(headerElement.href, '_blank'); 28 | }); 29 | const performerId = headerElement.href.split('/').pop(); 30 | const performer = stash.performers[performerId]; 31 | birthdateElement.innerText = performer.birthdate; 32 | if (performer.url) { 33 | const urlElement = createElementFromHTML(``); 40 | urlElement.classList.add('d-inline-block'); 41 | insertAfter(urlElement, headerElement); 42 | insertAfter(birthdateElement, urlElement); 43 | } 44 | else { 45 | insertAfter(birthdateElement, headerElement); 46 | } 47 | } 48 | } 49 | }); 50 | }); 51 | })(); -------------------------------------------------------------------------------- /dist/public/Stash Open Media Player.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Open Media Player 3 | // @namespace https://github.com/7dJx1qP/stash-userscripts 4 | // @description Open scene filepath links in an external media player. Requires userscript_functions stash plugin 5 | // @version 0.2.1 6 | // @author 7dJx1qP 7 | // @match http://localhost:9999/* 8 | // @grant unsafeWindow 9 | // @grant GM.getValue 10 | // @grant GM.setValue 11 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js 12 | // ==/UserScript== 13 | 14 | (function () { 15 | 'use strict'; 16 | 17 | const { 18 | stash, 19 | Stash, 20 | waitForElementId, 21 | waitForElementClass, 22 | waitForElementByXpath, 23 | getElementByXpath, 24 | } = unsafeWindow.stash; 25 | 26 | const MIN_REQUIRED_PLUGIN_VERSION = '0.4.0'; 27 | 28 | function openMediaPlayerTask(path) { 29 | // fixes decodeURI breaking on %'s because they are not encoded 30 | const encodedPctPath = path.replace(/%([^\d].)/, "%25$1"); 31 | // decode encoded path but then encode % and # otherwise VLC breaks 32 | const encodedPath = decodeURI(encodedPctPath).replaceAll('%', '%25').replaceAll('#', '%23'); 33 | 34 | stash.runPluginTask("userscript_functions", "Open in Media Player", {"key":"path", "value":{"str": encodedPath}}); 35 | } 36 | 37 | // scene filepath open with Media Player 38 | stash.addEventListener('page:scene', function () { 39 | waitForElementClass('scene-file-info', function () { 40 | const a = getElementByXpath("//dt[text()='Path']/following-sibling::dd/a"); 41 | if (a) { 42 | a.addEventListener('click', function () { 43 | openMediaPlayerTask(a.href); 44 | }); 45 | } 46 | }); 47 | }); 48 | 49 | const settingsId = 'userscript-settings-mediaplayer'; 50 | 51 | stash.addSystemSetting(async (elementId, el) => { 52 | const inputId = 'userscript-settings-mediaplayer-input'; 53 | if (document.getElementById(inputId)) return; 54 | const settingsHeader = 'Media Player Path'; 55 | const settingsSubheader = 'Path to external media player.'; 56 | const placeholder = 'Media Player Path…'; 57 | const textbox = await stash.createSystemSettingTextbox(el, settingsId, inputId, settingsHeader, settingsSubheader, placeholder, false); 58 | textbox.addEventListener('change', () => { 59 | const value = textbox.value; 60 | if (value) { 61 | stash.updateConfigValueTask('MEDIAPLAYER', 'path', value); 62 | alert(`Media player path set to ${value}`); 63 | } 64 | else { 65 | stash.getConfigValueTask('MEDIAPLAYER', 'path').then(value => { 66 | textbox.value = value; 67 | }); 68 | } 69 | }); 70 | textbox.disabled = true; 71 | stash.getConfigValueTask('MEDIAPLAYER', 'path').then(value => { 72 | textbox.value = value; 73 | textbox.disabled = false; 74 | }); 75 | }); 76 | 77 | stash.addEventListener('stash:pluginVersion', async function () { 78 | waitForElementId(settingsId, async (elementId, el) => { 79 | el.style.display = stash.pluginVersion != null ? 'flex' : 'none'; 80 | }); 81 | if (stash.comparePluginVersion(MIN_REQUIRED_PLUGIN_VERSION) < 0) { 82 | const alertedPluginVersion = await GM.getValue('alerted_plugin_version'); 83 | if (alertedPluginVersion !== stash.pluginVersion) { 84 | await GM.setValue('alerted_plugin_version', stash.pluginVersion); 85 | alert(`User functions plugin version is ${stash.pluginVersion}. Stash Open Media Player userscript requires version ${MIN_REQUIRED_PLUGIN_VERSION} or higher.`); 86 | } 87 | } 88 | }); 89 | })(); -------------------------------------------------------------------------------- /dist/public/Stash Userscripts Bundle.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Userscripts Bundle 3 | // @namespace https://github.com/7dJx1qP/stash-userscripts 4 | // @description Stash Userscripts Bundle 5 | // @version 0.24.2 6 | // @author 7dJx1qP 7 | // @match http://localhost:9999/* 8 | // @resource IMPORTED_CSS https://raw.githubusercontent.com/fengyuanchen/cropperjs/main/dist/cropper.min.css 9 | // @grant unsafeWindow 10 | // @grant GM_setClipboard 11 | // @grant GM_getResourceText 12 | // @grant GM_addStyle 13 | // @grant GM.getValue 14 | // @grant GM.setValue 15 | // @require https://raw.githubusercontent.com/fengyuanchen/cropperjs/main/dist/cropper.min.js 16 | // @require https://raw.githubusercontent.com/nodeca/js-yaml/master/dist/js-yaml.js 17 | // @require https://cdnjs.cloudflare.com/ajax/libs/marked/4.2.2/marked.min.js 18 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js 19 | // 20 | // ************************************************************************************************** 21 | // * YOU MAY REMOVE ANY OF THE @require LINES BELOW FOR SCRIPTS YOU DO NOT WANT * 22 | // ************************************************************************************************** 23 | // 24 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Batch Query Edit.user.js 25 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Batch Result Toggle.user.js 26 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Batch Save.user.js 27 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Batch Search.user.js 28 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Markdown.user.js 29 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Markers Autoscroll.user.js 30 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash New Performer Filter Button.user.js 31 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Open Media Player.user.js 32 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Performer Audit Task Button.user.js 33 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Performer Image Cropper.user.js 34 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Performer Markers Tab.user.js 35 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Performer Tagger Additions.user.js 36 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Performer URL Searchbox.user.js 37 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Scene Tagger Additions.user.js 38 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Scene Tagger Colorizer.user.js 39 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Scene Tagger Draft Submit.user.js 40 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Set Stashbox Favorite Performers.user.js 41 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash StashID Icon.user.js 42 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash StashID Input.user.js 43 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Stats.user.js 44 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src/body\Stash Tag Image Cropper.user.js 45 | 46 | // ==/UserScript== 47 | -------------------------------------------------------------------------------- /plugins/userscript_functions/userscript_functions.py: -------------------------------------------------------------------------------- 1 | import config_manager 2 | import json 3 | import log 4 | import os 5 | import pathlib 6 | import sys 7 | import subprocess 8 | from favorite_performers_sync import set_stashbox_favorite_performers, set_stashbox_favorite_performer 9 | from studiodownloader import update_studio_from_stashbox 10 | from audit_performer_urls import audit_performer_urls 11 | try: 12 | from stashlib.stash_database import StashDatabase 13 | from stashlib.stash_interface import StashInterface 14 | except ModuleNotFoundError: 15 | print("If you have pip (normally installed with python), run this command in a terminal (cmd): pip install pystashlib)", file=sys.stderr) 16 | sys.exit() 17 | 18 | json_input = json.loads(sys.stdin.read()) 19 | name = json_input['args']['name'] 20 | 21 | configpath = os.path.join(pathlib.Path(__file__).parent.resolve(), 'config.ini') 22 | 23 | def get_database_config(): 24 | client = StashInterface(json_input["server_connection"]) 25 | result = client.callGraphQL("""query Configuration { configuration { general { databasePath, blobsPath, blobsStorage } } }""") 26 | database_path = result["configuration"]["general"]["databasePath"] 27 | blobs_path = result["configuration"]["general"]["blobsPath"] 28 | blobs_storage = result["configuration"]["general"]["blobsStorage"] 29 | log.debug(f"databasePath: {database_path}") 30 | return database_path, blobs_path, blobs_storage 31 | 32 | if name == 'explorer': 33 | path = json_input['args']['path'] 34 | log.debug(f"{name}: {path}") 35 | subprocess.Popen(f'explorer "{path}"') 36 | elif name == 'mediaplayer': 37 | mediaplayer_path = config_manager.get_config_value(configpath, 'MEDIAPLAYER', 'path') 38 | path = json_input['args']['path'] 39 | log.debug(f"mediaplayer_path: {mediaplayer_path}") 40 | log.debug(f"{name}: {path}") 41 | subprocess.Popen([mediaplayer_path, path]) 42 | elif name == 'update_studio': 43 | studio_id = json_input['args']['studio_id'] 44 | endpoint = json_input['args']['endpoint'] 45 | remote_site_id = json_input['args']['remote_site_id'] 46 | log.debug(f"{name}: {studio_id} {endpoint} {remote_site_id}") 47 | update_studio_from_stashbox(studio_id, endpoint, remote_site_id) 48 | log.debug(f"{name}: Done.") 49 | elif name == 'audit_performer_urls': 50 | try: 51 | db = StashDatabase(*get_database_config()) 52 | except Exception as e: 53 | log.error(str(e)) 54 | sys.exit(0) 55 | audit_performer_urls(db) 56 | db.close() 57 | elif name == 'update_config_value': 58 | log.debug(f"configpath: {configpath}") 59 | section_key = json_input['args']['section_key'] 60 | prop_name = json_input['args']['prop_name'] 61 | value = json_input['args']['value'] 62 | if not section_key or not prop_name: 63 | log.error(f"{name}: Missing args") 64 | sys.exit(0) 65 | log.debug(f"{name}: [{section_key}][{prop_name}] = {value}") 66 | config_manager.update_config_value(configpath, section_key, prop_name, value) 67 | elif name == 'get_config_value': 68 | log.debug(f"configpath: {configpath}") 69 | section_key = json_input['args']['section_key'] 70 | prop_name = json_input['args']['prop_name'] 71 | if not section_key or not prop_name: 72 | log.error(f"{name}: Missing args") 73 | sys.exit(0) 74 | value = config_manager.get_config_value(configpath, section_key, prop_name) 75 | log.debug(f"{name}: [{section_key}][{prop_name}] = {value}") 76 | elif name == 'favorite_performers_sync': 77 | endpoint = json_input['args']['endpoint'] 78 | try: 79 | db = StashDatabase(*get_database_config()) 80 | except Exception as e: 81 | log.error(str(e)) 82 | sys.exit(0) 83 | set_stashbox_favorite_performers(db, endpoint) 84 | db.close() 85 | elif name == 'favorite_performer_sync': 86 | endpoint = json_input['args']['endpoint'] 87 | stash_id = json_input['args']['stash_id'] 88 | favorite = json_input['args']['favorite'] 89 | log.debug(f"Favorite performer sync: endpoint={endpoint}, stash_id={stash_id}, favorite={favorite}") 90 | set_stashbox_favorite_performer(endpoint, stash_id, favorite) -------------------------------------------------------------------------------- /dist/public/Stash Performer Tagger Additions.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stash Performer Tagger Additions 3 | // @namespace https://github.com/7dJx1qP/stash-userscripts 4 | // @description Adds performer birthdate and url to tagger view. Makes clicking performer name open stash profile in new tab instead of current tab. 5 | // @version 0.2.0 6 | // @author 7dJx1qP 7 | // @match http://localhost:9999/* 8 | // @grant unsafeWindow 9 | // @require https://raw.githubusercontent.com/7dJx1qP/stash-userscripts/master/src\StashUserscriptLibrary.js 10 | // ==/UserScript== 11 | 12 | (function () { 13 | 'use strict'; 14 | 15 | const { 16 | stash, 17 | Stash, 18 | waitForElementId, 19 | waitForElementClass, 20 | waitForElementByXpath, 21 | getElementByXpath, 22 | insertAfter, 23 | createElementFromHTML, 24 | } = unsafeWindow.stash; 25 | 26 | stash.addEventListener('page:performers', function () { 27 | waitForElementClass("tagger-container", function () { 28 | const performerElements = document.querySelectorAll('.PerformerTagger-details'); 29 | for (const performerElement of performerElements) { 30 | let birthdateElement = performerElement.querySelector('.PerformerTagger-birthdate'); 31 | if (!birthdateElement) { 32 | birthdateElement = document.createElement('h5'); 33 | birthdateElement.classList.add('PerformerTagger-birthdate'); 34 | const headerElement = performerElement.querySelector('.PerformerTagger-header'); 35 | headerElement.classList.add('d-inline-block', 'mr-2'); 36 | headerElement.addEventListener("click", (event) => { 37 | event.preventDefault(); 38 | window.open(headerElement.href, '_blank'); 39 | }); 40 | const performerId = headerElement.href.split('/').pop(); 41 | const performer = stash.performers[performerId]; 42 | birthdateElement.innerText = performer.birthdate; 43 | if (performer.url) { 44 | const urlElement = createElementFromHTML(``); 51 | urlElement.classList.add('d-inline-block'); 52 | insertAfter(urlElement, headerElement); 53 | insertAfter(birthdateElement, urlElement); 54 | } 55 | else { 56 | insertAfter(birthdateElement, headerElement); 57 | } 58 | } 59 | } 60 | }); 61 | }); 62 | })(); -------------------------------------------------------------------------------- /src/body/Stash Scene Tagger Draft Submit.user.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | const { 5 | stash, 6 | Stash, 7 | waitForElementId, 8 | waitForElementClass, 9 | waitForElementByXpath, 10 | getElementByXpath, 11 | getElementsByXpath, 12 | getClosestAncestor, 13 | insertAfter, 14 | createElementFromHTML, 15 | } = unsafeWindow.stash; 16 | 17 | document.body.appendChild(document.createElement('style')).textContent = ` 18 | .search-item > div.row:first-child > div.col-md-6.my-1 > div:first-child { display: flex; flex-direction: column; } 19 | .submit-draft { order: 5; } 20 | `; 21 | 22 | async function submitDraft(sceneId, stashBoxIndex) { 23 | const reqData = { 24 | "variables": { 25 | "input": { 26 | "id": sceneId, 27 | "stash_box_index": stashBoxIndex 28 | } 29 | }, 30 | "operationName": "SubmitStashBoxSceneDraft", 31 | "query": `mutation SubmitStashBoxSceneDraft($input: StashBoxDraftSubmissionInput!) { 32 | submitStashBoxSceneDraft(input: $input) 33 | }` 34 | } 35 | const res = await stash.callGQL(reqData); 36 | return res?.data?.submitStashBoxSceneDraft; 37 | } 38 | 39 | async function initDraftButtons() { 40 | const data = await stash.getStashBoxes(); 41 | let i = 0; 42 | const stashBoxes = data.data.configuration.general.stashBoxes; 43 | 44 | const nodes = getElementsByXpath("//button[contains(@class, 'btn-primary') and text()='Scrape by fragment']"); 45 | const buttons = []; 46 | let node = null; 47 | while (node = nodes.iterateNext()) { 48 | buttons.push(node); 49 | } 50 | for (const button of buttons) { 51 | const searchItem = getClosestAncestor(button, '.search-item'); 52 | const { 53 | urlNode, 54 | url, 55 | id, 56 | data, 57 | nameNode, 58 | name, 59 | queryInput, 60 | performerNodes 61 | } = stash.parseSearchItem(searchItem); 62 | 63 | const draftButtonExists = searchItem.querySelector('.submit-draft'); 64 | if (draftButtonExists) { 65 | continue; 66 | } 67 | 68 | const submit = createElementFromHTML('
'); 69 | const submitButton = submit.querySelector('button'); 70 | button.parentElement.parentElement.appendChild(submit); 71 | submitButton.addEventListener('click', async () => { 72 | const selectedStashbox = document.getElementById('scraper').value; 73 | if (!selectedStashbox.startsWith('stashbox:')) { 74 | alert('No stashbox source selected.'); 75 | return; 76 | } 77 | const selectedStashboxIndex = parseInt(selectedStashbox.replace(/^stashbox:/, '')); 78 | const existingStashId = data.stash_ids.find(o => o.endpoint === stashBoxes[selectedStashboxIndex].endpoint); 79 | if (existingStashId) { 80 | alert(`Scene already has StashID for ${stashBoxes[selectedStashboxIndex].endpoint}.`); 81 | return; 82 | } 83 | const draftId = await submitDraft(id, selectedStashboxIndex); 84 | const draftLink = createElementFromHTML(`Draft: ${draftId}`); 85 | submitButton.parentElement.appendChild(draftLink); 86 | submitButton.remove(); 87 | }); 88 | } 89 | } 90 | 91 | stash.addEventListener('page:studio:scenes', function () { 92 | waitForElementByXpath("//button[contains(@class, 'btn-primary') and text()='Scrape by fragment']", initDraftButtons); 93 | }); 94 | 95 | stash.addEventListener('page:performer:scenes', function () { 96 | waitForElementByXpath("//button[contains(@class, 'btn-primary') and text()='Scrape by fragment']", initDraftButtons); 97 | }); 98 | 99 | stash.addEventListener('page:scenes', function () { 100 | waitForElementByXpath("//button[contains(@class, 'btn-primary') and text()='Scrape by fragment']", initDraftButtons); 101 | }); 102 | })(); -------------------------------------------------------------------------------- /src/body/Stash StashID Icon.user.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | const { 5 | stash, 6 | Stash, 7 | waitForElementId, 8 | waitForElementClass, 9 | waitForElementByXpath, 10 | getElementByXpath, 11 | insertAfter, 12 | createElementFromHTML, 13 | } = unsafeWindow.stash; 14 | 15 | GM_addStyle(` 16 | .peformer-stashid-icon { 17 | position: absolute; 18 | bottom: .8rem; 19 | left: .8rem; 20 | } 21 | .studio-stashid-icon { 22 | position: absolute; 23 | top: 10px; 24 | right: 5px; 25 | } 26 | .col-3.d-xl-none .studio-stashid-icon { 27 | position: relative; 28 | top: 0; 29 | right: 0; 30 | } 31 | `); 32 | 33 | function createCheckmarkElement() { 34 | return createElementFromHTML(``); 40 | } 41 | 42 | function addPerformerStashIDIcons(performerDatas) { 43 | for (const performerCard of document.querySelectorAll('.performer-card')) { 44 | const performerLink = performerCard.querySelector('.thumbnail-section > a'); 45 | if (performerLink) { 46 | const performerUrl = performerLink.href; 47 | const performerId = performerUrl.split('/').pop(); 48 | const performerData = performerDatas[performerId]; 49 | if (performerData?.stash_ids.length) { 50 | const el = createElementFromHTML(`