From b461a5887b59275bdb5f54069c13db6df0ce7613 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:48:53 -0800 Subject: [PATCH 01/20] Updated versioning --- OFMetadataToStash.ps1 | 4 ++-- Utilities/OFMetadataDatabase_Sanitizer.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index 0ac9fd5..94a4e0a 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -1,5 +1,5 @@ <# ----OnlyFans Metadata DB to Stash PoSH Script 0.8--- +---OnlyFans Metadata DB to Stash PoSH Script 0.9--- AUTHOR JuiceBox @@ -1240,7 +1240,7 @@ if(($SearchSpecificity -notmatch '\blow\b|\bnormal\b|\bhigh\b')){ } else { clear-host - write-host "- OnlyFans Metadata DB to Stash PoSH Script 0.8 - `n(https://github.com/ALonelyJuicebox/OFMetadataToStash)`n" -ForegroundColor cyan + write-host "- OnlyFans Metadata DB to Stash PoSH Script 0.9 - `n(https://github.com/ALonelyJuicebox/OFMetadataToStash)`n" -ForegroundColor cyan write-output "By JuiceBox`n`n----------------------------------------------------`n" write-output "* Path to OnlyFans Media: $PathToOnlyFansContent" write-output "* Metadata Match Mode: $searchspecificity" diff --git a/Utilities/OFMetadataDatabase_Sanitizer.ps1 b/Utilities/OFMetadataDatabase_Sanitizer.ps1 index ae72cdb..34c1d54 100644 --- a/Utilities/OFMetadataDatabase_Sanitizer.ps1 +++ b/Utilities/OFMetadataDatabase_Sanitizer.ps1 @@ -1,5 +1,5 @@ <# ----OnlyFans Metadata Database Sanitizer PoSH Script 0.7--- +---OnlyFans Metadata Database Sanitizer PoSH Script 0.9--- AUTHOR JuiceBox @@ -17,7 +17,7 @@ REQUIREMENTS #> clear-host - write-host "- OnlyFans Metadata DB to Stash PoSH Script 0.8 - `n(https://github.com/ALonelyJuicebox/OFMetadataToStash)`n" -ForegroundColor cyan + write-host "- OnlyFans Metadata DB to Stash PoSH Script 0.9 - `n(https://github.com/ALonelyJuicebox/OFMetadataToStash)`n" -ForegroundColor cyan write-host "Database Sanitization Tool" write-host "--------------------------------`n" From cd7d2b04a9e2da6fc318ac30bd2ae2f65eb4eec9 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:50:57 -0800 Subject: [PATCH 02/20] Modified language for Docker Docker users no longer need to only use "low" when scanning. Updated language to reflect these changes --- OFMetadataToStash.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index 94a4e0a..c6c5dc6 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -127,9 +127,8 @@ function Set-Config{ write-output "(3 of 4) Define your Metadata Match Mode" write-output " * When importing OnlyFans Metadata, some users may want to tailor how this script matches metadata to files" write-output " * If you are an average user, just set this to 'Normal'" - write-output " * Do you host Stash on Docker? Be sure to set this to 'Low'! `n" write-output "Option 1: Normal - Will match based on Filesize and the Performer name being somewhere in the file path (Recommended)" - write-output "Option 2: Low - Will match based only on a matching Filesize (For Docker Users)" + write-output "Option 2: Low - Will match based only on a matching Filesize" write-output "Option 3: High - Will match based on a matching path and a matching Filesize" From bfed9cd03dd50fb667bde9da5b018baaff50f3dd Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Fri, 26 Jan 2024 18:08:50 -0800 Subject: [PATCH 03/20] Additional language change --- OFMetadataToStash.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index c6c5dc6..c57cb29 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -706,7 +706,7 @@ function Add-MetadataUsingOFDB{ #If our search for matching media in Stash itself comes up empty, let's check to see if the file even exists on the file system if ($StashGQL_Result.data.querySQL.rows.length -eq 0 ){ if (Test-Path $OFDBFullFilePath){ - write-host "`nInfo: There's a file in this OnlyFans metadata database that we couldn't find in your Stash database but the file IS on your filesystem.`nTry running a Scan Task in Stash then re-running this script or changing your Search Specificity mode to Low.`n`n - $OFDBFullFilePath`n" -ForegroundColor Cyan + write-host "`nInfo: There's a file in this OnlyFans metadata database that we couldn't find in your Stash database but the file IS on your filesystem.`nTry running a Scan Task in Stash then re-running this script.`n`n - $OFDBFullFilePath`n" -ForegroundColor Cyan } #In this case, the media isn't in Stash or on the filesystem so inform the user, log the file, and move on else{ From 9f4b2d5712d4418f4bc2cbc74680deef2b3d0bce Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Fri, 16 Feb 2024 23:04:40 -0800 Subject: [PATCH 04/20] bugfix for a GQL query fixes a bug looking for studios in GQL --- OFMetadataToStash.ps1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index c57cb29..07a4995 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -68,6 +68,7 @@ function Set-Config{ } catch{ write-host "(0) Error: Could not communicate to Stash at the provided address ($StashGQL_URL)" -ForegroundColor red + write-host "Try defining the address to Stash once more. Also, please ensure you do not have a password on your Stash!" } } } @@ -370,7 +371,7 @@ function Add-MetadataUsingOFDB{ ' $StashGQL_QueryVariables = '{ "filter": { - "q": "OnlyFans", + "q": "OnlyFans" } }' try{ @@ -1056,7 +1057,9 @@ function Add-MetadataUsingOFDB{ #The only reason we don't do it earlier is that now all the images have been added and associated and it's easy to select an image and go. if($creatednewperformer){ - #First let's look for an image where this performer has been associated and get the URL for that image + #First let's look for an image where this performer has been associated and get the URL, for that image + #Sometimes these OF downloaders pull profile/avatar photos into a specific folder. We'll look to see if we can match on that first before just choosing what we can get. + #FINISHME $performerimageURL_GQLQuery = 'query FindImages( $filter: FindFilterType $image_filter: ImageFilterType @@ -1208,7 +1211,7 @@ try{ } catch{ write-host "Hmm...Could not communicate to Stash using the URL in the config file ($StashGQL_URL)" - write-host "Are you sure Stash is running?" + write-host "Are you sure Stash is running? Make sure you don't have a password on your Stash account either." read-host "If Stash is running like normal, press [Enter] to recreate the configuration file for this script" Set-Config } From 9564eefc2d19af5dc08e426015467072276d75d7 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Sat, 24 Feb 2024 18:40:54 -0800 Subject: [PATCH 05/20] Feature: Range selector for metadata databases --- OFMetadataToStash.ps1 | 179 +++++++++++++++++++++++++++++++++--------- 1 file changed, 142 insertions(+), 37 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index 07a4995..3aaac1f 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -283,48 +283,60 @@ function Add-MetadataUsingOFDB{ #For the discovery of multiple database files elseif ($OFDatabaseFilesCollection.count -gt 1){ - write-output "Discovered multiple metadata databases" - write-output "0 - Process metadata for all performers" - - $i=1 # just used cosmetically - Foreach ($OFDBdatabase in $OFDatabaseFilesCollection){ - - #Getting the performer name from the profiles table (if it exists) - $Query = "PRAGMA table_info(medias)" - $OFDBColumnsToCheck = Invoke-SqliteQuery -Query $Query -DataSource $OFDBdatabase.FullName + $totalNumMetadataDatabases = $OFDatabaseFilesCollection.count + write-host "...Discovered $totalnummetadatadatabases metadata databases." + write-host "`nHow would you like to import metadata for these performers?" + write-host "1 - Import metadata for all discovered performers" + write-host "2 - Import metadata for a specific performer" + write-host "3 - Import metadata for a range of performers" + $selectednumberforprocess = read-host "Make your selection [Enter a number]" + + while([int]$selectednumberforprocess -notmatch "[1-3]" ){ + write-host "Invalid input" + $selectednumberforprocess = read-host "Make your selection [Enter a number]" + } - #There's probably a faster way to do this, but I'm throwing the collection into a string, with each column result (aka table name) seperated by a space. - $OFDBColumnsToCheck = [string]::Join(' ',$OFDBColumnsToCheck.name) - $performername = $null - if ($OFDBColumnsToCheck -match "profiles"){ - $Query = "SELECT username FROM profiles LIMIT 1" #I'm throwing that limit on as a precaution-- I'm not sure if multiple usernames will ever be stored in that SQL table - $performername = Invoke-SqliteQuery -Query $Query -DataSource $OFDatabaseFilesCollection[0].FullName - } + if ([int]$selectednumberforprocess -eq 1){ + write-host "OK, all performers will be processed." + } + #Logic for handling the process for selecting a single performer + elseif([int]$selectednumberforprocess -eq 2){ - #Either the query resulted in null or the profiles table didnt exist, so either way let's use the alternative directory based method. - if ($null -eq $performername){ - $performername = $OFDBdatabase.FullName | split-path | split-path -leaf - if ($performername -eq "metadata"){ - $performername = $OFDBdatabase.FullName | split-path | split-path | split-path -leaf + #logic for displaying all found performers for user to select + $i=1 # just used cosmetically + Foreach ($OFDBdatabase in $OFDatabaseFilesCollection){ + + #Getting the performer name from the profiles table (if it exists) + $Query = "PRAGMA table_info(medias)" + $OFDBColumnsToCheck = Invoke-SqliteQuery -Query $Query -DataSource $OFDBdatabase.FullName + + #There's probably a faster way to do this, but I'm throwing the collection into a string, with each column result (aka table name) seperated by a space. + $OFDBColumnsToCheck = [string]::Join(' ',$OFDBColumnsToCheck.name) + $performername = $null + if ($OFDBColumnsToCheck -match "profiles"){ + $Query = "SELECT username FROM profiles LIMIT 1" #I'm throwing that limit on as a precaution-- I'm not sure if multiple usernames will ever be stored in that SQL table + $performername = Invoke-SqliteQuery -Query $Query -DataSource $OFDatabaseFilesCollection[0].FullName } + + #Either the query resulted in null or the profiles table didnt exist, so either way let's use the alternative directory based method. + if ($null -eq $performername){ + $performername = $OFDBdatabase.FullName | split-path | split-path -leaf + if ($performername -eq "metadata"){ + $performername = $OFDBdatabase.FullName | split-path | split-path | split-path -leaf + } + } + + write-output "$i - $performername" + $i++ } - - write-output "$i - $performername" - $i++ - - } - $selectednumber = read-host "`n# Which performer would you like to select? [Enter a number]" - #Checking for bad input - while ($selectednumber -notmatch "^[\d\.]+$" -or ([int]$selectednumber -gt $OFDatabaseFilesCollection.Count)){ - $selectednumber = read-host "Invalid Input. Please select a number between 0 and" $OFDatabaseFilesCollection.Count".`nWhich performer would you like to select? [Enter a number]" - } + + $selectednumber = read-host "`n# Which performer would you like to select? [Enter a number]" + #Checking for bad input + while ($selectednumber -notmatch "^[\d\.]+$" -or ([int]$selectednumber -gt $totalNumMetadataDatabases)){ + $selectednumber = read-host "Invalid Input. Please select a number between 0 and" $totalNumMetadataDatabases".`nWhich performer would you like to select? [Enter a number]" + } - #If the user wants to process all performers, let's let them. - if ([int]$selectednumber -eq 0){ - write-output "OK, all performers will be processed." - } - else{ $selectednumber = $selectednumber-1 #Since we are dealing with a 0 based array, i'm realigning the user selection $performername = $OFDatabaseFilesCollection[$selectednumber].FullName | split-path | split-path -leaf if ($performername -eq "metadata"){ @@ -335,9 +347,102 @@ function Add-MetadataUsingOFDB{ $OFDatabaseFilesCollection = $OFDatabaseFilesCollection[$selectednumber] write-output "OK, the performer '$performername' will be processed." + + } + + #Logic for handling the range process + else{ + + #Logic for displaying all found performers + $i=1 # just used cosmetically + write-host "`nHere are all the performers that you can import metadata for:" + Foreach ($OFDBdatabase in $OFDatabaseFilesCollection){ + + #Getting the performer name from the profiles table (if it exists) + $Query = "PRAGMA table_info(medias)" + $OFDBColumnsToCheck = Invoke-SqliteQuery -Query $Query -DataSource $OFDBdatabase.FullName + + #There's probably a faster way to do this, but I'm throwing the collection into a string, with each column result (aka table name) seperated by a space. + $OFDBColumnsToCheck = [string]::Join(' ',$OFDBColumnsToCheck.name) + $performername = $null + if ($OFDBColumnsToCheck -match "profiles"){ + $Query = "SELECT username FROM profiles LIMIT 1" #I'm throwing that limit on as a precaution-- I'm not sure if multiple usernames will ever be stored in that SQL table + $performername = Invoke-SqliteQuery -Query $Query -DataSource $OFDatabaseFilesCollection[0].FullName + } + + #Either the query resulted in null or the profiles table didnt exist, so either way let's use the alternative directory based method. + if ($null -eq $performername){ + $performername = $OFDBdatabase.FullName | split-path | split-path -leaf + if ($performername -eq "metadata"){ + $performername = $OFDBdatabase.FullName | split-path | split-path | split-path -leaf + } + } + + write-output "$i - $performername" + $i++ + } + + #Some input handling/error handling for the user defined start of the range + $StartOfRange = read-host "Which performer is the first in the range? [Enter a number]" + $rangeInputCheck = $false + + while($rangeInputCheck -eq $false){ + if($StartOfRange -notmatch "^[\d\.]+$"){ + write-host "`nInvalid Input: You have to enter a number" + $StartOfRange = read-host "Which performer is at the start of the range? [Enter a number]" + } + elseif($StartOfRange -le 0){ + write-host "`nInvalid Input: You can't enter a number less than 1" + $StartOfRange = read-host "Which performer is at the start of the range? [Enter a number]" + } + elseif($StartOfRange -ge $totalNumMetadataDatabases){ + write-host "`nInvalid Input: You can't enter a number greater than or equal to $totalNumMetadataDatabases" + $StartOfRange = read-host "Which performer is at the start of the range? [Enter a number]" + } + else{ + $rangeInputCheck = $true + } + } + + #Some input handling/error handling for the user defined end of the range + $endOfRange = Read-Host "Which performer is at the end of the range? [Enter a number]" + $rangeInputCheck = $false + + while($rangeInputCheck -eq $false){ + if($EndOfRange -notmatch "^[\d\.]+$"){ + write-host "`nInvalid Input: You have to enter a number" + $endOfRange = read-host "Which performer is at the end of the range? [Enter a number]" + } + elseif($EndOfRange -le 0){ + write-host "`nInvalid Input: You can't enter a number less than 1" + $endOfRange = read-host "Which performer is at the end of the range? [Enter a number]" + } + elseif($endOfRange -gt $totalNumMetadataDatabases){ + write-host "`nInvalid Input: You can't enter a number greater than $totalNumMetadataDatabases" + $endOfRange = read-host "Which performer is at the end of the range? [Enter a number]" + } + elseif($endOfRange -le $StartOfRange){ + write-host "`nInvalid Input: Number has to be greater than $StartofRange" + $endOfRange = read-host "Which performer is at the end of the range? [Enter a number]" + } + else{ + $rangeInputCheck = $true + } + } + write-host "OK, all the performers between $startofrange and $endofrange will be processed." + + #We subtract 1 to account for us presenting the user with a 1 based start while PS arrays start at 0 + $endofrange = $endOfRange - 1 + $StartOfRange = $StartOfRange - 1 + + #Finally, let's define the new array of metadata databases based on the defined range + $OFDatabaseFilesCollection = $OFDatabaseFilesCollection[$startofrange..$endOfRange] + write-host $OFDatabaseFilesCollection } - write-host "`n# Which types of media do you want to import metadata for?" + + #Let's ask the user what type of media they want to parse + write-host "`nWhich types of media do you want to import metadata for?" write-host "1 - Both Videos & Images`n2 - Only Videos`n3 - Only Images" $mediaToProcessSelector = 0; From a3f0ab5712e25e23bbfa8790eb95d336fc05743e Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Sun, 25 Feb 2024 00:01:26 -0800 Subject: [PATCH 06/20] Feature - Added import tracking Databases that have been imported will be tracked with a timestamp in a imported_dbs.sqlite file. This significantly improves total runtime for folks who like to just ask the script to parse over a large collection of metadata databases-- the script will now skip over anything its already seen. --- OFMetadataToStash.ps1 | 905 +++++++++++++++++++++++------------------- 1 file changed, 498 insertions(+), 407 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index 3aaac1f..1e9bc33 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -218,6 +218,95 @@ function Set-Config{ } #End Set-Config +#Get-PerformerHistory does a check to see if a particular metadata database file actually needs to be parsed based on a history file. Increments history file and returns true if it does, returns false if it does not. +function Get-PerformerHistory{ + #Location for the history file to be stored + $PathToHistoryFile = "."+$directorydelimiter+"Utilities"+$directorydelimiter+"imported_dbs.sqlite" + + #Let's go ahead and create the history file if it does not exist + if(!(test-file $pathtohistoryfile)){ + try{ + out-file $PathToHistoryFile + } + catch{ + write-host "Error 1h - Unable to write the history file to the filesystem. Permissions issue?" -ForegroundColor red + read-host "Press [Enter] to exit" + exit + } + + #Query for defining the schema of the SQL database we're creating + $historyquery = 'CREATE TABLE "history" ( + "historyID" INTEGER NOT NULL UNIQUE, + "performer" TEXT NOT NULL UNIQUE COLLATE BINARY, + "import_date" TEXT NOT NULL, + PRIMARY KEY("historyID" AUTOINCREMENT) + );' + + try{ + $historyTimestamp = Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile + } + catch{ + write-host "Error 2h - Unable to create a history file using SQL." -ForegroundColor red + read-host "Press [Enter] to exit" + exit + } + } + + #First let's check to see if this performer is even in the history file + try{ + $historyQuery = 'SELECT * FROM history WHERE history.performer = "'+$performername+'"' + $performerFromHistory = Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile + } + catch{ + write-host "Error 3h - Something went wrong while trying to read from history file ($PathToHistoryFile)" -ForegroundColor red + read-host "Press [Enter] to exit" + exit + } + + #If this performer DOES exist in the history file... + if ($performerFromHistory){ + + #Let's get the timestamp from the metdata database file + $metadataLastWriteTime = get-item $OFDBFullFilePath + $metadataLastWriteTime = $metadataLastWriteTime.LastWriteTime + + #If the metdata database for this performer has been modified since the last time we read this metadata database in, let's go ahead and parse it, otherwise return false so we can skip it + if([datetime]$metadataLastWriteTime -gt [datetime]$historytimestamp){ + $currenttimestamp = get-date -format o + try { + $historyQuery = 'UPDATE import_date SET import_date = "'+$currenttimestamp+'" WHERE history.performer = "'+$performername+'"' + $historyTimestamp = Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile + } + catch{ + write-host "Error 4h - Something went wrong while trying to update the history file ($PathToHistoryFile)" -ForegroundColor red + read-output "Press [Enter] to exit" + exit + } + return $true + } + else{ + write-host "Nothing to update for this performer! Skipping..." + return $false + } + } + #Otherwise, this performer is entirely new to us, so let's add the performer to the history file and return true so it may be processed + else{ + try { + $historyQuery = 'INSERT INTO history(performer, import_date) VALUES ("'+$performername+'", "'+$currenttimestamp+'")' + $historyTimestamp = Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile + } + catch{ + write-host "Error 5h - Something went wrong while trying to add this performer to the history file ($PathToHistoryFile)" -ForegroundColor red + read-output "Press [Enter] to exit" + exit + } + return $true + } + + + +} #End Get-PerformerHistory + #Add-MetadataUsingOFDB adds metadata to Stash using metadata databases. function Add-MetadataUsingOFDB{ #Playing it safe and asking the user to back up their database first @@ -681,420 +770,300 @@ function Add-MetadataUsingOFDB{ $boolGetPerformerImage = $false } + #Let's check to see if we need to import this performer based on the history file. If this function returns false, move on to the next performer. + if (Get-PerformerHistory -or $ignorehistory -eq $true){ + #Select all the media (except audio) and the text the performer associated to them, if available from the OFDB + $Query = "SELECT messages.text, medias.directory, medias.filename, medias.size, medias.created_at, medias.post_id, medias.media_type FROM medias INNER JOIN messages ON messages.post_id=medias.post_id UNION SELECT posts.text, medias.directory, medias.filename, medias.size, medias.created_at, medias.post_id, medias.media_type FROM medias INNER JOIN posts ON posts.post_id=medias.post_id WHERE medias.media_type <> 'Audios'" + $OF_DBpath = $currentdatabase.fullname + $OFDBQueryResult = Invoke-SqliteQuery -Query $Query -DataSource $OF_DBpath + $progressCounter = 1 #Used for the progress UI + foreach ($OFDBMedia in $OFDBQueryResult){ - #Select all the media (except audio) and the text the performer associated to them, if available from the OFDB - $Query = "SELECT messages.text, medias.directory, medias.filename, medias.size, medias.created_at, medias.post_id, medias.media_type FROM medias INNER JOIN messages ON messages.post_id=medias.post_id UNION SELECT posts.text, medias.directory, medias.filename, medias.size, medias.created_at, medias.post_id, medias.media_type FROM medias INNER JOIN posts ON posts.post_id=medias.post_id WHERE medias.media_type <> 'Audios'" - $OF_DBpath = $currentdatabase.fullname - $OFDBQueryResult = Invoke-SqliteQuery -Query $Query -DataSource $OF_DBpath - - $progressCounter = 1 #Used for the progress UI - foreach ($OFDBMedia in $OFDBQueryResult){ - - #Let's help the user see how we are progressing through this metadata database - $currentProgress = [int]$(($progressCounter/$OFDBQueryResult.count)*100) - Write-Progress -Activity "Import Progress" -Status "$currentProgress% Complete" -PercentComplete $currentProgress - $progressCounter++ - - #Generating the URL for this post - $linktoOFpost = "https://www.onlyfans.com/"+$OFDBMedia.post_ID+"/"+$performername - - #Reformatting the date to something stash appropriate - $creationdatefromOF = $OFDBMedia.created_at - $creationdatefromOF = Get-Date $creationdatefromOF -format "yyyy-MM-dd" - - $OFDBfilesize = $OFDBMedia.size #filesize (in bytes) of the media, from the OF DB - $OFDBfilename = $OFDBMedia.filename #This defines filename of the media, from the OF DB - $OFDBdirectory = $OFDBMedia.directory #This defines the file directory of the media, from the OF DB - $OFDBFullFilePath = $OFDBdirectory+$directorydelimiter+$OFDBfilename #defines the full file path, using the OS appropriate delimeter - - #Storing separate variants of these variables with apostrophy and backslash sanitization so they don't ruin our SQL/GQL queries - $OFDBfilenameForQuery = $OFDBfilename.replace("'","''") - $OFDBdirectoryForQuery = $OFDBdirectory.replace("'","''") - $OFDBfilenameForQuery = $OFDBfilename.replace("\","\\") - $OFDBdirectoryForQuery = $OFDBdirectory.replace("\","\\") - - #Note that the OF downloader quantifies gifs as videos for some reason - #Since Stash doesn't (and rightfully so), we need to account for this - if(($OFDBMedia.media_type -eq "videos") -and ($OFDBfilename -notlike "*.gif")){ - $mediatype = "video" - } - #Condition for images. Again, we have to add an extra condition just in case the image is a gif due to the DG database - elseif(($OFDBMedia.media_type -eq "images") -or ($OFDBfilename -like "*.gif")){ - $mediatype = "image" - } - - #Depending on the user preference, we may not want to actually process the media we're currently looking at. Let's check before continuing. - if (($mediaToProcessSelector -eq 2) -and ($mediatype -eq "image")){ - #There's a scenario where because the user has not pulled any images for this performer, there will be no performer image. In that scenario, lets pull exactly one image for this purpose - if ($boolGetPerformerImage){ - $boolGetPerformerImage = $false #Let's make sure we don't pull any more photos + #Let's help the user see how we are progressing through this metadata database + $currentProgress = [int]$(($progressCounter/$OFDBQueryResult.count)*100) + Write-Progress -Activity "Import Progress" -Status "$currentProgress% Complete" -PercentComplete $currentProgress + $progressCounter++ + + #Generating the URL for this post + $linktoOFpost = "https://www.onlyfans.com/"+$OFDBMedia.post_ID+"/"+$performername + + #Reformatting the date to something stash appropriate + $creationdatefromOF = $OFDBMedia.created_at + $creationdatefromOF = Get-Date $creationdatefromOF -format "yyyy-MM-dd" + + $OFDBfilesize = $OFDBMedia.size #filesize (in bytes) of the media, from the OF DB + $OFDBfilename = $OFDBMedia.filename #This defines filename of the media, from the OF DB + $OFDBdirectory = $OFDBMedia.directory #This defines the file directory of the media, from the OF DB + $OFDBFullFilePath = $OFDBdirectory+$directorydelimiter+$OFDBfilename #defines the full file path, using the OS appropriate delimeter + + #Storing separate variants of these variables with apostrophy and backslash sanitization so they don't ruin our SQL/GQL queries + $OFDBfilenameForQuery = $OFDBfilename.replace("'","''") + $OFDBdirectoryForQuery = $OFDBdirectory.replace("'","''") + $OFDBfilenameForQuery = $OFDBfilename.replace("\","\\") + $OFDBdirectoryForQuery = $OFDBdirectory.replace("\","\\") + + #Note that the OF downloader quantifies gifs as videos for some reason + #Since Stash doesn't (and rightfully so), we need to account for this + if(($OFDBMedia.media_type -eq "videos") -and ($OFDBfilename -notlike "*.gif")){ + $mediatype = "video" } - else{ - continue #Skip to the next item in this foreach, user only wants to process videos + #Condition for images. Again, we have to add an extra condition just in case the image is a gif due to the DG database + elseif(($OFDBMedia.media_type -eq "images") -or ($OFDBfilename -like "*.gif")){ + $mediatype = "image" } - } - - if (($mediaToProcessSelector -eq 3) -and ($mediatype -eq "video")){ - continue #Skip to the next item in this foreach, user only wants to process images - } - - #Depending on user preference, we want to be more/less specific with our SQL queries to the Stash DB here, as determined by this condition tree (defined in order of percieved popularity) - #Normal specificity, search for videos based on having the performer name somewhere in the path and a matching filesize - if ($mediatype -eq "video" -and $searchspecificity -match "normal"){ - $StashGQL_Query = 'mutation { - querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, scenes.id AS scenes_id, scenes.title AS scenes_title, scenes.details AS scenes_details FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN scenes_files ON files.id = scenes_files.file_id JOIN scenes ON scenes.id = scenes_files.scene_id WHERE path LIKE ''%'+$performername+'%'' AND size = '''+$OFDBfilesize+'''") { - rows - } - }' - } - #Normal specificity, search for images based on having the performer name somewhere in the path and a matching filesize - elseif ($mediatype -eq "image" -and $searchspecificity -match "normal"){ - $StashGQL_Query = 'mutation { - querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, images.id AS images_id, images.title AS images_title FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN images_files ON files.id = images_files.file_id JOIN images ON images.id = images_files.image_id WHERE path LIKE ''%'+$performername+'%'' AND size = '''+$OFDBfilesize+'''") { - rows - } - }' - } - #Low specificity, search for videos based on filesize only - elseif ($mediatype -eq "video" -and $searchspecificity -match "low"){ - $StashGQL_Query = 'mutation { - querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, scenes.id AS scenes_id, scenes.title AS scenes_title, scenes.details AS scenes_details FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN scenes_files ON files.id = scenes_files.file_id JOIN scenes ON scenes.id = scenes_files.scene_id WHERE size = '''+$OFDBfilesize+'''") { - rows - } - }' - } - #Low specificity, search for images based on filesize only - elseif ($mediatype -eq "image" -and $searchspecificity -match "low"){ - $StashGQL_Query = 'mutation { - querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, images.id AS images_id, images.title AS images_title FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN images_files ON files.id = images_files.file_id JOIN images ON images.id = images_files.image_id WHERE size = '''+$OFDBfilesize+'''") { - rows - } - }' - } - - #High specificity, search for videos based on matching file path between OnlyFans DB and Stash DB as well as matching the filesize. - elseif ($mediatype -eq "video" -and $searchspecificity -match "high"){ - $StashGQL_Query = 'mutation { - querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, scenes.id AS scenes_id, scenes.title AS scenes_title, scenes.details AS scenes_details FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN scenes_files ON files.id = scenes_files.file_id JOIN scenes ON scenes.id = scenes_files.scene_id WHERE path ='''+$OFDBdirectoryForQuery+''' AND files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { - rows - } - }' - } - - #High specificity, search for images based on matching file path between OnlyFans DB and Stash DB as well as matching the filesize. - else{ - $StashGQL_Query = 'mutation { - querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, images.id AS images_id, images.title AS images_title FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN images_files ON files.id = images_files.file_id JOIN images ON images.id = images_files.image_id WHERE path ='''+$OFDBdirectoryForQuery+''' AND files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { - rows - } - }' - } - - #Now lets try running the GQL query and see if we have a match in the Stash DB - try{ - $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL - } - catch{ - write-host "(4) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red - write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" - read-host "Press [Enter] to exit" - exit - } - - if ($StashGQL_Result.data.querySQL.rows.length -ne 0){ - - #Because of how GQL returns data, these values are just positions in the $StashGQLQuery array. Not super memorable, so I'm putting them in variables. - $CurrentFileID = $StashGQL_Result.data.querySQL.rows[0][5] #This represents either the scene ID or the image ID - $CurrentFileTitle = $StashGQL_Result.data.querySQL.rows[0][6] - } - - #If our search for matching media in Stash itself comes up empty, let's check to see if the file even exists on the file system - if ($StashGQL_Result.data.querySQL.rows.length -eq 0 ){ - if (Test-Path $OFDBFullFilePath){ - write-host "`nInfo: There's a file in this OnlyFans metadata database that we couldn't find in your Stash database but the file IS on your filesystem.`nTry running a Scan Task in Stash then re-running this script.`n`n - $OFDBFullFilePath`n" -ForegroundColor Cyan + + #Depending on the user preference, we may not want to actually process the media we're currently looking at. Let's check before continuing. + if (($mediaToProcessSelector -eq 2) -and ($mediatype -eq "image")){ + #There's a scenario where because the user has not pulled any images for this performer, there will be no performer image. In that scenario, lets pull exactly one image for this purpose + if ($boolGetPerformerImage){ + $boolGetPerformerImage = $false #Let's make sure we don't pull any more photos + } + else{ + continue #Skip to the next item in this foreach, user only wants to process videos + } } - #In this case, the media isn't in Stash or on the filesystem so inform the user, log the file, and move on + + if (($mediaToProcessSelector -eq 3) -and ($mediatype -eq "video")){ + continue #Skip to the next item in this foreach, user only wants to process images + } + + #Depending on user preference, we want to be more/less specific with our SQL queries to the Stash DB here, as determined by this condition tree (defined in order of percieved popularity) + #Normal specificity, search for videos based on having the performer name somewhere in the path and a matching filesize + if ($mediatype -eq "video" -and $searchspecificity -match "normal"){ + $StashGQL_Query = 'mutation { + querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, scenes.id AS scenes_id, scenes.title AS scenes_title, scenes.details AS scenes_details FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN scenes_files ON files.id = scenes_files.file_id JOIN scenes ON scenes.id = scenes_files.scene_id WHERE path LIKE ''%'+$performername+'%'' AND size = '''+$OFDBfilesize+'''") { + rows + } + }' + } + #Normal specificity, search for images based on having the performer name somewhere in the path and a matching filesize + elseif ($mediatype -eq "image" -and $searchspecificity -match "normal"){ + $StashGQL_Query = 'mutation { + querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, images.id AS images_id, images.title AS images_title FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN images_files ON files.id = images_files.file_id JOIN images ON images.id = images_files.image_id WHERE path LIKE ''%'+$performername+'%'' AND size = '''+$OFDBfilesize+'''") { + rows + } + }' + } + #Low specificity, search for videos based on filesize only + elseif ($mediatype -eq "video" -and $searchspecificity -match "low"){ + $StashGQL_Query = 'mutation { + querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, scenes.id AS scenes_id, scenes.title AS scenes_title, scenes.details AS scenes_details FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN scenes_files ON files.id = scenes_files.file_id JOIN scenes ON scenes.id = scenes_files.scene_id WHERE size = '''+$OFDBfilesize+'''") { + rows + } + }' + } + #Low specificity, search for images based on filesize only + elseif ($mediatype -eq "image" -and $searchspecificity -match "low"){ + $StashGQL_Query = 'mutation { + querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, images.id AS images_id, images.title AS images_title FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN images_files ON files.id = images_files.file_id JOIN images ON images.id = images_files.image_id WHERE size = '''+$OFDBfilesize+'''") { + rows + } + }' + } + + #High specificity, search for videos based on matching file path between OnlyFans DB and Stash DB as well as matching the filesize. + elseif ($mediatype -eq "video" -and $searchspecificity -match "high"){ + $StashGQL_Query = 'mutation { + querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, scenes.id AS scenes_id, scenes.title AS scenes_title, scenes.details AS scenes_details FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN scenes_files ON files.id = scenes_files.file_id JOIN scenes ON scenes.id = scenes_files.scene_id WHERE path ='''+$OFDBdirectoryForQuery+''' AND files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { + rows + } + }' + } + + #High specificity, search for images based on matching file path between OnlyFans DB and Stash DB as well as matching the filesize. else{ - write-host "`nInfo: There's a file in this OnlyFans metadata database that we couldn't find in your Stash database.`nThis file also doesn't appear to be on your filesystem.`nTry rerunning the script you used to scrape this OnlyFans performer and redownloading the file.`n`n - $OFDBFullFilePath`n" -ForegroundColor Cyan - Add-Content -Path $PathToMissingFilesLog -value " $OFDBFullFilePath" - $nummissingfiles++ + $StashGQL_Query = 'mutation { + querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, images.id AS images_id, images.title AS images_title FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN images_files ON files.id = images_files.file_id JOIN images ON images.id = images_files.image_id WHERE path ='''+$OFDBdirectoryForQuery+''' AND files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { + rows + } + }' + } + + #Now lets try running the GQL query and see if we have a match in the Stash DB + try{ + $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL + } + catch{ + write-host "(4) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" + read-host "Press [Enter] to exit" + exit + } + + if ($StashGQL_Result.data.querySQL.rows.length -ne 0){ + + #Because of how GQL returns data, these values are just positions in the $StashGQLQuery array. Not super memorable, so I'm putting them in variables. + $CurrentFileID = $StashGQL_Result.data.querySQL.rows[0][5] #This represents either the scene ID or the image ID + $CurrentFileTitle = $StashGQL_Result.data.querySQL.rows[0][6] } - } - #Otherwise we have found a match! let's process the matching result and add the metadata we've found - else{ - #Before processing, and for the sake of accuracy, if there are multiple filesize matches (specifically for the normal specificity mode), add a filename check to the query to see if we can match more specifically. If not, just use whatever matched that initial query. - if (($StashGQL_Result.data.querySQL.rows.length -gt 1) -and ($searchspecificity -match "normal") ){ - #Search for videos based on having the performer name somewhere in the path and a matching filesize (and filename in this instance) - if ($mediatype -eq "video"){ - - $StashGQL_Query = 'mutation { - querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, scenes.id AS scenes_id, scenes.title AS scenes_title, scenes.details AS scenes_details FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN scenes_files ON files.id = scenes_files.file_id JOIN scenes ON scenes.id = scenes_files.scene_id path LIKE ''%'+$performername+'%'' AND files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { - rows - } - }' - } - - #Search for images based on having the performer name somewhere in the path and a matching filesize (and filename in this instance) - elseif ($mediatype -eq "image" ){ - - $StashGQL_Query = 'mutation { - querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, images.id AS images_id, images.title AS images_title FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN images_files ON files.id = images_files.file_id JOIN images ON images.id = images_files.image_id WHERE path LIKE ''%'+$performername+'%'' AND files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { - rows - } - }' + #If our search for matching media in Stash itself comes up empty, let's check to see if the file even exists on the file system + if ($StashGQL_Result.data.querySQL.rows.length -eq 0 ){ + if (Test-Path $OFDBFullFilePath){ + write-host "`nInfo: There's a file in this OnlyFans metadata database that we couldn't find in your Stash database but the file IS on your filesystem.`nTry running a Scan Task in Stash then re-running this script.`n`n - $OFDBFullFilePath`n" -ForegroundColor Cyan } - - #Now lets try running the GQL query and try to find the file in the Stash DB - try{ - $AlternativeStashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL - } - catch{ - write-host "(5) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red - write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" - read-host "Press [Enter] to exit" - exit + #In this case, the media isn't in Stash or on the filesystem so inform the user, log the file, and move on + else{ + write-host "`nInfo: There's a file in this OnlyFans metadata database that we couldn't find in your Stash database.`nThis file also doesn't appear to be on your filesystem.`nTry rerunning the script you used to scrape this OnlyFans performer and redownloading the file.`n`n - $OFDBFullFilePath`n" -ForegroundColor Cyan + Add-Content -Path $PathToMissingFilesLog -value " $OFDBFullFilePath" + $nummissingfiles++ } - - #If we have a match, substitute it in and lets get that metadata into the Stash DB - if($StashGQL_Result_2.data.querySQL.rows -eq 1){ - $StashGQL_Result = $AlternativeStashGQL_Result - $CurrentFileID = $StashGQL_Result.data.querySQL.rows[0][5] #This represents either the scene ID or the image ID - $CurrentFileTitle = $StashGQL_Result.data.querySQL.rows[0][6] - } } - - #Creating the title we want for the media, and defining Stash details for this media. - $proposedtitle = "$performername - $creationdatefromOF" - $detailsToAddToStash = $OFDBMedia.text - - - #Performers love to put links in their posts sometimes. Let's scrub those out in addition to any common HTML bits - $detailsToAddToStash = $detailsToAddToStash.Replace("
","") - $detailsToAddToStash = $detailsToAddToStash.Replace("',"") - $detailsToAddToStash = $detailsToAddToStash.Replace(""," ") - $detailsToAddToStash = $detailsToAddToStash.Replace('target="_blank"',"") - - #For some reason the invoke-graphqlquery module doesn't quite escape single/double quotes ' " (or their curly variants) or backslashs \ very well so let's do it manually for the sake of our JSON query - $detailsToAddToStash = $detailsToAddToStash.replace("'","''") - $detailsToAddToStash = $detailsToAddToStash.replace("\","\\") - $detailsToAddToStash = $detailsToAddToStash.replace('"','\"') - $detailsToAddToStash = $detailsToAddToStash.replace('“','\"') #literally removing the curly quote entirely - $detailsToAddToStash = $detailsToAddToStash.replace('”','\"') #literally removing the curly quote entirely - - $proposedtitle = $proposedtitle.replace("'","''") - $proposedtitle = $proposedtitle.replace("\","\\") - $proposedtitle = $proposedtitle.replace('"','\"') - $proposedtitle = $proposedtitle.replace('“','\"') #literally removing the curly quote entirely - $proposedtitle = $proposedtitle.replace('”','\"') #literally removing the curly quote entirely - - #Let's check to see if this is a file that already has metadata. - #For Videos, we check the title and the details - #For Images, we only check title (for now) - #If any metadata is missing, we don't bother with updating a specific column, we just update the entire row - if ($mediatype -eq "video"){ - #By default we will claim this file to be unmodified (we use this for user stats at the end of the script) - $filewasmodified = $false - - #Let's determine if this scene already has the right performer associated to it - $StashGQL_Query = 'query FindScene($id:ID!) { - findScene(id: $id){ - performers { - id - } - } - - }' - $StashGQL_QueryVariables = '{ - "id": "'+$CurrentFileID+'" - }' + #Otherwise we have found a match! let's process the matching result and add the metadata we've found + else{ - try{ - $DiscoveredPerformerIDFromStash = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables - } - catch{ - write-host "(6) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red - write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" - read-host "Press [Enter] to exit" - exit - } - - $performermatch = $false - if ($null -ne $DiscoveredPerformerIDFromStash.data.findscene.performers.length){ - foreach ($performer in $DiscoveredPerformerIDFromStash.data.findscene.performers.id){ - if($performer -eq $performerid){ - $performermatch = $true - break - } + #Before processing, and for the sake of accuracy, if there are multiple filesize matches (specifically for the normal specificity mode), add a filename check to the query to see if we can match more specifically. If not, just use whatever matched that initial query. + if (($StashGQL_Result.data.querySQL.rows.length -gt 1) -and ($searchspecificity -match "normal") ){ + #Search for videos based on having the performer name somewhere in the path and a matching filesize (and filename in this instance) + if ($mediatype -eq "video"){ + + $StashGQL_Query = 'mutation { + querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, scenes.id AS scenes_id, scenes.title AS scenes_title, scenes.details AS scenes_details FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN scenes_files ON files.id = scenes_files.file_id JOIN scenes ON scenes.id = scenes_files.scene_id path LIKE ''%'+$performername+'%'' AND files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { + rows + } + }' } - } - if (!$performermatch){ - $filewasmodified = $true - $StashGQL_Query = 'mutation sceneUpdate($sceneUpdateInput: SceneUpdateInput!){ - sceneUpdate(input: $sceneUpdateInput){ - id - performers{ - id - } - } - }' - $StashGQL_QueryVariables = ' { - "sceneUpdateInput": { - "id": "'+$CurrentFileID+'", - "performer_ids": "'+$performerID+'" - } - }' + + #Search for images based on having the performer name somewhere in the path and a matching filesize (and filename in this instance) + elseif ($mediatype -eq "image" ){ + + $StashGQL_Query = 'mutation { + querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, images.id AS images_id, images.title AS images_title FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN images_files ON files.id = images_files.file_id JOIN images ON images.id = images_files.image_id WHERE path LIKE ''%'+$performername+'%'' AND files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { + rows + } + }' + } + + #Now lets try running the GQL query and try to find the file in the Stash DB try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null + $AlternativeStashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL } catch{ - write-host "(7) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "(5) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" read-host "Press [Enter] to exit" exit } + + #If we have a match, substitute it in and lets get that metadata into the Stash DB + if($StashGQL_Result_2.data.querySQL.rows -eq 1){ + $StashGQL_Result = $AlternativeStashGQL_Result + $CurrentFileID = $StashGQL_Result.data.querySQL.rows[0][5] #This represents either the scene ID or the image ID + $CurrentFileTitle = $StashGQL_Result.data.querySQL.rows[0][6] + } } - - #If it's necessary, update the scene by modifying the title and adding details - if($CurrentFileTitle -ne $proposedtitle){ - $StashGQL_Query = 'mutation sceneUpdate($sceneUpdateInput: SceneUpdateInput!){ - sceneUpdate(input: $sceneUpdateInput){ - id - title - date - studio { - id - } - details - urls - } - }' - $StashGQL_QueryVariables = '{ - "sceneUpdateInput": { - "id": "'+$CurrentFileID+'", - "title": "'+$proposedtitle+'", - "date": "'+$creationdatefromOF+'", - "studio_id": "'+$OnlyFansStudioID+'", - "details": "'+$detailsToAddToStash+'", - "urls": "'+$linktoOFpost+'" + + #Creating the title we want for the media, and defining Stash details for this media. + $proposedtitle = "$performername - $creationdatefromOF" + $detailsToAddToStash = $OFDBMedia.text + + + #Performers love to put links in their posts sometimes. Let's scrub those out in addition to any common HTML bits + $detailsToAddToStash = $detailsToAddToStash.Replace("
","") + $detailsToAddToStash = $detailsToAddToStash.Replace("',"") + $detailsToAddToStash = $detailsToAddToStash.Replace(""," ") + $detailsToAddToStash = $detailsToAddToStash.Replace('target="_blank"',"") + + #For some reason the invoke-graphqlquery module doesn't quite escape single/double quotes ' " (or their curly variants) or backslashs \ very well so let's do it manually for the sake of our JSON query + $detailsToAddToStash = $detailsToAddToStash.replace("'","''") + $detailsToAddToStash = $detailsToAddToStash.replace("\","\\") + $detailsToAddToStash = $detailsToAddToStash.replace('"','\"') + $detailsToAddToStash = $detailsToAddToStash.replace('“','\"') #literally removing the curly quote entirely + $detailsToAddToStash = $detailsToAddToStash.replace('”','\"') #literally removing the curly quote entirely + + $proposedtitle = $proposedtitle.replace("'","''") + $proposedtitle = $proposedtitle.replace("\","\\") + $proposedtitle = $proposedtitle.replace('"','\"') + $proposedtitle = $proposedtitle.replace('“','\"') #literally removing the curly quote entirely + $proposedtitle = $proposedtitle.replace('”','\"') #literally removing the curly quote entirely + + #Let's check to see if this is a file that already has metadata. + #For Videos, we check the title and the details + #For Images, we only check title (for now) + #If any metadata is missing, we don't bother with updating a specific column, we just update the entire row + if ($mediatype -eq "video"){ + #By default we will claim this file to be unmodified (we use this for user stats at the end of the script) + $filewasmodified = $false + + #Let's determine if this scene already has the right performer associated to it + $StashGQL_Query = 'query FindScene($id:ID!) { + findScene(id: $id){ + performers { + id + } } + }' - + $StashGQL_QueryVariables = '{ + "id": "'+$CurrentFileID+'" + }' + try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -escapehandling EscapeNonAscii | out-null + $DiscoveredPerformerIDFromStash = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables } catch{ - write-host "(8) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "(6) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" - read-host "Press [Enter] to exit" + read-host "Press [Enter] to exit" exit } - - $filewasmodified = $true - } - - #Provide user feedback on what has occured and add to the "file modified" counter for stats later - if ($filewasmodified){ - if ($ConsoleVerbosity -eq "Verbose"){ - write-output "- Added metadata to Stash's database for the following file:`n $OFDBFullFilePath" - } - $numModified++ - } - else{ - if ($ConsoleVerbosity -eq "Verbose"){ - write-output "- This file already has metadata, moving on...`n $OFDBFullFilePath" - } - $numUnmodified++ - } - } - - #For images - else{ - #By default we will claim this file to be unmodified (we use this for user stats at the end of the script) - $filewasmodified = $false - - #Let's determine if this Image already has the right performer associated to it - $StashGQL_Query = 'query FindImage($id:ID!) { - findImage(id: $id){ - performers { - id - } - } - - }' - $StashGQL_QueryVariables = '{ - "id": "'+$CurrentFileID+'" - }' - - try{ - $DiscoveredPerformerIDFromStash = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables - } - catch{ - write-host "(6) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red - write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" - read-host "Press [Enter] to exit" - exit - } - - $performermatch = $false - if ($null -ne $DiscoveredPerformerIDFromStash.data.findimage.performers.length){ - foreach ($performer in $DiscoveredPerformerIDFromStash.data.findimage.performers.id){ - if($performer -eq $performerid){ - $performermatch = $true - break + + $performermatch = $false + if ($null -ne $DiscoveredPerformerIDFromStash.data.findscene.performers.length){ + foreach ($performer in $DiscoveredPerformerIDFromStash.data.findscene.performers.id){ + if($performer -eq $performerid){ + $performermatch = $true + break + } } } - } - if (!$performermatch){ - $filewasmodified = $true - $StashGQL_Query = 'mutation imageUpdate($imageUpdateInput: ImageUpdateInput!){ - imageUpdate(input: $imageUpdateInput){ - id - performers{ + if (!$performermatch){ + $filewasmodified = $true + $StashGQL_Query = 'mutation sceneUpdate($sceneUpdateInput: SceneUpdateInput!){ + sceneUpdate(input: $sceneUpdateInput){ id + performers{ + id + } } + }' + $StashGQL_QueryVariables = ' { + "sceneUpdateInput": { + "id": "'+$CurrentFileID+'", + "performer_ids": "'+$performerID+'" + } + }' + try{ + Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null } - }' - $StashGQL_QueryVariables = ' { - "imageUpdateInput": { - "id": "'+$CurrentFileID+'", - "performer_ids": "'+$performerID+'" + catch{ + write-host "(7) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" + read-host "Press [Enter] to exit" + exit } - }' - try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null } - catch{ - write-host "(7) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red - write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" - read-host "Press [Enter] to exit" - exit - } - - } - - #If it's necessary, update the image by modifying the title and adding details - if($CurrentFileTitle -ne $proposedtitle){ - if ($boolSetImageDetails -eq $true){ - $StashGQL_Query = 'mutation imageUpdate($imageUpdateInput: ImageUpdateInput!){ - imageUpdate(input: $imageUpdateInput){ + + #If it's necessary, update the scene by modifying the title and adding details + if($CurrentFileTitle -ne $proposedtitle){ + $StashGQL_Query = 'mutation sceneUpdate($sceneUpdateInput: SceneUpdateInput!){ + sceneUpdate(input: $sceneUpdateInput){ id title date studio { id } - urls details + urls } }' - $StashGQL_QueryVariables = '{ - "imageUpdateInput": { + "sceneUpdateInput": { "id": "'+$CurrentFileID+'", "title": "'+$proposedtitle+'", "date": "'+$creationdatefromOF+'", @@ -1103,61 +1072,183 @@ function Add-MetadataUsingOFDB{ "urls": "'+$linktoOFpost+'" } }' + + try{ + Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -escapehandling EscapeNonAscii | out-null + } + catch{ + write-host "(8) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" + read-host "Press [Enter] to exit" + exit + } + + $filewasmodified = $true + } + + #Provide user feedback on what has occured and add to the "file modified" counter for stats later + if ($filewasmodified){ + if ($ConsoleVerbosity -eq "Verbose"){ + write-output "- Added metadata to Stash's database for the following file:`n $OFDBFullFilePath" + } + $numModified++ } else{ - $StashGQL_Query = 'mutation imageUpdate($imageUpdateInput: ImageUpdateInput!){ - imageUpdate(input: $imageUpdateInput){ - id - title - date - studio { - id - } - urls - } - }' - - $StashGQL_QueryVariables = '{ - "imageUpdateInput": { - "id": "'+$CurrentFileID+'", - "title": "'+$proposedtitle+'", - "date": "'+$creationdatefromOF+'", - "studio_id": "'+$OnlyFansStudioID+'", - "urls": "'+$linktoOFpost+'" - } - }' + if ($ConsoleVerbosity -eq "Verbose"){ + write-output "- This file already has metadata, moving on...`n $OFDBFullFilePath" + } + $numUnmodified++ } + } + + #For images + else{ + #By default we will claim this file to be unmodified (we use this for user stats at the end of the script) + $filewasmodified = $false + + #Let's determine if this Image already has the right performer associated to it + $StashGQL_Query = 'query FindImage($id:ID!) { + findImage(id: $id){ + performers { + id + } + } + }' + $StashGQL_QueryVariables = '{ + "id": "'+$CurrentFileID+'" + }' try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null + $DiscoveredPerformerIDFromStash = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables } catch{ - write-host "(8) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "(6) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" read-host "Press [Enter] to exit" exit } - - $filewasmodified = $true - } - - #Provide user feedback on what has occured and add to the "file modified" counter for stats later - if ($filewasmodified){ - if ($ConsoleVerbosity -eq "Verbose"){ - write-output "- Added metadata to Stash's database for the following file:`n $OFDBFullFilePath" + + $performermatch = $false + if ($null -ne $DiscoveredPerformerIDFromStash.data.findimage.performers.length){ + foreach ($performer in $DiscoveredPerformerIDFromStash.data.findimage.performers.id){ + if($performer -eq $performerid){ + $performermatch = $true + break + } + } } - $numModified++ - } - else{ - if ($ConsoleVerbosity -eq "Verbose"){ - write-output "- This file already has metadata, moving on...`n $OFDBFullFilePath" + if (!$performermatch){ + $filewasmodified = $true + $StashGQL_Query = 'mutation imageUpdate($imageUpdateInput: ImageUpdateInput!){ + imageUpdate(input: $imageUpdateInput){ + id + performers{ + id + } + } + }' + $StashGQL_QueryVariables = ' { + "imageUpdateInput": { + "id": "'+$CurrentFileID+'", + "performer_ids": "'+$performerID+'" + } + }' + try{ + Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null + } + catch{ + write-host "(7) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" + read-host "Press [Enter] to exit" + exit + } + } - $numUnmodified++ - } - } + + #If it's necessary, update the image by modifying the title and adding details + if($CurrentFileTitle -ne $proposedtitle){ + if ($boolSetImageDetails -eq $true){ + $StashGQL_Query = 'mutation imageUpdate($imageUpdateInput: ImageUpdateInput!){ + imageUpdate(input: $imageUpdateInput){ + id + title + date + studio { + id + } + urls + details + } + }' + + $StashGQL_QueryVariables = '{ + "imageUpdateInput": { + "id": "'+$CurrentFileID+'", + "title": "'+$proposedtitle+'", + "date": "'+$creationdatefromOF+'", + "studio_id": "'+$OnlyFansStudioID+'", + "details": "'+$detailsToAddToStash+'", + "urls": "'+$linktoOFpost+'" + } + }' + } + else{ + $StashGQL_Query = 'mutation imageUpdate($imageUpdateInput: ImageUpdateInput!){ + imageUpdate(input: $imageUpdateInput){ + id + title + date + studio { + id + } + urls + } + }' + + $StashGQL_QueryVariables = '{ + "imageUpdateInput": { + "id": "'+$CurrentFileID+'", + "title": "'+$proposedtitle+'", + "date": "'+$creationdatefromOF+'", + "studio_id": "'+$OnlyFansStudioID+'", + "urls": "'+$linktoOFpost+'" + } + }' + } + + + try{ + Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null + } + catch{ + write-host "(8) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" + read-host "Press [Enter] to exit" + exit + } + + $filewasmodified = $true + } + + #Provide user feedback on what has occured and add to the "file modified" counter for stats later + if ($filewasmodified){ + if ($ConsoleVerbosity -eq "Verbose"){ + write-output "- Added metadata to Stash's database for the following file:`n $OFDBFullFilePath" + } + $numModified++ + } + else{ + if ($ConsoleVerbosity -eq "Verbose"){ + write-output "- This file already has metadata, moving on...`n $OFDBFullFilePath" + } + $numUnmodified++ + } + } + } } } + #Before we move on, if we had created a new performer, let's update that performer with a profile image. #The only reason we don't do it earlier is that now all the images have been added and associated and it's easy to select an image and go. if($creatednewperformer){ From e201111e07669cd5b79a87f9b1f14af532652c45 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Sun, 25 Feb 2024 01:45:29 -0800 Subject: [PATCH 07/20] Feature - Better avatar image selection Iterating on the previous build, script will now do a better job of hunting down the official performer avatar image, if it was downloaded to the filesystem. --- OFMetadataToStash.ps1 | 144 ++++++++++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 54 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index 1e9bc33..dca4b8d 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -1255,60 +1255,19 @@ function Add-MetadataUsingOFDB{ #First let's look for an image where this performer has been associated and get the URL, for that image #Sometimes these OF downloaders pull profile/avatar photos into a specific folder. We'll look to see if we can match on that first before just choosing what we can get. - #FINISHME - $performerimageURL_GQLQuery = 'query FindImages( - $filter: FindFilterType - $image_filter: ImageFilterType - $image_ids: [Int!] - ) { - findImages( - filter: $filter, - image_filter: $image_filter, - image_ids: $image_ids){ - images{ - paths{ - image - } - } - } - }' - $performerimageURLVariables_GQLQuery = ' - { - "filter": { - "q": "", - "page": 1, - "per_page": 1, - "sort": "date", - "direction": "DESC" - }, - "image_filter": { - "performers": { - "value": [ - "'+$performerID+'" - ], - "excludes": [], - "modifier": "INCLUDES_ALL" - } - } - }' + #Using the filepath of the metadata database as our starting point, we'll go a folder up and then look for an image containing the keyword "avatar" + $pathToAvatarImage = (get-item $OF_DBpath).Parent + $pathToAvatarImage = "$pathToAvatarImage"+"$directorydelimiter"+"Profile"+$directorydelimiter+"Images" + $pathToAvatarImage = Get-ChildItem $pathToAvatarImage where-object {$_.name -like "avatar*"} - try{ - $performerimageURL = Invoke-GraphQLQuery -Query $performerimageURL_GQLQuery -Uri $StashGQL_URL -Variables $performerimageURLVariables_GQLQuery - - } - catch{ - write-host "(11) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red - write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" - read-host "Press [Enter] to exit" - exit - } + #If we DID find an avatar on the filesystem + if ($null -ne $pathToAvatarImage){ - #If there are any Performer images to be used, we update the performer using the URL path. - if ($performerimageURL.data.findimages.images.length -ne 0){ - $performerimageURL = $performerimageURL.data.findimages.images.paths.image + #Convert the image to base64. Note that this is designed for jpegs-- I don't think OnlyFans supports anything else anyway. + $avatarImageBase64 = [convert]::ToBase64String((Get-Content $path -AsByteStream)) + $avatarImageBase64 = "data:image/jpeg;base64,"+$avatarImageBase64 - $UpdatePerformerImage_GQLQuery ='mutation PerformerUpdate($input: PerformerUpdateInput!) { performerUpdate(input: $input) { id @@ -1317,19 +1276,97 @@ function Add-MetadataUsingOFDB{ $UpdatePerformerImage_GQLVariables = '{ "input": { "id": "'+$performerID+'", - "image": "'+$performerimageURL+'" + "image": "'+$avatarImageBase64+'" } }' try{ - $performerimageURL = Invoke-GraphQLQuery -Query $UpdatePerformerImage_GQLQuery -Uri $StashGQL_URL -Variables $UpdatePerformerImage_GQLVariables | out-null + Invoke-GraphQLQuery -Query $UpdatePerformerImage_GQLQuery -Uri $StashGQL_URL -Variables $UpdatePerformerImage_GQLVariables | out-null + } + catch{ + write-host "(46) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" + read-host "Press [Enter] to exit" + exit + } + } + #If we didn't find anything on the filesystem, let's just query Stash and use a random image from this performer's OF page + else{ + $performerimageURL_GQLQuery = 'query FindImages( + $filter: FindFilterType + $image_filter: ImageFilterType + $image_ids: [Int!] + ) { + findImages( + filter: $filter, + image_filter: $image_filter, + image_ids: $image_ids){ + images{ + paths{ + image + } + } + } + }' + + $performerimageURLVariables_GQLQuery = ' + { + "filter": { + "q": "", + "page": 1, + "per_page": 1, + "sort": "date", + "direction": "DESC" + }, + "image_filter": { + "performers": { + "value": [ + "'+$performerID+'" + ], + "excludes": [], + "modifier": "INCLUDES_ALL" + } + } + }' + + try{ + $performerimageURL = Invoke-GraphQLQuery -Query $performerimageURL_GQLQuery -Uri $StashGQL_URL -Variables $performerimageURLVariables_GQLQuery + } catch{ - write-host "(12) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "(11) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" read-host "Press [Enter] to exit" exit } + + #If there are any Performer images to be used, we update the performer using the URL path. + if ($performerimageURL.data.findimages.images.length -ne 0){ + $performerimageURL = $performerimageURL.data.findimages.images.paths.image + + + $UpdatePerformerImage_GQLQuery ='mutation PerformerUpdate($input: PerformerUpdateInput!) { + performerUpdate(input: $input) { + id + } + }' + $UpdatePerformerImage_GQLVariables = '{ + "input": { + "id": "'+$performerID+'", + "image": "'+$performerimageURL+'" + } + }' + + try{ + $performerimageURL = Invoke-GraphQLQuery -Query $UpdatePerformerImage_GQLQuery -Uri $StashGQL_URL -Variables $UpdatePerformerImage_GQLVariables | out-null + } + catch{ + write-host "(12) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red + write-host "Additional Error Info: `n`n$StashGQL_Query `n$StashGQL_QueryVariables" + read-host "Press [Enter] to exit" + exit + } + } } } } @@ -1346,7 +1383,6 @@ function Add-MetadataUsingOFDB{ write-host "`n****** Import Complete ******"-ForegroundColor Cyan write-output "- Modified Scenes/Images: $numModified`n- Scenes/Images that already had metadata: $numUnmodified" - #Some quick date arithmetic to calculate elapsed time $scriptEndTime = Get-Date $scriptduration = ($scriptEndTime-$scriptStartTime).totalseconds From 4fd8b4f66060459c8988d1f101cb072550520b18 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Sun, 25 Feb 2024 02:01:28 -0800 Subject: [PATCH 08/20] Improvements to Progress Bar UI Script now displays both total progress for both the total scan and for the individual performer --- OFMetadataToStash.ps1 | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index dca4b8d..edeed33 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -635,7 +635,15 @@ function Add-MetadataUsingOFDB{ } + $totalprogressCounter = 1 #Used for the progress UI + foreach ($currentdatabase in $OFDatabaseFilesCollection) { + #Let's help the user see how we are progressing through this metadata database (this is the parent progress UI, there's an additional child below as well) + $currentTotalProgress = [int]$(($totalprogressCounter/$OFDatabaseFilesCollection.count)*100) + Write-Progress -Activity "Total Import Progress" -Status "$currentTotalProgress% Complete" -PercentComplete $currentTotalProgress + $totalprogressCounter++ + + #Gotta reparse the performer name as we may be parsing through a full collection of performers. #Otherwise you'll end up with a whole bunch of performers having the same name #This is also where we will make the determination if this onlyfans database has the right tables to be used here @@ -678,7 +686,6 @@ function Add-MetadataUsingOFDB{ $performername = $currentdatabase.FullName | split-path | split-path | split-path -leaf } } - write-host "`nInfo: Parsing media for $performername" -ForegroundColor Cyan #Let's see if we can find this performer in Stash $StashGQL_Query = ' @@ -780,9 +787,9 @@ function Add-MetadataUsingOFDB{ $progressCounter = 1 #Used for the progress UI foreach ($OFDBMedia in $OFDBQueryResult){ - #Let's help the user see how we are progressing through this metadata database + #Let's help the user see how we are progressing through this performer's metadata database $currentProgress = [int]$(($progressCounter/$OFDBQueryResult.count)*100) - Write-Progress -Activity "Import Progress" -Status "$currentProgress% Complete" -PercentComplete $currentProgress + Write-Progress -Activity "$performername Import Progress" -Status "$currentProgress% Complete" -PercentComplete $currentProgress $progressCounter++ #Generating the URL for this post From 2faa7b431a5adeaf0b26e33f50ce6134bd8082e4 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Sun, 25 Feb 2024 02:18:41 -0800 Subject: [PATCH 09/20] History Feature - Ignore history file flag Added a command line argument for temporarily disabling the use of the history file --- OFMetadataToStash.ps1 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index edeed33..bb0fe97 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -24,6 +24,11 @@ REQUIREMENTS Import-Module PSGraphQL Import-Module PSSQLite +#Command Line Arguments +param ( + [switch]$ignorehistory = $true #If the user doesn't want the script to make use of the history file, this will toggle its use. + ) + ### Functions @@ -778,7 +783,8 @@ function Add-MetadataUsingOFDB{ } #Let's check to see if we need to import this performer based on the history file. If this function returns false, move on to the next performer. - if (Get-PerformerHistory -or $ignorehistory -eq $true){ + #The ignorehistory variable is a command line flag that the user may set if they want to have the script ignore the use of the history file + if (Get-PerformerHistory -or ($ignorehistory -eq $true)){ #Select all the media (except audio) and the text the performer associated to them, if available from the OFDB $Query = "SELECT messages.text, medias.directory, medias.filename, medias.size, medias.created_at, medias.post_id, medias.media_type FROM medias INNER JOIN messages ON messages.post_id=medias.post_id UNION SELECT posts.text, medias.directory, medias.filename, medias.size, medias.created_at, medias.post_id, medias.media_type FROM medias INNER JOIN posts ON posts.post_id=medias.post_id WHERE medias.media_type <> 'Audios'" $OF_DBpath = $currentdatabase.fullname From 0c0d5db57a0aa1f4d83471ee2090ce910aa8c51e Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Sun, 25 Feb 2024 03:29:43 -0800 Subject: [PATCH 10/20] Feature - Added support for username/password Script now makes use of Stash API keys to allow folks who enable username/password on their Stash instances. Big shoutout to user maestra as I've copied their far more efficient use of subexpressions to get the job done. Kudos! --- OFMetadataToStash.ps1 | 116 ++++++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index bb0fe97..7ebf226 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -41,42 +41,57 @@ function Set-Config{ write-output "(1 of 4) Define the URL to your Stash" write-output "Option 1: Stash is hosted on the computer I'm using right now (localhost:9999)" write-output "Option 2: Stash is hosted at a different address and/or port (Ex. 192.168.1.2:6969)`n" + do{ + do { + $userselection = read-host "Enter your selection (1 or 2)" + } + while (($userselection -notmatch "[1-2]")) - do { - $userselection = read-host "Enter your selection (1 or 2)" - } - while (($userselection -notmatch "[1-2]")) - - if ($userselection -eq 1){ - $StashGQL_URL = "http://localhost:9999/graphql" - } + if ($userselection -eq 1){ + $StashGQL_URL = "http://localhost:9999/graphql" + } - #Asking the user for the Stash URL, with some error handling - else { - while ($null -eq $StashGQL_URL ){ - $StashGQL_URL = read-host "`nPlease enter the URL to your Stash" - $StashGQL_URL = $StashGQL_URL + '/graphql' #Tacking on the gql endpoint - - while (!($StashGQL_URL.contains(":"))){ - write-host "Error: Oops, looks like you forgot to enter the port number (Ex. :9999)." -ForegroundColor red + #Asking the user for the Stash URL, with some error handling + else { + while ($null -eq $StashGQL_URL ){ $StashGQL_URL = read-host "`nPlease enter the URL to your Stash" + $StashGQL_URL = $StashGQL_URL + '/graphql' #Tacking on the gql endpoint + + while (!($StashGQL_URL.contains(":"))){ + write-host "Error: Oops, looks like you forgot to enter the port number (Ex. :9999)." -ForegroundColor red + $StashGQL_URL = read-host "`nPlease enter the URL to your Stash" + } + + if (!($StashGQL_URL.contains("http"))){ + $StashGQL_URL = "http://"+$StashGQL_URL + } } - - if (!($StashGQL_URL.contains("http"))){ - $StashGQL_URL = "http://"+$StashGQL_URL - } - - #Now to check to ensure this address is valid-- we'll use a very simple GQL query and get the Stash version - $StashGQL_Query = 'query version{version{version}}' - try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL |out-null - } - catch{ - write-host "(0) Error: Could not communicate to Stash at the provided address ($StashGQL_URL)" -ForegroundColor red - write-host "Try defining the address to Stash once more. Also, please ensure you do not have a password on your Stash!" - } + } + do{ + write-host "Do you have a username/password configured on your Stash? (It's optional!)" + $userselection = read-host "Enter your selection (Y/N)" + } + while(($userselection -notmatch "Y" -and $userselection -notmatch "N")) + if($userselection -like "Y"){ + write-host "As you have set a username/password on your Stash, You'll need to provide this script with your API key." + write-host "Navigate to this page in your browser to generate one in Stash" + write-host "$StashGQL_URL/settings?tab=security" + write-host "`n- WARNING: The API key will be stored in cleartext in the config file of this script. - " + write-host "If someone who has access to your Stash gets access to the config file, they may be able to use it to bypass the username and password you've set." + $StashAPIKey = read-host "`nWhat is your API key?" + } + + #Now we can check to ensure this address is valid-- we'll use a very simple GQL query and get the Stash version + $StashGQL_Query = 'query version{version{version}}' + try{ + $stashversion = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) | out-null + } + catch{ + write-host "(0) Error: Could not communicate to Stash at the provided address ($StashGQL_URL)" + read-host "No worries, press [Enter] to start from the top." } } + while ($null -eq $stashversion) clear-host write-host "OnlyFans Metadata DB to Stash PoSH Script" -ForegroundColor Cyan @@ -326,7 +341,7 @@ function Add-MetadataUsingOFDB{ }' try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null + Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) | out-null } catch{ write-host "(10) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -574,7 +589,7 @@ function Add-MetadataUsingOFDB{ } }' try{ - $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables + $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "(1) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -601,7 +616,7 @@ function Add-MetadataUsingOFDB{ }' try{ - $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables + $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "(9) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -626,7 +641,7 @@ function Add-MetadataUsingOFDB{ } }' try{ - $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables + $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "(9a) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -710,7 +725,7 @@ function Add-MetadataUsingOFDB{ } }' try{ - $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables + $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "(2) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -738,7 +753,7 @@ function Add-MetadataUsingOFDB{ }' try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null + Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) | out-null } catch{ write-host "(3) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -763,7 +778,7 @@ function Add-MetadataUsingOFDB{ } }' try{ - $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables + $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "(22) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -895,7 +910,7 @@ function Add-MetadataUsingOFDB{ #Now lets try running the GQL query and see if we have a match in the Stash DB try{ - $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL + $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "(4) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -950,7 +965,7 @@ function Add-MetadataUsingOFDB{ #Now lets try running the GQL query and try to find the file in the Stash DB try{ - $AlternativeStashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL + $AlternativeStashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "(5) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -1016,7 +1031,7 @@ function Add-MetadataUsingOFDB{ }' try{ - $DiscoveredPerformerIDFromStash = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables + $DiscoveredPerformerIDFromStash = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "(6) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -1051,7 +1066,7 @@ function Add-MetadataUsingOFDB{ } }' try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null + Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) | out-null } catch{ write-host "(7) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -1087,7 +1102,7 @@ function Add-MetadataUsingOFDB{ }' try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -escapehandling EscapeNonAscii | out-null + Invoke-GraphQLQuery -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -escapehandling EscapeNonAscii | out-null } catch{ write-host "(8) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -1133,7 +1148,7 @@ function Add-MetadataUsingOFDB{ }' try{ - $DiscoveredPerformerIDFromStash = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables + $DiscoveredPerformerIDFromStash = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "(6) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -1168,7 +1183,7 @@ function Add-MetadataUsingOFDB{ } }' try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null + Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) | out-null } catch{ write-host "(7) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -1232,7 +1247,7 @@ function Add-MetadataUsingOFDB{ try{ - Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables | out-null + Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) | out-null } catch{ write-host "(8) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -1294,7 +1309,7 @@ function Add-MetadataUsingOFDB{ }' try{ - Invoke-GraphQLQuery -Query $UpdatePerformerImage_GQLQuery -Uri $StashGQL_URL -Variables $UpdatePerformerImage_GQLVariables | out-null + Invoke-GraphQLQuery -Query $UpdatePerformerImage_GQLQuery -Uri $StashGQL_URL -Variables $UpdatePerformerImage_GQLVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) | out-null } catch{ write-host "(46) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -1343,7 +1358,7 @@ function Add-MetadataUsingOFDB{ }' try{ - $performerimageURL = Invoke-GraphQLQuery -Query $performerimageURL_GQLQuery -Uri $StashGQL_URL -Variables $performerimageURLVariables_GQLQuery + $performerimageURL = Invoke-GraphQLQuery -Query $performerimageURL_GQLQuery -Uri $StashGQL_URL -Variables $performerimageURLVariables_GQLQuery -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ @@ -1371,7 +1386,7 @@ function Add-MetadataUsingOFDB{ }' try{ - $performerimageURL = Invoke-GraphQLQuery -Query $UpdatePerformerImage_GQLQuery -Uri $StashGQL_URL -Variables $UpdatePerformerImage_GQLVariables | out-null + $performerimageURL = Invoke-GraphQLQuery -Query $UpdatePerformerImage_GQLQuery -Uri $StashGQL_URL -Variables $UpdatePerformerImage_GQLVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) | out-null } catch{ write-host "(12) Error: There was an issue with the GraphQL query/mutation." -ForegroundColor red @@ -1442,6 +1457,7 @@ $StashGQL_URL = (Get-Content $pathtoconfigfile)[1] $PathToOnlyFansContent = (Get-Content $pathtoconfigfile)[3] $SearchSpecificity = (Get-Content $pathtoconfigfile)[5] $ConsoleVerbosity = (Get-Content $pathtoconfigfile)[7] +$StashAPIKey = (Get-Content $pathtoconfigfile)[9] $PathToMissingFilesLog = "."+$directorydelimiter+"OFMetadataToStash_MissingFiles.txt" $pathToSanitizerScript = "."+$directorydelimiter+"Utilities"+$directorydelimiter+"OFMetadataDatabase_Sanitizer.ps1" @@ -1452,11 +1468,11 @@ $pathToSanitizerScript = "."+$directorydelimiter+"Utilities"+$directorydelimiter #This query also serves a second purpose-- as of Stash v0.24, images will support details. We'll check for that and add details if possible. $StashGQL_Query = 'query version{version{version}}' try{ - $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL + $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "Hmm...Could not communicate to Stash using the URL in the config file ($StashGQL_URL)" - write-host "Are you sure Stash is running? Make sure you don't have a password on your Stash account either." + write-host "Are you sure Stash is running?" read-host "If Stash is running like normal, press [Enter] to recreate the configuration file for this script" Set-Config } From 52f27c6f6526d072e7e88b4182ad10ddacca5c87 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Sun, 25 Feb 2024 03:45:43 -0800 Subject: [PATCH 11/20] Changed Console Verbose to be an argument I don't believe most folks make use of the verbose console mode. As a result, I've moved it to be a command argument (-v) --- OFMetadataToStash.ps1 | 63 +++++++++++++------------------------------ 1 file changed, 18 insertions(+), 45 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index 7ebf226..bf185b2 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -26,7 +26,8 @@ Import-Module PSSQLite #Command Line Arguments param ( - [switch]$ignorehistory = $true #If the user doesn't want the script to make use of the history file, this will toggle its use. + [switch]$ignorehistory, #If the user doesn't want the script to make use of the history file, this will toggle its use. + [switch]$v #Toggles console verbosity, useful for troubleshooting ) @@ -38,7 +39,7 @@ function Set-Config{ write-host "OnlyFans Metadata DB to Stash PoSH Script" -ForegroundColor Cyan write-output "Configuration Setup Wizard" write-output "--------------------------`n" - write-output "(1 of 4) Define the URL to your Stash" + write-output "(1 of 3) Define the URL to your Stash" write-output "Option 1: Stash is hosted on the computer I'm using right now (localhost:9999)" write-output "Option 2: Stash is hosted at a different address and/or port (Ex. 192.168.1.2:6969)`n" do{ @@ -97,7 +98,7 @@ function Set-Config{ write-host "OnlyFans Metadata DB to Stash PoSH Script" -ForegroundColor Cyan write-output "Configuration Setup Wizard" write-output "--------------------------`n" - write-output "(2 of 4) Define the path to your OnlyFans content`n" + write-output "(2 of 3) Define the path to your OnlyFans content`n" write-host " * OnlyFans metadata database files are named 'user_data.db' and they are commonly `n located under $directorydelimiter metadata $directorydelimiter , as defined by your OnlyFans scraper of choice" write-output "`n * You have the option of linking directly to the 'user_data.db' file, `n or you can link to the top level OnlyFans folder of several metadata databases." write-output "`n * When multiple database are detected, this script can help you select one (or even import them all in batch!)`n" @@ -145,7 +146,7 @@ function Set-Config{ write-host "OnlyFans Metadata DB to Stash PoSH Script" -ForegroundColor Cyan write-output "Configuration Setup Wizard" write-output "--------------------------`n" - write-output "(3 of 4) Define your Metadata Match Mode" + write-output "(3 of 3) Define your Metadata Match Mode" write-output " * When importing OnlyFans Metadata, some users may want to tailor how this script matches metadata to files" write-output " * If you are an average user, just set this to 'Normal'" write-output "Option 1: Normal - Will match based on Filesize and the Performer name being somewhere in the file path (Recommended)" @@ -169,32 +170,6 @@ function Set-Config{ else{ $SearchSpecificity = "High" } - clear-host - write-host "OnlyFans Metadata DB to Stash PoSH Script" -ForegroundColor Cyan - write-output "Configuration Setup Wizard" - write-output "--------------------------`n" - write-output "(4 of 4) Define your Console Output Verbosity Mode" - write-output " * If you are an average user, just set this to 'Normal'" - write-output " * Setting this to Verbose does incur a small performance penalty.`n" - write-output "Option 1: Normal - You will see a normal amount of information while the script is running (Recommended)" - write-output "Option 2: Verbose - You will see more information while the script is running" - - - - $verbosityselection = 0; - do { - $verbosityselection = read-host "`nEnter selection (1 or 2)" - } - while (($verbosityselection -notmatch "[1-2]")) - - #Code for parsing metadata files - if($verbosityselection -eq 1){ - $ConsoleVerbosity = "Normal" - } - else{ - $ConsoleVerbosity = "Verbose" - } - clear-host write-host "OnlyFans Metadata DB to Stash PoSH Script" -ForegroundColor Cyan @@ -205,7 +180,6 @@ function Set-Config{ write-output "URL to Stash API:`n - $StashGQL_URL`n" write-output "Path to OnlyFans Content:`n - $PathToOnlyFansContent`n" write-output "Metadata Match Mode:`n - $SearchSpecificity`n" - write-output "Console Verbosity Mode:`n - $ConsoleVerbosity`n" read-host "Press [Enter] to save this configuration and return to the Main Menu" @@ -227,8 +201,8 @@ function Set-Config{ Add-Content -path $PathToConfigFile -value $PathToOnlyFansContent Add-Content -path $PathToConfigFile -value "## Search Specificity mode. (Normal | High | Low) ##" Add-Content -path $PathToConfigFile -value $SearchSpecificity - Add-Content -path $PathToConfigFile -value "## Console Verbosity Mode. (Normal | Verbose) ##" - Add-Content -path $PathToConfigFile -value $ConsoleVerbosity + Add-Content -path $PathToConfigFile -value "## Stash API Key (Danger!)##" + Add-Content -path $PathToConfigFile -value $StashAPIKey } catch { write-output "Error - Something went wrong while trying add your configurations to the config file ($PathToConfigFile)" -ForegroundColor red @@ -1116,13 +1090,13 @@ function Add-MetadataUsingOFDB{ #Provide user feedback on what has occured and add to the "file modified" counter for stats later if ($filewasmodified){ - if ($ConsoleVerbosity -eq "Verbose"){ + if ($v){ write-output "- Added metadata to Stash's database for the following file:`n $OFDBFullFilePath" } $numModified++ } else{ - if ($ConsoleVerbosity -eq "Verbose"){ + if ($v){ write-output "- This file already has metadata, moving on...`n $OFDBFullFilePath" } $numUnmodified++ @@ -1261,13 +1235,13 @@ function Add-MetadataUsingOFDB{ #Provide user feedback on what has occured and add to the "file modified" counter for stats later if ($filewasmodified){ - if ($ConsoleVerbosity -eq "Verbose"){ + if ($v){ write-output "- Added metadata to Stash's database for the following file:`n $OFDBFullFilePath" } $numModified++ } else{ - if ($ConsoleVerbosity -eq "Verbose"){ + if ($v){ write-output "- This file already has metadata, moving on...`n $OFDBFullFilePath" } $numUnmodified++ @@ -1456,14 +1430,12 @@ if (!(Test-path $PathToConfigFile)){ $StashGQL_URL = (Get-Content $pathtoconfigfile)[1] $PathToOnlyFansContent = (Get-Content $pathtoconfigfile)[3] $SearchSpecificity = (Get-Content $pathtoconfigfile)[5] -$ConsoleVerbosity = (Get-Content $pathtoconfigfile)[7] -$StashAPIKey = (Get-Content $pathtoconfigfile)[9] +$StashAPIKey = (Get-Content $pathtoconfigfile)[7] $PathToMissingFilesLog = "."+$directorydelimiter+"OFMetadataToStash_MissingFiles.txt" $pathToSanitizerScript = "."+$directorydelimiter+"Utilities"+$directorydelimiter+"OFMetadataDatabase_Sanitizer.ps1" - #Before we continue, let's make sure everything in the configuration file is good to go #This query also serves a second purpose-- as of Stash v0.24, images will support details. We'll check for that and add details if possible. $StashGQL_Query = 'query version{version{version}}' @@ -1490,11 +1462,6 @@ if (!(test-path $PathToOnlyFansContent)){ read-host "Hmm...The defined path to your OnlyFans content does not seem to exist at the location specified in your config file.`n($PathToOnlyFansContent)`n`nPress [Enter] to run through the config wizard" Set-Config } -if($ConsoleVerbosity -notmatch '\bverbose\b|\bnormal\b'){ - read-host "Hmm...looks like the console output verbosity setting isn't defined in the config file. No worries!`n`n Press [Enter] to run through the config wizard" - Set-Config -} - if(($SearchSpecificity -notmatch '\blow\b|\bnormal\b|\bhigh\b')){ #Something goofy with the variable? Send the user to recreate their config file with the set-config function @@ -1508,6 +1475,12 @@ else { write-output "* Path to OnlyFans Media: $PathToOnlyFansContent" write-output "* Metadata Match Mode: $searchspecificity" write-output "* Stash URL: $StashGQL_URL`n" + if($v){ + write-host "Special Mode: Verbose Output" + } + if($ignorehistory){ + write-host "Special Mode: Ignore History File" + } write-output "----------------------------------------------------`n" write-output "What would you like to do?" write-output " 1 - Add Metadata to my Stash using OnlyFans Metadata Database(s)" From 36cd396c2dc61ddd6aaf2a804b560c1398290020 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Sun, 25 Feb 2024 06:40:48 -0800 Subject: [PATCH 12/20] Bugfixes --- .gitignore | 3 +- OFMetadataToStash.ps1 | 80 +++++++++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 65809bf..0df4580 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ OFMetadataToStash_Config OFMetadataToStash_MissingFiles.txt -OFMetadataToStash_Builder.ps1 \ No newline at end of file +OFMetadataToStash_Builder.ps1 +Utilities/imported_dbs.sqlite diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index bf185b2..a4b0a7e 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -1,4 +1,6 @@ -<# +param ([switch]$ignorehistory, [switch]$v) + + <# ---OnlyFans Metadata DB to Stash PoSH Script 0.9--- AUTHOR @@ -25,10 +27,7 @@ Import-Module PSGraphQL Import-Module PSSQLite #Command Line Arguments -param ( - [switch]$ignorehistory, #If the user doesn't want the script to make use of the history file, this will toggle its use. - [switch]$v #Toggles console verbosity, useful for troubleshooting - ) + ### Functions @@ -69,10 +68,10 @@ function Set-Config{ } } do{ - write-host "Do you have a username/password configured on your Stash? (It's optional!)" + write-host "`nDo you happen to have a username/password configured on your Stash?" $userselection = read-host "Enter your selection (Y/N)" } - while(($userselection -notmatch "Y" -and $userselection -notmatch "N")) + while(($userselection -notlike "Y" -and $userselection -notlike "N")) if($userselection -like "Y"){ write-host "As you have set a username/password on your Stash, You'll need to provide this script with your API key." write-host "Navigate to this page in your browser to generate one in Stash" @@ -85,11 +84,11 @@ function Set-Config{ #Now we can check to ensure this address is valid-- we'll use a very simple GQL query and get the Stash version $StashGQL_Query = 'query version{version{version}}' try{ - $stashversion = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) | out-null + $stashversion = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } catch{ write-host "(0) Error: Could not communicate to Stash at the provided address ($StashGQL_URL)" - read-host "No worries, press [Enter] to start from the top." + read-host "No worries, press [Enter] to start from the top" } } while ($null -eq $stashversion) @@ -218,9 +217,9 @@ function Get-PerformerHistory{ $PathToHistoryFile = "."+$directorydelimiter+"Utilities"+$directorydelimiter+"imported_dbs.sqlite" #Let's go ahead and create the history file if it does not exist - if(!(test-file $pathtohistoryfile)){ + if(!(test-path $pathtohistoryfile)){ try{ - out-file $PathToHistoryFile + new-item $PathToHistoryFile } catch{ write-host "Error 1h - Unable to write the history file to the filesystem. Permissions issue?" -ForegroundColor red @@ -229,15 +228,10 @@ function Get-PerformerHistory{ } #Query for defining the schema of the SQL database we're creating - $historyquery = 'CREATE TABLE "history" ( - "historyID" INTEGER NOT NULL UNIQUE, - "performer" TEXT NOT NULL UNIQUE COLLATE BINARY, - "import_date" TEXT NOT NULL, - PRIMARY KEY("historyID" AUTOINCREMENT) - );' + $historyquery = 'CREATE TABLE "history" ("historyID" INTEGER NOT NULL UNIQUE,"performer" TEXT NOT NULL UNIQUE COLLATE BINARY,"import_date" TEXT NOT NULL,PRIMARY KEY("historyID" AUTOINCREMENT));' try{ - $historyTimestamp = Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile + Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile } catch{ write-host "Error 2h - Unable to create a history file using SQL." -ForegroundColor red @@ -261,15 +255,15 @@ function Get-PerformerHistory{ if ($performerFromHistory){ #Let's get the timestamp from the metdata database file - $metadataLastWriteTime = get-item $OFDBFullFilePath + $metadataLastWriteTime = get-item $currentdatabase $metadataLastWriteTime = $metadataLastWriteTime.LastWriteTime #If the metdata database for this performer has been modified since the last time we read this metadata database in, let's go ahead and parse it, otherwise return false so we can skip it - if([datetime]$metadataLastWriteTime -gt [datetime]$historytimestamp){ + if([datetime]$metadataLastWriteTime -gt [datetime]$performerFromHistory.import_date){ $currenttimestamp = get-date -format o try { $historyQuery = 'UPDATE import_date SET import_date = "'+$currenttimestamp+'" WHERE history.performer = "'+$performername+'"' - $historyTimestamp = Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile + Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile } catch{ write-host "Error 4h - Something went wrong while trying to update the history file ($PathToHistoryFile)" -ForegroundColor red @@ -285,9 +279,10 @@ function Get-PerformerHistory{ } #Otherwise, this performer is entirely new to us, so let's add the performer to the history file and return true so it may be processed else{ + $currenttimestamp = get-date -format o try { $historyQuery = 'INSERT INTO history(performer, import_date) VALUES ("'+$performername+'", "'+$currenttimestamp+'")' - $historyTimestamp = Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile + Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile } catch{ write-host "Error 5h - Something went wrong while trying to add this performer to the history file ($PathToHistoryFile)" -ForegroundColor red @@ -634,7 +629,7 @@ function Add-MetadataUsingOFDB{ foreach ($currentdatabase in $OFDatabaseFilesCollection) { #Let's help the user see how we are progressing through this metadata database (this is the parent progress UI, there's an additional child below as well) $currentTotalProgress = [int]$(($totalprogressCounter/$OFDatabaseFilesCollection.count)*100) - Write-Progress -Activity "Total Import Progress" -Status "$currentTotalProgress% Complete" -PercentComplete $currentTotalProgress + Write-Progress -id 1 -Activity "Total Import Progress" -Status "$currentTotalProgress% Complete" -PercentComplete $currentTotalProgress $totalprogressCounter++ @@ -763,7 +758,7 @@ function Add-MetadataUsingOFDB{ $PerformerID = $StashGQL_Result.data.findPerformers.performers[0].id $creatednewperformer = $true #We'll use this later after images have been added in order to give the performer a profile picture $boolGetPerformerImage = $true #We'll use this to get an image to use for the profile picture - write-host "`nInfo: Added a new Performer ($performername) to Stash's database`n" -ForegroundColor Cyan + } else{ @@ -784,7 +779,7 @@ function Add-MetadataUsingOFDB{ #Let's help the user see how we are progressing through this performer's metadata database $currentProgress = [int]$(($progressCounter/$OFDBQueryResult.count)*100) - Write-Progress -Activity "$performername Import Progress" -Status "$currentProgress% Complete" -PercentComplete $currentProgress + Write-Progress -parentId 1 -Activity "$performername Import Progress" -Status "$currentProgress% Complete" -PercentComplete $currentProgress $progressCounter++ #Generating the URL for this post @@ -1254,20 +1249,36 @@ function Add-MetadataUsingOFDB{ #Before we move on, if we had created a new performer, let's update that performer with a profile image. #The only reason we don't do it earlier is that now all the images have been added and associated and it's easy to select an image and go. if($creatednewperformer){ - + #First let's look for an image where this performer has been associated and get the URL, for that image #Sometimes these OF downloaders pull profile/avatar photos into a specific folder. We'll look to see if we can match on that first before just choosing what we can get. #Using the filepath of the metadata database as our starting point, we'll go a folder up and then look for an image containing the keyword "avatar" - $pathToAvatarImage = (get-item $OF_DBpath).Parent - $pathToAvatarImage = "$pathToAvatarImage"+"$directorydelimiter"+"Profile"+$directorydelimiter+"Images" - $pathToAvatarImage = Get-ChildItem $pathToAvatarImage where-object {$_.name -like "avatar*"} - - #If we DID find an avatar on the filesystem - if ($null -ne $pathToAvatarImage){ - + $pathToAvatarImage = (get-item $currentdatabase.FullName) + $pathToAvatarImage = split-path -parent $pathToAvatarImage + $pathToAvatarImage = split-path -parent $pathToAvatarImage + $pathToAvatarImage = "$pathToAvatarImage"+"$directorydelimiter"+"Profile" + + #If there's a profile folder to look into, let's do it + if((test-path $pathToAvatarImage)){ + $avatarfolder = "$pathToAvatarImage"+"$directorydelimiter"+"Avatars" + $profileimagesfolder = "$pathToAvatarImage"+"$directorydelimiter"+"images" + + if(test-path $avatarfolder){ + $pathToAvatarImage = Get-ChildItem $avatarfolder | where-object{ $_.extension -in ".jpg", ".jpeg"} + $pathToAvatarImage = $pathToAvatarImage + } + elseif (test-path $profileimagesfolder){ + $pathToAvatarImage = Get-ChildItem $profileimagesfolder | where-object{ $_.extension -in ".jpg", ".jpeg"} + $pathToAvatarImage = $pathToAvatarImage + } + #otherwise, let's just take whatever image we can get + else{ + $pathToAvatarImage = Get-ChildItem $pathToAvatarImage -recurse | where-object{ $_.extension -in ".jpg", ".jpeg"} + } + #Convert the image to base64. Note that this is designed for jpegs-- I don't think OnlyFans supports anything else anyway. - $avatarImageBase64 = [convert]::ToBase64String((Get-Content $path -AsByteStream)) + $avatarImageBase64 = [convert]::ToBase64String((Get-Content $pathToAvatarImage -AsByteStream)) $avatarImageBase64 = "data:image/jpeg;base64,"+$avatarImageBase64 $UpdatePerformerImage_GQLQuery ='mutation PerformerUpdate($input: PerformerUpdateInput!) { @@ -1292,6 +1303,7 @@ function Add-MetadataUsingOFDB{ exit } } + #If we didn't find anything on the filesystem, let's just query Stash and use a random image from this performer's OF page else{ $performerimageURL_GQLQuery = 'query FindImages( From e05ab65a5e1c3730dd837ad210d7dccd82042a53 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Sun, 25 Feb 2024 21:31:53 -0800 Subject: [PATCH 13/20] Bugfix - Addresses misselected studio selection Previous query for pulling the OnlyFans studio was not specific enough. If both "OnlyFans" and "OnlyFans (jane)" were present, the latter would be selected rather than the former. I've added a filter to the GQL query to address this. --- OFMetadataToStash.ps1 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index a4b0a7e..31c15da 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -553,10 +553,16 @@ function Add-MetadataUsingOFDB{ } ' $StashGQL_QueryVariables = '{ - "filter": { - "q": "OnlyFans" - } - }' + "filter": { + "q": "" + }, + "studio_filter": { + "name": { + "value": "OnlyFans", + "modifier": "EQUALS" + } + } + }' try{ $StashGQL_Result = Invoke-GraphQLQuery -Query $StashGQL_Query -Uri $StashGQL_URL -Variables $StashGQL_QueryVariables -Headers $(if ($StashAPIKey){ @{ApiKey = "$StashAPIKey" }}) } From 758f8123bab76f93b2178f99154cf33132981b8b Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Thu, 29 Feb 2024 00:25:11 -0800 Subject: [PATCH 14/20] Added versioning support to config file + bugfixes --- OFMetadataToStash.ps1 | 146 ++++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 69 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index 31c15da..51443c6 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -31,7 +31,6 @@ Import-Module PSSQLite ### Functions - #Set-Config is a wizard that walks the user through the configuration settings function Set-Config{ clear-host @@ -194,6 +193,8 @@ function Set-Config{ } try{ + Add-Content -path $PathToConfigFile -value "#### OFMetadataToStash Config File v1 ####" + Add-Content -path $PathToConfigFile -value "------------------------------------------" Add-Content -path $PathToConfigFile -value "## URL to the Stash GraphQL API endpoint ##" Add-Content -path $PathToConfigFile -value $StashGQL_URL Add-Content -path $PathToConfigFile -value "## Direct Path to OnlyFans Metadata Database or top level folder containing OnlyFans content ##" @@ -211,90 +212,92 @@ function Set-Config{ } #End Set-Config -#Get-PerformerHistory does a check to see if a particular metadata database file actually needs to be parsed based on a history file. Increments history file and returns true if it does, returns false if it does not. -function Get-PerformerHistory{ - #Location for the history file to be stored - $PathToHistoryFile = "."+$directorydelimiter+"Utilities"+$directorydelimiter+"imported_dbs.sqlite" +#DatabaseHasBeenImported does a check to see if a particular metadata database file actually needs to be parsed based on a history file. Returns true if this database needs to be parsed +function DatabaseHasAlreadyBeenImported{ + if ($ignorehistory -eq $true){ + return $false + } + else{ + #Location for the history file to be stored + $PathToHistoryFile = "."+$directorydelimiter+"Utilities"+$directorydelimiter+"imported_dbs.sqlite" - #Let's go ahead and create the history file if it does not exist - if(!(test-path $pathtohistoryfile)){ - try{ - new-item $PathToHistoryFile - } - catch{ - write-host "Error 1h - Unable to write the history file to the filesystem. Permissions issue?" -ForegroundColor red - read-host "Press [Enter] to exit" - exit - } + #Let's go ahead and create the history file if it does not exist + if(!(test-path $pathtohistoryfile)){ + try{ + new-item $PathToHistoryFile + } + catch{ + write-host "Error 1h - Unable to write the history file to the filesystem. Permissions issue?" -ForegroundColor red + read-host "Press [Enter] to exit" + exit + } - #Query for defining the schema of the SQL database we're creating - $historyquery = 'CREATE TABLE "history" ("historyID" INTEGER NOT NULL UNIQUE,"performer" TEXT NOT NULL UNIQUE COLLATE BINARY,"import_date" TEXT NOT NULL,PRIMARY KEY("historyID" AUTOINCREMENT));' + #Query for defining the schema of the SQL database we're creating + $historyquery = 'CREATE TABLE "history" ("historyID" INTEGER NOT NULL UNIQUE,"performer" TEXT NOT NULL UNIQUE COLLATE BINARY,"import_date" TEXT NOT NULL,PRIMARY KEY("historyID" AUTOINCREMENT));' + try{ + Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile + } + catch{ + write-host "Error 2h - Unable to create a history file using SQL." -ForegroundColor red + read-host "Press [Enter] to exit" + exit + } + } + + #First let's check to see if this performer is even in the history file try{ - Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile + $historyQuery = 'SELECT * FROM history WHERE history.performer = "'+$performername+'"' + $performerFromHistory = Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile } catch{ - write-host "Error 2h - Unable to create a history file using SQL." -ForegroundColor red + write-host "Error 3h - Something went wrong while trying to read from history file ($PathToHistoryFile)" -ForegroundColor red read-host "Press [Enter] to exit" exit } - } - - #First let's check to see if this performer is even in the history file - try{ - $historyQuery = 'SELECT * FROM history WHERE history.performer = "'+$performername+'"' - $performerFromHistory = Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile - } - catch{ - write-host "Error 3h - Something went wrong while trying to read from history file ($PathToHistoryFile)" -ForegroundColor red - read-host "Press [Enter] to exit" - exit - } - #If this performer DOES exist in the history file... - if ($performerFromHistory){ + #If this performer DOES exist in the history file... + if ($performerFromHistory){ - #Let's get the timestamp from the metdata database file - $metadataLastWriteTime = get-item $currentdatabase - $metadataLastWriteTime = $metadataLastWriteTime.LastWriteTime + #Let's get the timestamp from the metdata database file + $metadataLastWriteTime = get-item $currentdatabase + $metadataLastWriteTime = $metadataLastWriteTime.LastWriteTime - #If the metdata database for this performer has been modified since the last time we read this metadata database in, let's go ahead and parse it, otherwise return false so we can skip it - if([datetime]$metadataLastWriteTime -gt [datetime]$performerFromHistory.import_date){ + #If the metdata database for this performer has been modified since the last time we read this metadata database in, let's go ahead and parse it + if([datetime]$metadataLastWriteTime -gt [datetime]$performerFromHistory.import_date){ + $currenttimestamp = get-date -format o + try { + $historyQuery = 'UPDATE import_date SET import_date = "'+$currenttimestamp+'" WHERE history.performer = "'+$performername+'"' + Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile + } + catch{ + write-host "Error 4h - Something went wrong while trying to update the history file ($PathToHistoryFile)" -ForegroundColor red + read-output "Press [Enter] to exit" + exit + } + return $false + } + else{ + write-host "- The metadata database for $performername hasn't changed since your last import! Skipping..." + return $true + } + } + #Otherwise, this performer is entirely new to us, so let's add the performer to the history file + else{ $currenttimestamp = get-date -format o try { - $historyQuery = 'UPDATE import_date SET import_date = "'+$currenttimestamp+'" WHERE history.performer = "'+$performername+'"' + $historyQuery = 'INSERT INTO history(performer, import_date) VALUES ("'+$performername+'", "'+$currenttimestamp+'")' Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile } catch{ - write-host "Error 4h - Something went wrong while trying to update the history file ($PathToHistoryFile)" -ForegroundColor red + write-host "Error 5h - Something went wrong while trying to add this performer to the history file ($PathToHistoryFile)" -ForegroundColor red read-output "Press [Enter] to exit" exit } - return $true - } - else{ - write-host "Nothing to update for this performer! Skipping..." return $false } } - #Otherwise, this performer is entirely new to us, so let's add the performer to the history file and return true so it may be processed - else{ - $currenttimestamp = get-date -format o - try { - $historyQuery = 'INSERT INTO history(performer, import_date) VALUES ("'+$performername+'", "'+$currenttimestamp+'")' - Invoke-SqliteQuery -Query $historyQuery -DataSource $PathToHistoryFile - } - catch{ - write-host "Error 5h - Something went wrong while trying to add this performer to the history file ($PathToHistoryFile)" -ForegroundColor red - read-output "Press [Enter] to exit" - exit - } - return $true - } - - - -} #End Get-PerformerHistory +} #End DatabaseHasBeenImported #Add-MetadataUsingOFDB adds metadata to Stash using metadata databases. function Add-MetadataUsingOFDB{ @@ -379,7 +382,7 @@ function Add-MetadataUsingOFDB{ } #Logic for handling the process for selecting a single performer elseif([int]$selectednumberforprocess -eq 2){ - + write-host " " #Just adding a new line for a better UX #logic for displaying all found performers for user to select $i=1 # just used cosmetically Foreach ($OFDBdatabase in $OFDatabaseFilesCollection){ @@ -772,9 +775,10 @@ function Add-MetadataUsingOFDB{ $boolGetPerformerImage = $false } - #Let's check to see if we need to import this performer based on the history file. If this function returns false, move on to the next performer. + #Let's check to see if we need to import this performer based on the history file using the DatabaseHasBeenImported function #The ignorehistory variable is a command line flag that the user may set if they want to have the script ignore the use of the history file - if (Get-PerformerHistory -or ($ignorehistory -eq $true)){ + + if (!(DatabaseHasAlreadyBeenImported)){ #Select all the media (except audio) and the text the performer associated to them, if available from the OFDB $Query = "SELECT messages.text, medias.directory, medias.filename, medias.size, medias.created_at, medias.post_id, medias.media_type FROM medias INNER JOIN messages ON messages.post_id=medias.post_id UNION SELECT posts.text, medias.directory, medias.filename, medias.size, medias.created_at, medias.post_id, medias.media_type FROM medias INNER JOIN posts ON posts.post_id=medias.post_id WHERE medias.media_type <> 'Audios'" $OF_DBpath = $currentdatabase.fullname @@ -1443,12 +1447,16 @@ $pathtoconfigfile = "."+$directorydelimiter+"OFMetadataToStash_Config" if (!(Test-path $PathToConfigFile)){ Set-Config } +$ConfigFileVersion = (Get-Content $pathtoconfigfile)[0] +if ($ConfigFileVersion -ne "#### OFMetadataToStash Config File v1 ####"){ + Set-Config +} ## Global Variables ## -$StashGQL_URL = (Get-Content $pathtoconfigfile)[1] -$PathToOnlyFansContent = (Get-Content $pathtoconfigfile)[3] -$SearchSpecificity = (Get-Content $pathtoconfigfile)[5] -$StashAPIKey = (Get-Content $pathtoconfigfile)[7] +$StashGQL_URL = (Get-Content $pathtoconfigfile)[3] +$PathToOnlyFansContent = (Get-Content $pathtoconfigfile)[5] +$SearchSpecificity = (Get-Content $pathtoconfigfile)[7] +$StashAPIKey = (Get-Content $pathtoconfigfile)[9] $PathToMissingFilesLog = "."+$directorydelimiter+"OFMetadataToStash_MissingFiles.txt" $pathToSanitizerScript = "."+$directorydelimiter+"Utilities"+$directorydelimiter+"OFMetadataDatabase_Sanitizer.ps1" From 5bb75307594dd5e94944c15204f5ded6cb055555 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Thu, 29 Feb 2024 01:04:35 -0800 Subject: [PATCH 15/20] Feature - Better Error handling --- OFMetadataToStash.ps1 | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index 51443c6..ad4f6d4 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -907,14 +907,35 @@ function Add-MetadataUsingOFDB{ #If our search for matching media in Stash itself comes up empty, let's check to see if the file even exists on the file system if ($StashGQL_Result.data.querySQL.rows.length -eq 0 ){ - if (Test-Path $OFDBFullFilePath){ - write-host "`nInfo: There's a file in this OnlyFans metadata database that we couldn't find in your Stash database but the file IS on your filesystem.`nTry running a Scan Task in Stash then re-running this script.`n`n - $OFDBFullFilePath`n" -ForegroundColor Cyan - } - #In this case, the media isn't in Stash or on the filesystem so inform the user, log the file, and move on - else{ - write-host "`nInfo: There's a file in this OnlyFans metadata database that we couldn't find in your Stash database.`nThis file also doesn't appear to be on your filesystem.`nTry rerunning the script you used to scrape this OnlyFans performer and redownloading the file.`n`n - $OFDBFullFilePath`n" -ForegroundColor Cyan - Add-Content -Path $PathToMissingFilesLog -value " $OFDBFullFilePath" - $nummissingfiles++ + + #Let's be extra about this error message. If there's no match, swap the directory path delimeters and try again. + if (!(Test-Path $OFDBFullFilePath)){ + if ($OFDBFullFilePath.Contains('/')){ + $OFDBFullFilePath = $OFDBFullFilePath.Replace("/","\") + } + else{ + $OFDBFullFilePath = $OFDBFullFilePath.Replace("\","/") + } + if (!(Test-Path $OFDBFullFilePath)){ + write-host "`nInfo: There's a file in this OnlyFans metadata database that we couldn't find in your Stash database.`nThis file also doesn't appear to be on your filesystem (we checked with both Windows and *nix path delimeters).`nTry rerunning the script you used to scrape this OnlyFans performer and redownloading the file." -ForegroundColor Cyan + write-host "- Scan Specificity Mode: $SearchSpecificity" + write-host "- Filename: $OFDBfilename" + write-host "- Directory: $OFDBdirectory" + write-host "- Filesize: $OFDBfilesize" + write-host "^ (Filename, Directory and Filesize are as defined by the OF Metadata Database) ^" + Add-Content -Path $PathToMissingFilesLog -value " $OFDBFullFilePath" + $nummissingfiles++ + + } + else{ + write-host "`nInfo: There's a file in this OnlyFans metadata database that we couldn't find in your Stash database but the file IS on your filesystem.`nTry running a Scan Task in Stash then re-running this script." -ForegroundColor Cyan + write-host "- Filename: $OFDBfilename" + write-host "- Directory: $OFDBdirectory" + write-host "- Filesize: $OFDBfilesize" + write-host "^ (Filename, Directory and Filesize are as defined by the OF Metadata Database) ^" + Add-Content -Path $PathToMissingFilesLog -value " $OFDBFullFilePath" + $nummissingfiles++ + } } } #Otherwise we have found a match! let's process the matching result and add the metadata we've found @@ -989,8 +1010,6 @@ function Add-MetadataUsingOFDB{ $proposedtitle = $proposedtitle.replace('”','\"') #literally removing the curly quote entirely #Let's check to see if this is a file that already has metadata. - #For Videos, we check the title and the details - #For Images, we only check title (for now) #If any metadata is missing, we don't bother with updating a specific column, we just update the entire row if ($mediatype -eq "video"){ #By default we will claim this file to be unmodified (we use this for user stats at the end of the script) @@ -1314,7 +1333,7 @@ function Add-MetadataUsingOFDB{ } } - #If we didn't find anything on the filesystem, let's just query Stash and use a random image from this performer's OF page + #If we didn't find anything on the filesystem, let's just query Stash and use a random image from this performer's associated images else{ $performerimageURL_GQLQuery = 'query FindImages( $filter: FindFilterType From 3c596a3c234d48005ad0e32185574c10f16f1429 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Thu, 29 Feb 2024 01:26:46 -0800 Subject: [PATCH 16/20] Feature - CLI flag to toggle performer avatar selection Now that the script is able to select whatever the OnlyFans performer had intended to be their profile picture on the real website, this command line flag lets the user alternatively have the script select a random picture instead. --- OFMetadataToStash.ps1 | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index ad4f6d4..8e03333 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -1,4 +1,4 @@ -param ([switch]$ignorehistory, [switch]$v) +param ([switch]$ignorehistory, [switch]$randomavatar, [switch]$v) <# ---OnlyFans Metadata DB to Stash PoSH Script 0.9--- @@ -901,7 +901,7 @@ function Add-MetadataUsingOFDB{ if ($StashGQL_Result.data.querySQL.rows.length -ne 0){ #Because of how GQL returns data, these values are just positions in the $StashGQLQuery array. Not super memorable, so I'm putting them in variables. - $CurrentFileID = $StashGQL_Result.data.querySQL.rows[0][5] #This represents either the scene ID or the image ID + $CurrentFileID = $StashGQL_Result.data.querySQL.rows[0][5] #This represents either the scene ID or the image ID. To be generic, I'm defining it as "CurrentFileID" $CurrentFileTitle = $StashGQL_Result.data.querySQL.rows[0][6] } @@ -997,7 +997,6 @@ function Add-MetadataUsingOFDB{ $detailsToAddToStash = $detailsToAddToStash.Replace('target="_blank"',"") #For some reason the invoke-graphqlquery module doesn't quite escape single/double quotes ' " (or their curly variants) or backslashs \ very well so let's do it manually for the sake of our JSON query - $detailsToAddToStash = $detailsToAddToStash.replace("'","''") $detailsToAddToStash = $detailsToAddToStash.replace("\","\\") $detailsToAddToStash = $detailsToAddToStash.replace('"','\"') $detailsToAddToStash = $detailsToAddToStash.replace('“','\"') #literally removing the curly quote entirely @@ -1288,8 +1287,9 @@ function Add-MetadataUsingOFDB{ $pathToAvatarImage = split-path -parent $pathToAvatarImage $pathToAvatarImage = "$pathToAvatarImage"+"$directorydelimiter"+"Profile" - #If there's a profile folder to look into, let's do it - if((test-path $pathToAvatarImage)){ + + #If there's a profile folder to look into, let's do it (unless the flag to just randomize the profile picture is there) + if(!($randomavatar) -and (test-path $pathToAvatarImage)){ $avatarfolder = "$pathToAvatarImage"+"$directorydelimiter"+"Avatars" $profileimagesfolder = "$pathToAvatarImage"+"$directorydelimiter"+"images" @@ -1521,10 +1521,13 @@ else { write-output "* Metadata Match Mode: $searchspecificity" write-output "* Stash URL: $StashGQL_URL`n" if($v){ - write-host "Special Mode: Verbose Output" + write-host "Special Mode Enabled: Verbose Output" } if($ignorehistory){ - write-host "Special Mode: Ignore History File" + write-host "Special Mode Enabled: Ignore History File" + } + if($randomavatar){ + write-host "Special Mode Enabled: Random Avatar " } write-output "----------------------------------------------------`n" write-output "What would you like to do?" From 3dd3950a2d7bbbf1c7c3eae49b7bd0cb2f80cedf Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Thu, 29 Feb 2024 01:44:34 -0800 Subject: [PATCH 17/20] Improvement - "High" Scan Specificity Mode Previous iteration searched for the entire path, that's entirely too much, and the complication of various OS types/path delimiters exacerbated the issue. This mode now just looks for base filename and filesize. --- OFMetadataToStash.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/OFMetadataToStash.ps1 b/OFMetadataToStash.ps1 index 8e03333..b576cf8 100644 --- a/OFMetadataToStash.ps1 +++ b/OFMetadataToStash.ps1 @@ -147,9 +147,9 @@ function Set-Config{ write-output "(3 of 3) Define your Metadata Match Mode" write-output " * When importing OnlyFans Metadata, some users may want to tailor how this script matches metadata to files" write-output " * If you are an average user, just set this to 'Normal'" - write-output "Option 1: Normal - Will match based on Filesize and the Performer name being somewhere in the file path (Recommended)" + write-output "`nOption 1: Normal - Will match based on Filesize and the Performer name being somewhere in the file path (Recommended)" write-output "Option 2: Low - Will match based only on a matching Filesize" - write-output "Option 3: High - Will match based on a matching path and a matching Filesize" + write-output "Option 3: High - Will match based on Filename and a matching Filesize" $specificityselection = 0; @@ -869,19 +869,19 @@ function Add-MetadataUsingOFDB{ }' } - #High specificity, search for videos based on matching file path between OnlyFans DB and Stash DB as well as matching the filesize. + #High specificity, search for videos based on matching file name between OnlyFans DB and Stash DB as well as matching the filesize. elseif ($mediatype -eq "video" -and $searchspecificity -match "high"){ $StashGQL_Query = 'mutation { - querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, scenes.id AS scenes_id, scenes.title AS scenes_title, scenes.details AS scenes_details FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN scenes_files ON files.id = scenes_files.file_id JOIN scenes ON scenes.id = scenes_files.scene_id WHERE path ='''+$OFDBdirectoryForQuery+''' AND files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { + querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, scenes.id AS scenes_id, scenes.title AS scenes_title, scenes.details AS scenes_details FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN scenes_files ON files.id = scenes_files.file_id JOIN scenes ON scenes.id = scenes_files.scene_id WHERE files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { rows } }' } - #High specificity, search for images based on matching file path between OnlyFans DB and Stash DB as well as matching the filesize. + #High specificity, search for images based on matching file name between OnlyFans DB and Stash DB as well as matching the filesize. else{ $StashGQL_Query = 'mutation { - querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, images.id AS images_id, images.title AS images_title FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN images_files ON files.id = images_files.file_id JOIN images ON images.id = images_files.image_id WHERE path ='''+$OFDBdirectoryForQuery+''' AND files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { + querySQL(sql: "SELECT folders.path, files.basename, files.size, files.id AS files_id, folders.id AS folders_id, images.id AS images_id, images.title AS images_title FROM files JOIN folders ON files.parent_folder_id=folders.id JOIN images_files ON files.id = images_files.file_id JOIN images ON images.id = images_files.image_id WHERE files.basename ='''+$OFDBfilenameForQuery+''' AND size = '''+$OFDBfilesize+'''") { rows } }' From 36570db705383bf66d63e20a6b983d2ab04bad23 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Thu, 29 Feb 2024 02:01:07 -0800 Subject: [PATCH 18/20] Update README.md --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 75b9a27..522b41b 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,26 @@ ## 🍦 How it Works -- This script primarily relies on the SQLIte files (`user_data.db`) an OnlyFans scraper generates containing all the metadata you might want. +- This script primarily relies on the SQLIte files (`user_data.db`) that a OnlyFans scraper generates. These files contain all the metadata you might want! - This script does **not** access/scrape/download/or otherwise pull down metadata from OnlyFans or any other service. - That said, if you don't have metadata DB files, this script _can_ try and make a good guess as your performers and associated content based on file path + ## 💻 Requirements - Stash v0.24.3 ([Released 2024-1-14](https://github.com/stashapp/stash/releases/)) - Any major operating system (Windows/macOS/Linux) running [Microsoft Powershell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.3) -- The [PSSQLite](https://github.com/RamblingCookieMonster/PSSQLite) and [PSGraphQL](https://www.powershellgallery.com/packages/PSGraphQL/1.6.0) PowerShell modules ## 📖 How to Run 1. Ensure the latest version of [Microsoft Powershell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.3) is installed. -5. Open a Powershell prompt in the same directory as the script and run the command `.\OFMetadataToStash.ps1` to start the short configuration wizard +2. Open a Powershell prompt in the same directory as the script and run the command `.\OFMetadataToStash.ps1` to start its short configuration wizard +

