# BTN 服务器与版本信息等在此定义
Remove-Variable * -ErrorAction Ignore
$Host.UI.RawUI.WindowTitle = "BTNScriptBC_$PID"
$Global:ProgressPreference = "SilentlyContinue"
$CONFIGURL = "https://sparkle.ghostchu.com/ping/config"
$IPLISTURL = "https://bt-ban.pages.dev/IPLIST.txt"
$SCRIPTURL = "btn-bc.pages.dev"
$SCRIPTVER = "0.1.7"
$USERAGENT = "WindowsPowerShell/$([String]$Host.Version) BTNScriptBC/$SCRIPTVER BTN-Protocol/0.0.1"
$APPWTPATH = "$ENV:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe"
Write-Host
# 检测管理员权限
if ((Fltmc).Count -eq 3) {
if (Test-Path $APPWTPATH) {
$PROCESS = "$APPWTPATH -ArgumentList `"powershell $($MyInvocation.MyCommand.Definition)`""
} else {
$PROCESS = "powershell -ArgumentList `"$($MyInvocation.MyCommand.Definition)`""
}
Write-Host " 10 秒后以管理员权限继续执行"
timeout 10
Invoke-Expression "Start-Process $PROCESS -Verb RunAs"
return
}
# 名称变更,此部分保留一段时间
if (!(Test-Path $ENV:USERPROFILE\BTNScriptBC\VERSION.txt)) {
$OLDPATH = "$ENV:USERPROFILE\BTN_BC"
$NEWPATH = "$ENV:USERPROFILE\BTNScriptBC"
if (Test-Path $NEWPATH) {
Remove-Item $OLDPATH -Force -ErrorAction Ignore
} else {
Move-Item $OLDPATH $NEWPATH -Force -ErrorAction Ignore
}
if ($OLDLIST = (Get-NetFirewallRule -DisplayName BTN_* | Get-NetFirewallApplicationFilter).Program | Sort-Object | Get-Unique) {
$DYKWID = "{da62ac48-4707-4adf-97ea-676470a460f5}"
foreach ($APPPATH in $OLDLIST) {
$APPNAME = [System.IO.Path]::GetFileName($APPPATH)
New-NetFirewallRule -DisplayName "BTNScript_$APPNAME" -Direction Inbound -Action Block -Program $APPPATH -RemoteDynamicKeywordAddresses $DYKWID | Out-Null
New-NetFirewallRule -DisplayName "BTNScript_$APPNAME" -Direction Outbound -Action Block -Program $APPPATH -RemoteDynamicKeywordAddresses $DYKWID | Out-Null
}
Remove-NetFirewallRule -DisplayName BTN_*
}
if ($OLDTASK = Get-ScheduledTask BTN_BC_STARTUP -ErrorAction Ignore) {
$NEWTASK = New-ScheduledTask -Principal $OLDTASK.Principal -Settings $OLDTASK.Settings -Trigger $OLDTASK.Triggers -Action $OLDTASK.Actions
Unregister-ScheduledTask BTN_BC_STARTUP -Confirm:$false -ErrorAction Ignore
Register-ScheduledTask BTNScriptBC_STARTUP -InputObject $NEWTASK | Out-Null
}
Set-ScheduledTask BTNScriptBC_STARTUP -Action (New-ScheduledTaskAction -Execute "$NEWPATH\STARTUP.cmd") -ErrorAction Ignore | Out-Null
}
# 检测重复运行
# $MUTEX = New-Object System.Threading.Mutex($False,'BTNScriptBC')
# if (!($MUTEX.WaitOne(0,$False))) {
# Write-Host " 请关闭正在运行的脚本后重试"
# pause
# return
# }
Get-Item $ENV:TEMP\BTNScriptBC_* -Exclude *$PID | ForEach-Object {
Stop-Process ($_.Name -Split '_')[-1] -Force -ErrorAction Ignore
Remove-Item $_
}
New-Item $ENV:TEMP\BTNScriptBC_$PID -ErrorAction Ignore | Out-Null
try {
[System.IO.File]::Open("$ENV:TEMP\BTNScriptBC_$PID",[System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::None) | Out-Null
} catch {}
# 检测动态关键字可用性
$TESTGUID = "{62809d89-9d3b-486b-808f-8c893c1c3378}"
Remove-NetFirewallDynamicKeywordAddress -Id $TESTGUID -ErrorAction Ignore
if (New-NetFirewallDynamicKeywordAddress -Id $TESTGUID -Keyword "BTNScriptBC_TEST" -Address 1.2.3.4 -ErrorAction Ignore) {
Remove-NetFirewallDynamicKeywordAddress -Id $TESTGUID
} else {
Write-Host " 当前 Windows 版本不支持动态关键字,请升级操作系统`n"
Write-Host " 如不使用过滤规则,仅提交 Peers 列表至 BTN,请执行以下命令`n"
Write-Host " iex (irm btn-bc.pages.dev/nofw)`n"
pause
return
}
# 检测防火墙状态
if ($DISABLED = Get-NetFirewallProfile | Where-Object {$_.Enabled -eq 0}) {
$ACTIVEPF = ((Get-NetFirewallSetting -PolicyStore ActiveStore).ActiveProfile) -Replace ', ','|'
$NEEDEDPF = @()
foreach ($PFNAME in $DISABLED.Name) {if ($PFNAME -Match $ACTIVEPF) {$NEEDEDPF += $PFNAME}}
if ($NEEDEDPF) {
Write-Host " 当前网络下未启用 Windows 防火墙`n"
Write-Host " 通常防护软件可与 Windows 防火墙共存,不建议禁用`n"
Write-Host " 仍可继续配置,在 Windows 防火墙启用时生效`n"
Write-Host " 如不使用过滤规则,仅提交 Peers 列表至 BTN 服务器"
Write-Host " 请按 Ctrl + C 键退出本脚本后执行以下命令"
Write-Host " iex (irm btn-bc.pages.dev/nofw)`n"
$ENABLEPF = Read-Host "输入 Y 启用 Windows 防火墙,否则跳过"
Clear-Host
switch -regex ($ENABLEPF) {
'Y|y' {
Set-NetFirewallProfile $NEEDEDPF -Enabled 1
Write-Host "`n 成功启用 Windows 防火墙`n"
}
default {
Write-Host "`n 跳过启用 Windows 防火墙`n"
}
}
}
}
# 初始配置
function Invoke-Setup {
Write-Host " BTNScriptBC 是 BitComet 的外挂脚本,作为 BTN 兼容客户端`n"
Write-Host " 脚本从 BitComet 的 WebUI 中获取 Peers 列表,并格式化数据提交至 BTN 服务器`n"
Write-Host " 提交内容包括活动任务的种子识别符与种子大小`n"
Write-Host " 种子识别符由种子特征码经过不可逆哈希算法生成,不会透露用户的下载内容`n"
Write-Host " 更多信息请查阅以下网页`n"
Write-Host " https://github.com/Oniicyan/BTNScriptBC"
Write-Host " https://github.com/PBH-BTN/BTN-Spec"
Write-Host "`n 同意请继续`n"
pause
Clear-Host
Write-Host
Write-Host " --------------------------------"
Write-Host " 请指定启用过滤规则的 BT 应用程序"
Write-Host " --------------------------------`n"
Write-Host " 1. 自动识别"
Write-Host " 从现有的 Windows 防火墙过滤规则中识别 BT 应用程序路径"
Write-Host " 仅识别常见的 BT 应用程序`n"
Write-Host " 2. 手动选择"
Write-Host " 可选择快捷方式"
Write-Host " 每次选择单个 BT 应用程序`n"
Write-Host " 如不使用过滤规则,仅提交 Peers 列表至 BTN 服务器"
Write-Host " 请按 Ctrl + C 键退出脚本后执行以下命令"
Write-Host " iex (irm btn-bc.pages.dev/nofw)"
$BTRULE = Read-Host "`n请输入 1 或 2(默认为 自动识别)"
switch ($BTRULE) {
2 {
Add-Type -AssemblyName System.Windows.Forms
$BTINFO = New-Object System.Windows.Forms.OpenFileDialog -Property @{InitialDirectory = [Environment]::GetFolderPath('Desktop')}
while ($True) {
$BTINFO.ShowDialog() | Out-Null
if ($BTINFO.FileName) {break} else {Read-Host `n未选择文件,按 Enter 键重新选择}
}
$BTPATH = $BTINFO.FileName
$BTNAME = [System.IO.Path]::GetFileName($BTPATH)
Remove-NetFirewallRule -DisplayName "BTNScript_$BTNAME" -ErrorAction Ignore
New-NetFirewallRule -DisplayName "BTNScript_$BTNAME" -Direction Inbound -Action Block -Program $BTPATH -RemoteDynamicKeywordAddresses $DYKWID | Out-Null
New-NetFirewallRule -DisplayName "BTNScript_$BTNAME" -Direction Outbound -Action Block -Program $BTPATH -RemoteDynamicKeywordAddresses $DYKWID | Out-Null
}
default {
$BTPTRN = 'Azureus\.exe|BitComet\.exe|BitComet_.*\.exe|biglybt\.exe|BitTorrent\.exe|btweb\.exe|deluge\.exe|qbittorrent\.exe|transmission-qt\.exe|uTorrent\.exe|utweb\.exe|tixati\.exe'
$FWLIST = (Get-NetFirewallApplicationFilter).Program | Select-String $BTPTRN | Sort-Object | Get-Unique
$BTLIST =@()
foreach ($BTPATH in $FWLIST) {
if ($BTPATH -Match '^%') {
$BTTEST = Invoke-Expression (($BTPATH -Replace '^%','${ENV:').Replace('%','} + ''') + "'")
} else {
$BTTEST = $BTPATH
}
if (Test-Path $BTTEST) {$BTLIST += $BTPATH}
}
if (!$BTLIST) {
Write-Host "`n 识别不到 BT 应用程序`n 请重新执行脚本并手动选择"
Read-Host `n操作失败,按 Enter 键结束...
exit
}
foreach ($BTPATH in $BTLIST) {
$BTNAME = [System.IO.Path]::GetFileName($BTPATH)
Remove-NetFirewallRule -DisplayName "BTNScript_$BTNAME" -ErrorAction Ignore
New-NetFirewallRule -DisplayName "BTNScript_$BTNAME" -Direction Inbound -Action Block -Program $BTPATH -RemoteDynamicKeywordAddresses $DYKWID | Out-Null
New-NetFirewallRule -DisplayName "BTNScript_$BTNAME" -Direction Outbound -Action Block -Program $BTPATH -RemoteDynamicKeywordAddresses $DYKWID | Out-Null
}
}
}
Write-Host "`n 已配置以下过滤规则`n"
Get-NetFirewallRule -DisplayName BTNScript_* | Select-Object -Property Displayname,Direction | ForEach-Object {' ' + $_.DisplayName + ' (' + $_.Direction + ')'}
Write-Host
Write-Host " --------------------"
Write-Host " 请指定脚本的启动方式"
Write-Host " --------------------`n"
Write-Host " 脚本会在通知区域显示图标,可点击显示/隐藏窗口"
Write-Host " BitComet 未启动时,每 60 秒检测一次`n"
Write-Host " 1. 跟随用户启动"
Write-Host " 通过任务计划程序,在用户登录时启动脚本`n"
Write-Host " 2. 桌面快捷方式"
Write-Host " 在桌面生成快捷方式,由用户按需启动`n"
Write-Host " 3. 跳过启动配置"
Write-Host " 不配置启动方式,用户自行操作"
$STARTUP = Read-Host "`n请输入 1-3(默认为 跟随用户启动)"
if (Test-Path $APPWTPATH) {
"@start /min $APPWTPATH powershell iex (cat -raw $USERPATH\BTNScriptBC.ps1)" | Out-File -Encoding ASCII $USERPATH\STARTUP.cmd
} else {
"@start /min powershell iex (cat -raw $USERPATH\BTNScriptBC.ps1)" | Out-File -Encoding ASCII $USERPATH\STARTUP.cmd
}
$LINKPATH = "$USERPATH\BTNScriptBC.lnk"
$WshShell = New-Object -COMObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut($LINKPATH)
$Shortcut.TargetPath = "$USERPATH\STARTUP.cmd"
$Shortcut.IconLocation = "$ENV:WINDIR\System32\EaseOfAccessDialog.exe"
$Shortcut.Save()
$LINKBYTE = [System.IO.File]::ReadAllBytes($LINKPATH)
$LINKBYTE[0x15] = $LINKBYTE[0x15] -bor 0x20
[System.IO.File]::WriteAllBytes($LINKPATH,$LINKBYTE)
Unregister-ScheduledTask BTNScriptBC_STARTUP -Confirm:$false -ErrorAction Ignore
switch ($STARTUP) {
2 {
Copy-Item $LINKPATH $([Environment]::GetFolderPath("Desktop"))
Write-Host "`n 已配置桌面快捷方式:BTNScriptBC.lnk`n"
}
3 {}
default {
$PRINCIPAL = New-ScheduledTaskPrincipal -UserId $ENV:COMPUTERNAME\$ENV:USERNAME -RunLevel Highest
$SETTINGS = New-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable -RestartCount 5 -RestartInterval (New-TimeSpan -Seconds 60) -AllowStartIfOnBatteries
$TRIGGER = New-ScheduledTaskTrigger -AtLogon -User $ENV:COMPUTERNAME\$ENV:USERNAME
$ACTION = New-ScheduledTaskAction -Execute "$USERPATH\STARTUP.cmd"
$TASK = New-ScheduledTask -Principal $PRINCIPAL -Settings $SETTINGS -Trigger $TRIGGER -Action $ACTION
Register-ScheduledTask BTNScriptBC_STARTUP -InputObject $TASK | Out-Null
Write-Host "`n 已配置自启动任务计划:BTNScriptBC_STARTUP`n"
}
}
Write-Host " ----------------------------------"
Write-Host " 请填写用户信息(点击鼠标右键粘贴)"
Write-Host " ----------------------------------`n"
Write-Host " 需要填写的项目如下`n"
Write-Host " BitComet WebUI 地址"
Write-Host " BitComet WebUI 端口"
Write-Host " BitComet WebUI 账号"
Write-Host " BitComet WebUI 密码"
Write-Host " BTN AppId"
Write-Host " BTN AppSecret`n"
Write-Host " WebUI 地址可直接粘贴 URL"
Write-Host " 也可填写 IPv4、IPv6 或域名,可加端口`n"
Write-Host " WebUI 密码将明文保存至本地文件"
Write-Host " 不建议重复使用常用密码"
$UIADDR = Read-Host -Prompt "`n BitComet WebUI 地址"
if ($UIADDR -Match ':') {
if ($UIADDR -Match '([a-z0-9]*\..*:\d{1,5})|(\w{5,}:\d{1,5})') {
$UIADDR = $Matches[0].Split(':')[0]
$UIPORT = $Matches[0].Split(':')[1]
} elseif ($UIADDR -Match '\[.*]:\d{1,5}') {
$UIADDR = ($Matches[0] -Split ']:')[0] -Replace '\['
$UIPORT = ($Matches[0] -Split ']:')[1]
}
}
if (!$UIPORT) {$UIPORT = Read-Host -Prompt "`n BitComet WebUI 端口"}
$UIUSER = Read-Host -Prompt "`n BitComet WebUI 账号"
$UIPASS = Read-Host -Prompt "`n BitComet WebUI 密码"
$APPUID = Read-Host -Prompt "`n BTN AppId"
$APPSEC = Read-Host -Prompt "`n BTN AppSecret"
Write-Output @"
UIADDR = $UIADDR
UIPORT = $UIPORT
UIUSER = $UIUSER
UIPASS = $UIPASS
APPUID = $APPUID
APPSEC = $APPSEC
"@| Out-File $INFOPATH
Write-Host
Write-Host " --------------------------------------`n"
Write-Host " 用户信息已保存至 $INFOPATH`n"
Write-Host " 可直接编辑用户信息,也可删除以重新配置`n"
Write-Host " --------------------------------------`n"
Write-Host " 初始配置完成,脚本即将开始工作`n"
Write-Host " 脚本开始工作后" -ForegroundColor Green
Write-Host " 可点击右下角通知区域图标显示/隐藏窗口`n" -ForegroundColor Green
Write-Host " 执行以下命令清除所有配置" -ForegroundColor Cyan
Write-Host " iex (irm btn-bc.pages.dev/unset)`n"
Write-Host " 执行以下命令添加过滤规则" -ForegroundColor Cyan
Write-Host " iex (irm btn-bc.pages.dev/add)`n"
Write-Host " 执行以下命令重建桌面快捷方式" -ForegroundColor Cyan
Write-Host " iex (irm btn-bc.pages.dev/link)`n"
Write-Host " 执行以下命令重建自启动任务计划" -ForegroundColor Cyan
Write-Host " iex (irm btn-bc.pages.dev/task)"
timeout 120
Clear-Host
}
# 加载 user32.dll,获取窗口句柄
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Tricks {
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public static IntPtr FindWindowByName(string lpWindowName) {return FindWindow(null, lpWindowName);}
[DllImport("user32.dll")]
public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
}
"@
$hwnd = [Tricks]::FindWindowByName($Host.UI.RawUI.WindowTitle)
# 用户配置与动态关键字信息的初始化
# 仅在检测不到 USERINFO.txt 时,执行初始配置
# 初始配置时显示窗口,否则隐藏
$USERPATH = "$ENV:USERPROFILE\BTNScriptBC"
$INFOPATH = "$USERPATH\USERINFO.txt"
$DYKWID = "{da62ac48-4707-4adf-97ea-676470a460f5}"
New-NetFirewallDynamicKeywordAddress -Id $DYKWID -Keyword "BTN_IPLIST" -Addresses 1.2.3.4 -ErrorAction Ignore | Out-Null
New-Item -ItemType Directory -Path $USERPATH -ErrorAction Ignore | Out-Null
if (!(Test-Path $INFOPATH)) {
$SETUP = 1
[Tricks]::SetForegroundWindow($hwnd) | Out-Null
[Tricks]::ShowWindowAsync($hwnd,1) | Out-Null
Invoke-Setup
}
if ($SETUP) {
$WINDOW = 1
} else {
[Tricks]::ShowWindowAsync($hwnd,0) | Out-Null
$WINDOW = 0
}
# 通知区域图标
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | Out-Null
$ICON = [System.Drawing.Icon]::ExtractAssociatedIcon("$ENV:WINDIR\System32\EaseOfAccessDialog.exe")
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "BTNScriptBC"
$Main_Tool_Icon.Icon = $ICON
$Main_Tool_Icon.Visible = $True
# 通知区域按键
$Main_Tool_Icon.Add_Click({
switch ($_.Button) {
([Windows.Forms.MouseButtons]::Left) {
switch ($WINDOW) {
0 {
[Tricks]::SetForegroundWindow($hwnd)
[Tricks]::ShowWindowAsync($hwnd,1)
$Global:WINDOW = 1
}
1 {
[Tricks]::ShowWindowAsync($hwnd,0)
$Global:WINDOW = 0
}
}
}
([Windows.Forms.MouseButtons]::Right) {[System.Windows.Forms.SendKeys]::SendWait('^')}
}
})
# 通知区域菜单
$Menu_Peer = New-Object System.Windows.Forms.MenuItem
$Menu_Peer.Enabled = $False
$Menu_Peer.Text = "强制提交快照"
$Menu_Rule = New-Object System.Windows.Forms.MenuItem
$Menu_Rule.Enabled = $False
$Menu_Rule.Text = "强制更新规则"
$Menu_List = New-Object System.Windows.Forms.MenuItem
$Menu_List.Enabled = $False
$Menu_List.Text = "强制更新订阅"
$Menu_Conf= New-Object System.Windows.Forms.MenuItem
$Menu_Conf.Enabled = $False
$Menu_Conf.Text = "强制更新配置"
$Menu_Show = New-Object System.Windows.Forms.MenuItem
$Menu_Show.Enabled = $False
$Menu_Show.Text = "显示任务安排"
$Contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $Contextmenu
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($Menu_Peer)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($Menu_Rule)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($Menu_List)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($Menu_Conf)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($Menu_Show)
$Menu_Peer.add_Click({
$Global:JOBFLAG = 1
$NOWCONFIG.ability.submit_peers.next = 0
Get-Job | Stop-Job
})
$Menu_Rule.add_Click({
$Global:JOBFLAG = 1
Remove-Item $RULESJSON -Force -ErrorAction Ignore
$NOWCONFIG.ability.rules.next = 0
Get-Job | Stop-Job
})
$Menu_List.add_Click({
$Global:JOBFLAG = 1
Remove-Item $ALLIPLIST -Force -ErrorAction Ignore
$NOWCONFIG.ability.iplist.next = 0
Get-Job | Stop-Job
})
$Menu_Conf.add_Click({
$Global:JOBFLAG = 2
Get-Job | Stop-Job
})
$Menu_Show.add_Click({
Write-Host (Get-Date) [ 下次提交快照在 $($NOWCONFIG.ability.submit_peers.next) ] -ForegroundColor Cyan
Write-Host (Get-Date) [ 下次查询规则在 $($NOWCONFIG.ability.rules.next) ] -ForegroundColor Cyan
Write-Host (Get-Date) [ 下次查询订阅在 $($NOWCONFIG.ability.iplist.next) ] -ForegroundColor Cyan
Write-Host (Get-Date) [ 下次查询配置在 $($NOWCONFIG.ability.reconfigure.next) ] -ForegroundColor Cyan
})
Clear-Host
[System.GC]::Collect()
# 查询脚本更新
$LOCALVER = Get-Content $USERPATH\VERSION.txt -ErrorAction Ignore
$REMOTEVER = (Invoke-RestMethod -TimeoutSec 15 btn-bc.pages.dev/ver).Trim()
if ($LOCALVER -ne $REMOTEVER) {
try {
Invoke-RestMethod -TimeoutSec 30 $SCRIPTURL | Out-File $USERPATH\BTNScriptBC.ps1
$REMOTEVER | Out-File $USERPATH\VERSION.txt
Write-Host (Get-Date) [ BTNScriptBC/$REMOTEVER 已保存至本地 ] -ForegroundColor Green
if ($SCRIPTVER -ne $REMOTEVER) {Write-Host (Get-Date) [ BTNScriptBC/$REMOTEVER 下次启动时生效 ] -ForegroundColor Cyan}
} catch {
if (Test-Path $USERPATH\BTNScriptBC.ps1) {
Write-Host (Get-Date) [ 脚本更新失败,已跳过 ] -ForegroundColor Yellow
} else{
Write-Host (Get-Date) [ 脚本保存失败,请重试 ] -ForegroundColor Red
Write-Host (Get-Date) [ 退出 BTNScriptBC ] -ForegroundColor Red
pause
$Main_Tool_Icon.Dispose()
return
}
}
if (Test-Path $APPWTPATH) {
"@start /min $APPWTPATH powershell iex (cat -raw $USERPATH\BTNScriptBC.ps1)" | Out-File -Encoding ASCII $USERPATH\STARTUP.cmd
} else {
"@start /min powershell iex (cat -raw $USERPATH\BTNScriptBC.ps1)" | Out-File -Encoding ASCII $USERPATH\STARTUP.cmd
}
}
# 启动信息
Write-Host (Get-Date) [ $USERAGENT ] -ForegroundColor Cyan
$CONFIGURL -Match '(\w+:\/\/)([^\/:]+)(:\d*)?([^# ]*)' | Out-Null
Write-Host (Get-Date) [ BTN 服务器:$($Matches[1] + $Matches[2]) ] -ForegroundColor Cyan
Write-Host (Get-Date) [ 点击通知区域图标以显示/隐藏窗口 ] -ForegroundColor Cyan
$RULESLIST = Get-NetFirewallRule -DisplayName BTNScript_* | Sort-Object DisplayName
if ($RULESLIST) {
Write-Host (Get-Date) [ 以下应用程序已配置过滤规则 ] -ForegroundColor Cyan
($RULESLIST | Get-NetFirewallApplicationFilter).Program | Sort-Object | Get-Unique | ForEach-Object {Write-Host (Get-Date) [ $_ ] -ForegroundColor Green}
$TESTSTR = -Join $RULESLIST.Enabled
if ($TESTSTR -Match "False") {
Write-Host (Get-Date) [ 以下过滤规则未启用 ] -ForegroundColor Yellow
foreach ($RULE in $RULESLIST) {
if ($RULE.Enabled -Match "False") {
switch ($RULE.Direction) {
Inbound {$DIRE = "入站规则"}
Outbound {$DIRE = "出站规则"}
}
Write-Host (Get-Date) [ $RULE.DisplayName $DIRE ] -ForegroundColor Yellow
}
}
}
if ($TESTSTR -Notmatch "True") {Write-Host (Get-Date) [ 没有启用的过滤规则 ] -ForegroundColor Yellow}
} else {
Write-Host (Get-Date) [ 没有配置过滤规则 ] -ForegroundColor Yellow
}
# 载入用户信息并定义基本变量
$USERINFO = ConvertFrom-StringData (Get-Content -Raw $INFOPATH)
if ($USERINFO.Count -eq 6) {
Write-Host (Get-Date) [ 用户信息载入成功 ] -ForegroundColor Green
} else {
Write-Host (Get-Date) [ 用户信息已载入,但条目数量不符 ] -ForegroundColor Yellow
Write-Host (Get-Date) [ 如在运行中发生错误,请删除 USERINFO.txt 后重试 ] -ForegroundColor Yellow
}
$UIADDR = $USERINFO['UIADDR']
$UIPORT = $USERINFO['UIPORT']
$UIUSER = $USERINFO['UIUSER']
$UIPASS = ConvertTo-SecureString ($USERINFO['UIPASS']) -AsPlainText -Force
$APPUID = $USERINFO['APPUID']
$APPSEC = $USERINFO['APPSEC']
if ($UIADDR -Match ':') {
$UIHOST = "[${UIADDR}]:${UIPORT}"
} else {
$UIHOST = "${UIADDR}:${UIPORT}"
}
$AUTHHEADS = @{"Authorization"="Bearer $APPUID@$APPSEC"; "X-BTN-AppID"="$APPUID"; "X-BTN-AppSecret"="$APPSEC"}
Write-Host (Get-Date) [ BitComet WebUI 目标主机为 $UIHOST ] -ForegroundColor Cyan
# BC WebUI 的生存检测
# 第一次检测传递参数 1,仅检测端口连通性,在失败时显示一次消息
# 第二次检测传递参数 2,测试网页是否 BC WebUI,成功与否都显示消息
# 循环工作时,不提供参数,在端口检测失败时显示一次消息,并在端口连通后测试网页
function Test-WebUIPort {
param($FLAG)
while (!(Test-NetConnection $UIADDR -port $UIPORT -InformationLevel Quiet -WarningAction SilentlyContinue)) {
if ((!$FLAG) -or ($FLAG -eq 1)) {Write-Host (Get-Date) [ BitComet WebUI 未开启,每 60 秒检测一次 ] -ForegroundColor Yellow}
if (!$FLAG) {$FLAG = 2}
if ($FLAG -eq 1) {$FLAG = 3}
[System.GC]::Collect()
Start-Job {Start-Sleep 60} | Out-Null
Get-Job | Wait-Job | Out-Null
Get-Job | Remove-Job -Force
if ($JOBFLAG) {
Write-Host (Get-Date) [ 结束正在等待的操作,强制执行任务 ] -ForegroundColor Yellow
return
}
}
if ($FLAG -eq 2) {
if ($BCRESP = (Invoke-RestMethod -TimeoutSec 15 -Credential $UIAUTH $UIHOME) -Split '
' | Select-String 'BitComet') {
Write-Host (Get-Date) [ BitComet WebUI 访问成功 ] -ForegroundColor Green
Write-Host (Get-Date) [ $([Regex]::Matches($BCRESP[-1],'\w.*\w]?').Value) ] -ForegroundColor Cyan
} else {
Write-Host (Get-Date) [ 目标网页不是 BitComet WebUI,请重新配置 ] -ForegroundColor Red
Write-Host (Get-Date) [ 退出 BTNScriptBC ] -ForegroundColor Red
pause
$Main_Tool_Icon.Dispose()
exit
}
}
}
Test-WebUIPort 1
# 允许不安全的证书,考虑 BC WebUI 可能开启强制 HTTPS
Add-Type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
# 获取主页 URL,需要捕获 301 / 302 重定向信息
$UIHOME = "http://$UIHOST"
$UIAUTH = New-Object System.Management.Automation.PSCredential($UIUSER,($UIPASS))
while ($UIRESP.StatusCode -ne 200) {
try {
$UIRESP = Invoke-Webrequest -UseBasicParsing -TimeoutSec 15 -Credential $UIAUTH $UIHOME -MaximumRedirection 0 -ErrorAction Ignore
} catch {
Write-Host (Get-Date) [ $_ ] -ForegroundColor Red
if ($_ -Match '401') {
Write-Host (Get-Date) [ 目标网页认证失败,请确认 WebUI 的账号与密码 ] -ForegroundColor Red
} else {
Write-Host (Get-Date) [ 目标网页访问失败,请排查后重试 ] -ForegroundColor Red
}
Write-Host (Get-Date) [ 退出 BTNScriptBC ] -ForegroundColor Red
pause
$Main_Tool_Icon.Dispose()
return
}
switch ($UIRESP.StatusCode) {
200 {break}
301 {$UIHOME = (-Split ($UIRESP.RawContent.Split([Environment]::NewLine) | Select-String 'Location:.*'))[1]}
302 {
$UIPATH = (-Split ($UIRESP.RawContent.Split([Environment]::NewLine) | Select-String 'Location:.*'))[1]
$UIHOME = [String]($UIHOME | Select-String '.*:\d*') + $UIPATH
}
default {
$RETRY++
if ($RETRY -le 10) {
Write-Host (Get-Date) [ 网页返回代码 $UIRESP.StatusCode,10 秒后第 $RETRY 次重试 ] -ForegroundColor Yellow
Start-Job {Start-Sleep 10} | Out-Null
Get-Job | Wait-Job | Out-Null
Get-Job | Remove-Job -Force
if ($JOBFLAG) {Write-Host (Get-Date) [ 无法跳过本操作,完成后执行 ] -ForegroundColor Yellow}
} else {
Write-Host (Get-Date) [ 目标网页访问失败,请排查后重试 ] -ForegroundColor Red
Write-Host (Get-Date) [ 退出 BTNScriptBC ] -ForegroundColor Red
pause
$Main_Tool_Icon.Dispose()
return
}
}
}
}
Test-WebUIPort 2
# 捕获远程服务器的错误响应
function Get-ErrorMessage {
if (!$Error[0].Exception.Response) {return}
$streamReader = [System.IO.StreamReader]::new($Error[0].Exception.Response.GetResponseStream())
try {
$ErrResp = $streamReader.ReadToEnd() | ConvertFrom-Json
} catch {
$streamReader.Close()
return
}
$streamReader.Close()
if ($ErrResp.message) {Write-Host (Get-Date) [ $ErrResp.message ] -ForegroundColor Red}
}
# 百分数转小数,精确到小数点后 4 位
function Get-QuadFloat {
param ($PERCENT)
if ($PERCENT -Match '100%') {
$QUADFLOAT = "1"
} else {
$QUADFLOAT = ('0.' + ($PERCENT -Replace '%|\.') + '00').Substring(0,6)
}
Write-Output $QUADFLOAT
}
# 从种子特征码计算种子标识符
$CRC32 = add-type @"
[DllImport("ntdll.dll")]
public static extern uint RtlComputeCrc32(uint dwInitial, byte[] pData, int iLen);
"@ -Name CRC32 -PassThru
function Get-SaltedHash {
param ($INFOHASH)
$BYTE = [System.Text.Encoding]::UTF8.GetBytes($INFOHASH.ToLower())
$SALT = ($CRC32::RtlComputeCrc32(0,$BYTE,$BYTE.Count)).ToString("x8")
$SALT = $SALT.Substring(6,2) + $SALT.Substring(4,2) + $SALT.Substring(2,2) + $SALT.Substring(0,2)
([System.BitConverter]::ToString(([System.Security.Cryptography.HashAlgorithm]::Create('SHA256')).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($INFOHASH + $SALT))) -Replace '-').ToLower()
}
# 获取 BTN 服务器配置
function Get-BTNConfig {
# if (!$NOWCONFIG) {$CONFIGURL = $CONFIGURL + "?rand=$(Get-Random)"}
Write-Host (Get-Date) [ 开始查询 BTN 服务器配置更新... ] -ForegroundColor Cyan
while ($True) {
try {
$NEWCONFIG = Invoke-RestMethod -TimeoutSec 30 -UserAgent $USERAGENT -Headers $AUTHHEADS $CONFIGURL
if ($NOWCONFIG.ability.reconfigure.version -ne $NEWCONFIG.ability.reconfigure.version) {
$NEWCONFIG | ConvertTo-Json | Out-File $USERPATH\CONFIG.json
Write-Host (Get-Date) [ 当前 BTN 服务器配置版本为 $NEWCONFIG.ability.reconfigure.version.SubString(0,8) ] -ForegroundColor Green
}
break
} catch {
Get-ErrorMessage
Write-Host (Get-Date) [ $_ ] -ForegroundColor Red
if ($_.Exception.Response.StatusCode.value__ -Match '403|400') {
Write-Host (Get-Date) [ 获取 BTN 服务器配置失败,请排查后重试 ] -ForegroundColor Red
Write-Host (Get-Date) [ 退出 BTNScriptBC ] -ForegroundColor Red
pause
$Main_Tool_Icon.Dispose()
exit
}
$RETRY++
if ($RETRY -gt 3) {
if (Test-Path $USERPATH\CONFIG.json) {
$NEWCONFIG = Get-Content $USERPATH\CONFIG.json | ConvertFrom-Json
Write-Host (Get-Date) [ 获取 BTN 服务器配置失败,使用上次获取的配置 ] -ForegroundColor Yellow
break
} else {
Write-Host (Get-Date) [ 获取 BTN 服务器配置失败,请确认服务器后重试 ] -ForegroundColor Red
Write-Host (Get-Date) [ 退出 BTNScriptBC ] -ForegroundColor Red
pause
$Main_Tool_Icon.Dispose()
exit
}
}
Write-Host (Get-Date) [ 获取 BTN 服务器配置失败,600 秒后第 $RETRY 次重试 ] -ForegroundColor Yellow
Start-Job {Start-Sleep 600} | Out-Null
Get-Job | Wait-Job | Out-Null
Get-Job | Remove-Job -Force
if ($JOBFLAG) {
Write-Host (Get-Date) [ 结束正在等待的操作,强制执行任务 ] -ForegroundColor Yellow
return
}
}
}
$Global:NEWCONFIG = $NEWCONFIG
}
# 获取给定任务的 Peers 信息并记录到哈希表
function Get-TaskPeers {
param ($TASKURL)
try {
$SUMMARY = Invoke-RestMethod -TimeoutSec 5 -Credential $UIAUTH $TASKURL
$PEERS = Invoke-RestMethod -TimeoutSec 5 -Credential $UIAUTH ${TASKURL}`&show=peers
} catch {
Write-Host (Get-Date) [ 获取任务详情超时,跳过一个任务 ] -ForegroundColor Yellow
return
}
try {
$torrent_identifier = Get-SaltedHash (($SUMMARY.Split([Environment]::NewLine) | Select-String 'InfoHash') -Replace '.*>(?=[0-9a-z])| Piece.*')
} catch {
Write-Host (Get-Date) [ 获取任务详情失败,跳过一个任务 ] -ForegroundColor Yellow
return
}
if ($torrent_identifier -Match '1ca334e65d854658cf4398db9f2e1c350a1d80b4aa29b2a87b47a1534bb961d2') {
Write-Host (Get-Date) [ 跳过一个 BTv2 任务 ] -ForegroundColor Yellow
return
}
$BIBYTE = (($SUMMARY -Split '>' | Select-String '\d*\.?\d* [KMGTPEZY]?B' | Select-String 'Selected') -Replace 'Selected.*') -Replace ' |,'
if ($BIBYTE -Match '\dB') {
$torrent_size = $BIBYTE -Replace 'B'
} else {
$torrent_size = Invoke-Expression $BIBYTE
}
$downloader_progress = Get-QuadFloat ([Regex]::Matches(($SUMMARY.Split([Environment]::NewLine) | Select-String 'left \)'),'\d*.?\d%').Value)
$PEERS -Split '