mirror of
https://github.com/stashapp/CommunityScrapers.git
synced 2025-12-13 16:46:15 -06:00
1242 lines
25 KiB
Python
1242 lines
25 KiB
Python
import requests
|
|
|
|
import py_common.log as log
|
|
from py_common.config import get_config
|
|
from py_common.util import dig
|
|
|
|
|
|
config = get_config(
|
|
default="""
|
|
# URL for your local Stash server
|
|
url = http://localhost:9999
|
|
|
|
# API key can be generated in Stash's settings page: `Settings > Security > Authentication`
|
|
api_key =
|
|
"""
|
|
)
|
|
|
|
|
|
def callGraphQL(query: str, variables: dict | None = None):
|
|
api_key = config.api_key
|
|
url = config.url
|
|
if not url:
|
|
log.error("You need to set the URL in 'config.ini'")
|
|
return None
|
|
elif "stashdb.org" in url:
|
|
log.error("You need to set the URL in 'config.ini' to your own stash server")
|
|
return None
|
|
|
|
stash_url = f"{url}/graphql"
|
|
headers = {
|
|
"Accept-Encoding": "gzip, deflate",
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json",
|
|
"Connection": "keep-alive",
|
|
"DNT": "1",
|
|
"ApiKey": api_key,
|
|
}
|
|
json: dict[str, str | dict] = {"query": query}
|
|
if variables:
|
|
json["variables"] = variables
|
|
response = requests.post(stash_url, json=json, headers=headers)
|
|
if response.status_code == 200:
|
|
result = response.json()
|
|
if errors := result.get("error"):
|
|
errors = "\n".join(errors)
|
|
log.error(f"[GraphQL] {errors}")
|
|
return result.get("data")
|
|
elif response.status_code == 401:
|
|
if not api_key:
|
|
log.error(
|
|
"[GraphQL] Unable to authenticate with Stash: you need to set the API Key in 'config.ini' in the 'py_common' folder"
|
|
)
|
|
else:
|
|
log.error(
|
|
"[GraphQL] Unable to authenticate with Stash: make sure 'config.ini' in the 'py_common' folder has the correct API Key"
|
|
)
|
|
return None
|
|
elif response.status_code == 404:
|
|
log.error(
|
|
"[GraphQL] Unable to connect to Stash: make sure 'config.ini' in the 'py_common' folder has the correct URL"
|
|
)
|
|
return None
|
|
|
|
raise ConnectionError(
|
|
f"[GraphQL] Unexpected error: {response.status_code} - {response.content}"
|
|
)
|
|
|
|
|
|
def configuration() -> dict | None:
|
|
query = """
|
|
query Configuration {
|
|
configuration {
|
|
...ConfigData
|
|
}
|
|
}
|
|
fragment ConfigData on ConfigResult {
|
|
general {
|
|
...ConfigGeneralData
|
|
}
|
|
interface {
|
|
...ConfigInterfaceData
|
|
}
|
|
dlna {
|
|
...ConfigDLNAData
|
|
}
|
|
scraping {
|
|
...ConfigScrapingData
|
|
}
|
|
defaults {
|
|
...ConfigDefaultSettingsData
|
|
}
|
|
}
|
|
fragment ConfigGeneralData on ConfigGeneralResult {
|
|
stashes {
|
|
path
|
|
excludeVideo
|
|
excludeImage
|
|
}
|
|
databasePath
|
|
generatedPath
|
|
metadataPath
|
|
cachePath
|
|
calculateMD5
|
|
videoFileNamingAlgorithm
|
|
parallelTasks
|
|
previewAudio
|
|
previewSegments
|
|
previewSegmentDuration
|
|
previewExcludeStart
|
|
previewExcludeEnd
|
|
previewPreset
|
|
maxTranscodeSize
|
|
maxStreamingTranscodeSize
|
|
writeImageThumbnails
|
|
apiKey
|
|
username
|
|
password
|
|
maxSessionAge
|
|
logFile
|
|
logOut
|
|
logLevel
|
|
logAccess
|
|
createGalleriesFromFolders
|
|
videoExtensions
|
|
imageExtensions
|
|
galleryExtensions
|
|
excludes
|
|
imageExcludes
|
|
customPerformerImageLocation
|
|
stashBoxes {
|
|
name
|
|
endpoint
|
|
api_key
|
|
}
|
|
}
|
|
fragment ConfigInterfaceData on ConfigInterfaceResult {
|
|
menuItems
|
|
soundOnPreview
|
|
wallShowTitle
|
|
wallPlayback
|
|
maximumLoopDuration
|
|
noBrowser
|
|
autostartVideo
|
|
autostartVideoOnPlaySelected
|
|
continuePlaylistDefault
|
|
showStudioAsText
|
|
css
|
|
cssEnabled
|
|
language
|
|
imageLightbox {
|
|
slideshowDelay
|
|
displayMode
|
|
scaleUp
|
|
resetZoomOnNav
|
|
scrollMode
|
|
scrollAttemptsBeforeChange
|
|
}
|
|
disableDropdownCreate {
|
|
performer
|
|
tag
|
|
studio
|
|
}
|
|
handyKey
|
|
funscriptOffset
|
|
}
|
|
fragment ConfigDLNAData on ConfigDLNAResult {
|
|
serverName
|
|
enabled
|
|
whitelistedIPs
|
|
interfaces
|
|
}
|
|
fragment ConfigScrapingData on ConfigScrapingResult {
|
|
scraperUserAgent
|
|
scraperCertCheck
|
|
scraperCDPPath
|
|
excludeTagPatterns
|
|
}
|
|
fragment ConfigDefaultSettingsData on ConfigDefaultSettingsResult {
|
|
scan {
|
|
scanGeneratePreviews
|
|
scanGenerateImagePreviews
|
|
scanGenerateSprites
|
|
scanGeneratePhashes
|
|
scanGenerateThumbnails
|
|
}
|
|
identify {
|
|
sources {
|
|
source {
|
|
...ScraperSourceData
|
|
}
|
|
options {
|
|
...IdentifyMetadataOptionsData
|
|
}
|
|
}
|
|
options {
|
|
...IdentifyMetadataOptionsData
|
|
}
|
|
}
|
|
autoTag {
|
|
performers
|
|
studios
|
|
tags
|
|
__typename
|
|
}
|
|
generate {
|
|
sprites
|
|
previews
|
|
imagePreviews
|
|
previewOptions {
|
|
previewSegments
|
|
previewSegmentDuration
|
|
previewExcludeStart
|
|
previewExcludeEnd
|
|
previewPreset
|
|
}
|
|
markers
|
|
markerImagePreviews
|
|
markerScreenshots
|
|
transcodes
|
|
phashes
|
|
}
|
|
deleteFile
|
|
deleteGenerated
|
|
}
|
|
fragment ScraperSourceData on ScraperSource {
|
|
stash_box_endpoint
|
|
scraper_id
|
|
}
|
|
fragment IdentifyMetadataOptionsData on IdentifyMetadataOptions {
|
|
fieldOptions {
|
|
...IdentifyFieldOptionsData
|
|
}
|
|
setCoverImage
|
|
setOrganized
|
|
includeMalePerformers
|
|
}
|
|
fragment IdentifyFieldOptionsData on IdentifyFieldOptions {
|
|
field
|
|
strategy
|
|
createMissing
|
|
}
|
|
"""
|
|
result = callGraphQL(query) or {}
|
|
return dig(result, "configuration")
|
|
|
|
|
|
def getScene(scene_id: str | int) -> dict | None:
|
|
query = """
|
|
query FindScene($id: ID!, $checksum: String) {
|
|
findScene(id: $id, checksum: $checksum) {
|
|
...SceneData
|
|
}
|
|
}
|
|
fragment SceneData on Scene {
|
|
id
|
|
title
|
|
code
|
|
details
|
|
urls
|
|
date
|
|
rating100
|
|
o_counter
|
|
organized
|
|
interactive
|
|
files {
|
|
path
|
|
size
|
|
duration
|
|
video_codec
|
|
audio_codec
|
|
width
|
|
height
|
|
frame_rate
|
|
bit_rate
|
|
}
|
|
paths {
|
|
screenshot
|
|
preview
|
|
stream
|
|
webp
|
|
vtt
|
|
sprite
|
|
funscript
|
|
}
|
|
scene_markers {
|
|
...SceneMarkerData
|
|
}
|
|
galleries {
|
|
...SlimGalleryData
|
|
}
|
|
studio {
|
|
...SlimStudioData
|
|
}
|
|
movies {
|
|
movie {
|
|
...MovieData
|
|
}
|
|
scene_index
|
|
}
|
|
tags {
|
|
...SlimTagData
|
|
}
|
|
performers {
|
|
...PerformerData
|
|
}
|
|
stash_ids {
|
|
endpoint
|
|
stash_id
|
|
}
|
|
}
|
|
fragment SceneMarkerData on SceneMarker {
|
|
id
|
|
title
|
|
seconds
|
|
stream
|
|
preview
|
|
screenshot
|
|
scene {
|
|
id
|
|
}
|
|
primary_tag {
|
|
id
|
|
name
|
|
aliases
|
|
}
|
|
tags {
|
|
id
|
|
name
|
|
aliases
|
|
}
|
|
}
|
|
fragment SlimGalleryData on Gallery {
|
|
id
|
|
title
|
|
code
|
|
date
|
|
urls
|
|
details
|
|
photographer
|
|
rating100
|
|
organized
|
|
image_count
|
|
cover {
|
|
paths {
|
|
thumbnail
|
|
}
|
|
}
|
|
studio {
|
|
id
|
|
name
|
|
image_path
|
|
}
|
|
tags {
|
|
id
|
|
name
|
|
}
|
|
performers {
|
|
id
|
|
name
|
|
gender
|
|
favorite
|
|
image_path
|
|
}
|
|
scenes {
|
|
id
|
|
title
|
|
files {
|
|
path
|
|
basename
|
|
}
|
|
}
|
|
}
|
|
fragment SlimStudioData on Studio {
|
|
id
|
|
name
|
|
image_path
|
|
stash_ids {
|
|
endpoint
|
|
stash_id
|
|
}
|
|
parent_studio {
|
|
id
|
|
}
|
|
details
|
|
rating100
|
|
aliases
|
|
}
|
|
fragment MovieData on Movie {
|
|
id
|
|
name
|
|
aliases
|
|
duration
|
|
date
|
|
rating100
|
|
director
|
|
studio {
|
|
...SlimStudioData
|
|
}
|
|
synopsis
|
|
url
|
|
front_image_path
|
|
back_image_path
|
|
scene_count
|
|
scenes {
|
|
id
|
|
title
|
|
files {
|
|
path
|
|
}
|
|
}
|
|
}
|
|
fragment SlimTagData on Tag {
|
|
id
|
|
name
|
|
aliases
|
|
image_path
|
|
}
|
|
fragment PerformerData on Performer {
|
|
id
|
|
name
|
|
url
|
|
gender
|
|
twitter
|
|
instagram
|
|
birthdate
|
|
ethnicity
|
|
country
|
|
eye_color
|
|
height_cm
|
|
measurements
|
|
fake_tits
|
|
career_length
|
|
tattoos
|
|
piercings
|
|
alias_list
|
|
favorite
|
|
image_path
|
|
scene_count
|
|
image_count
|
|
gallery_count
|
|
movie_count
|
|
tags {
|
|
...SlimTagData
|
|
}
|
|
stash_ids {
|
|
stash_id
|
|
endpoint
|
|
}
|
|
rating100
|
|
details
|
|
death_date
|
|
hair_color
|
|
weight
|
|
}
|
|
"""
|
|
variables = {"id": str(scene_id)}
|
|
result = callGraphQL(query, variables) or {}
|
|
return dig(result, "findScene")
|
|
|
|
|
|
def getSceneScreenshot(scene_id: str | int) -> str | None:
|
|
query = """
|
|
query FindScene($id: ID!, $checksum: String) {
|
|
findScene(id: $id, checksum: $checksum) {
|
|
id
|
|
paths {
|
|
screenshot
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
variables = {"id": str(scene_id)}
|
|
result = callGraphQL(query, variables) or {}
|
|
return dig(result, "findScene", "paths", "screenshot")
|
|
|
|
|
|
def getSceneByPerformerId(performer_id: str | int) -> dict | None:
|
|
query = """
|
|
query FindScenes($filter: FindFilterType, $scene_filter: SceneFilterType, $scene_ids: [Int!]) {
|
|
findScenes(filter: $filter, scene_filter: $scene_filter, scene_ids: $scene_ids) {
|
|
count
|
|
filesize
|
|
duration
|
|
scenes {
|
|
...SceneData
|
|
__typename
|
|
}
|
|
__typename
|
|
}
|
|
}
|
|
|
|
fragment SceneData on Scene {
|
|
id
|
|
title
|
|
details
|
|
urls
|
|
date
|
|
rating100
|
|
o_counter
|
|
organized
|
|
files {
|
|
path
|
|
size
|
|
duration
|
|
video_codec
|
|
audio_codec
|
|
width
|
|
height
|
|
frame_rate
|
|
bit_rate
|
|
__typename
|
|
}
|
|
interactive
|
|
interactive_speed
|
|
captions {
|
|
language_code
|
|
caption_type
|
|
__typename
|
|
}
|
|
created_at
|
|
updated_at
|
|
paths {
|
|
screenshot
|
|
preview
|
|
stream
|
|
webp
|
|
vtt
|
|
sprite
|
|
funscript
|
|
interactive_heatmap
|
|
caption
|
|
__typename
|
|
}
|
|
scene_markers {
|
|
...SceneMarkerData
|
|
__typename
|
|
}
|
|
galleries {
|
|
...SlimGalleryData
|
|
__typename
|
|
}
|
|
studio {
|
|
...SlimStudioData
|
|
__typename
|
|
}
|
|
movies {
|
|
movie {
|
|
...MovieData
|
|
__typename
|
|
}
|
|
scene_index
|
|
__typename
|
|
}
|
|
tags {
|
|
...SlimTagData
|
|
__typename
|
|
}
|
|
performers {
|
|
...PerformerData
|
|
__typename
|
|
}
|
|
stash_ids {
|
|
endpoint
|
|
stash_id
|
|
__typename
|
|
}
|
|
sceneStreams {
|
|
url
|
|
mime_type
|
|
label
|
|
__typename
|
|
}
|
|
__typename
|
|
}
|
|
|
|
fragment SceneMarkerData on SceneMarker {
|
|
id
|
|
title
|
|
seconds
|
|
stream
|
|
preview
|
|
screenshot
|
|
scene {
|
|
id
|
|
__typename
|
|
}
|
|
primary_tag {
|
|
id
|
|
name
|
|
aliases
|
|
__typename
|
|
}
|
|
tags {
|
|
id
|
|
name
|
|
aliases
|
|
__typename
|
|
}
|
|
__typename
|
|
}
|
|
|
|
fragment SlimGalleryData on Gallery {
|
|
id
|
|
title
|
|
code
|
|
date
|
|
urls
|
|
details
|
|
photographer
|
|
rating100
|
|
organized
|
|
image_count
|
|
cover {
|
|
paths {
|
|
thumbnail
|
|
__typename
|
|
}
|
|
__typename
|
|
}
|
|
studio {
|
|
id
|
|
name
|
|
image_path
|
|
__typename
|
|
}
|
|
tags {
|
|
id
|
|
name
|
|
__typename
|
|
}
|
|
performers {
|
|
id
|
|
name
|
|
gender
|
|
favorite
|
|
image_path
|
|
__typename
|
|
}
|
|
scenes {
|
|
id
|
|
title
|
|
files {
|
|
path
|
|
}
|
|
__typename
|
|
}
|
|
__typename
|
|
}
|
|
|
|
fragment SlimStudioData on Studio {
|
|
id
|
|
name
|
|
image_path
|
|
stash_ids {
|
|
endpoint
|
|
stash_id
|
|
__typename
|
|
}
|
|
parent_studio {
|
|
id
|
|
__typename
|
|
}
|
|
details
|
|
rating100
|
|
aliases
|
|
__typename
|
|
}
|
|
|
|
fragment MovieData on Movie {
|
|
id
|
|
name
|
|
aliases
|
|
duration
|
|
date
|
|
rating100
|
|
director
|
|
studio {
|
|
...SlimStudioData
|
|
__typename
|
|
}
|
|
synopsis
|
|
url
|
|
front_image_path
|
|
back_image_path
|
|
scene_count
|
|
scenes {
|
|
id
|
|
title
|
|
files {
|
|
path
|
|
}
|
|
__typename
|
|
}
|
|
__typename
|
|
}
|
|
|
|
fragment SlimTagData on Tag {
|
|
id
|
|
name
|
|
aliases
|
|
image_path
|
|
__typename
|
|
}
|
|
|
|
fragment PerformerData on Performer {
|
|
id
|
|
name
|
|
url
|
|
gender
|
|
twitter
|
|
instagram
|
|
birthdate
|
|
ethnicity
|
|
country
|
|
eye_color
|
|
height_cm
|
|
measurements
|
|
fake_tits
|
|
career_length
|
|
tattoos
|
|
piercings
|
|
alias_list
|
|
favorite
|
|
ignore_auto_tag
|
|
image_path
|
|
scene_count
|
|
image_count
|
|
gallery_count
|
|
movie_count
|
|
tags {
|
|
...SlimTagData
|
|
__typename
|
|
}
|
|
stash_ids {
|
|
stash_id
|
|
endpoint
|
|
__typename
|
|
}
|
|
rating100
|
|
details
|
|
death_date
|
|
hair_color
|
|
weight
|
|
__typename
|
|
}
|
|
"""
|
|
variables = {
|
|
"filter": {"page": 1, "per_page": 20, "sort": "title", "direction": "ASC"},
|
|
"scene_filter": {
|
|
"performers": {"value": [str(performer_id)], "modifier": "INCLUDES_ALL"}
|
|
},
|
|
}
|
|
result = callGraphQL(query, variables) or {}
|
|
return dig(result, "findScenes")
|
|
|
|
|
|
def getSceneIdByPerformerId(performer_id: str | int) -> dict | None:
|
|
query = """
|
|
query FindScenes($filter: FindFilterType, $scene_filter: SceneFilterType, $scene_ids: [Int!]) {
|
|
findScenes(filter: $filter, scene_filter: $scene_filter, scene_ids: $scene_ids) {
|
|
scenes {
|
|
id
|
|
title
|
|
files {
|
|
path
|
|
}
|
|
paths {
|
|
screenshot
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
variables = {
|
|
"filter": {"page": 1, "per_page": 20, "sort": "id", "direction": "DESC"},
|
|
"scene_filter": {
|
|
"performers": {"value": [str(performer_id)], "modifier": "INCLUDES_ALL"}
|
|
},
|
|
}
|
|
result = callGraphQL(query, variables) or {}
|
|
return dig(result, "findScenes")
|
|
|
|
|
|
def getPerformersByName(performer_name: str) -> dict | None:
|
|
query = """
|
|
query FindPerformers($filter: FindFilterType, $performer_filter: PerformerFilterType) {
|
|
findPerformers(filter: $filter, performer_filter: $performer_filter) {
|
|
count
|
|
performers {
|
|
...PerformerData
|
|
__typename
|
|
}
|
|
__typename
|
|
}
|
|
}
|
|
|
|
fragment PerformerData on Performer {
|
|
id
|
|
name
|
|
url
|
|
gender
|
|
twitter
|
|
instagram
|
|
birthdate
|
|
ethnicity
|
|
country
|
|
eye_color
|
|
height_cm
|
|
measurements
|
|
fake_tits
|
|
career_length
|
|
tattoos
|
|
piercings
|
|
alias_list
|
|
favorite
|
|
ignore_auto_tag
|
|
image_path
|
|
scene_count
|
|
image_count
|
|
gallery_count
|
|
movie_count
|
|
tags {
|
|
...SlimTagData
|
|
__typename
|
|
}
|
|
stash_ids {
|
|
stash_id
|
|
endpoint
|
|
__typename
|
|
}
|
|
rating100
|
|
details
|
|
death_date
|
|
hair_color
|
|
weight
|
|
__typename
|
|
}
|
|
|
|
fragment SlimTagData on Tag {
|
|
id
|
|
name
|
|
aliases
|
|
image_path
|
|
__typename
|
|
}
|
|
"""
|
|
|
|
variables = {
|
|
"filter": {
|
|
"q": performer_name,
|
|
"page": 1,
|
|
"per_page": 20,
|
|
"sort": "name",
|
|
"direction": "ASC",
|
|
},
|
|
"performer_filter": {},
|
|
}
|
|
result = callGraphQL(query, variables) or {}
|
|
return dig(result, "findPerformers")
|
|
|
|
|
|
def getPerformersIdByName(performer_name: str) -> dict | None:
|
|
query = """
|
|
query FindPerformers($filter: FindFilterType, $performer_filter: PerformerFilterType) {
|
|
findPerformers(filter: $filter, performer_filter: $performer_filter) {
|
|
count
|
|
performers {
|
|
...PerformerData
|
|
}
|
|
}
|
|
}
|
|
|
|
fragment PerformerData on Performer {
|
|
id
|
|
name
|
|
alias_list
|
|
}
|
|
"""
|
|
|
|
variables = {
|
|
"filter": {
|
|
"q": performer_name,
|
|
"page": 1,
|
|
"per_page": 20,
|
|
"sort": "name",
|
|
"direction": "ASC",
|
|
},
|
|
"performer_filter": {},
|
|
}
|
|
|
|
result = callGraphQL(query, variables) or {}
|
|
return dig(result, "findPerformers")
|
|
|
|
|
|
def getGallery(gallery_id: str | int) -> dict | None:
|
|
query = """
|
|
query FindGallery($id: ID!) {
|
|
findGallery(id: $id) {
|
|
...GalleryData
|
|
}
|
|
}
|
|
fragment GalleryData on Gallery {
|
|
id
|
|
created_at
|
|
updated_at
|
|
title
|
|
code
|
|
date
|
|
urls
|
|
details
|
|
photographer
|
|
rating100
|
|
organized
|
|
folder {
|
|
path
|
|
}
|
|
cover {
|
|
...SlimImageData
|
|
}
|
|
studio {
|
|
...SlimStudioData
|
|
}
|
|
tags {
|
|
...SlimTagData
|
|
}
|
|
performers {
|
|
...PerformerData
|
|
}
|
|
scenes {
|
|
...SlimSceneData
|
|
}
|
|
}
|
|
fragment SlimImageData on Image {
|
|
id
|
|
title
|
|
rating100
|
|
organized
|
|
o_counter
|
|
visual_files {
|
|
... on ImageFile {
|
|
path
|
|
size
|
|
height
|
|
width
|
|
}
|
|
}
|
|
|
|
paths {
|
|
thumbnail
|
|
image
|
|
}
|
|
|
|
galleries {
|
|
id
|
|
files {
|
|
path
|
|
}
|
|
title
|
|
}
|
|
|
|
studio {
|
|
id
|
|
name
|
|
image_path
|
|
}
|
|
|
|
tags {
|
|
id
|
|
name
|
|
}
|
|
|
|
performers {
|
|
id
|
|
name
|
|
gender
|
|
favorite
|
|
image_path
|
|
}
|
|
}
|
|
fragment SlimStudioData on Studio {
|
|
id
|
|
name
|
|
image_path
|
|
stash_ids {
|
|
endpoint
|
|
stash_id
|
|
}
|
|
parent_studio {
|
|
id
|
|
}
|
|
details
|
|
rating100
|
|
aliases
|
|
}
|
|
fragment SlimTagData on Tag {
|
|
id
|
|
name
|
|
aliases
|
|
image_path
|
|
}
|
|
fragment PerformerData on Performer {
|
|
id
|
|
name
|
|
url
|
|
gender
|
|
twitter
|
|
instagram
|
|
birthdate
|
|
ethnicity
|
|
country
|
|
eye_color
|
|
height_cm
|
|
measurements
|
|
fake_tits
|
|
career_length
|
|
tattoos
|
|
piercings
|
|
alias_list
|
|
favorite
|
|
image_path
|
|
scene_count
|
|
image_count
|
|
gallery_count
|
|
movie_count
|
|
|
|
tags {
|
|
...SlimTagData
|
|
}
|
|
|
|
stash_ids {
|
|
stash_id
|
|
endpoint
|
|
}
|
|
rating100
|
|
details
|
|
death_date
|
|
hair_color
|
|
weight
|
|
}
|
|
fragment SlimSceneData on Scene {
|
|
id
|
|
title
|
|
code
|
|
details
|
|
urls
|
|
date
|
|
rating100
|
|
o_counter
|
|
organized
|
|
interactive
|
|
|
|
files {
|
|
path
|
|
size
|
|
duration
|
|
video_codec
|
|
audio_codec
|
|
width
|
|
height
|
|
frame_rate
|
|
bit_rate
|
|
}
|
|
|
|
paths {
|
|
screenshot
|
|
preview
|
|
stream
|
|
webp
|
|
vtt
|
|
sprite
|
|
funscript
|
|
}
|
|
|
|
scene_markers {
|
|
id
|
|
title
|
|
seconds
|
|
}
|
|
|
|
galleries {
|
|
id
|
|
title
|
|
files {
|
|
path
|
|
}
|
|
}
|
|
|
|
studio {
|
|
id
|
|
name
|
|
image_path
|
|
}
|
|
|
|
movies {
|
|
movie {
|
|
id
|
|
name
|
|
front_image_path
|
|
}
|
|
scene_index
|
|
}
|
|
|
|
tags {
|
|
id
|
|
name
|
|
}
|
|
|
|
performers {
|
|
id
|
|
name
|
|
gender
|
|
favorite
|
|
image_path
|
|
}
|
|
|
|
stash_ids {
|
|
endpoint
|
|
stash_id
|
|
}
|
|
}
|
|
"""
|
|
variables = {"id": gallery_id}
|
|
result = callGraphQL(query, variables) or {}
|
|
return dig(result, "findGallery")
|
|
|
|
|
|
def getGalleryPath(gallery_id: str | int) -> str | None:
|
|
query = """
|
|
query FindGallery($id: ID!) {
|
|
findGallery(id: $id) {
|
|
folder {
|
|
path
|
|
}
|
|
files {
|
|
path
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
variables = {"id": gallery_id}
|
|
result = callGraphQL(query, variables) or {}
|
|
# Galleries can either be a folder full of files or a zip file
|
|
return dig(result, "findGallery", "folder", "path") or dig(
|
|
result, "findGallery", "files", 0, "path"
|
|
)
|
|
|
|
|
|
# GraphQL introspection
|
|
#
|
|
# if a graphQL API changes, you can use this as the query string value to
|
|
# discover available API fields, queries, etc.
|
|
GRAPHQL_INTROSPECTION = """
|
|
fragment FullType on __Type {
|
|
kind
|
|
name
|
|
fields(includeDeprecated: true) {
|
|
name
|
|
args {
|
|
...InputValue
|
|
}
|
|
type {
|
|
...TypeRef
|
|
}
|
|
isDeprecated
|
|
deprecationReason
|
|
}
|
|
inputFields {
|
|
...InputValue
|
|
}
|
|
interfaces {
|
|
...TypeRef
|
|
}
|
|
enumValues(includeDeprecated: true) {
|
|
name
|
|
isDeprecated
|
|
deprecationReason
|
|
}
|
|
possibleTypes {
|
|
...TypeRef
|
|
}
|
|
}
|
|
fragment InputValue on __InputValue {
|
|
name
|
|
type {
|
|
...TypeRef
|
|
}
|
|
defaultValue
|
|
}
|
|
fragment TypeRef on __Type {
|
|
kind
|
|
name
|
|
ofType {
|
|
kind
|
|
name
|
|
ofType {
|
|
kind
|
|
name
|
|
ofType {
|
|
kind
|
|
name
|
|
ofType {
|
|
kind
|
|
name
|
|
ofType {
|
|
kind
|
|
name
|
|
ofType {
|
|
kind
|
|
name
|
|
ofType {
|
|
kind
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
query IntrospectionQuery {
|
|
__schema {
|
|
queryType {
|
|
name
|
|
}
|
|
mutationType {
|
|
name
|
|
}
|
|
types {
|
|
...FullType
|
|
}
|
|
directives {
|
|
name
|
|
locations
|
|
args {
|
|
...InputValue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|