+ +## 🚩 Command Line Flags +- `-ignorehistory` Script won't skip over metadata databases have already been imported to your Stash. +- `-randomavatar` Script will ignore the use of the profile image that a particular performer selected for themselves. +- `-v` Script will be more verbose with its output while running. Can be useful for troubleshooting. + +## 📌 Additional Notes +- This script makes use of the excellent [PSSQLite](https://github.com/RamblingCookieMonster/PSSQLite) and [PSGraphQL](https://www.powershellgallery.com/packages/PSGraphQL/1.6.0) PowerShell modules. Kudos to those developers! From 494ee51b9827becd5e9bcfbf377a549a27ced481 Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Thu, 29 Feb 2024 02:01:39 -0800 Subject: [PATCH 19/20] Add files via upload --- readme_assets/ConfigWizard.png | Bin 0 -> 22081 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 readme_assets/ConfigWizard.png diff --git a/readme_assets/ConfigWizard.png b/readme_assets/ConfigWizard.png new file mode 100644 index 0000000000000000000000000000000000000000..9c1bba1b7235144ec465ff5401470def580d9ae7 GIT binary patch literal 22081 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 literal 0 HcmV?d00001 From a89d0fb19f5c8b7d7e84f3a8bb18fd792b1316bc Mon Sep 17 00:00:00 2001 From: JuiceBox <76471868+ALonelyJuicebox@users.noreply.github.com> Date: Thu, 29 Feb 2024 02:04:12 -0800 Subject: [PATCH 20/20] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 522b41b..438dffb 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,12 @@ 1. Ensure the latest version of [Microsoft Powershell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.3) is installed. 2. Open a Powershell prompt in the same directory as the script and run the command `.\OFMetadataToStash.ps1` to start its short configuration wizard -

+

## 🚩 Command Line Flags - `-ignorehistory` Script won't skip over metadata databases have already been imported to your Stash. - `-randomavatar` Script will ignore the use of the profile image that a particular performer selected for themselves. -- `-v` Script will be more verbose with its output while running. Can be useful for troubleshooting. +- `-v` Script will be more verbose with its output while running. Useful for troubleshooting. ## 📌 Additional Notes - This script makes use of the excellent [PSSQLite](https://github.com/RamblingCookieMonster/PSSQLite) and [PSGraphQL](https://www.powershellgallery.com/packages/PSGraphQL/1.6.0) PowerShell modules. Kudos to those developers!