This repository has been archived by the owner on Nov 13, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathBTNScriptBC.ps1
971 lines (939 loc) · 42.4 KB
/
BTNScriptBC.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
# 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 '<br>' | 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 '<tr>' | Select-String '>[IciC_]{4}<' | ForEach-Object {
$RAW = $_
if ($RAW -Match '(\d{1,3}\.){3}\d{1,3}:\d{1,5}') {
$ip_address = $Matches[0].Split(':')[0]
$peer_port = $Matches[0].Split(':')[1]
switch -Regex ($ip_address) {
'^10\.' {return}
'^172\.(1[6-9]|2[0-9]|3[01])\.' {return}
'^192\.168\.' {return}
'^100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.' {return}
'^127\.' {return}
'^169\.254\.' {return}
}
} elseif ($RAW -Match '[0-9a-f]{4}:([0-9a-f]{1,4}):(:?[0-9a-f]{1,4}:?){1,6}:?:\d{1,5}') {
$ip_address = $Matches[0] -Replace ':[0-9]{1,5}$'
$peer_port = ($Matches[0] -Split ':')[-1]
if ($ip_address -Notmatch '^2') {return}
} else {
Write-Host (Get-Date) [ 无法识别的 IP 地址,记录原始数据至 UNKNOWN.txt ] -ForegroundColor Yellow
[String](Get-Date) + ' 无法识别的 IP 地址' | Out-File -Append $USERPATH\UNKNOWN.txt
[String](Get-Date) + ' ' + $RAW | Out-File -Append $USERPATH\UNKNOWN.txt
if ((Get-Content $USERPATH\UNKNOWN.txt -ErrorAction Ignore).Count -ge 1000) {
Move-Item $USERPATH\UNKNOWN.txt $USERPATH\UNKNOWN.txt.old -Force -ErrorAction Ignore
Write-Host (Get-Date) [ UNKNOWN.txt 达到 1000 行,已转存为 UNKNOWN.txt.old ] -ForegroundColor Yellow
}
return
}
if ($RAW -Match '[0-9a-f]{40}') {
$peer_id = -Join ($Matches[0].SubString(0,16) -Replace '(..)','[char]0x${0};'| Invoke-Expression)
} else {
$peer_id = ""
}
$RAW -Match '(?<=\d:\d\d:\d\d<\/td><td>).*?(?=<)' | Out-Null
if ($Matches[0] -Match 'n/a') {
$client_name = ""
} else {
$client_name = $Matches[0]
}
$downloaded = Invoke-Expression ((([Regex]::Matches($RAW,'(?<=>)\d*\.?\d* [KMGTPEZY]?B(?=<)')).Value[0]) -Replace ' |,')
$uploaded = Invoke-Expression ((([Regex]::Matches($RAW,'(?<=>)\d*\.?\d* [KMGTPEZY]?B(?=<)')).Value[1]) -Replace ' |,')
$peer_progress = Get-QuadFloat ([Regex]::Matches($RAW ,'\d*.?\d%'))
$BCFLAGS = [Regex]::Matches($RAW,'(?<=>)[IciC_]{4}(?=<)').Value
$peer_flag = ""
switch -Regex ($BCFLAGS) {
'Ic..' {$peer_flag = $peer_flag + 'd '}
'I_..' {$peer_flag = $peer_flag + 'D '}
'__..' {$peer_flag = $peer_flag + 'K '}
}
switch -Regex ($BCFLAGS) {
'..iC' {$peer_flag = $peer_flag + 'u '}
'..i_' {$peer_flag = $peer_flag + 'U '}
'..__' {$peer_flag = $peer_flag + '? '}
}
if ($RAW -Match 'Remote') {
$peer_flag = $peer_flag + 'I'
} else {
$peer_flag = $peer_flag -Replace ' $'
}
$RATESTR = $RAW -Replace '(Remote|Local).*'
$RATEVAL = [Regex]::Matches($RATESTR,'(?<=>)\d*\.?\d* [KMGTPEZY]?B\/s(?=<)')
switch ($RATEVAL.Count) {
0 {
$rt_download_speed = 0
$rt_upload_speed = 0
}
1 {
if (($peer_progress -eq 1 -or $peer_flag -Cmatch 'D|u' -or $peer_flag -Cnotmatch 'K|U') -and $downloader_progress -ne 1) {
$rt_download_speed = Invoke-Expression ($RATEVAL[0].Value -Replace ' |,|/s')
$rt_upload_speed = 0
} else {
$rt_download_speed = 0
$rt_upload_speed = Invoke-Expression ($RATEVAL[0].Value -Replace ' |,|/s')
}
}
2 {
$rt_download_speed = Invoke-Expression ($RATEVAL[0].Value -Replace ' |,|/s')
$rt_upload_speed = Invoke-Expression ($RATEVAL[1].Value -Replace ' |,|/s')
}
}
try {
$PEERHASH = @{
ip_address = $ip_address
peer_port = [Int]$peer_port
peer_id = $peer_id
client_name = $client_name
torrent_identifier = $torrent_identifier
torrent_size = [Math]::Round($torrent_size)
downloaded = [Math]::Round($downloaded)
rt_download_speed = [Math]::Round($rt_download_speed)
uploaded = [Math]::Round($uploaded)
rt_upload_speed = [Math]::Round($rt_upload_speed)
peer_progress = [decimal]$peer_progress
downloader_progress = [decimal]$downloader_progress
peer_flag = $peer_flag -Replace ' $'
}
$SUBMITHASH.peers += $PEERHASH
} catch {
Write-Host (Get-Date) [ 格式错误的 Peer 信息,记录原始数据至 UNKNOWN.txt ] -ForegroundColor Yellow
[String](Get-Date) + ' ' + $_.Exception.Message | Out-File -Append $USERPATH\UNKNOWN.txt
[String](Get-Date) + ' ' + $RAW | Out-File -Append $USERPATH\UNKNOWN.txt
[String](Get-Date) + " {torrent_identifier = $torrent_identifier, torrent_size = $torrent_size, downloader_progress = $downloader_progress}" | Out-File -Append $USERPATH\UNKNOWN.txt
if ((Get-Content $USERPATH\UNKNOWN.txt -ErrorAction Ignore).Count -ge 1000) {
Move-Item $USERPATH\UNKNOWN.txt $USERPATH\UNKNOWN.txt.old -Force -ErrorAction Ignore
Write-Host (Get-Date) [ UNKNOWN.txt 达到 1000 行,已转存为 UNKNOWN.txt.old ] -ForegroundColor Yellow
}
}
}
}
# 获取活动任务列表,并调用上述函数传递 URL 参数获取 Peers 哈希表
# Peers 哈希表转换为 JSON 保存
function Get-PeersJson {
Write-Host (Get-Date) [ 开始提交 Peers 快照... ] -ForegroundColor Cyan
Test-WebUIPort
try {
$ACTIVE = ((Invoke-RestMethod -TimeoutSec 15 -Credential $UIAUTH ${UIHOME}task_list) -Split '<.?tr>' -Replace '> (HTTPS|HTTP|FTP) <.*' -Split "'" | Select-String '.*action=stop') -Split '&|=' | Select-String '.*\d' | ForEach-Object {"${UIHOME}task_detail?id=" + $_}
} catch {
Write-Host (Get-Date) [ $_ ] -ForegroundColor Red
Write-Host (Get-Date) [ 获取任务列表超时,跳过本次提交 ] -ForegroundColor Yellow
$Global:SUBMIT = 0
return
}
Write-Host (Get-Date) [ 分析 $ACTIVE.Count 个活动任务 ] -ForegroundColor Cyan
$SUBMITHASH = @"
{
"populate_time": $([DateTimeOffset]::Now.ToUnixTimeMilliseconds()),
"peers": []
}
"@ | ConvertFrom-Json
$ACTIVE | ForEach-Object {Get-TaskPeers $_}
Write-Host (Get-Date) [ 提取 $($SUBMITHASH.peers.Count) 个活动 Peers,耗时 $((([DateTimeOffset]::Now.ToUnixTimeMilliseconds()) - $SUBMITHASH.populate_time) / 1000) 秒 ] -ForegroundColor Cyan
if ($SUBMITHASH.peers.Count -eq 0) {
$Global:SUBMIT = 0
return
}
$SUBMITHASH | ConvertTo-Json | Out-File $PEERSJSON
$Global:SUBMIT = 1
}
# JSON 打包为 Gzip 并提交至 BTN 服务器
$PEERSJSON = "$USERPATH\PEERS.json"
$PEERSGZIP = "$USERPATH\PEERS.gzip"
function Invoke-SumbitPeers {
if ($SUBMIT -eq 0) {
Write-Host (Get-Date) [ 没有需要提交的数据 ] -ForegroundColor Green
return
}
$JSONSTREAM = New-Object System.IO.FileStream($PEERSJSON,([IO.FileMode]::Open),([IO.FileAccess]::Read),([IO.FileShare]::Read))
$GZIPSTREAM = New-Object System.IO.FileStream($PEERSGZIP,([IO.FileMode]::Create),([IO.FileAccess]::Write),([IO.FileShare]::None))
$GZIPBUFFER = New-Object System.IO.Compression.GZipStream($GZIPSTREAM,[System.IO.Compression.CompressionMode]::Compress)
$JSONSTREAM.CopyTo($GZIPBUFFER)
$GZIPBUFFER.Dispose()
$JSONSTREAM.Dispose()
$GZIPSTREAM.Dispose()
$GZIPLENGTH = [Regex]::Matches(((Get-Item $PEERSGZIP).Length / 1KB),'\d*\.?\d')[0].Value
if ($GZIPLENGTH -Notmatch '\.\d') {$GZIPLENGTH = $GZIPLENGTH + '.0'}
try {
Invoke-RestMethod -TimeoutSec 30 -UserAgent $USERAGENT -Headers ($AUTHHEADS + @{"Content-Encoding"="gzip"; "Content-Type"="application/json"}) -Method Post -InFile $PEERSGZIP $NEWCONFIG.ability.submit_peers.endpoint | Out-Null
Write-Host (Get-Date) [ 提交 Peers 快照成功,数据大小 $GZIPLENGTH KiB ] -ForegroundColor Green
} catch {
Get-ErrorMessage
Write-Host (Get-Date) [ $_ ] -ForegroundColor Red
Write-Host (Get-Date) [ 提交 Peers 快照失败,数据大小 $GZIPLENGTH KiB ] -ForegroundColor Yellow
}
Remove-Item $PEERSGZIP
}
# 更新 BTN 封禁规则
$RULESJSON = "$USERPATH\RULES.json"
$BTNIPLIST = "$USERPATH\RULES.txt"
function Get-BTNRules {
Write-Host (Get-Date) [ 开始查询 BTN 封禁规则更新... ] -ForegroundColor Cyan
if (Test-Path $RULESJSON) {
$REVURL = "?rev=$(([Regex]::Matches((Get-Content $RULESJSON | Select-String 'version'),'[0-9a-f]{8}')).Value)"
}
try {
$RULESIWR = Invoke-Webrequest -UseBasicParsing -TimeoutSec 30 -UserAgent $USERAGENT -Headers $AUTHHEADS ($NEWCONFIG.ability.rules.endpoint + $REVURL)
if ($RULESIWR.Content.Length -eq 0) {
Write-Host (Get-Date) [ 当前 BTN 封禁规则已是最新 ] -ForegroundColor Green
return
}
$RULESOBJ = [system.Text.Encoding]::UTF8.GetString($RULESIWR.RawContentStream.ToArray()) | ConvertFrom-Json
$RULESOBJ | ConvertTo-Json | Out-File $RULESJSON
$RULESOBJ.IP.PSObject.Properties.value | Select-String -Notmatch '^::1$' | Out-File $BTNIPLIST
if (Test-Path $ALLIPLIST) {
$ADDRESS = ((Get-Content $BTNIPLIST) + (Get-Content $ALLIPLIST)) -Join ','
} else {
$ADDRESS = (Get-Content $BTNIPLIST) -Join ','
}
Update-NetFirewallDynamicKeywordAddress -Id $DYKWID -Addresses $ADDRESS | Out-Null
$Global:IPCOUNT = ((Get-NetFirewallDynamicKeywordAddress -Id $DYKWID).Addresses -Split ',').Count
$VERSION = ([Regex]::Matches(((Get-Content $RULESJSON) | Select-String 'version'),'[0-9a-f]{8}')).Value
Write-Host (Get-Date) [ 更新 BTN 封禁规则成功,当前版本 ${VERSION},共 $((Get-Content $BTNIPLIST).Count) 条 IP 规则 ] -ForegroundColor Green
Write-Host (Get-Date) [ 更新动态关键字成功,合并后共 $IPCOUNT 条 IP 规则 ] -ForegroundColor Green
} catch {
Get-ErrorMessage
Write-Host (Get-Date) [ $_ ] -ForegroundColor Red
Write-Host (Get-Date) [ 获取 BTN 封禁规则失败,当前共 $(((Get-NetFirewallDynamicKeywordAddress -Id $DYKWID).Addresses -Split ',').Count) 条动态关键字规则 ] -ForegroundColor Yellow
}
}
# 更新 IP 黑名单订阅
$ALLIPLIST = "$USERPATH\IPLIST.txt"
function Get-IPList {
Write-Host (Get-Date) [ 开始查询 IP 黑名单订阅更新... ] -ForegroundColor Cyan
try {
$NEWIPLIST = Invoke-RestMethod -TimeoutSec 30 $IPLISTURL
if ((-Split $NEWIPLIST).Count -eq (Get-Content $ALLIPLIST -ErrorAction Ignore).Count) {
Write-Host (Get-Date) [ 当前 IP 黑名单订阅已是最新 ] -ForegroundColor Green
return
}
$NEWIPLIST | Out-File $ALLIPLIST
if (Test-Path $BTNIPLIST) {
$ADDRESS = ((Get-Content $ALLIPLIST) + (Get-Content $BTNIPLIST)) -Join ','
} else {
$ADDRESS = (Get-Content $ALLIPLIST) -Join ','
}
Update-NetFirewallDynamicKeywordAddress -Id $DYKWID -Addresses $ADDRESS | Out-Null
$Global:IPCOUNT = ((Get-NetFirewallDynamicKeywordAddress -Id $DYKWID).Addresses -Split ',').Count
Write-Host (Get-Date) [ 更新 IP 黑名单订阅成功,共 $((Get-Content $ALLIPLIST).Count) 条 IP 规则 ] -ForegroundColor Green
Write-Host (Get-Date) [ 更新动态关键字成功,合并后共 $IPCOUNT 条 IP 规则 ] -ForegroundColor Green
} catch {
Get-ErrorMessage
Write-Host (Get-Date) [ $_ ] -ForegroundColor Red
Write-Host (Get-Date) [ 获取 IP 黑名单订阅失败,当前共 $(((Get-NetFirewallDynamicKeywordAddress -Id $DYKWID).Addresses -Split ',').Count) 条动态关键字规则 ] -ForegroundColor Yellow
}
}
# 循环工作前,更新 IP 黑名单订阅
Get-IPList
if (!$IPCOUNT) {$IPCOUNT = ((Get-NetFirewallDynamicKeywordAddress -Id $DYKWID).Addresses -Split ',').Count}
$Main_Tool_Icon.Text = "BTNScriptBC - 共 $IPCOUNT 条 IP 规则"
# 首次启动时,先获取 BTN 服务器配置,并添加下次执行时间
# 遵守 BTN 规范的首次随机延迟要求
# 以下循环工作流程
# 1. 按照下次执行时间排列任务
# 2. 等待并执行最近的一个(排列首位的)任务
# 3. 执行完成后,安排下次时间,回到 1.
# 当 BTN 服务器配置的间隔要求发生变化时,重新配置下次执行时间
Write-Host (Get-Date) [ BTNScriptBC 开始循环工作 ] -ForegroundColor Cyan
while ($True) {
Get-Job | Remove-Job -Force
$Global:JOBFLAG = 0
if (!$NOWCONFIG) {
Get-BTNConfig
$Menu_Peer.Enabled = $True
$Menu_Rule.Enabled = $True
$Menu_List.Enabled = $True
$Menu_Conf.Enabled = $True
$Menu_Show.Enabled = $True
}
if (
$NOWCONFIG.ability.submit_peers.interval -ne $NEWCONFIG.ability.submit_peers.interval -or
$NOWCONFIG.ability.rules.interval -ne $NEWCONFIG.ability.rules.interval -or
$NOWCONFIG.ability.reconfigure.interval -ne $NEWCONFIG.ability.reconfigure.interval
) {
$NOWCONFIG = $NEWCONFIG
$NOWCONFIG.ability | Add-Member iplist @{}
$NOWCONFIG.ability.iplist | Add-Member interval 3600000
$NOWCONFIG.ability.iplist | Add-Member random_initial_delay 1
$NOWCONFIG.ability.PSObject.Properties.Name | ForEach-Object {
$DELAY = Get-Random -Maximum $NOWCONFIG.ability.$_.random_initial_delay
$NOWCONFIG.ability.$_ | Add-Member next ((Get-Date) + (New-TimeSpan -Seconds (($NOWCONFIG.ability.$_.interval + $DELAY) / 1000))) -ErrorAction Ignore
}
$NOWCONFIG.ability.submit_peers | Add-Member cmd "Get-PeersJson; Invoke-SumbitPeers"
$NOWCONFIG.ability.rules | Add-Member cmd "Get-BTNRules"
$NOWCONFIG.ability.iplist | Add-Member cmd "Get-IPList"
$NOWCONFIG.ability.reconfigure | Add-Member cmd "Get-BTNConfig"
Write-Host (Get-Date) [ 每 $($NOWCONFIG.ability.submit_peers.interval / 1000) 秒提交 Peers 快照 ] -ForegroundColor Cyan
Write-Host (Get-Date) [ 每 $($NOWCONFIG.ability.rules.interval / 1000) 秒查询 BTN 封禁规则更新 ] -ForegroundColor Cyan
Write-Host (Get-Date) [ 每 $($NOWCONFIG.ability.iplist.interval / 1000) 秒查询 IP 黑名单订阅更新 ] -ForegroundColor Cyan
Write-Host (Get-Date) [ 每 $($NOWCONFIG.ability.reconfigure.interval / 1000) 秒查询 BTN 服务器配置更新 ] -ForegroundColor Cyan
}
$Main_Tool_Icon.Text = "BTNScriptBC - 共 $IPCOUNT 条 IP 规则"
[System.GC]::Collect()
$JOBLIST = $NOWCONFIG.ability.PSObject.Properties.Value | Sort-Object next
if (!$JOBLIST[0].cmd) {$JOBLIST[0].next = ((Get-Date) + (New-TimeSpan -Hours $JOBLIST[0].interval)); continue}
if ((Get-Date) -lt $JOBLIST[0].next) {
Start-Job {Start-Sleep ($Using:JOBLIST[0].next - (Get-Date)).TotalSeconds} | Out-Null
Get-Job | Wait-Job | Out-Null
}
switch ($JOBFLAG) {
0 {Invoke-Expression $JOBLIST[0].cmd; $JOBLIST[0].next = ((Get-Date) + (New-TimeSpan -Seconds ($JOBLIST[0].interval / 1000)))}
1 {continue}
2 {Remove-Variable NOWCONFIG -ErrorAction Ignore}
}
}