# 输出欢迎信息

function Write-Welcome {

   Write-Output "---------------------------------------------"

   Write-Output "未来之窗批量文件下载器 PowerShell版"

   Write-Output "@product  spany-down-ps"

   Write-Output "@author   felix"

   Write-Output "@date     2019-04-19"

   Write-Output "---------------------------------------------"

   Write-Output ""  

}


#region 接收输入


# 接收是否输入

function ReadInput_YesOrNo {

   param([string]$message, [switch]$defaultValue)

   while($true) {

       $input = Read-Host $message

       if(![String]::IsNullOrWhiteSpace($input)) {

           if($input -eq 'y' -or $input -eq 'n') {

               return $input -eq 'y'

           }

       }

       if($defaultValue) {

           return $args -eq $true

       }

   }

}


# 接收文本输入

function ReadInput_Text {

   param([string]$message, [string]$defaultValue)

   while($true) {

       $input = Read-Host $message

       if(![String]::IsNullOrWhiteSpace($input)) {

           return $input.Trim()

       }

       if($defaultValue) {

           return $defaultValue

       }

   }

}


# 接收整数输入

function ReadInput_Integer {

   param([string]$message, [int]$minValue=[Int32]::MinValue, [int]$maxValue=[Int32]::MaxValue)

   $defaultValue = $message

   while($true) {

       $input = Read-Host $message

       if([String]::IsNullOrWhiteSpace($input)) {

           $message = $defaultValue

           continue

       }


       $input = $input.Trim()

       [int]$result = 0

       if(![Int32]::TryParse($input, [ref]$result)) {

           $message = "必须输入一个整数"

           continue

       }

       elseif($result -lt $minValue -or $result -gt $maxValue) {

           $message = "整数范围必须是 $minValue-$maxValue,请重新输入"

           continue

       }


       return $result

   }

}


# 接收URL输入

function ReadInput_Url {

   param([string]$message, [string]$defaultValue)

   $defaultMessage = $message

   while($true) {

       $input = Read-Host $message

       if([String]::IsNullOrWhiteSpace($input)) {

           if($defaultValue){

               return $defaultValue

           }

           $message = $defaultMessage

           continue

       }

       

       $input = $input.Trim()

       if($input.Length -lt 18 -or !($input -clike 'http://*' -or $input -clike 'https://*')) {

           $message = "URL格式不正确,请重新输入"

           continue

       }


       return $input

   }

}


# 接收路径输入

function ReadInput_Path {

   param([string]$message, [string]$defaultValue, [bool]$createIfNotExist = $true)    

   $defaultMessage = $message

   $path = ""

   while($true) {

       $input = Read-Host $message

       if(![String]::IsNullOrWhiteSpace($input)) {

           $path = $input.Trim()

       }

       elseif($defaultValue) {

           $path = $defaultValue

       }

       else {

           $message = $defaultMessage

           continue

       }


       try {

           $dir = New-Object -TypeName System.IO.DirectoryInfo($path)   # 新建DirectoryInfo对象,如果抛出异常说明路径非法

           if($createIfNotExist -and !$dir.Exists) {    # 或者 Test-Path -Path $path 但无法识别路径中的括号

               $path = $dir.FullName

               New-Item -Path $path -ItemType Directory | Out-Null

           }

       } catch [System.ArgumentException], [System.IO.PathTooLongException] {

           $message = "该路径不合法,请重新输入"

           $path = ""

           continue

       }


       return $path

   }

}


#endregion


# 构建URL列表

function BuildUrlList {

   param([string]$urlFormat, [int]$start, [int]$end, [int]$len)

   if(!$urlFormat.Contains("(*)")) {

       return ,$urlFormat

   }

   $list = @()

   $last = $end + 1

   for($i = $start; $i -lt $last; $i++) {    

       $tmp_url = $urlFormat -replace "\(\*\)",("{0:D$len}" -f $i) ## 或者 $i.ToString("D$len")

       $list += $tmp_url

   }

   return $list

}


