├── LICENSE ├── README.md ├── Saverr.lnk ├── Saverr.ps1 └── screenshots ├── README.MD ├── Saverr - Downloading.png ├── Saverr - Movie Search.png ├── Saverr - Music.png ├── Saverr - Settings.png └── Saverr - TV Search.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ninthwalker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Saverr 2 | 3 | ## Developers Note: 4 | This script/repo is no longer maintained. 5 | While it still works as far as i know, I'm not adding/changing to it any longer. I would suggest to check out a project called [PlexRipper](https://github.com/PlexRipper/PlexRipper) if you are interested in something like my Saverr, but with a lot better GUI and feature set. Thanks! 6 | 7 | 8 | Powershell script with a front-end GUI to download media from Plex Servers. 9 | Allows you to search and save movies, tv shows, and music for offline/personal use. 10 | Expanded from the original non-gui version: [directplexDownloader](https://github.com/ninthwalker/directPlexDownloader). 11 | 12 | ## Details/Requirements 13 | 1. Windows 7/8/10 14 | 2. Powershell 3.0+ 15 | 3. .Net Framework 3.5+ (Usually already on your windows computer) 16 | 4. Plex Server (Can be used on your own server or a remote friends server as well) 17 | 18 | Supports: 19 | * **Movies, TV Shows, and Music:** Download any and all media. Full TV Series or Albulms as well. 20 | * **Searching:** Enter the name of what you are looking for to watch what you want, when you want. 21 | * **Multiple Server support:** Quickly switch between multiple plex servers. 22 | * **Size/ETA Estimates:** Shows current progress, size of download as well as an estimated time of finishing. 23 | * **Pause/Resume:** Supports pausing and resuming of downloads. 24 | 25 | ## Launching Saverr 26 | 27 | **Note:** 28 | Depending on your download method/settings, you may need to 'unblock' the files after downloading them from Github. This is normal behavior for Microsoft Windows to do for files downloaded from the internet. 29 | `Right click > Properties > Check 'Unblock'` 30 | 31 | **Option One** 32 | 1. Copy the Saverr.lnk shortcut, as well as the Saverr.ps1 file to your computer. Place in the same directory. 33 | 2. Double click the Saverr shortcut to launch the app. 34 | 3. Click the gear icon to configure initial settings. (See settings section below) 35 | 36 | **Option Two** 37 | 1. Copy the Saverr.ps1 file to your desktop. 38 | 2. Open a powershell console (Not ISE) and navigate to the folder you saved the Saverr.ps1 file. 39 | 3. Enter the below command to temporarily set the execution policy: 40 | `Set-ExecutionPolicy -Scope Process Bypass` 41 | Alternatively, set the execution policy to permanently allow powershell scripts: 42 | `Set-ExecutionPolicy -Scope Currentuser Unrestricted` 43 | 4. Then enter the below command to launch the app: 44 | `.\Saverr.ps1` 45 | 46 | **Option Three** 47 | 48 | Not available anymore. Even though the .exe was just the powershell script compiled, windows would flag it as a virus so I removed this option. You can compile it yourself into an executable using this tool here if you would like: 49 | (https://gallery.technet.microsoft.com/scriptcenter/PS2EXE-GUI-Convert-e7cb69d5) 50 | ~~If you trust me, I've compiled an .exe here as well.~~ 51 | ~~source is just the Saverr.ps1 file that is located here converted with the [PS2EXE] tool. You can do it yourself if you want using the Saverr.ps1 file.~~ 52 | ~~Save the Saverr.exe to your computer and double click it to launch the app.~~ 53 | 54 | ## How to use 55 | 1. Launch the script using one of the methods listed above. 56 | 2. Enter the name of the Movie, TV show or Music artist to search for. 57 | 3. Select the desired result from the results box. 58 | 3a. If a Movie: Just click download. 59 | 3b. If a TV Show, select the season or episodes, then click download. (Can also select All seasons or All episodes) 60 | 3c. If Music, select the album or tack, then click download. (Can also select All albums or All tracks) 61 | 62 | ## Config/Settings 63 | All config is done in the settings menu (Accessed by clicking the orange gear icon) 64 | 65 | * **Plex Username:** Plex Username. Can be an email or a username. (this is not saved) 66 | * **Plex Password:** Your Plex password. (this is not saved, only used to retrieve your plex token) 67 | Note for 2FA: If 2FA is enabled, please add your 2FA token to the end of your password. ie: MyP@ssword347821 68 | * **Server:** After a token has been saved from entering a username/password you can select a Plex server to search from. 69 | * **Download Path:** Navigate and select a path to save downloads to. 70 | * **SSL Required:** Select this if the server you are trying to connect to has 'Secure Connections: Required' enabled. 71 | * **Debug:** Check to enable a log file for debugging issues. (Saves to saverrLog.txt) 72 | 73 | ## Known Issues 74 | 75 | 1. Some plex servers will not work or may act a bit funky. 76 | Not much I can do about this. Everyones plex servers are set up a bit differently. Some have firewalls, special routing configuration, or nginx proxy servers in front. However, from my experience using mutiple plex servers, this works the majority of the time and the servers that do not work are outliers. 77 | 78 | 3. No mimimize button during an active download. 79 | This is intended. Because of the powershell script functions used in the background, a pause is required to prevent hanging of the script. The mimimize button becomes available after you pause a download. Click resume after maximizing the window again. You can also continue to do other things on your computer by opening other windows while the Saverr app downloads. Saverr will remain open behind these windows and can be re-selected again to use it without pausing of downloads. 80 | 81 | 4. Maximum number of downloads is 200. 82 | This is the default setting within windows for the BITS download function being used. 83 | More than 200 items will automatically be truncated to 200. 84 | If you need to increase the maximum download amount, please set this registry setting below to the value desired: 85 | **Path:** `HKLM\Software\Policies\Microsoft\Windows\BITS` 86 | **Dword:** `MaxFilesPerJob` 87 | **Decimal Value:** `Dealers Choice` 88 | Reference: [Bits](https://docs.microsoft.com/en-us/windows/desktop/bits/group-policies) 89 | 90 | 91 | ## Errors 92 | Some errors are self explanatory and output to the main app window, others are not. You can enable debugging in the settings menu. This will create a log file (saverrLog.txt) in the current Saverr directory that will give more information on the error or issue. 93 | 94 | ## Screenshots 95 | 96 | ![](https://raw.githubusercontent.com/ninthwalker/saverr/master/screenshots/Saverr%20-%20Movie%20Search.png) 97 | 98 | ![](https://raw.githubusercontent.com/ninthwalker/saverr/master/screenshots/Saverr%20-%20TV%20Search.png) 99 | 100 | ![](https://raw.githubusercontent.com/ninthwalker/saverr/master/screenshots/Saverr%20-%20Music.png) 101 | 102 | ![](https://raw.githubusercontent.com/ninthwalker/saverr/master/screenshots/Saverr%20-%20Downloading.png) 103 | 104 | ![](https://raw.githubusercontent.com/ninthwalker/saverr/master/screenshots/Saverr%20-%20Settings.png) 105 | -------------------------------------------------------------------------------- /Saverr.lnk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninthwalker/saverr/08c2d0ad47d46360152a5a19ff75368b360a8649/Saverr.lnk -------------------------------------------------------------------------------- /Saverr.ps1: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Name: Saverr # 3 | # Desc: d/l media from Plex # 4 | # Author: Ninthwalker # 5 | # Date: 16NOV2021 # 6 | # Version: 1.1.2 # 7 | ####################################### 8 | 9 | 10 | ###### NOTES FOR USER ####### 11 | 12 | # See Instruction online at: https://github.com/ninthwalker/saverr 13 | 14 | # Execution policy may need to be set to run powershell scripts if not using the shortcut from Github 15 | # ie: Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass 16 | 17 | # Enforce TLS 1.1/1.2 if wanting. Uses HTTPS/SSL by default to retrieve plex tokens. May/may not break functionality depending on network setup. 18 | #[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls 19 | 20 | # If you need to increase the maximum downloads, please set this registry setting below to value desired. 21 | # Bitstransfers default limit is 200. see: https://docs.microsoft.com/en-us/windows/desktop/bits/group-policies 22 | # Path: HKLM\Software\Policies\Microsoft\Windows\BITS 23 | # Dword: MaxFilesPerJob 24 | # Decimal Value: Dealers Choice 25 | 26 | 27 | ############################# 28 | ####### DO NOT MODIFY ####### 29 | ############################# 30 | 31 | ### setup environment ### 32 | 33 | if ($MyInvocation.MyCommand.CommandType -eq "ExternalScript") { 34 | $ScriptPath = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition 35 | } 36 | else { 37 | $ScriptPath = Split-Path -Parent -Path ([Environment]::GetCommandLineArgs()[0]) 38 | if (!$ScriptPath) { 39 | $ScriptPath = "." 40 | } 41 | } 42 | 43 | # Set-Location $PSScriptRoot. Changed to above so it works with an .exe as well. 44 | Set-Location $ScriptPath 45 | 46 | #import Bitstransfer if not 47 | if (!(Get-Module BitsTransfer)) { 48 | Import-Module BitsTransfer 49 | } 50 | 51 | # Set timeout value for how long to wait for download to start before giving up 52 | $timeout = 30 53 | 54 | # Get maximum BITS files value if set 55 | $bitsRegistry = 'HKLM:\Software\Policies\Microsoft\Windows\BITS' 56 | $key = Get-Item -LiteralPath $bitsRegistry -ErrorAction SilentlyContinue 57 | if ($key) { 58 | $limit = $key.GetValue("MaxFilesPerJob", 200) 59 | } 60 | 61 | # diff ways of d/l. invoke-restmethod seems to fail sometimes while webclient method does not. 62 | # Set-Alias -Name plx -Value Invoke-RestMethod -Scope Script 63 | 64 | # download function shortcut 65 | function plx { 66 | 67 | Param([Parameter(Mandatory=$true)] 68 | [string]$url 69 | ) 70 | 71 | # Will timeout after 20sec by default 72 | [xml](New-Object System.Net.WebClient).DownloadString($url) 73 | } 74 | 75 | # check invalid char's function 76 | Function Remove-InvalidChars { 77 | 78 | Param([Parameter(Mandatory=$true)] 79 | [string]$name 80 | ) 81 | 82 | $strip = $name -replace('[][]','') 83 | $invalidChars = [IO.Path]::GetInvalidFileNameChars() -join '' 84 | $re = "[{0}]" -f [RegEx]::Escape($invalidChars) 85 | return ($strip -replace $re) 86 | } 87 | 88 | # download path function 89 | Function Get-SavePath($initialDirectory) { 90 | [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")|Out-Null 91 | 92 | $foldername = New-Object System.Windows.Forms.FolderBrowserDialog 93 | $foldername.Description = "Select a folder to save downloads to" 94 | $foldername.rootfolder = "MyComputer" 95 | 96 | if($foldername.ShowDialog() -eq "OK") 97 | { 98 | $folder += $foldername.SelectedPath 99 | } 100 | else { 101 | $folder = $false 102 | } 103 | return $folder 104 | } 105 | 106 | # logging function 107 | function logIt { 108 | if ($debug) { 109 | $e = $_.Exception 110 | $line = $_.InvocationInfo.ScriptLineNumber 111 | $msg = $e.Message 112 | 113 | $eMSG = "$(Get-Date): caught exception: $e at $line. $msg" 114 | $eMSG | Out-File ".\saverrLog.txt" -Append 115 | } 116 | } 117 | 118 | # display size function 119 | function byteSize($num) 120 | { 121 | $suffix = "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" 122 | $index = 0 123 | while ($num -gt 1kb) 124 | { 125 | $num = $num / 1kb 126 | $index++ 127 | } 128 | 129 | "{0:N1} {1}" -f $num, $suffix[$index] 130 | } 131 | 132 | # Import settings 133 | if (Test-Path .\saverrSettings.xml) { 134 | $script:settings = Import-Clixml .\saverrSettings.xml 135 | if ((!($settings.name)) -or (!($settings.server)) -or (!($settings.userToken)) -or (!($settings.serverToken)) -or (!($settings.dlPath))) { 136 | $errorMsg = "Settings are not fully configured.`nPlease click the gear icon before searching." 137 | } 138 | else { 139 | $errorMsg = "" 140 | } 141 | } 142 | else { 143 | $errorMsg = "Settings file not detected. Please configure settings before searching." 144 | } 145 | 146 | # enable/disable debug 147 | $debug = $settings.logging 148 | 149 | # enable/disable ssl 150 | $ssl = $settings.ssl 151 | 152 | # The below is needed when the plex server has 'Secure connections: required' set. 153 | # when SSL is enforced, and the 'SSL Required' is checked on the Saverr settings page we will use HTTPS. 154 | # However, because we have to access by IP, the cert will show as 'invalid' since the CN will not match the IP. 155 | # These settings here, allow us to download from servers that enforce the SSL. 156 | 157 | # also, later on we do a similar 'ignore cert errors' for the bitstransfer job 158 | # using the below command/info: 159 | # https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/bitsadmin-setsecurityflags 160 | # https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc753211(v=ws.10)?redirectedfrom=MSDN 161 | # bitsadmin /SetSecurityFlags myJob 30 162 | 163 | if ($ssl -eq $True) { 164 | $scheme = "https://" 165 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} 166 | } 167 | else { 168 | $scheme = "http://" 169 | } 170 | 171 | # Plex signin for token url 172 | $plexSignInUrl = "https://plex.tv/users/sign_in.xml" 173 | 174 | # Plex servers list URL 175 | $plexServersUrl = "https://plex.tv/pms/servers" 176 | 177 | # init the cancel/pauseLoop variables 178 | $script:cancelLoop = $false 179 | $script:pauseLoop = $false 180 | 181 | 182 | ### Load required libraries ### 183 | 184 | Add-Type -AssemblyName System.Windows.Forms, PresentationFramework, PresentationCore, WindowsBase, System.Drawing 185 | [System.Windows.Forms.Application]::EnableVisualStyles() 186 | 187 | ################ Images ################## 188 | 189 | 190 | # load these images inside the script so external calls arents 191 | $plexImg = @' 192 | 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 244 | '@ 245 | 246 | $loadingImg = @' 247 | iVBORw0KGgoAAAANSUhEUgAAAHEAAACmCAYAAADtRWBHAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xMkM 248 | Ea+wAABC7SURBVHhe7Z0LlFTFncZ5DDCDAiIg4mOAYYZ5dE8/pqe7ZwbQAA4rIIxCQIYguCoqKL5FEZEEBBQTWRE0AopgwPg6RvNAjTGaXTdns48TyZo1iWvM6mbduBqjJhLpqt7v33 249 | RP/tVTPV0TWovb1HfO79z57nTfrqqv696qupehRzKZdHgc7U6Ht9DudHgL7U6Ht9DudHgL7U6Ht9DudHgL7U6Ht9DudHgL7U6Ht9DudHgL7U6Ht9DuzIeTfSl5cGOKk30peXBjipN9K 250 | XlwY4qTfSl5cGOKk30peXBjipN9KXlwY4qTfSl5cGOKk30peXBjipN9KXlwY4qTfSl5cGOKk30peXBjipN9KXlwY4qTfSl5cGOKk30peXBjipN9KXlwY4qTfSl5cGOKk30peXBjipN9 251 | KXlwY4qTfSl5cGOKk30peXBjipN9KXlwY4qTfSl5cGOKk30peXBjipN9KXlwY4qTfSl5cGOKk30peXBjipN9KXlwY4qTfSl5cGOKk30peXBjipN9KXlwY4qTfSl5cGOKk30peXBjipN 252 | 9KXlwY4qTfSl5cGOKk30peXBjilNn+Xy+njU1NQOwDfv9/i/X1dW1V1VVDUr/uuBS8uDGlKNdCKoXGAhGgji4EfyD3+f7IBjwi5ZonQwF/BJh7sf+cQg3/c7CScmDG1OORiGQEvSu4R 253 | QKmA9/J7b7sf2YgpsQr5VfmjpGLm8/VT547dDEhFitwO/+jNdsQIh90ocpmJQ8uDHlaFF9fX0fhFADFiGQ1djuwvY18El9vV9ScEtnjRR3XnKieOym48V/bC0TH+3tLV+6bWAiHPQl8 254 | TrqjTtra2uPTx+yYFLy4MaUYhYavRfCG4ntxWA3eBn8Dnxaj1Ci4TqxYGpFYvtVw8SL6wfKN7aVyg/3lMhPHuktDzzSO0nbC9tGCRwjCSjEp9GDR6UPXzApeXBjSrEpHA73RmNXobGv 255 | x/af0fhvYPt7cBBICmTmxCqx4+phif2b+4u37+8nP9xbQqHJPz/aO8l5c3s/EYvUZkKk3vgKenBL+qMKJiUPbkzxqtCgdIqkbU8E1heNGwPbwG+w71P8LhVYNqGAT77+9VKRHVg2FOr 256 | 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 283 | gpae6E4FPbBU7Oie+iuKEOk+Id3QPRmMKGJOAUuy6k4UR4hgdiAQKOo78giwF5iXVXfCkyF+LasSLsQCSMmDG1O6IxeiUnfCkyF+NasSLsQCSMmDG1O6IxeiUnfChegVuRCLQC7EIpA 284 | LsQjkQiwCFVWIqIgLUcWTId6RVQkXYgGk5MGNKd2RC1GpO+FC9IpciEWgYgtxY1YlXIgFkJIHN6Z0Ry5Epe6EC9ErciEWgVyIRaBiC/H2rEq4EAsgJQ9uTOmOXIhK3YniCTEUCvWaM2 285 | dOj2KFvqRFE6LP57stqxL03Ok6bKeDaUUM/dHe7C+wZ/+59wpNRegP2n6In4sWqh9Q/iwaoKffp6eb5rCk5MGNKd0RQqQ/BJv3j9geDaAdXgdD001zWFLy4MaU7giFp2vDFwD9Pxcm/ 286 | 0FJ0YF6f4rLyvPY+ugPNBVCSh7cmOJkX0oe3JjiZF9KHtyY4mRfSh7cmOJkX0oe3JjiZF9KHtyY4mRfSh7cmOJkX0oe3JjiZF9KHtyY4mRfSh7cmOJkX0oe3JjiZF9KHtyY4mRfSh7c 287 | mOJkX0oe3JjiZF9KHtyY4mRfSh7cmOJkX0oe3JjiZF9KHtyY4mRfSh7cmOJkX0oe3JjiZF9KHtyY4mRfSh7cmOJkX0oe3JjiZF9KHtyY4mRfSh7cmOJkX0oe3JjiZF9KHtyY4mRfSh7 288 | cmOJkX0oe3JjiZF9KHtyY4mRfSh7cmOJkX0oe3JjiZF9KHtyY4mRfSh7cmOJkX0oe3JjiZF9KHtyY4mRfSh7cOLyJdqfDW2h3OryFdqfDW2h3OryFdqfDW2h3OryFdqfDW2h3OryFdq 289 | fDW2h3OryFdqfDSyR7/D/88mPYQlLgrwAAAABJRU5ErkJggg== 290 | '@ 291 | 292 | 293 | $iconImg = @' 294 | AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAMMOAADDDgAAAAAAAAAAAAAtLS0ZLS0tyS0tLbQtLS2gLS0toS0tLaEtLS2hLS0toS0tLaEtLS2hLS0toS0 295 | tLaEtLS2hLS0toC0tLbItLS3qLS0tIS0tLcgtLS0yLCwsAi0tLQUtLS0FLS0tBS0tLQUtLS0FLS0tBS0tLQUtLS0FLS0tBSwsLAItLS0xLS0t6C0tLSEtLS3ILS0tLi0tLQAAAAAAAAAAAA 296 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtLS0ALS0tLS0tLegtLS0hLS0tyC0tLS4tLS0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALS0tAC0tLS0tLS3oLS0tI 297 | S0tLcgtLS0uLS0tAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC0tLQAtLS0tLS0t6C0tLSEtLS3JLiopNzQVBggxIRoLLS0sCyMmKwsYHyoLGyEqCykrLAsuKigLMh0T 298 | CzMbEAswIhsILS0tNi0tLektLSwgLS4u1yk5QdAoPUfCKjc9wy0sK8M2My7DQDkvwz03L8MuLi3DKy8ywyg8RsMoPEbDKzM3wi0sK84tLS36LSwsIC0tLtwfWHT/C5LS/w2Myf8iS1//RDw 299 | w/5NtNf+oeTb/b1cy/zA1Nv8XbZX/CZfa/xGAtP8oPEb/LSws/y0tLSEtLCvcLC8w/xpni/QLk9TzEn6w9ig8RfdRRC/3mnE1951zNfdbSjH3KTlA9xR5qPYLktL2GWuS9ysyNfAuKylRKD 300 | tD7yVFVPssMTTEKjY7eydBTk0pNz4/KSosQD04L0JHPjBCQzwwQi8rKUIrMzdCJ0JOQiZDUUMrMzY4LSsrzic+Sf8MkdD/EIO5/ic/S+wuKyq5LS0tbR8kLCYKFCgDERsqAAAAAAAAAAAAA 301 | AAAAAAAAAAAAAAAAAAAAC0tLFgsLzCkGWiN4wqW2P0aZor/MC4s/2RQMv5dSzHmOjUuqSwsLVovKCUbOwMAAjYRBQAAAAAAAAAAAAAAAAAvJyMAMCQeBC4pJyYkSVpqIk1htystL+1sVDL/ 302 | uYM2/4ZlNP8xLy37KTg+2Co2PJEvJiJCLiglDk0AAAAsLCwAAAAAAAAAAACEAAAAB0lIADUgHwkqKyw0PzkvfnhcM8iPajX1Qzot/yBRaP8QhLv/HV188ysyNcMtLCtALS0tAAAAAAAAAAA 303 | AAAAAAAAAAAAAAAAAAAEhAAAAAAAVHSwQQDowRDYxLJAoO0TUDorF+Aee5v8jTGD+LikmYS0tLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC0tLQAsLS4CLiooHiVFVGMZaI 304 | y9IFNq5C4pJjQtLS0AAAAAAAAAAAAf/AAAH/wAAB/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/AAAADwAAgAMAAPABAAD8AQAA/4EAAA== 305 | '@ 306 | 307 | $settingsImg = @' 308 | iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xMkMEa+w 309 | AAALtSURBVEhLtVVPSBRRGJ/V3ZnR3XVllS1rzf4RIWWWVFJ06FZ46Bh08ZS3oGMk1bW6RIHXwpz3ZmZ3dS3QEgLzVJQdoqAo8iIlRaQEIevO+17fPF+5jU93gvrBjx3e9/t+385733xPWw 310 | /lIb2HUd1ixHy14Han5bIAjJ8wGDVfMFsfgpHNJ+VyeECh6TgjsSlOIpwTTZDRukE+3ReTEs1z4qO/Yj6B6pMlq75ThqsDbH0X0NqlSpNlo9hbZsXGGI1+CMZEPJ/pkBbhwJzkrMpoTTpxL 311 | lPDAcYPZRmp/aE0W5MRXnKze6XFapSKbZ18ukvsN4zvNDyiP1IbrU88x0mea9sofN6dMxaLbVv9ZwHsoCnc7xlmRR/g75zKICzRa55R4wnQ6BwWvScKlGmyp7KL/jXBSRzFtzAsVTBIZpu3 312 | IN9yDGyzFfIbDjOn4TqQyKoODJKRuqsas+ufqYKV9OxUn3jtADy3+XS1QtjWgxpMdMSZnRxQCXwyYoxKTyXKxHBVeZzW8LLddErKlsFI9KVKDG7qrJQowXOZPlUejpwBKVkBnk1BJfbcpjN 313 | SooSX392rygOqnZeSFWBl5ajgtn5BSpTwiHlZlYdD9envWccnexs9J/nHsKskHuwsFHe0CnEAy5M4+l6V5xNjFp55Bs/CrNpd2L7PFwtbtktvARg+0IIf3mOVvpLMTb/xt+m2Khgks2q+YX 314 | NQRiI3gNTeBVLzRaUL0rPMK9qSk+z+r198ftMR8er+HQFOfAbwYgJqfFaJwxLsOumjf8JvbEgU8MGLqa0f73fV+88LD9vTuC0jKoNqxIF4k+faE77PfHFfIww3t/jPSoCbTeM0/q4yWovYB 315 | F95ThMFQuNvb0ZWyLyWqeEAww0HmRUprTKi5phnm/3gmBPBGN4fSziCtkmL6ig52T3MSTiVXefZDf0yLIBD9VplEUb0O4t2auUmDAueS3XhP7/EaGICD1SXywL+yPDnHd4vF0s0tV8uK6Bp 316 | PwEAHUcTE+ClVQAAAABJRU5ErkJggg== 317 | '@ 318 | 319 | # Image logo function 320 | function DecodeBase64Image { 321 | param ([Parameter(Mandatory=$true)][String]$ImageBase64) 322 | $ObjBitmapImage = New-Object System.Windows.Media.Imaging.BitmapImage #Provides a specialized BitmapSource that is optimized for loading images using Extensible Application Markup Language (XAML). 323 | $ObjBitmapImage.BeginInit() #Signals the start of the BitmapImage initialization. 324 | $ObjBitmapImage.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($ImageBase64) #Creates a stream whose backing store is memory. 325 | $ObjBitmapImage.EndInit() #Signals the end of the BitmapImage initialization. 326 | $ObjBitmapImage.Freeze() #Makes the current object unmodifiable and sets its IsFrozen property to true. 327 | $ObjBitmapImage 328 | } 329 | 330 | $plexImgDecoded = DecodeBase64Image -ImageBase64 $plexImg 331 | $loadingImgDecoded = DecodeBase64Image -ImageBase64 $loadingImg 332 | $settingsImgDecoded = DecodeBase64Image -ImageBase64 $settingsImg 333 | $loading = [System.Drawing.Bitmap][System.Drawing.Image]::FromStream($loadingImgDecoded.StreamSource) 334 | $logo = [System.Drawing.Bitmap][System.Drawing.Image]::FromStream($plexImgDecoded.StreamSource) 335 | $gear = [System.Drawing.Bitmap][System.Drawing.Image]::FromStream($settingsImgDecoded.StreamSource) 336 | 337 | 338 | # Icon 339 | $iconBase64 = $iconImg 340 | $iconBytes = [Convert]::FromBase64String($IconBase64) 341 | $stream = New-Object IO.MemoryStream($iconBytes, 0, $iconBytes.Length) 342 | $stream.Write($iconBytes, 0, $iconBytes.Length); 343 | 344 | #################### FORMS ############################## 345 | 346 | #Defaults 347 | $label_mediaTitle_default_xy = New-Object System.Drawing.Point(140,255) 348 | $label_mediaRating_default_xy = New-Object System.Drawing.Point(140,275) 349 | $label_mediaScore_default_xy = New-Object System.Drawing.Point(215,275) 350 | $label_mediaSummary_default_xy = New-Object System.Drawing.Point(140,295) 351 | $label_mediaSummary_default_height = 110 352 | 353 | # main form 354 | $form = New-Object system.Windows.Forms.Form 355 | $form.ClientSize = '550,500' 356 | $form.text = "Saverr" 357 | $form.BackColor = "#4a4a4a" 358 | $form.TopMost = $false 359 | $form.StartPosition = 'CenterScreen' 360 | $form.Icon = [System.Drawing.Icon]::FromHandle((New-Object System.Drawing.Bitmap -Argument $stream).GetHIcon()) 361 | $form.FormBorderStyle = "FixedDialog" 362 | $form.MaximizeBox = $false 363 | 364 | $label_title = New-Object system.Windows.Forms.Label 365 | $label_title.text = "Saverr" 366 | $label_title.AutoSize = $true 367 | $label_title.width = 30 368 | $label_title.height = 20 369 | $label_title.location = New-Object System.Drawing.Point(143,12) 370 | $label_title.Font = 'Microsoft Sans Serif,15,style=Bold' 371 | $label_title.ForeColor = "#f5a623" 372 | 373 | $pictureBox_logo = New-Object system.Windows.Forms.PictureBox 374 | $pictureBox_logo.width = 75 375 | $pictureBox_logo.height = 75 376 | $pictureBox_logo.location = New-Object System.Drawing.Point(15,15) 377 | $pictureBox_logo.image = $logo 378 | $pictureBox_logo.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::normal 379 | 380 | $pictureBox_thumb = new-object Windows.Forms.PictureBox 381 | $pictureBox_thumb.ImageLocation = "" 382 | $pictureBox_thumb.Visible = $false 383 | $pictureBox_thumb.Width = 113 #680 384 | $pictureBox_thumb.Height = 166 #1000 385 | $pictureBox_thumb.Anchor = [System.Windows.Forms.AnchorStyles]::Top 386 | $pictureBox_thumb.Location = New-object System.Drawing.Size(15,250) 387 | $pictureBox_thumb.image = $loading 388 | $pictureBox_thumb.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom 389 | 390 | $label_search = New-Object system.Windows.Forms.Label 391 | $label_search.text = "Search Movie:" 392 | $label_search.AutoSize = $true 393 | $label_search.width = 30 394 | $label_search.height = 15 395 | $label_search.location = New-Object System.Drawing.Point(15,105) 396 | $label_search.Font = 'Microsoft Sans Serif,8' 397 | $label_search.ForeColor = "#f5a623" 398 | 399 | $textBox_search = New-Object system.Windows.Forms.TextBox 400 | $textBox_search.multiline = $false 401 | $textBox_search.text = "" 402 | $textBox_search.width = 300 403 | $textBox_search.height = 20 404 | $textBox_search.location = New-Object System.Drawing.Point(15,125) 405 | $textBox_search.Font = 'Microsoft Sans Serif,10' 406 | 407 | $button_search = New-Object system.Windows.Forms.Button 408 | $button_search.BackColor = "#f5a623" 409 | $button_search.text = "Search" 410 | $button_search.width = 80 411 | $button_search.height = 25 412 | $button_search.location = New-Object System.Drawing.Point(345,124) 413 | $button_search.Font = 'Microsoft Sans Serif,9,style=Bold' 414 | $button_search.FlatStyle = "Flat" 415 | 416 | $button_download = New-Object system.Windows.Forms.Button 417 | $button_download.Enabled = $false 418 | $button_download.BackColor = "#f5a623" 419 | $button_download.text = "Download" 420 | $button_download.width = 80 421 | $button_download.height = 25 422 | $button_download.location = New-Object System.Drawing.Point(345,174) 423 | $button_download.Font = 'Microsoft Sans Serif,9,style=Bold' 424 | $button_download.FlatStyle = "Flat" 425 | 426 | $button_settings = New-Object system.Windows.Forms.Button 427 | $button_settings.width = 30 428 | $button_settings.height = 30 429 | $button_settings.location = New-Object System.Drawing.Point(508,8) 430 | $button_settings.image = $gear 431 | $button_settings.FlatStyle = "Flat" 432 | $button_settings.BackColor = "Transparent" 433 | $button_settings.FlatAppearance.BorderSize = 0 434 | $button_settings.FlatAppearance.MouseDownBackColor = "Transparent" 435 | $button_settings.FlatAppearance.MouseOverBackColor = "#666666" 436 | 437 | $groupBox_type = New-Object system.Windows.Forms.Groupbox 438 | $groupBox_type.height = 40 439 | $groupBox_type.width = 285 440 | $groupBox_type.text = "Select Media Type" 441 | $groupBox_type.location = New-Object System.Drawing.Point(140,46) 442 | 443 | $RadioButton_movie = New-Object system.Windows.Forms.RadioButton 444 | $RadioButton_movie.text = "Movies" 445 | $RadioButton_movie.AutoSize = $true 446 | $RadioButton_movie.Checked = $true 447 | $RadioButton_movie.width = 80 448 | $RadioButton_movie.height = 20 449 | $RadioButton_movie.location = New-Object System.Drawing.Point(15,16) 450 | $RadioButton_movie.Font = 'Microsoft Sans Serif,9' 451 | $RadioButton_movie.ForeColor = "#ffffff" 452 | 453 | $RadioButton_tv = New-Object system.Windows.Forms.RadioButton 454 | $RadioButton_tv.text = "TV Shows" 455 | $RadioButton_tv.AutoSize = $true 456 | $RadioButton_tv.width = 80 457 | $RadioButton_tv.height = 20 458 | $RadioButton_tv.location = New-Object System.Drawing.Point(110,16) 459 | $RadioButton_tv.Font = 'Microsoft Sans Serif,9' 460 | $RadioButton_tv.ForeColor = "#ffffff" 461 | 462 | $RadioButton_music = New-Object system.Windows.Forms.RadioButton 463 | $RadioButton_music.text = "Artists" 464 | $RadioButton_music.AutoSize = $true 465 | $RadioButton_music.width = 80 466 | $RadioButton_music.height = 20 467 | $RadioButton_music.location = New-Object System.Drawing.Point(215,16) 468 | $RadioButton_music.Font = 'Microsoft Sans Serif,9' 469 | $RadioButton_music.ForeColor = "#ffffff" 470 | 471 | $label_results = New-Object system.Windows.Forms.Label 472 | $label_results.text = "Results:" 473 | $label_results.AutoSize = $true 474 | $label_results.width = 30 475 | $label_results.height = 15 476 | $label_results.location = New-Object System.Drawing.Point(15,155) 477 | $label_results.Font = 'Microsoft Sans Serif,8' 478 | $label_results.ForeColor = "#f5a623" 479 | 480 | $comboBox_results = New-Object system.Windows.Forms.ComboBox 481 | $comboBox_results.text = "" 482 | $comboBox_results.width = 300 483 | $comboBox_results.height = 20 484 | $comboBox_results.location = New-Object System.Drawing.Point(15,175) 485 | $comboBox_results.Font = 'Microsoft Sans Serif,10' 486 | 487 | $label_seasons = New-Object system.Windows.Forms.Label 488 | $label_seasons.text = "" 489 | $label_seasons.AutoSize = $true 490 | $label_seasons.width = 30 491 | $label_seasons.height = 15 492 | $label_seasons.location = New-Object System.Drawing.Point(15,225) 493 | $label_seasons.Font = 'Microsoft Sans Serif,8' 494 | $label_seasons.ForeColor = "#f5a623" 495 | 496 | $comboBox_seasons = New-Object system.Windows.Forms.ComboBox 497 | $comboBox_seasons.Visible = $false 498 | $comboBox_seasons.text = "" 499 | $comboBox_seasons.width = 130 500 | $comboBox_seasons.height = 20 501 | $comboBox_seasons.location = New-Object System.Drawing.Point(65,215) 502 | $comboBox_seasons.Font = 'Microsoft Sans Serif,10' 503 | 504 | $label_episodes = New-Object system.Windows.Forms.Label 505 | $label_episodes.text = "" 506 | $label_episodes.AutoSize = $true 507 | $label_episodes.width = 30 508 | $label_episodes.height = 15 509 | $label_episodes.location = New-Object System.Drawing.Point(215,225) 510 | $label_episodes.Font = 'Microsoft Sans Serif,8' 511 | $label_episodes.ForeColor = "#f5a623" 512 | 513 | $comboBox_episodes = New-Object system.Windows.Forms.ComboBox 514 | $comboBox_episodes.Visible = $false 515 | $comboBox_episodes.text = "" 516 | $comboBox_episodes.width = 45 517 | $comboBox_episodes.height = 20 518 | $comboBox_episodes.location = New-Object System.Drawing.Point(265,215) 519 | $comboBox_episodes.Font = 'Microsoft Sans Serif,10' 520 | 521 | $label_mediaTitle = New-Object system.Windows.Forms.Label 522 | $label_mediaTitle.text = "" 523 | $label_mediaTitle.AutoSize = $false 524 | $label_mediaTitle.AutoEllipsis = $true 525 | $label_mediaTitle.width = 400 526 | $label_mediaTitle.height = 20 527 | $label_mediaTitle.location = $label_mediaTitle_default_xy 528 | $label_mediaTitle.Font = 'Microsoft Sans Serif,10,style=Bold' 529 | $label_mediaTitle.ForeColor = "#f5a623" 530 | 531 | $label_mediaRating = New-Object system.Windows.Forms.Label 532 | $label_mediaRating.text = "" 533 | $label_mediaRating.AutoSize = $true 534 | $label_mediaRating.width = 50 535 | $label_mediaRating.height = 20 536 | $label_mediaRating.location = $label_mediaRating_default_xy 537 | $label_mediaRating.Font = 'Microsoft Sans Serif,9,style=Bold' 538 | $label_mediaRating.ForeColor = "#ffffff" 539 | 540 | $label_mediaScore = New-Object system.Windows.Forms.Label 541 | $label_mediaScore.text = "" 542 | $label_mediaScore.AutoSize = $true 543 | $label_mediaScore.width = 50 544 | $label_mediaScore.height = 20 545 | $label_mediaScore.location = $label_mediaScore_default_xy 546 | $label_mediaScore.Font = 'Microsoft Sans Serif,9,style=Bold' 547 | $label_mediaScore.ForeColor = "#ffffff" 548 | 549 | $label_mediaSummary = New-Object system.Windows.Forms.Label 550 | $label_mediaSummary.text = $errorMsg 551 | $label_mediaSummary.AutoSize = $false 552 | $label_mediaSummary.AutoEllipsis = $true 553 | $label_mediaSummary.width = 400 554 | $label_mediaSummary.height = $label_mediaSummary_default_height 555 | $label_mediaSummary.location = $label_mediaSummary_default_xy 556 | $label_mediaSummary.Font = 'Microsoft Sans Serif,9' 557 | $label_mediaSummary.ForeColor = "#ffffff" 558 | 559 | $label_DLTitle = New-Object system.Windows.Forms.Label 560 | $label_DLTitle.text = "" 561 | $label_DLTitle.AutoSize = $false 562 | $label_DLTitle.AutoEllipsis = $true 563 | $label_DLTitle.width = 440 564 | $label_DLTitle.height = 20 565 | $label_DLTitle.location = New-Object System.Drawing.Point(15,430) 566 | $label_DLTitle.Font = 'Microsoft Sans Serif,9' 567 | $label_DLTitle.ForeColor = "#f5a623" 568 | 569 | $label_DLProgress = New-Object system.Windows.Forms.Label 570 | $label_DLProgress.text = "" 571 | $label_DLProgress.AutoSize = $false 572 | $label_DLProgress.AutoEllipsis = $true 573 | $label_DLProgress.width = 410 574 | $label_DLProgress.height = 20 575 | $label_DLProgress.location = New-Object System.Drawing.Point(15,455) 576 | $label_DLProgress.Font = 'Microsoft Sans Serif,9' 577 | $label_DLProgress.ForeColor = "#f5a623" 578 | 579 | $button_cancel = New-Object system.Windows.Forms.Button 580 | $button_cancel.Visible = $false 581 | $button_cancel.BackColor = "#f5a623" 582 | $button_cancel.text = "Cancel" 583 | $button_cancel.width = 80 584 | $button_cancel.height = 25 585 | $button_cancel.location = New-Object System.Drawing.Point(456,470) 586 | $button_cancel.Font = 'Microsoft Sans Serif,9,style=Bold' 587 | $button_cancel.FlatStyle = "Flat" 588 | 589 | $checkBoxButton_pause = New-Object System.Windows.Forms.Checkbox 590 | $checkBoxButton_pause.location = New-Object System.Drawing.Point(456,440) 591 | $checkBoxButton_pause.Size = New-Object System.Drawing.Size(80,25) 592 | $checkBoxButton_pause.Appearance = [System.Windows.Forms.Appearance]::Button 593 | $checkBoxButton_pause.Visible = $false 594 | $checkBoxButton_pause.Text = "Pause" 595 | $checkBoxButton_pause.FlatStyle = "Flat" 596 | $checkBoxButton_pause.BackColor = "#f5a623" 597 | $checkBoxButton_pause.width = 80 598 | $checkBoxButton_pause.height = 25 599 | $checkBoxButton_pause.TextAlign = "MiddleCenter" 600 | $checkBoxButton_pause.AllowDrop = $false 601 | $checkBoxButton_pause.Font = 'Microsoft Sans Serif,9,style=Bold' 602 | $checkBoxButton_pause.FlatAppearance.BorderColor = "#000000" 603 | 604 | $progressBar = New-Object system.Windows.Forms.ProgressBar 605 | $progressBar.Name = 'progressBar1' 606 | $progressBar.BackColor = "#f5a623" 607 | $progressBar.ForeColor = "#f5a623" 608 | $progressBar.width = 430 609 | $progressBar.height = 12 610 | $progressBar.location = New-Object System.Drawing.Point(15,480) 611 | $progressBar.Value = 0 612 | $progressBar.Style = "Continuous" #"Blocks" 613 | $progressBar.Visible = $false 614 | $progressBar.Minimum = 0 615 | $progressBar.Maximum = 110 616 | 617 | # add tooltips 618 | $toolTip = New-Object System.Windows.Forms.ToolTip 619 | $toolTip.SetToolTip($label_search, "Searches by first Letter. Excluding 'The' and 'A'") 620 | 621 | $form.controls.AddRange(@($groupbox_type,$label_title,$pictureBox_logo,$pictureBox_thumb,$label_search,$textBox_search,$progressBar,$button_search,$button_download,$button_settings,$label_mediaTitle,$label_mediaScore,$comboBox_results,$comboBox_seasons,$comboBox_episodes,$label_mediaRating,$label_mediaSummary,$label_results,$label_seasons,$label_episodes,$label_DLTitle,$label_DLProgress,$button_cancel,$checkBoxButton_pause)) 622 | $groupBox_type.controls.AddRange(@($RadioButton_movie,$RadioButton_tv,$RadioButton_music)) 623 | 624 | 625 | # settings form 626 | $form2 = New-Object system.Windows.Forms.Form 627 | $form2.ClientSize = '550,500' 628 | $form2.text = "Saverr Settings" 629 | $form2.BackColor = "#4a4a4a" 630 | $form2.TopMost = $false 631 | $form2.StartPosition = 'CenterScreen' 632 | $form2.Icon = [System.Drawing.Icon]::FromHandle((New-Object System.Drawing.Bitmap -Argument $stream).GetHIcon()) 633 | $form2.FormBorderStyle = "FixedDialog" 634 | $form2.MaximizeBox = $false 635 | $form2.StartPosition = "CenterParent" 636 | 637 | $label2_title = New-Object system.Windows.Forms.Label 638 | $label2_title.text = "Settings" 639 | $label2_title.AutoSize = $true 640 | $label2_title.width = 30 641 | $label2_title.height = 20 642 | $label2_title.location = New-Object System.Drawing.Point(143,12) 643 | $label2_title.Font = 'Microsoft Sans Serif,15,style=Bold' 644 | $label2_title.ForeColor = "#f5a623" 645 | 646 | $label2_notice = New-Object system.Windows.Forms.Label 647 | $label2_notice.text = "Username/password is not saved.`nOnly used to retrieve Plex token" 648 | $label2_notice.AutoSize = $true 649 | $label2_notice.width = 70 650 | $label2_notice.height = 20 651 | $label2_notice.location = New-Object System.Drawing.Point(143,50) 652 | $label2_notice.Font = 'Microsoft Sans Serif,8' 653 | $label2_notice.ForeColor = "#ffffff" 654 | 655 | $pictureBox2_logo = New-Object system.Windows.Forms.PictureBox 656 | $pictureBox2_logo.width = 75 657 | $pictureBox2_logo.height = 75 658 | $pictureBox2_logo.location = New-Object System.Drawing.Point(15,15) 659 | $pictureBox2_logo.image = [System.Drawing.Bitmap][System.Drawing.Image]::FromStream($plexImgDecoded.StreamSource) 660 | $pictureBox2_logo.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::normal 661 | 662 | $label2_username = New-Object system.Windows.Forms.Label 663 | $label2_username.text = "Plex Username:" 664 | $label2_username.AutoSize = $true 665 | $label2_username.width = 30 666 | $label2_username.height = 20 667 | $label2_username.location = New-Object System.Drawing.Point(15,121) 668 | $label2_username.Font = 'Microsoft Sans Serif,8' 669 | $label2_username.ForeColor = "#ffffff" 670 | 671 | $textBox2_username = New-Object system.Windows.Forms.TextBox 672 | $textBox2_username.multiline = $false 673 | $textBox2_username.width = 225 674 | $textBox2_username.height = 20 675 | $textBox2_username.location = New-Object System.Drawing.Point(110,110) 676 | $textBox2_username.Font = 'Microsoft Sans Serif,10' 677 | 678 | $label2_password = New-Object system.Windows.Forms.Label 679 | $label2_password.text = "Plex Password:" 680 | $label2_password.AutoSize = $true 681 | $label2_password.width = 70 682 | $label2_password.height = 20 683 | $label2_password.location = New-Object System.Drawing.Point(15,166) 684 | $label2_password.Font = 'Microsoft Sans Serif,8' 685 | $label2_password.ForeColor = "#ffffff" 686 | 687 | $textBox2_password = New-Object system.Windows.Forms.TextBox 688 | $textBox2_password.PasswordChar = '*' 689 | $textBox2_password.multiline = $false 690 | $textBox2_password.width = 225 691 | $textBox2_password.height = 20 692 | $textBox2_password.location = New-Object System.Drawing.Point(110,155) 693 | $textBox2_password.Font = 'Microsoft Sans Serif,10' 694 | 695 | $button2_getToken = New-Object system.Windows.Forms.Button 696 | $button2_getToken.BackColor = "#f5a623" 697 | $button2_getToken.text = "Get Token" 698 | $button2_getToken.width = 95 699 | $button2_getToken.height = 25 700 | $button2_getToken.location = New-Object System.Drawing.Point(350,154) 701 | $button2_getToken.Font = 'Microsoft Sans Serif,9,style=Bold' 702 | $button2_getToken.FlatStyle = "Flat" 703 | 704 | $label2_tokenStatus = New-Object system.Windows.Forms.Label 705 | $label2_tokenStatus.text = "" 706 | $label2_tokenStatus.AutoSize = $true 707 | $label2_tokenStatus.width = 70 708 | $label2_tokenStatus.height = 20 709 | $label2_tokenStatus.location = New-Object System.Drawing.Point(350,180) 710 | $label2_tokenStatus.Font = 'Microsoft Sans Serif,8' 711 | $label2_tokenStatus.ForeColor = "#ffff00" 712 | 713 | $label2_server = New-Object system.Windows.Forms.Label 714 | $label2_server.text = "Select Server:" 715 | $label2_server.AutoSize = $true 716 | $label2_server.width = 70 717 | $label2_server.height = 20 718 | $label2_server.location = New-Object System.Drawing.Point(15,211) 719 | $label2_server.Font = 'Microsoft Sans Serif,8' 720 | $label2_server.ForeColor = "#ffffff" 721 | 722 | $comboBox2_servers = New-Object system.Windows.Forms.ComboBox 723 | $comboBox2_servers.text = $settings.name 724 | $comboBox2_servers.width = 225 725 | $comboBox2_servers.height = 20 726 | $comboBox2_servers.location = New-Object System.Drawing.Point(110,200) 727 | $comboBox2_servers.Font = 'Microsoft Sans Serif,10' 728 | 729 | $label2_dlPath = New-Object system.Windows.Forms.Label 730 | $label2_dlPath.text = "Download Path:" 731 | $label2_dlPath.AutoSize = $true 732 | $label2_dlPath.width = 70 733 | $label2_dlPath.height = 20 734 | $label2_dlPath.location = New-Object System.Drawing.Point(15,256) 735 | $label2_dlPath.Font = 'Microsoft Sans Serif,8' 736 | $label2_dlPath.ForeColor = "#ffffff" 737 | 738 | $textBox2_dlPath = New-Object system.Windows.Forms.TextBox 739 | $textBox2_dlPath.text = $settings.dlPath 740 | $textBox2_dlPath.ReadOnly = $true 741 | $textBox2_dlPath.multiline = $false 742 | $textBox2_dlPath.width = 225 743 | $textBox2_dlPath.height = 20 744 | $textBox2_dlPath.location = New-Object System.Drawing.Point(110,245) 745 | $textBox2_dlPath.Font = 'Microsoft Sans Serif,10' 746 | $textBox2_dlPath.Enabled = $false 747 | 748 | $button2_dlPath = New-Object system.Windows.Forms.Button 749 | $button2_dlPath.BackColor = "#f5a623" 750 | $button2_dlPath.text = "Select Path" 751 | $button2_dlPath.width = 95 752 | $button2_dlPath.height = 25 753 | $button2_dlPath.location = New-Object System.Drawing.Point(350,244) 754 | $button2_dlPath.Font = 'Microsoft Sans Serif,9,style=Bold' 755 | $button2_dlPath.FlatStyle = "Flat" 756 | 757 | $label2_pathStatus = New-Object system.Windows.Forms.Label 758 | $label2_pathStatus.text = "" 759 | $label2_pathStatus.AutoSize = $true 760 | $label2_pathStatus.width = 70 761 | $label2_pathStatus.height = 20 762 | $label2_pathStatus.location = New-Object System.Drawing.Point(350,270) 763 | $label2_pathStatus.Font = 'Microsoft Sans Serif,8' 764 | $label2_pathStatus.ForeColor = "#ffff00" 765 | 766 | $label2_ssl = New-Object system.Windows.Forms.Label 767 | $label2_ssl.text = "SSL Required:" 768 | $label2_ssl.AutoSize = $true 769 | $label2_ssl.width = 70 770 | $label2_ssl.height = 20 771 | $label2_ssl.location = New-Object System.Drawing.Point(15,290) 772 | $label2_ssl.Font = 'Microsoft Sans Serif,8' 773 | $label2_ssl.ForeColor = "#ffffff" 774 | 775 | $checkBox_ssl = New-Object System.Windows.Forms.Checkbox 776 | $checkBox_ssl.location = New-Object System.Drawing.Point(110,285) 777 | $checkBox_ssl.Size = New-Object System.Drawing.Size(80,25) 778 | $checkBox_ssl.width = 80 779 | $checkBox_ssl.height = 25 780 | $checkBox_ssl.checked = $settings.ssl 781 | 782 | $label2_debug = New-Object system.Windows.Forms.Label 783 | $label2_debug.text = "Debug Logging:" 784 | $label2_debug.AutoSize = $true 785 | $label2_debug.width = 70 786 | $label2_debug.height = 20 787 | $label2_debug.location = New-Object System.Drawing.Point(15,325) 788 | $label2_debug.Font = 'Microsoft Sans Serif,8' 789 | $label2_debug.ForeColor = "#ffffff" 790 | 791 | $checkBox_debug = New-Object System.Windows.Forms.Checkbox 792 | $checkBox_debug.location = New-Object System.Drawing.Point(110,320) 793 | $checkBox_debug.Size = New-Object System.Drawing.Size(80,25) 794 | $checkBox_debug.width = 80 795 | $checkBox_debug.height = 25 796 | $checkBox_debug.checked = $settings.logging 797 | 798 | $label2_ssl_info = New-Object system.Windows.Forms.Label 799 | $label2_ssl_info.text = "[Restart Saverr after changing SSL or Debug options]" 800 | $label2_ssl_info.AutoSize = $true 801 | $label2_ssl_info.width = 70 802 | $label2_ssl_info.height = 20 803 | $label2_ssl_info.location = New-Object System.Drawing.Point(15,360) 804 | $label2_ssl_info.Font = 'Microsoft Sans Serif,8' 805 | $label2_ssl_info.ForeColor = "#ffffff" 806 | 807 | $button2_servers = New-Object system.Windows.Forms.Button 808 | $button2_servers.BackColor = "#f5a623" 809 | $button2_servers.text = "List Servers" 810 | $button2_servers.width = 95 811 | $button2_servers.height = 25 812 | $button2_servers.location = New-Object System.Drawing.Point(350,199) 813 | $button2_servers.Font = 'Microsoft Sans Serif,9,style=Bold' 814 | $button2_servers.FlatStyle = "Flat" 815 | 816 | $label2_serverStatus = New-Object system.Windows.Forms.Label 817 | $label2_serverStatus.text = "" 818 | $label2_serverStatus.AutoSize = $true 819 | $label2_serverStatus.width = 70 820 | $label2_serverStatus.height = 20 821 | $label2_serverStatus.location = New-Object System.Drawing.Point(350,225) 822 | $label2_serverStatus.Font = 'Microsoft Sans Serif,8' 823 | $label2_serverStatus.ForeColor = "#ffff00" 824 | 825 | $label2_notice2 = New-Object system.Windows.Forms.Label 826 | $label2_notice2.text = "" 827 | $label2_notice2.AutoSize = $true 828 | $label2_notice2.width = 70 829 | $label2_notice2.height = 20 830 | $label2_notice2.location = New-Object System.Drawing.Point(15,395) 831 | $label2_notice2.Font = 'Microsoft Sans Serif,8' 832 | $label2_notice2.ForeColor = "#ffffff" 833 | 834 | $label2_saveStatus = New-Object system.Windows.Forms.Label 835 | $label2_saveStatus.text = "" 836 | $label2_saveStatus.AutoSize = $true 837 | $label2_saveStatus.width = 70 838 | $label2_saveStatus.height = 20 839 | $label2_saveStatus.location = New-Object System.Drawing.Point(110,275) 840 | $label2_saveStatus.Font = 'Microsoft Sans Serif,8' 841 | $label2_saveStatus.ForeColor = "#00ff00" 842 | 843 | $label2_help = New-Object system.Windows.Forms.LinkLabel 844 | $label2_help.text = "Help" 845 | $label2_help.AutoSize = $true 846 | $label2_help.width = 70 847 | $label2_help.height = 20 848 | $label2_help.location = New-Object System.Drawing.Point(15,480) 849 | $label2_help.Font = 'Microsoft Sans Serif,9' 850 | $label2_help.ForeColor = "#00ff00" 851 | $label2_help.LinkColor = "#f5a623" 852 | $label2_help.ActiveLinkColor = "#f5a623" 853 | $label2_help.add_Click({[system.Diagnostics.Process]::start("https://github.com/ninthwalker/saverr")}) 854 | 855 | $label2_version = New-Object system.Windows.Forms.Label 856 | $label2_version.text = "Ver. 1.1.2" 857 | $label2_version.AutoSize = $true 858 | $label2_version.width = 70 859 | $label2_version.height = 20 860 | $label2_version.location = New-Object System.Drawing.Point(480,480) 861 | $label2_version.Font = 'Microsoft Sans Serif,9' 862 | $label2_version.ForeColor = "#f5a623" 863 | 864 | $form2.controls.AddRange(@($label2_title,$label2_username,$label2_password,$label2_dlPath,$label2_server,$label2_pathStatus,$label2_serverStatus,$label2_notice,$label2_notice2,$label2_saveStatus,$label2_tokenStatus,$label2_help,$label2_version,$textBox2_username,$textBox2_password,$textBox2_dlPath,$label2_debug,$checkBox_debug,$label2_ssl,$checkBox_ssl,$label2_ssl_info,$comboBox2_servers,$button2_servers,$pictureBox2_logo,$button2_getToken,$button2_dlPath)) 865 | 866 | 867 | ############################## CODE ################################ 868 | 869 | 870 | # search server for media. Movies/tv are by firstletter. Music is by artist. 871 | function search { 872 | 873 | Try { 874 | 875 | # clear old searches 876 | clearMediaInfo 877 | clearDLStatus 878 | $comboBox_results.Text = "Searching ..." 879 | 880 | # get sections 881 | $sections = $scheme + $settings.server + "/library/sections/" + "?X-Plex-Token=" + $settings.serverToken 882 | $xmlsearch = plx $sections 883 | 884 | #$sectionType = $groupBox_type.Controls | ? { $_.Checked } | Select-Object Text 885 | 886 | # GUI Selection of 'type' to search for. Remove spaces and leading 'the' and 'a' since plex removes those from search words 887 | $searchName = ($textBox_search.Text).TrimStart().TrimEnd() 888 | $searchName = $searchName -replace '^the |^a ', '' 889 | $sectionType = $groupBox_type.Controls | ? { $_.Checked -eq $true} | Select-Object Text 890 | 891 | Switch ($sectionType.Text) 892 | { 893 | 'Movies' {$script:type = "movie" 894 | $subSection = "Video"; Break 895 | } 896 | 'TV Shows' {$script:type = "show" 897 | $subSection = "Directory"; Break 898 | } 899 | 'Artists' {$script:type = "artist" 900 | $subSection = "Directory"; Break 901 | } 902 | } 903 | 904 | # get 'type' of key 905 | $sections2search = $xmlsearch.MediaContainer.Directory | ? {$_.type -eq $type} | select key 906 | $firstChar = $searchName.ToUpper()[0] 907 | 908 | # Search movies/tv 909 | if ($type -ne "artist") { 910 | $sectionsList = new-object collections.generic.list[object] 911 | 912 | foreach ($section in $sections2search) { 913 | 914 | $sectionsUrl = $scheme + $settings.server + "/library/sections/$($section.key)/firstCharacter/$firstChar/" + "?X-Plex-Token=" + $settings.serverToken 915 | $sectionsList.Add((plx $sectionsUrl)) 916 | } 917 | 918 | # search through list for match 919 | $script:searchResults = $sectionsList.MediaContainer.$subSection | ? {$_.title -like "*$searchName*" -and $_.type -eq $type} | select title,type,key,tagline,summary,year,contentrating,thumb,rating 920 | 921 | } 922 | 923 | #search artists 924 | else { 925 | $artistList = new-object collections.generic.list[object] 926 | 927 | foreach ($section in $sections2search) { 928 | 929 | $sectionsUrl = $scheme + $settings.server + "/library/sections/$($section.key)/all" + "?X-Plex-Token=" + $settings.serverToken 930 | $artistList.Add((plx $sectionsUrl)) 931 | 932 | } 933 | 934 | $script:searchResults = $artistList.MediaContainer.$subSection | ? {$_.title -like "*$searchName*" -and $_.type -eq $type} | select title,type,key,thumb 935 | 936 | # search through list for match 937 | $trackList = new-object collections.generic.list[object] 938 | $artistPath = $artistsList.MediaContainer.$subSection | select key 939 | foreach ($artist in $artistPath) { 940 | $artistURL = $scheme + $settings.server + "$($artist.key)" + "?X-Plex-Token=" + $settings.serverToken 941 | $trackList.Add((plx $artistURl)) 942 | } 943 | 944 | 945 | } 946 | 947 | #show results 948 | if ($searchresults) { 949 | $comboBox_results.Text = "$(@($searchResults).count) $($sectionType.Text) found!" 950 | $comboBox_results.Text = "$(@($searchResults).count) $($sectionType.Text) found!" 951 | foreach ($item in $searchresults) { 952 | if ($type -ne "artist") { 953 | if ($item.year) { 954 | $comboBox_results.Items.Add($item.title + ' (' + $item.year + ')') 955 | } 956 | else { 957 | $comboBox_results.Items.Add($item.title) 958 | } 959 | } 960 | else { 961 | $comboBox_results.Items.Add($item.title) 962 | } 963 | } 964 | } 965 | else { 966 | $ComboBox_results.Text = "No results found!" 967 | } 968 | } 969 | 970 | Catch { 971 | logit 972 | $comboBox_results.Items.Clear() 973 | $ComboBox_results.Text = "Error! Check settings/token/server status?" 974 | } 975 | } 976 | 977 | function mediaInfo { 978 | 979 | clearDLStatus 980 | $comboBox_index = $comboBox_results.SelectedIndex 981 | $script:info = $searchResults[$comboBox_index] | Select title,type,key,tagline,summary,year,contentrating,thumb,rating,size 982 | $thumb = $scheme + $settings.server + $info.thumb + "?X-Plex-Token=" + $settings.serverToken 983 | 984 | # enable download button if no other downloads in progress 985 | if (!(Get-BitsTransfer)) { 986 | $button_download.Enabled = $true 987 | } 988 | 989 | if ($info.thumb) { 990 | $pictureBox_thumb.ImageLocation = "$thumb" 991 | $pictureBox_thumb.Visible = $true 992 | } 993 | else { 994 | $pictureBox_thumb.ImageLocation = "" 995 | $pictureBox_thumb.Image = $loading 996 | $pictureBox_thumb.Visible = $true 997 | } 998 | if ($info.year) { 999 | $label_mediaTitle.Text = "$($info.title) ($($info.year))" 1000 | } 1001 | else { 1002 | $label_mediaTitle.Text = "$($info.title)" 1003 | } 1004 | if ($info.contentrating) { 1005 | $label_mediaRating.Text = "$($info.contentrating)" 1006 | } 1007 | elseif ($type -ne "artist") { 1008 | $label_mediaRating.Text = "No Rating" 1009 | } 1010 | if ($info.rating) { 1011 | $label_mediaScore.Text = "$($info.rating)" 1012 | } 1013 | if ($info.summary) { 1014 | $label_mediaSummary.Text = "$($info.summary)" 1015 | } 1016 | 1017 | # show season/ep boxes 1018 | if ($type -eq "show") { 1019 | 1020 | #get seasons 1021 | $comboBox_seasons.Items.Clear() 1022 | $seasonPath = $scheme + $settings.server + "$($info.key)" + "?X-Plex-Token=" + $settings.serverToken 1023 | $xmlSeason = plx $seasonPath 1024 | $script:seasons = $xmlSeason.MediaContainer.directory | select title,key,index 1025 | foreach ($season in $seasons) { 1026 | $comboBox_seasons.Items.Add($season.title) 1027 | } 1028 | if (($comboBox_seasons.Items).count -ne "1" ) { 1029 | $comboBox_seasons.Text = $comboBox_seasons.items[1] 1030 | } 1031 | else { 1032 | $comboBox_seasons.Text = $comboBox_seasons.items[0] 1033 | } 1034 | } 1035 | 1036 | if ($type -eq "artist") { 1037 | 1038 | #get albums 1039 | $comboBox_seasons.Items.Clear() 1040 | $seasonPath = $scheme + $settings.server + "$($info.key)" + "?X-Plex-Token=" + $settings.serverToken 1041 | $xmlSeason = plx $seasonPath 1042 | $script:seasons = $xmlSeason.MediaContainer.directory | select title,key,index,year 1043 | 1044 | foreach ($season in $seasons) { 1045 | $comboBox_seasons.Items.Add($season.title) 1046 | } 1047 | 1048 | if (($comboBox_seasons.Items).count -ge 2) { 1049 | $comboBox_seasons.Items.Add("All Albums") 1050 | } 1051 | 1052 | $comboBox_seasons.Text = $comboBox_seasons.items[0] 1053 | } 1054 | 1055 | } 1056 | 1057 | #get episodes/tracks 1058 | function episodeSelection { 1059 | 1060 | clearDLStatus 1061 | # Movies and tv shows 1062 | if ($type -ne "artist") { 1063 | $comboBox_episodes.Items.Clear() 1064 | $script:comboBox_seasons_index = $comboBox_seasons.SelectedIndex 1065 | 1066 | if ($comboBox_seasons.Text -ne "All Episodes") { 1067 | $episodePath = $scheme + $settings.server + "$($seasons[$comboBox_seasons_index].key)" + "?X-Plex-Token=" + $settings.serverToken 1068 | $script:xmlEpisode = plx $episodePath 1069 | $script:episodes = $xmlEpisode.MediaContainer.video | select title,key,contentrating,summary,rating,year,thumb,originallyAvailableAt,index,duration 1070 | 1071 | foreach ($episode in $episodes) { 1072 | $comboBox_episodes.Items.Add($episode.index) 1073 | } 1074 | 1075 | if ($comboBox_episodes.Items -ge 2) { 1076 | $comboBox_episodes.Items.Add("All") 1077 | } 1078 | 1079 | $comboBox_episodes.Text = $comboBox_episodes.items[0] 1080 | } 1081 | else { 1082 | $comboBox_episodes.Text = "All" 1083 | $label_mediaTitle.Text = "Download All Episodes from All Seasons" 1084 | $label_mediaRating.Text = "" 1085 | $label_mediaScore.Text = "" 1086 | $label_mediaSummary.Text = "Notice: This may take a very long time depending on number of seasons/episodes." 1087 | } 1088 | } 1089 | 1090 | # music 1091 | else { 1092 | $comboBox_episodes.Items.Clear() 1093 | $comboBox_seasons_index = $comboBox_seasons.SelectedIndex 1094 | $episodePath = $scheme + $settings.server + "$($seasons[$comboBox_seasons_index].key)" + "?X-Plex-Token=" + $settings.serverToken 1095 | $script:xmlEpisode = plx $episodePath 1096 | $script:episodes = $xmlEpisode.MediaContainer.track | select title,key,index,duration,summary,parentYear,thumb,grandparentthumb,addedAt 1097 | 1098 | foreach ($episode in $episodes) { 1099 | $comboBox_episodes.Items.Add($episode.title) 1100 | } 1101 | if (($comboBox_episodes.Items).count -ge 2) { 1102 | $comboBox_episodes.Items.Add("All Tracks") 1103 | } 1104 | 1105 | if ($comboBox_seasons.text -ne "All Albums") { 1106 | $comboBox_episodes.Text = $comboBox_episodes.items[0] 1107 | } 1108 | else { 1109 | $comboBox_episodes.Text = "All Tracks" 1110 | $label_mediaTitle.Text = "Download All Albums" 1111 | $label_mediaRating.Text = "" 1112 | $label_mediaScore.Text = "" 1113 | $label_mediaSummary.Text = "Notice: This may take a very long time depending on number of albums/tracks." 1114 | 1115 | } 1116 | 1117 | } 1118 | } 1119 | 1120 | 1121 | 1122 | function mediaEpInfo { 1123 | 1124 | clearDLStatus 1125 | $comboBox_episode_index = $comboBox_episodes.SelectedIndex 1126 | if ($comboBox_episode_index -ne $null -and $episodes -ne $null) { 1127 | $script:infoEp = $episodes[$comboBox_episode_index] | Select title,type,key,tagline,summary,year,contentrating,thumb,rating 1128 | } 1129 | 1130 | if ($comboBox_episodes.Text -eq "All" -and $comboBox_seasons.Text -ne "All episodes") { 1131 | $label_mediaTitle.Text = "Download All Episodes from $($comboBox_seasons.Text)" 1132 | $label_mediaRating.Text = "" 1133 | $label_mediaScore.Text = "" 1134 | $label_mediaSummary.Text = "Notice: This may take a very long time depending on number of episodes." 1135 | } 1136 | elseif ($comboBox_episodes.Text -eq "All Tracks" -and $comboBox_seasons.Text -ne "All Albums") { 1137 | $label_mediaTitle.Text = "Download All Tracks from $($comboBox_seasons.Text)" 1138 | $label_mediaRating.Text = "" 1139 | $label_mediaScore.Text = "" 1140 | $label_mediaSummary.Text = "Notice: This may take a very long time depending on number of tracks." 1141 | } 1142 | else { 1143 | if ($infoEp.year) { 1144 | $label_mediaTitle.Text = "$($infoEp.title) ($($infoEp.year))" 1145 | } 1146 | else { 1147 | $label_mediaTitle.Text = "$($infoEp.title)" 1148 | } 1149 | if ($infoEp.contentrating) { 1150 | $label_mediaRating.Text = "$($infoEp.contentrating)" 1151 | } 1152 | elseif ($type -ne "artist") { 1153 | $label_mediaRating.Text = "No Rating" 1154 | } 1155 | if ($infoEp.rating) { 1156 | $label_mediaScore.Text = "$($infoEp.rating)" 1157 | } 1158 | if ($infoEp.summary) { 1159 | $label_mediaSummary.Text = "$($infoEp.summary)" 1160 | } 1161 | else { 1162 | $label_mediaSummary.Text = "" 1163 | } 1164 | } 1165 | } 1166 | 1167 | # retrieve token. Does not store username/password, only the token 1168 | function getToken { 1169 | try { 1170 | $label2_tokenStatus.ForeColor = "#ffff00" 1171 | $label2_tokenStatus.Text = "Retrieving ..." 1172 | $username = $textBox2_username.Text 1173 | $password = $textBox2_password.Text 1174 | 1175 | # Use this method for now for more powershell version backwards compatability instead of -credential 1176 | $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password))) 1177 | 1178 | $headers = @{ 1179 | "X-Plex-Version" = "1.1.2" 1180 | "X-Plex-Product" = "Saverr" 1181 | "X-Plex-Client-Identifier" = "271938" 1182 | "Content-Type" = "application/xml" 1183 | "Authorization" = ("Basic {0}" -f $base64AuthInfo) 1184 | } 1185 | 1186 | $data = Invoke-RestMethod -Method POST -Uri $plexSignInUrl -Headers $headers 1187 | $script:userToken = $data.user.authToken 1188 | 1189 | # update settings file 1190 | if (Test-Path .\saverrSettings.xml) { 1191 | $script:settings = Import-Clixml .\saverrSettings.xml 1192 | Add-Member -InputObject $settings -MemberType NoteProperty -Name 'userToken' -Value $userToken -force 1193 | $settings | Export-Clixml .\saverrSettings.xml 1194 | } 1195 | else { 1196 | $script:settings = [pscustomobject] @{ 1197 | userToken = $userToken 1198 | } 1199 | $settings | Export-Clixml .\saverrSettings.xml 1200 | } 1201 | $settings = Import-Clixml .\saverrSettings.xml 1202 | $label2_tokenStatus.ForeColor = "#00ff00" 1203 | $label2_tokenStatus.Text = "Token Saved!" 1204 | } 1205 | catch { 1206 | logit 1207 | $label2_tokenStatus.ForeColor = "#ff0000" 1208 | $label2_tokenStatus.Text = "Error! User/Pass?" 1209 | } 1210 | } 1211 | 1212 | function getServers { 1213 | Try { 1214 | $label2_serverStatus.text = "Searching ..." 1215 | $label2_notice2.text = "" 1216 | # full server url 1217 | $serversUrl = $plexServersUrl + "?X-Plex-Token=" + $settings.userToken 1218 | 1219 | # get servers 1220 | $serversXml = plx $serversUrl 1221 | $script:serverList = $serversxml.MediaContainer.Server | select name,host,port,accessToken,localAddresses,owned 1222 | 1223 | #output servers 1224 | if ($serverList) { 1225 | $comboBox2_servers.Items.Clear(); 1226 | $comboBox2_servers.Text = "$(@($serverList).count) Plex Servers found!" 1227 | foreach ($server in $serverList) { 1228 | $comboBox2_servers.Items.Add($server.Name) 1229 | } 1230 | } 1231 | else { 1232 | $comboBox2_servers.Items.Clear() 1233 | $ComboBox2_servers.Text = "No Plex servers found! Got Token?" 1234 | } 1235 | $label2_serverStatus.text = "" 1236 | } 1237 | Catch { 1238 | logit 1239 | $label2_serverStatus.text = "" 1240 | $comboBox2_servers.Items.Clear() 1241 | $ComboBox2_servers.Text = "Error! Check token?" 1242 | } 1243 | } 1244 | 1245 | 1246 | # save all setttings to file 1247 | function saveServer { 1248 | 1249 | Try { 1250 | $comboBox2_index = $comboBox2_servers.SelectedIndex 1251 | $selectedServer = $serverList[$comboBox2_index] 1252 | 1253 | if ($selectedServer.owned -ne "1") { 1254 | $serverUrl = $selectedServer.Host + ":" + $selectedServer.Port 1255 | } 1256 | else { 1257 | $ipCheck = Invoke-RestMethod http://ipinfo.io/json | Select -exp ip 1258 | if ($ipcheck -eq $selectedServer.Host) { 1259 | 1260 | if ( (($selectedServer.localaddresses).GetType().name) -eq "String" ) { 1261 | $serverUrl = $selectedServer.localAddresses + ":" + "32400" 1262 | } 1263 | elseif ( (($selectedServer.localaddresses).GetType().name) -eq "Array" ) { 1264 | $serverUrl = $selectedServer.localAddresses[0] + ":" + "32400" 1265 | } 1266 | } 1267 | else { 1268 | $serverUrl = $selectedServer.Host + ":" + $selectedServer.Port 1269 | } 1270 | } 1271 | 1272 | $serverToken = $($selectedServer.accessToken) 1273 | $serverName = $($selectedServer.name) 1274 | 1275 | [PsCustomObject] @{ 1276 | name = "$serverName" 1277 | server = "$serverUrl" 1278 | dlPath = "$($textBox2_dlPath.Text)" 1279 | userToken = "$($settings.userToken)" 1280 | serverToken = "$serverToken" 1281 | ssl = "$ssl" 1282 | logging = "$debug" 1283 | } | Export-Clixml .\saverrSettings.xml 1284 | 1285 | $label2_serverStatus.ForeColor = "#00ff00" 1286 | $label2_serverStatus.text = "Server Saved!" 1287 | } 1288 | Catch { 1289 | logit 1290 | $label2_notice2.ForeColor = "#ff0000" 1291 | $label2_notice2.text = "Error saving! Got token? selected a server?" 1292 | } 1293 | } 1294 | 1295 | function clearStatusSave { 1296 | 1297 | # save path 1298 | # $dlPath = "$($textBox2_dlPath.Text)" # old way before dialog box 1299 | 1300 | if (Test-Path .\saverrSettings.xml) { 1301 | $script:settings = Import-Clixml .\saverrSettings.xml 1302 | if ($dlPath) { 1303 | Add-Member -InputObject $settings -MemberType NoteProperty -Name 'dlPath' -Value $dlPath -force 1304 | $settings | Export-Clixml .\saverrSettings.xml 1305 | } 1306 | } 1307 | else { 1308 | if ($dlPath) { 1309 | $script:settings = [pscustomobject] @{ 1310 | dlPath = $dlPath 1311 | } 1312 | } 1313 | } 1314 | 1315 | # clear status 1316 | clearMediaInfo 1317 | clearDLStatus 1318 | $errorMsg = "" 1319 | $label2_saveStatus.text = "" 1320 | $label2_serverStatus.text = "" 1321 | $label2_tokenStatus.Text = "" 1322 | $label2_pathStatus.Text = "" 1323 | $label2_notice2.Text = "" 1324 | $textBox_search.Text = "" 1325 | $textBox2_dlPath.Text = $settings.dlPath 1326 | $textBox2_username.Text = "" 1327 | $textBox2_password.Text = "" 1328 | $comboBox2_servers.Items.Clear() 1329 | $comboBox2_servers.Text = $settings.name 1330 | 1331 | Try { 1332 | if ($dlPath) { 1333 | New-Item -ItemType Directory -Force -Path $dlPath 1334 | } 1335 | $label_mediaTitle.ForeColor = "#ffffff" 1336 | $label_mediaTitle.Text = "" 1337 | $label_mediaSummary.Text = "" 1338 | } 1339 | Catch { 1340 | logit 1341 | $label_mediaTitle.ForeColor = "#ff0000" 1342 | $label_mediaTitle.Text = "Error creating download Path" 1343 | $label_mediaSummary.Text = "Could Not validate download directory:`n$dlPath.`n`nCheck path name or system permissions maybe?" 1344 | } 1345 | 1346 | Try { 1347 | if ($settings) { 1348 | $settings | Export-Clixml .\saverrSettings.xml 1349 | } 1350 | $label_mediaTitle.ForeColor = "#ffffff" 1351 | $label_mediaTitle.Text = "" 1352 | $label_mediaSummary.Text = "" 1353 | } 1354 | Catch { 1355 | logit 1356 | $label_mediaTitle.ForeColor = "#ff0000" 1357 | $label_mediaTitle.Text = "Error saving settings" 1358 | $label_mediaSummary.Text = "Could Not Create settings file at:`n$PSScriptRoot.`n`nCheck path or system permissions maybe?" 1359 | } 1360 | 1361 | if ((!($settings.name)) -or (!($settings.server)) -or (!($settings.userToken)) -or (!($settings.serverToken)) -or (!($settings.dlPath))) { 1362 | $label_mediaSummary.text = "Settings are not fully configured.`nPlease click the gear icon before searching." 1363 | } 1364 | else { 1365 | $label_mediaSummary.text = "" 1366 | } 1367 | 1368 | } 1369 | 1370 | function clearMediaInfo { 1371 | 1372 | $comboBox_results.Items.Clear() 1373 | $comboBox_results.Text = "" 1374 | $pictureBox_thumb.Visible = $false 1375 | $pictureBox_thumb.image = $loading 1376 | $pictureBox_thumb.ImageLocation = "" 1377 | $label_mediaTitle.Text = "" 1378 | $label_mediaRating.Text = "" 1379 | $label_mediaScore.Text = "" 1380 | $label_mediaSummary.Text = "" 1381 | $comboBox_seasons.Text = "" 1382 | $comboBox_episodes.Text = "" 1383 | $comboBox_seasons.Items.Clear() 1384 | $comboBox_episodes.Items.Clear() 1385 | $button_download.Enabled = $false 1386 | 1387 | } 1388 | 1389 | function clearDLStatus { 1390 | 1391 | if ($label_DLTitle.Text -match "^Download Completed|^Download Failed|^Download Cancelled|^There was an error|^Error") { 1392 | $label_DLTitle.Text = "" 1393 | } 1394 | if ($label_DLProgress.Text -ne "Download Paused!") { 1395 | $label_DLProgress.Text = "" 1396 | $label_DLProgress.ForeColor = "#f5a623" 1397 | } 1398 | 1399 | } 1400 | 1401 | function cancelJob { 1402 | $progressBar.Value = 0 1403 | $label_DLTitle.ForeColor = "#ff0000" 1404 | $label_DLTitle.Text = "Download Cancelled!" 1405 | $label_DLProgress.Text = "" 1406 | $progressBar.Visible = $false 1407 | if ($comboBox_results.Items) { 1408 | $button_download.Enabled = $true 1409 | } 1410 | $button_cancel.Visible = $false 1411 | $button_cancel.Enabled = $false 1412 | $CheckBoxButton_pause.Visible = $false 1413 | $CheckboxButton_pause.Enabled = $false 1414 | $script:pauseLoop = $false 1415 | $checkBoxButton_pause.Text = "Pause" 1416 | Get-BitsTransfer | Complete-BitsTransfer 1417 | 1418 | # clean up any empty folders created. 1419 | # This can throw an error in the console sometimes if the path is deleted too fast and then it doesn't exist. Not worth it to remove error, doesn't stop the app. 1420 | if ($dlType -eq "allEp" -or $dlType -eq "allSeasons") { 1421 | if (Test-Path $allSeasonPath) { 1422 | Get-ChildItem $allSeasonPath -Directory -recurse | where {-NOT $_.GetFiles("*","AllDirectories")} | del -recurse -ErrorAction SilentlyContinue 1423 | if (Test-Path $allSeasonPath) { 1424 | if ((Get-ChildItem $allSeasonPath | Measure-Object).Count -eq 0) { 1425 | del $allSeasonPath -ErrorAction SilentlyContinue 1426 | } 1427 | } 1428 | } 1429 | } 1430 | 1431 | # enable minimize again 1432 | $form.MinimizeBox = $true 1433 | 1434 | } 1435 | 1436 | ########### OPERATIONS ############### 1437 | 1438 | # Buttons 1439 | $button_download.Add_Click({ 1440 | 1441 | Try { 1442 | if ((Get-BitsTransfer).JobState -ne "Suspended") { 1443 | # tell them what's going on, reset status 1444 | $button_download.Enabled = $false 1445 | $label_DLTitle.ForeColor = "#f5a623" 1446 | $label_DLProgress.ForeColor = "#f5a623" 1447 | $label_DLProgress.Text = "" 1448 | $label_DLTitle.Text = "Processing download request" 1449 | $label_DLTitle.Refresh() 1450 | $button_download.Text = "Download" 1451 | $checkBoxButton_pause.Checked = $false 1452 | $status = "success" 1453 | 1454 | # movie dl links 1455 | if ($type -eq "movie") { 1456 | $mediaURL = $scheme + $settings.server + $info.key + "?X-Plex-Token=" + $settings.serverToken 1457 | $mediaPath = plx $mediaURL 1458 | $mediaInfo = $mediaPath.MediaContainer.Video.Media.Part | select key,file -First 1 1459 | $dlURL = $scheme + $settings.server + $mediaInfo.key + "?download=1" + "&X-Plex-Token=" + $settings.serverToken 1460 | $script:dlName = Split-Path $mediaInfo.file -Leaf 1461 | $script:dlType = "one" 1462 | } 1463 | 1464 | # tv show dl links 1465 | if ($type -eq "show") { 1466 | 1467 | # if all episodes from one season is selected 1468 | if ($comboBox_episodes.Text -eq "All" -and $comboBox_seasons.Text -ne "All episodes") { 1469 | $script:allSeasonPath = "$($settings.dlPath)\$(Remove-InvalidChars $info.title)" 1470 | $allEpPath = "$allSeasonPath\$(Remove-InvalidChars $comboBox_seasons.Text)" 1471 | New-Item -ItemType Directory -Force -Path $allEpPath 1472 | $allEp = $xmlepisode.MediaContainer.Video.Media.Part | select @{n="Source";e={$scheme + $settings.server + $_.key + "?X-Plex-Token=" + $settings.serverToken}},@{n="Destination";e={$allEpPath + "\" + (Split-Path $_.file -Leaf)}} 1473 | $script:dlType = "allEp" 1474 | 1475 | # remove links that have already been downloaded 1476 | $allEpData = @() 1477 | For ($I=0; $I -lt $allep.count; $I++) { 1478 | 1479 | if (!(Test-Path $allEp.destination[$I])) { 1480 | $allEpData += [pscustomobject] @{ 1481 | Source = $allep.source[$I] 1482 | Destination = $allEp.destination[$I] 1483 | } 1484 | } 1485 | 1486 | } 1487 | 1488 | } 1489 | # if all seasons and all episodes is selected 1490 | elseif ($comboBox_episodes.Text -eq "All" -and $comboBox_seasons.Text -eq "All episodes") { 1491 | $script:allSeasonPath = "$($settings.dlPath)\$(Remove-InvalidChars $info.title)" 1492 | $mediaURL = $scheme + $settings.server + $seasons.key[0] + "?X-Plex-Token=" + $settings.serverToken 1493 | $mediaPath = plx $mediaURL 1494 | $seasonNumber = $mediaPath.MediaContainer.Video | select parenttitle 1495 | $seasonClean = $seasonNumber.parenttitle | % {Remove-InvalidChars $_} 1496 | $allEp = $mediaPath.MediaContainer.Video.Media.Part | select @{n="Source";e={$scheme + $settings.server + $_.key + "?X-Plex-Token=" + $settings.serverToken}},@{n="Destination";e={(Split-Path $_.file -Leaf)}} 1497 | $allEpClean = $allEp.destination | % {Remove-InvalidChars $_} 1498 | $script:dlType = "allSeasons" 1499 | 1500 | # combine source/destination/season data for Bitstransfer import 1501 | # remove links that have already been downloaded 1502 | $allEpData = @() 1503 | For ($I=0; $I -lt $allep.count; $I++) { 1504 | 1505 | $finalDestination = $allSeasonPath + "\" + $seasonClean[$I] + "\" + $allEpClean[$I] 1506 | 1507 | if (!(Test-Path $finalDestination)) { 1508 | $allEpData += [pscustomobject] @{ 1509 | Source = $allep.source[$I] 1510 | Destination = $finalDestination 1511 | } 1512 | } 1513 | } 1514 | 1515 | 1516 | # Bitstransfer defaults to 200 max files per job. Truncate download to 200 unless registry value is set to something else. 1517 | if ($allEpData.length -ge $limit) { 1518 | $setLimit = $limit - 1 1519 | $allEpData = $allEpData[0..$setLimit] 1520 | $noLimitStatus = "(Max limit set to $limit)" 1521 | } 1522 | 1523 | # pre-create directories for seasons 1524 | $seasonClean | select -Unique | % {New-Item -ItemType Directory -Force -Path "$allSeasonPath\$_"} 1525 | 1526 | } 1527 | 1528 | # if just one episode or a movie is selected 1529 | else { 1530 | $mediaURL = $scheme + $settings.server + $infoEp.key + "?X-Plex-Token=" + $settings.serverToken 1531 | $mediaPath = plx $mediaURL 1532 | $mediaInfo = $mediaPath.MediaContainer.Video.Media.Part | select key,file -First 1 1533 | $dlURL = $scheme + $settings.server + $mediaInfo.key + "?download=1" + "&X-Plex-Token=" + $settings.serverToken 1534 | $script:dlName = Split-Path $mediaInfo.file -Leaf | % {Remove-InvalidChars $_} 1535 | $script:dlType = "one" 1536 | } 1537 | 1538 | } 1539 | 1540 | # music dl links 1541 | if ($type -eq "artist") { 1542 | 1543 | # if all tracks from one album is selected 1544 | if ($comboBox_episodes.Text -eq "All Tracks" -and $comboBox_seasons.Text -ne "All Albums") { 1545 | $script:allSeasonPath = "$($settings.dlPath)\$(Remove-InvalidChars $info.title)" 1546 | $allEpPath = "$allSeasonPath\$(Remove-InvalidChars $comboBox_seasons.Text)" 1547 | New-Item -ItemType Directory -Force -Path $allEpPath 1548 | $allEp = $xmlepisode.MediaContainer.Track.Media.Part | select @{n="Source";e={$scheme + $settings.server + $_.key + "?X-Plex-Token=" + $settings.serverToken}},@{n="Destination";e={$allEpPath + "\" + (Split-Path $_.file -Leaf)}} 1549 | $script:dlType = "allTracks" 1550 | 1551 | # remove links that have already been downloaded 1552 | $allEpData = @() 1553 | For ($I=0; $I -lt $allep.count; $I++) { 1554 | 1555 | if (!(Test-Path $allEp.destination[$I])) { 1556 | $allEpData += [pscustomobject] @{ 1557 | Source = $allep.source[$I] 1558 | Destination = $allEp.destination[$I] 1559 | } 1560 | } 1561 | 1562 | } 1563 | 1564 | } 1565 | 1566 | # if all tracks and all albums is selected 1567 | elseif ($comboBox_episodes.Text -eq "All Tracks" -and $comboBox_seasons.Text -eq "All Albums") { 1568 | $script:allSeasonPath = "$($settings.dlPath)\$(Remove-InvalidChars $info.title)" 1569 | 1570 | # collect all album metadata paths 1571 | $mediaURL = @() 1572 | $seasons | % { $mediaURL += $scheme + $settings.server + $_.key + "?X-Plex-Token=" + $settings.serverToken } 1573 | 1574 | # get all tracks 1575 | $all = $mediaURL | % {(plx $_).MediaContainer.Track} 1576 | $allEp = $all.media.part | select @{n="Source";e={$scheme + $settings.server + $_.key + "?X-Plex-Token=" + $settings.serverToken}},@{n="Destination";e={(Split-Path $_.file -Leaf)}} 1577 | $allClean = $all.parenttitle | % {Remove-InvalidChars $_} 1578 | $allSeasonClean = $all.parenttitle | % {Remove-InvalidChars $_} 1579 | $allEpClean = $allEp.destination | % {Remove-InvalidChars $_} 1580 | $script:dlType = "allAlbums" 1581 | 1582 | # combine source/destination/season data for Bitstransfer import 1583 | $allEpData = @() 1584 | For ($I=0; $I -lt $allEp.count; $I++) { 1585 | 1586 | $finalDestination = $allSeasonPath + "\" + $allSeasonClean[$I] + "\" + $allEpClean[$I] 1587 | 1588 | if (!(Test-Path $finalDestination)) { 1589 | $allEpData += [pscustomobject] @{ 1590 | Source = $allEp.source[$I] 1591 | Destination = $finalDestination 1592 | } 1593 | } 1594 | 1595 | } 1596 | 1597 | # Bitstransfer defaults to 200 max files per job. Truncate download to 200 unless registry value is set to something else. 1598 | if ($allEpData.length -ge $limit) { 1599 | $setLimit = $limit - 1 1600 | $allEpData = $allEpData[0..$setLimit] 1601 | $noLimitStatus = "(Max limit set to $limit)" 1602 | } 1603 | 1604 | # pre-create directories for seasons 1605 | $allClean | select -Unique | % {New-Item -ItemType Directory -Force -Path "$allSeasonPath\$_"} 1606 | 1607 | } 1608 | 1609 | # if just one music track is selected 1610 | else { 1611 | $mediaURL = $scheme + $settings.server + $infoEp.key + "?X-Plex-Token=" + $settings.serverToken 1612 | $mediaPath = plx $mediaURL 1613 | $mediaInfo = $mediaPath.MediaContainer.track.media.part | select key,file -First 1 1614 | $mediaInfo2 = $mediaPath.MediaContainer.track | select grandparentTitle,parentTitle,title -First 1 1615 | $dlURL = $scheme + $settings.server + $mediaInfo.key + "?download=1" + "&X-Plex-Token=" + $settings.serverToken 1616 | $script:dlName = Split-Path $mediaInfo.file -Leaf | % {Remove-InvalidChars $_} 1617 | $script:dlType = "one" 1618 | } 1619 | 1620 | } 1621 | 1622 | # Cancelling all old Bits jobs 1623 | Get-BitsTransfer | Remove-BitsTransfer 1624 | 1625 | # get starting time 1626 | $startTime = Get-Date 1627 | 1628 | # disable minimize since it causes issues during downloads 1629 | $form.MinimizeBox = $false 1630 | 1631 | # download all episodes from a season or album 1632 | if ($dlType -eq "allEp" -or $dlType -eq "allTracks") { 1633 | $script:myjob = Start-BitsTransfer -source "$($allEpData.Source[0])" -Destination "$($allEpData.Destination[0])" -DisplayName "Downloading ..." -Description "All Episodes" -Asynchronous -Suspended 1634 | $allEpData[1..($allEpData.Length -1)] | Add-BitsFile $myjob 1635 | if ($ssl -eq $True) {bitsadmin /SetSecurityFlags $myjob.displayname 30} 1636 | Resume-BitsTransfer $myjob -Asynchronous 1637 | } 1638 | 1639 | # download all seasons or all albums 1640 | elseif ($dltype -eq "allSeasons" -or $dlType -eq "allAlbums") { 1641 | $script:myjob = Start-BitsTransfer -source "$($allEpData.Source[0])" -Destination "$($allEpData.Destination[0])" -DisplayName "Downloading ..." -Description "All Episodes" -Asynchronous -Suspended 1642 | $allEpData[1..($allEpData.Length -1)] | Add-BitsFile $myjob 1643 | if ($ssl -eq $True) {bitsadmin /SetSecurityFlags $myjob.displayname 30} 1644 | Resume-BitsTransfer $myjob -Asynchronous 1645 | } 1646 | 1647 | # download a movie or one episode or one song 1648 | else { 1649 | $script:myjob = Start-BitsTransfer -Source $dlURL -Destination "$($settings.dlPath)\$dlName" -DisplayName "Downloading ..." -Description $dlName -Asynchronous -Suspended 1650 | if ($ssl -eq $True) {bitsadmin /SetSecurityFlags $myjob.displayname 30} 1651 | Resume-BitsTransfer $myjob -Asynchronous 1652 | } 1653 | 1654 | } 1655 | elseif ((Get-BitsTransfer).JobState -eq "Suspended") { 1656 | # resume if they paused the download 1657 | $button_download.Enabled = $false 1658 | $button_download.Text = "Download" 1659 | $label_DLTitle.ForeColor = "#f5a623" 1660 | $label_DLProgress.ForeColor = "#f5a623" 1661 | $label_DLTitle.Text = "Paused download detected. Resuming progress ..." 1662 | $checkBoxButton_pause.Text = "Pause" 1663 | $label_DLProgress.Text = "" 1664 | $label_DLProgress.ForeColor = "#f5a623" 1665 | Resume-BitsTransfer $myjob -Asynchronous 1666 | $label_DLTitle.Refresh() 1667 | $status = "success" 1668 | } 1669 | # disable minimize since it causes issues during downloads 1670 | $form.MinimizeBox = $false 1671 | 1672 | # Pause to let it start before checking progress. Timeout after 30sec 1673 | $count = 0 1674 | $noDot = (0,4,8,12,16,20,24,28) 1675 | $oneDot = (1,5,9,13,17,21,25,29) 1676 | $twoDot = (2,6,10,14,18,22,26,30) 1677 | $threeDot = (3,7,11,15,19,23,27) 1678 | 1679 | # timeout 1680 | :check while ($count -lt $timeout) { 1681 | 1682 | if (((Get-BitsTransfer | ? { $_.JobState -eq "Transferring" }).Count -gt 0) -or (Get-BitsTransfer | ? { $_.JobState -eq "Transferred" }) -or (Get-BitsTransfer | ? { $_.JobState -eq "Error" })) { 1683 | # exit check 1684 | break check 1685 | } 1686 | 1687 | if ($noDot -contains $count) { 1688 | $label_DLTitle.Text = "Processing download request" 1689 | $label_DLTitle.Refresh() 1690 | } 1691 | elseif ($oneDot -contains $count) { 1692 | $label_DLTitle.Text = "Processing download request ." 1693 | $label_DLTitle.Refresh() 1694 | } 1695 | elseif ($twoDot -contains $count) { 1696 | $label_DLTitle.Text = "Processing download request . ." 1697 | $label_DLTitle.Refresh() 1698 | } 1699 | elseif ($threeDot -contains $count) { 1700 | $label_DLTitle.Text = "Processing download request . . ." 1701 | $label_DLTitle.Refresh() 1702 | } 1703 | 1704 | # increase counter 1705 | Start-Sleep -Seconds 1 1706 | $count++ 1707 | 1708 | } 1709 | 1710 | if ($count -ge $timeout) { 1711 | $status = "failed" 1712 | 1713 | if (Get-BitsTransfer | ? { $_.JobState -like "*Error*" }) { 1714 | $bitsError = (Get-BitsTransfer | select ErrorDescription).ErrorDescription 1715 | if ($debug) { 1716 | $eMSG = "$(Get-Date): Connection Timed out after $timeout seconds. $bitsError" 1717 | $eMSG | Out-File ".\saverrLog.txt" -Append 1718 | } 1719 | } 1720 | 1721 | # remove bitstransfer jobs and clean up empty directories created 1722 | Get-BitsTransfer | Remove-BitsTransfer 1723 | if ($dlType -like "all*") { 1724 | if (Test-Path $allSeasonPath) { 1725 | Get-ChildItem $allSeasonPath -Directory -recurse | where {-NOT $_.GetFiles("*","AllDirectories")} | del -recurse -ErrorAction SilentlyContinue 1726 | if (Test-Path $allSeasonPath) { 1727 | if ((Get-ChildItem $allSeasonPath | Measure-Object).Count -eq 0) { 1728 | del $allSeasonPath -ErrorAction SilentlyContinue 1729 | } 1730 | } 1731 | } 1732 | } 1733 | 1734 | $label_DLTitle.ForeColor = "#ff0000" 1735 | $label_DLTitle.Text = "Download Failed! Timed out.`n Error: $bitsError" 1736 | $button_download.Enabled = $true 1737 | $label_DLProgress.Text = "" 1738 | } 1739 | 1740 | if ((Get-BitsTransfer | select ErrorDescription).ErrorDescription -like "*404*") { 1741 | $status = "failed" 1742 | 1743 | # remove bitstransfer jobs and clean up empty directories created 1744 | Get-BitsTransfer | Remove-BitsTransfer 1745 | if ($dlType -like "all*") { 1746 | if (Test-Path $allSeasonPath) { 1747 | Get-ChildItem $allSeasonPath -Directory -recurse | where {-NOT $_.GetFiles("*","AllDirectories")} | del -recurse -ErrorAction SilentlyContinue 1748 | if (Test-Path $allSeasonPath) { 1749 | if ((Get-ChildItem $allSeasonPath | Measure-Object).Count -eq 0) { 1750 | del $allSeasonPath -ErrorAction SilentlyContinue 1751 | } 1752 | } 1753 | } 1754 | } 1755 | 1756 | $label_DLTitle.ForeColor = "#ff0000" 1757 | $label_DLTitle.Text = "Download Failed! File not found. Check server" 1758 | $button_download.Enabled = $true 1759 | $label_DLProgress.Text = "" 1760 | 1761 | } 1762 | 1763 | if ($status -eq "success") { 1764 | # Show progress 1765 | $progressBar.Visible = $true 1766 | $progressBar.Value = 0 1767 | $label_DLProgress.Text = "" 1768 | $label_DLProgress.ForeColor = "#f5a623" 1769 | 1770 | # Init CancelLoop 1771 | $script:cancelLoop = $false 1772 | $button_cancel.Enabled = $true 1773 | $button_cancel.Visible = $true 1774 | $checkBoxButton_pause.Enabled = $true 1775 | $checkBoxButton_pause.Visible = $true 1776 | 1777 | if (Get-BitsTransfer | ? { $_.JobState -ne "Transferred" }) { 1778 | 1779 | :xfer while ((Get-BitsTransfer | ? { $_.JobState -eq "Transferring" }).Count -gt 0) { 1780 | $totalbytes=0; 1781 | $bytestransferred=0; 1782 | $timeTaken = 0; 1783 | foreach ($job in (Get-BitsTransfer | ? { $_.JobState -eq "Transferring" } | Sort-Object CreationTime)) { 1784 | 1785 | $totalbytes += [math]::round($job.BytesTotal /1MB); 1786 | $totalSize = byteSize $($job.BytesTotal) 1787 | $bytestransferred += [math]::round($job.bytestransferred /1MB) 1788 | $transferSize = byteSize $($job.bytestransferred) 1789 | if ($timeTaken -eq 0) { 1790 | #Get the time of the oldest transfer aka the one that started first 1791 | $timeTaken = ((Get-Date) - $job.CreationTime).TotalMinutes 1792 | } 1793 | } 1794 | #TimeRemaining = (TotalFileSize - BytesDownloaded) * TimeElapsed/BytesDownloaded 1795 | [System.Windows.Forms.Application]::DoEvents() 1796 | 1797 | # cancel download if asked 1798 | if ($script:cancelLoop -eq $true) { 1799 | cancelJob 1800 | # exit loop 1801 | break xfer 1802 | } 1803 | 1804 | # pause download if asked 1805 | if ($script:pauseLoop -eq $true) { 1806 | $label_DLProgress.Text = "Download Paused!" 1807 | $label_DLProgress.ForeColor = "#ffff00" # yellow 1808 | Get-BitsTransfer | Suspend-BitsTransfer 1809 | 1810 | # allow minimize while paused 1811 | $form.MinimizeBox = $true 1812 | 1813 | # exit loop 1814 | break xfer 1815 | } 1816 | 1817 | if ($totalbytes -gt 0 -and $bytestransferred -gt 0 -and $timetaken -gt 0) { 1818 | [int]$timeLeft = ($totalBytes - $bytestransferred) * ($timeTaken / $bytestransferred) 1819 | [int]$pctComplete = $(($bytestransferred*100)/$totalbytes); 1820 | $label_DLProgress.Text = "$transferSize of $totalSize ($pctComplete%) - Approx. $timeLeft minutes remaining" 1821 | $progressBar.Value = $pctComplete 1822 | #$label_DLProgress.Refresh() 1823 | 1824 | Switch ($dlType) 1825 | { 1826 | 'one' { $label_DLTitle.Text = "Downloading: $dlName"; Break } 1827 | 'allEp' { $label_DLTitle.Text = "Completed: $([int]$myjob.FilesTransferred) of $([int]$myjob.FilesTotal) Episodes $noLimitStatus"; Break } 1828 | 'allTracks' { $label_DLTitle.Text = "Completed: $([int]$myjob.FilesTransferred) of $([int]$myjob.FilesTotal) Tracks $noLimitStatus"; Break } 1829 | 'allSeasons' { $label_DLTitle.Text = "Completed: $([int]$myjob.FilesTransferred) of $([int]$myjob.FilesTotal) Episodes $noLimitStatus"; Break } 1830 | 'allAlbums' { $label_DLTitle.Text = "Completed: $([int]$myjob.FilesTransferred) of $([int]$myjob.FilesTotal) Tracks $noLimitStatus"; Break } 1831 | } 1832 | 1833 | $progressBar.PerformStep() 1834 | Start-Sleep -Seconds 1 1835 | } 1836 | } 1837 | } 1838 | 1839 | # download went too fast. Pause to show title downloaded 1840 | else { 1841 | Switch ($dlType) 1842 | { 1843 | 'one' { $label_DLTitle.Text = "Downloading: $dlName"; Break } 1844 | 'allEp' { $label_DLTitle.Text = "Completed: $([int]$myjob.FilesTransferred) of $([int]$myjob.FilesTotal) Episodes"; Break } 1845 | 'allTracks' { $label_DLTitle.Text = "Completed: $([int]$myjob.FilesTransferred) of $([int]$myjob.FilesTotal) Tracks"; Break } 1846 | 'allSeasons' { $label_DLTitle.Text = "Completed: $([int]$myjob.FilesTransferred) of $([int]$myjob.FilesTotal) Episodes"; Break } 1847 | 'allAlbums' { $label_DLTitle.Text = "Completed: $([int]$myjob.FilesTransferred) of $([int]$myjob.FilesTotal) Tracks"; Break } 1848 | } 1849 | 1850 | Start-Sleep -Seconds 2 1851 | 1852 | } 1853 | 1854 | # Finish and close Bitstransfer 1855 | if ($script:cancelLoop -eq $false -and $script:pauseLoop -eq $false) { 1856 | 1857 | if (Get-BitsTransfer | ? { $_.JobState -like "*Error*" }) { 1858 | $bitsError = (Get-BitsTransfer | select ErrorDescription).ErrorDescription 1859 | if ($debug) { 1860 | $eMSG = "$(Get-Date): Download Error. $bitsError" 1861 | $eMSG | Out-File ".\saverrLog.txt" -Append 1862 | } 1863 | $label_DLTitle.ForeColor = "#ff0000" 1864 | $label_DLProgress.Text = "" 1865 | $label_DLTitle.Text = "Error: $bitsError" 1866 | $progressBar.Visible = $false 1867 | $progressBar.Value = 0 1868 | $button_download.Enabled = $true 1869 | $button_cancel.Visible = $false 1870 | $button_cancel.Enabled = $false 1871 | $checkBoxButton_pause.Visible = $false 1872 | $CheckBoxButton_pause.Enabled = $false 1873 | 1874 | } 1875 | else { 1876 | if ($startTime -ne $null) { 1877 | $howLong = (get-date).Subtract($startTime) 1878 | if ($howLong.Minutes -eq "0") { 1879 | $dlTime = "$($howLong.Seconds) seconds" 1880 | } 1881 | else { 1882 | $dlTime = "$($howLong.Minutes) minutes" 1883 | } 1884 | } 1885 | else { 1886 | $dlTime = "Unknown time" 1887 | } 1888 | 1889 | $label_DLProgress.Text = "" 1890 | $label_DLTitle.ForeColor = "#00ff00" 1891 | $label_DLTitle.Text = "Download Completed in: $dlTime" 1892 | $button_download.Enabled = $true 1893 | $button_cancel.Visible = $false 1894 | $button_cancel.Enabled = $false 1895 | $checkBoxButton_pause.Visible = $false 1896 | $CheckBoxButton_pause.Enabled = $false 1897 | $progressBar.Visible = $false 1898 | $progressBar.Value = 0 1899 | } 1900 | 1901 | Get-BitsTransfer | Complete-BitsTransfer 1902 | 1903 | # remove any empty folder created 1904 | if ($dlType -like "all*") { 1905 | if (Test-Path $allSeasonPath) { 1906 | Get-ChildItem $allSeasonPath -Directory -recurse | where {-NOT $_.GetFiles("*","AllDirectories")} | del -recurse -ErrorAction SilentlyContinue 1907 | if (Test-Path $allSeasonPath) { 1908 | if ((Get-ChildItem $allSeasonPath | Measure-Object).Count -eq 0) { 1909 | del $allSeasonPath -ErrorAction SilentlyContinue 1910 | } 1911 | } 1912 | } 1913 | } 1914 | 1915 | # allow minimize again 1916 | $form.MinimizeBox = $true 1917 | 1918 | } 1919 | } 1920 | 1921 | } 1922 | 1923 | Catch { 1924 | logit 1925 | Get-BitsTransfer | Remove-BitsTransfer 1926 | 1927 | # clean up any empty folders 1928 | if ($dlType -like "all*") { 1929 | if (Test-Path $allSeasonPath) { 1930 | Get-ChildItem $allSeasonPath -Directory -recurse | where {-NOT $_.GetFiles("*","AllDirectories")} | del -recurse -ErrorAction SilentlyContinue 1931 | if (Test-Path $allSeasonPath) { 1932 | if ((Get-ChildItem $allSeasonPath | Measure-Object).Count -eq 0) { 1933 | del $allSeasonPath -ErrorAction SilentlyContinue 1934 | } 1935 | } 1936 | } 1937 | } 1938 | 1939 | $label_DLTitle.ForeColor = "#ff0000" 1940 | $label_DLProgress.Text = "" 1941 | $label_DLTitle.Text = "There was an error with the download." 1942 | $progressBar.Visible = $false 1943 | $progressBar.Value = 0 1944 | $button_download.Enabled = $true 1945 | $button_cancel.Visible = $false 1946 | $button_cancel.Enabled = $false 1947 | 1948 | # enable minimize again 1949 | $form.MinimizeBox = $true 1950 | } 1951 | 1952 | }) 1953 | 1954 | 1955 | $button_search.Add_Click({search}) 1956 | 1957 | $button_settings.Add_Click({[void]$form2.ShowDialog()}) 1958 | 1959 | $button_cancel.Add_Click({ 1960 | $cancelMsg = [System.Windows.Forms.MessageBox]::Show("Are you sure you want to cancel the download?`nAll non-completed files will be deleted!",'Cancel Download','YesNo','Question') 1961 | 1962 | if ($cancelMsg -eq "Yes" -and $script:pauseLoop -ne $true) { 1963 | $script:cancelLoop = $true 1964 | } 1965 | elseif ($cancelMsg -eq "Yes" -and $script:pauseLoop -eq $true) { 1966 | $script:cancelLoop = $true 1967 | cancelJob 1968 | } 1969 | }) 1970 | 1971 | $button2_getToken.Add_Click({getToken}) 1972 | 1973 | $button2_servers.Add_Click({getServers}) 1974 | 1975 | $button2_dlPath.Add_Click({ 1976 | try { 1977 | $script:dlPath = Get-SavePath 1978 | if ($dlPath) { 1979 | $textBox2_dlPath.Text = $dlPath 1980 | $label2_pathStatus.ForeColor = "#00ff00" 1981 | $label2_pathStatus.Text = "Path Saved!" 1982 | } 1983 | } 1984 | catch { 1985 | logit 1986 | $label2_pathStatus.ForeColor = "#ff0000" 1987 | $label2_pathStatus.Text = "Error! Check log" 1988 | } 1989 | 1990 | }) 1991 | 1992 | # save token on enter key 1993 | $textBox2_password.Add_KeyUp({ 1994 | if ($_.KeyCode -eq "Enter") { 1995 | getToken 1996 | } 1997 | }) 1998 | 1999 | # search on enter key 2000 | $textBox_search.Add_KeyUp({ 2001 | if ($_.KeyCode -eq "Enter") {search} 2002 | }) 2003 | 2004 | $checked_type ={ 2005 | if ($RadioButton_movie.Checked){ 2006 | clearMediaInfo 2007 | clearDLStatus 2008 | $textBox_search.Text = "" 2009 | $label_seasons.Text = "" 2010 | $label_episodes.Text = "" 2011 | $label_search.text = "Search Movie:" 2012 | $combobox_seasons.Visible = $false 2013 | $combobox_episodes.Visible = $false 2014 | $label_mediaTitle.location = $label_mediaTitle_default_xy 2015 | $label_mediaRating.location = $label_mediaRating_default_xy 2016 | $label_mediaScore.location = $label_mediaScore_default_xy 2017 | $label_mediaSummary.location = $label_mediaSummary_default_xy 2018 | $label_mediaSummary.height = $label_mediaSummary_default_height 2019 | $toolTip.SetToolTip($label_search, "Searches by First Letter. Excluding 'The' and 'A'") 2020 | } 2021 | if ($RadioButton_tv.Checked){ 2022 | clearMediaInfo 2023 | clearDLStatus 2024 | $textBox_search.Text = "" 2025 | $label_search.text = "Search TV Show:" 2026 | $label_seasons.Text = "Season:" 2027 | $label_episodes.text = "Episode:" 2028 | $combobox_seasons.Visible = $true 2029 | $combobox_episodes.Visible = $true 2030 | $label_episodes.location = New-Object System.Drawing.Point(215,225) 2031 | $comboBox_episodes.location = New-Object System.Drawing.Point(270,215) 2032 | $label_mediaTitle.location = $label_mediaTitle_default_xy 2033 | $label_mediaRating.location = $label_mediaRating_default_xy 2034 | $label_mediaScore.location = $label_mediaScore_default_xy 2035 | $label_mediaSummary.location = $label_mediaSummary_default_xy 2036 | $label_mediaSummary.height = $label_mediaSummary_default_height 2037 | $combobox_seasons.width = 130 2038 | $combobox_episodes.width = 45 2039 | $toolTip.SetToolTip($label_search, "Searches by first Letter. Excluding 'The' and 'A'") 2040 | } 2041 | elseif ($radiobutton_music.Checked) { 2042 | clearMediaInfo 2043 | clearDLStatus 2044 | $textBox_search.Text = "" 2045 | $label_search.text = "Search Artist:" 2046 | $label_seasons.Text = "Album:" 2047 | $label_episodes.Text = "Track:" 2048 | $label_episodes.location = New-Object System.Drawing.Point(280,225) 2049 | $comboBox_episodes.location = New-Object System.Drawing.Point(325,215) 2050 | $combobox_seasons.Visible = $true 2051 | $combobox_episodes.Visible = $true 2052 | $combobox_seasons.width = 195 2053 | $combobox_episodes.width = 210 2054 | $label_mediaTitle.location = New-Object System.Drawing.Point(140,280) 2055 | $label_mediaRating.location = New-Object System.Drawing.Point(140,300) 2056 | $label_mediaScore.location = New-Object System.Drawing.Point(140,300) 2057 | $label_mediaSummary.location = New-Object System.Drawing.Point(140,320) 2058 | $label_mediaSummary.height = 85 2059 | $toolTip.SetToolTip($label_search, "Searches by Artist Name") 2060 | } 2061 | } 2062 | 2063 | $checkBoxButton_pause.Add_CheckedChanged({ 2064 | if ($checkBoxButton_pause.Checked -eq $true){ 2065 | $script:pauseLoop = $true 2066 | $checkBoxButton_pause.Text = "Resume" 2067 | } 2068 | else { 2069 | if ((Get-BitsTransfer).JobState -eq "Suspended") { 2070 | $script:pauseLoop = $false 2071 | $checkBoxButton_pause.Text = "Pause" 2072 | $button_download.Enabled = $true 2073 | $button_download.PerformClick() 2074 | } 2075 | } 2076 | }) 2077 | 2078 | $checkBox_debug.Add_CheckedChanged({ 2079 | if ($checkBox_debug.Checked -eq $true){ 2080 | $setDebug = $true 2081 | } 2082 | else { 2083 | $setDebug = $false 2084 | } 2085 | # update settings file 2086 | if (Test-Path .\saverrSettings.xml) { 2087 | $script:settings = Import-Clixml .\saverrSettings.xml 2088 | Add-Member -InputObject $settings -MemberType NoteProperty -Name 'logging' -Value $setDebug -force 2089 | $settings | Export-Clixml .\saverrSettings.xml 2090 | } 2091 | else { 2092 | $script:settings = [pscustomobject] @{ 2093 | logging = $setDebug 2094 | } 2095 | $settings | Export-Clixml .\saverrSettings.xml 2096 | } 2097 | $settings = Import-Clixml .\saverrSettings.xml 2098 | $debug = $settings.logging 2099 | }) 2100 | 2101 | $checkBox_ssl.Add_CheckedChanged({ 2102 | if ($checkBox_ssl.Checked -eq $true){ 2103 | $setSSL = $true 2104 | } 2105 | else { 2106 | $setSSL = $false 2107 | } 2108 | # update settings file 2109 | if (Test-Path .\saverrSettings.xml) { 2110 | $script:settings = Import-Clixml .\saverrSettings.xml 2111 | Add-Member -InputObject $settings -MemberType NoteProperty -Name 'ssl' -Value $setSSL -force 2112 | $settings | Export-Clixml .\saverrSettings.xml 2113 | } 2114 | else { 2115 | $script:settings = [pscustomobject] @{ 2116 | ssl = $setSSL 2117 | } 2118 | $settings | Export-Clixml .\saverrSettings.xml 2119 | } 2120 | $settings = Import-Clixml .\saverrSettings.xml 2121 | $ssl = $settings.ssl 2122 | }) 2123 | 2124 | 2125 | # show extra season/artist fields 2126 | $RadioButton_movie.Add_CheckedChanged($checked_type) 2127 | $RadioButton_tv.Add_CheckedChanged($checked_type) 2128 | $RadioButton_music.Add_CheckedChanged($checked_type) 2129 | 2130 | # Show season media info on selection 2131 | $comboBox_results.Add_SelectedIndexChanged({mediaInfo}) 2132 | 2133 | # show episodes after season selection 2134 | $comboBox_seasons.Add_SelectedIndexChanged({episodeSelection}) 2135 | 2136 | # show ep media info after season selection 2137 | $comboBox_episodes.Add_SelectedIndexChanged({mediaEpInfo}) 2138 | 2139 | # save server on selection 2140 | $comboBox2_servers.Add_SelectedValueChanged({saveServer}) 2141 | 2142 | $form2.add_FormClosing({clearStatusSave}) 2143 | 2144 | # confirm closing. clear any downloads on close. 2145 | $form.add_FormClosing({ 2146 | if (Get-BitsTransfer) { 2147 | $question = [System.Windows.Forms.MessageBox]::Show("Are you sure you want to Exit?`nAll non-completed files will be deleted!", 'Exit Saverr', 'YesNo', 'Question') 2148 | 2149 | if ($question -eq 'Yes') { 2150 | $script:cancelLoop = $true 2151 | Get-BitsTransfer | Complete-BitsTransfer 2152 | 2153 | # remove empty folders created 2154 | if (($dlType -like "all*") -and ($script:pauseLoop -eq $true)) { 2155 | if (Test-Path $allSeasonPath) { 2156 | Get-ChildItem $allSeasonPath -Directory -recurse | where {-NOT $_.GetFiles("*","AllDirectories")} | del -recurse -ErrorAction SilentlyContinue 2157 | if (Test-Path $allSeasonPath) { 2158 | if ((Get-ChildItem $allSeasonPath | Measure-Object).Count -eq 0) { 2159 | del $allSeasonPath -ErrorAction SilentlyContinue 2160 | } 2161 | } 2162 | } 2163 | } 2164 | 2165 | } 2166 | else { 2167 | $_.Cancel = $true 2168 | } 2169 | } 2170 | }) 2171 | 2172 | # show the form 2173 | [void]$form.ShowDialog() 2174 | 2175 | # close the forms 2176 | $form.Dispose() 2177 | $form2.Dispose() 2178 | # end 2179 | -------------------------------------------------------------------------------- /screenshots/README.MD: -------------------------------------------------------------------------------- 1 | ## Screenshots 2 | -------------------------------------------------------------------------------- /screenshots/Saverr - Downloading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninthwalker/saverr/08c2d0ad47d46360152a5a19ff75368b360a8649/screenshots/Saverr - Downloading.png -------------------------------------------------------------------------------- /screenshots/Saverr - Movie Search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninthwalker/saverr/08c2d0ad47d46360152a5a19ff75368b360a8649/screenshots/Saverr - Movie Search.png -------------------------------------------------------------------------------- /screenshots/Saverr - Music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninthwalker/saverr/08c2d0ad47d46360152a5a19ff75368b360a8649/screenshots/Saverr - Music.png -------------------------------------------------------------------------------- /screenshots/Saverr - Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninthwalker/saverr/08c2d0ad47d46360152a5a19ff75368b360a8649/screenshots/Saverr - Settings.png -------------------------------------------------------------------------------- /screenshots/Saverr - TV Search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninthwalker/saverr/08c2d0ad47d46360152a5a19ff75368b360a8649/screenshots/Saverr - TV Search.png --------------------------------------------------------------------------------