[int]$script:completed = 0  # 下载完成数量

[int]$script:succeed = 0    # 下载成功数量


# 开始下载(普通方法)

function StartDownload {

   param([array]$urlList, [string]$path, [string]$referer)

   $last = $urlList.Count

   $watch = Measure-Command {

       for($i = 0; $i -lt $last; $i++) {

           DownloadItem -url $urlList[$i] -path $path -referer $referer

           Start-Sleep -Milliseconds 200  # 延迟0.2秒

       }

   }

   $failed = $script:completed - $succeed

   $elapsed = [Math]::Round($watch.TotalMilliseconds/1000, 2)  # 总计耗时(秒)

   Write-Output ""

   Write-Host "总共下载 $script:completed,成功 $script:succeed,失败 $failed,耗时 $elapsed s" -ForegroundColor Red -BackgroundColor Yellow

   $script:completed = 0

   $script:succeed = 0

}


# 下载单个文件

function DownloadItem {

   param([string]$url, [string]$path, [string]$referer)

   $url_file = $url.Substring($url.LastIndexOf('/') + 1);

   if($referer.Contains("(*)")) {

       $referer = $referer -replace "\(\*\)", $url

   }

   try {

       $tmpFileName = [System.IO.Path]::GetTempFileName()

       $destFileName = [System.IO.Path]::Combine($path, $url_file)

       $watch = Measure-Command {

           # 下载文件到临时文件夹

           Invoke-WebRequest -Uri $url -Method Get -Headers @{"Referer"=$referer} -UserAgent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36" -TimeoutSec 120 -OutFile $tmpFileName

           # 将临时文件移动到目标文件夹

           Move-Item -Path $tmpFileName -Destination $destFileName -Force

       }

       $script:succeed += 1

       $fileLength = [Math]::Ceiling((Get-Item -LiteralPath $destFileName).Length / 1024.0)

       $elapsed = [Math]::Round($watch.TotalMilliseconds)

       # 下载成功!12.jpg - 115KB/2356ms

       Write-Host "下载成功!$url_file - $fileLength KB/$elapsed ms" -ForegroundColor Green

   } catch {

       Write-Error $PSItem.ToString()

   } finally {

       $script:completed += 1

   }

}


# 主函数 运行 AppStart 即可启动

function AppStart {

   Clear-Host

   Write-Welcome

   $urlFormat = ReadInput_Url -message "输入URL(含通配符,例如 https://51.onelink.ynwlzc.cn/cyberwin-static/cwpd_static/./2020/(*).jpg)"

   $start = ReadInput_Integer -message "通配符数字开始(0~200)" -minValue 0 -maxValue 200

   $end = $start + 200

   $end = ReadInput_Integer -message "通配符数字结束($start~$end)" -minValue: $start -maxValue $end

   $len = ReadInput_Integer -message "通配符数字长度(1~5)" -minValue: 1 -maxValue 5

   $referer = ReadInput_Url -message "输入Referer为破解防盗链(如果Referer中含有通配符(*),则将被当前URL替换,如无须Referer则直接回车)" -defaultValue "https://www.baidu.com/visit"

   Write-Output ""


   $urlList = BuildUrlList -urlFormat $urlFormat -start $start -end $end -len $len

   if($urlList.Count -gt 0) {

       Write-Output "URL列表如下:"

       foreach($url in $urlList) {

           Write-Output "`t$url"

       }

       Write-Output ""

       if(ReadInput_YesOrNo -message "是否开始下载?(y/n)") {

           $path = ReadInput_Path -message "输入文件存储路径(例如 E:\album\travel)"

           Write-Output ""

           StartDownload -urlList $urlList -path $path -referer $referer

       }

   } else {

       Write-Warning "不能创建URL列表,请核对参数!"

   }

   Write-Output ""

   Write-Output "按任意键退出..."

   [System.Console]::ReadKey($true) | Out-Null

}


AppStart