<# .SYNOPSIS Bookworm 授权码生成器 (管理员 GUI 工具) .DESCRIPTION 内部调用 node gen-authcode.js, 提供可视化界面生成多用户授权码。 打包命令: 见 build.ps1 -Admin #> $ErrorActionPreference = "Stop" Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing [System.Windows.Forms.Application]::EnableVisualStyles() # ─── 路径 ───────────────────────────────────────────── $ScriptDir = if ($PSScriptRoot) { $PSScriptRoot } elseif ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName -match '\.exe$') { Split-Path -Parent ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName) } elseif ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { $PWD.Path } # gen-authcode.js / secrets.txt 查找: 当前目录 → 父目录 (dist/ 内运行时) $GenScript = Join-Path $ScriptDir "gen-authcode.js" if (-not (Test-Path $GenScript)) { $parentDir = Split-Path $ScriptDir -Parent $GenScript = Join-Path $parentDir "gen-authcode.js" if (Test-Path $GenScript) { $ScriptDir = $parentDir } # 切到父目录 } $SecretsTxt = Join-Path $ScriptDir "secrets.txt" # ─── 品牌色 ─────────────────────────────────────────── $brandBlue = [System.Drawing.Color]::FromArgb(88, 101, 242) $brandDark = [System.Drawing.Color]::FromArgb(30, 31, 46) $brandLight = [System.Drawing.Color]::FromArgb(245, 245, 250) $successGreen = [System.Drawing.Color]::FromArgb(46, 160, 67) $warningOrange = [System.Drawing.Color]::FromArgb(227, 137, 29) # ─── 主窗口 ─────────────────────────────────────────── $form = New-Object System.Windows.Forms.Form $form.Text = "Bookworm 授权码生成器 — 管理员工具" $form.Size = New-Object System.Drawing.Size(560, 580) $form.StartPosition = "CenterScreen" $form.FormBorderStyle = "FixedDialog" $form.MaximizeBox = $false $form.BackColor = [System.Drawing.Color]::White $form.Font = New-Object System.Drawing.Font("Segoe UI", 9) # ─── 标题栏 ─────────────────────────────────────────── $header = New-Object System.Windows.Forms.Panel $header.Location = New-Object System.Drawing.Point(0, 0) $header.Size = New-Object System.Drawing.Size(560, 56) $header.BackColor = $brandDark $form.Controls.Add($header) $titleLabel = New-Object System.Windows.Forms.Label $titleLabel.Location = New-Object System.Drawing.Point(20, 14) $titleLabel.Size = New-Object System.Drawing.Size(520, 28) $titleLabel.Text = "Bookworm 授权码生成器" $titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 14, [System.Drawing.FontStyle]::Bold) $titleLabel.ForeColor = [System.Drawing.Color]::White $header.Controls.Add($titleLabel) # ─── 输入区 ─────────────────────────────────────────── $y = 72 # 用户名 $lblUser = New-Object System.Windows.Forms.Label $lblUser.Location = New-Object System.Drawing.Point(24, $y) $lblUser.Size = New-Object System.Drawing.Size(120, 22) $lblUser.Text = "用户标识 (--user):" $form.Controls.Add($lblUser) $txtUser = New-Object System.Windows.Forms.TextBox $txtUser.Location = New-Object System.Drawing.Point(150, $y - 2) $txtUser.Size = New-Object System.Drawing.Size(370, 28) $txtUser.Font = New-Object System.Drawing.Font("Consolas", 11) $txtUser.PlaceholderText = "例如: alice, 张三, 茶师兄" $form.Controls.Add($txtUser) $y += 38 # Relay Sub-Key $lblKey = New-Object System.Windows.Forms.Label $lblKey.Location = New-Object System.Drawing.Point(24, $y) $lblKey.Size = New-Object System.Drawing.Size(120, 22) $lblKey.Text = "Relay Sub-Key:" $form.Controls.Add($lblKey) $txtKey = New-Object System.Windows.Forms.TextBox $txtKey.Location = New-Object System.Drawing.Point(150, $y - 2) $txtKey.Size = New-Object System.Drawing.Size(370, 28) $txtKey.Font = New-Object System.Drawing.Font("Consolas", 10) $txtKey.PasswordChar = '*' $txtKey.PlaceholderText = "sk-... (中转站后台创建的子 Key)" $form.Controls.Add($txtKey) $y += 28 # 显示/隐藏 Key $chkShowKey = New-Object System.Windows.Forms.CheckBox $chkShowKey.Location = New-Object System.Drawing.Point(150, $y) $chkShowKey.Size = New-Object System.Drawing.Size(120, 22) $chkShowKey.Text = "显示 Key" $chkShowKey.Add_CheckedChanged({ $txtKey.PasswordChar = if ($chkShowKey.Checked) { [char]0 } else { '*' } }) $form.Controls.Add($chkShowKey) $y += 32 # 有效期 $lblDays = New-Object System.Windows.Forms.Label $lblDays.Location = New-Object System.Drawing.Point(24, $y) $lblDays.Size = New-Object System.Drawing.Size(120, 22) $lblDays.Text = "有效期 (天):" $form.Controls.Add($lblDays) $cmbDays = New-Object System.Windows.Forms.ComboBox $cmbDays.Location = New-Object System.Drawing.Point(150, $y - 2) $cmbDays.Size = New-Object System.Drawing.Size(100, 28) $cmbDays.DropDownStyle = "DropDownList" @(7, 14, 30, 60, 90, 180, 365) | ForEach-Object { $cmbDays.Items.Add($_) | Out-Null } $cmbDays.SelectedIndex = 2 # 默认 30 天 $form.Controls.Add($cmbDays) $lblDaysHint = New-Object System.Windows.Forms.Label $lblDaysHint.Location = New-Object System.Drawing.Point(260, $y) $lblDaysHint.Size = New-Object System.Drawing.Size(260, 22) $lblDaysHint.Text = "" $lblDaysHint.ForeColor = [System.Drawing.Color]::Gray $form.Controls.Add($lblDaysHint) $cmbDays.Add_SelectedIndexChanged({ $d = [int]$cmbDays.SelectedItem $exp = (Get-Date).AddDays($d).ToString("yyyy-MM-dd") $lblDaysHint.Text = "到期: $exp" }) # 触发初始值 $cmbDays.SelectedIndex = 2 $y += 38 # secrets.txt 状态 $lblSecrets = New-Object System.Windows.Forms.Label $lblSecrets.Location = New-Object System.Drawing.Point(24, $y) $lblSecrets.Size = New-Object System.Drawing.Size(500, 22) if (Test-Path $SecretsTxt) { $lineCount = (Get-Content $SecretsTxt -ErrorAction SilentlyContinue | Where-Object { $_ -match '=' }).Count $lblSecrets.Text = "secrets.txt: $lineCount 个凭证已配置" $lblSecrets.ForeColor = $successGreen } else { $lblSecrets.Text = "secrets.txt: 未找到 ($SecretsTxt)" $lblSecrets.ForeColor = [System.Drawing.Color]::Red } $form.Controls.Add($lblSecrets) $y += 28 # Node.js 状态 $lblNode = New-Object System.Windows.Forms.Label $lblNode.Location = New-Object System.Drawing.Point(24, $y) $lblNode.Size = New-Object System.Drawing.Size(500, 22) $nodeOK = [bool](Get-Command node -ErrorAction SilentlyContinue) if ($nodeOK) { $nodeVer = try { (& node --version 2>$null) } catch { "" } $lblNode.Text = "Node.js: $nodeVer" $lblNode.ForeColor = $successGreen } else { $lblNode.Text = "Node.js: 未安装 (必需)" $lblNode.ForeColor = [System.Drawing.Color]::Red } $form.Controls.Add($lblNode) $y += 36 # ─── 生成按钮 ───────────────────────────────────────── $btnGenerate = New-Object System.Windows.Forms.Button $btnGenerate.Location = New-Object System.Drawing.Point(150, $y) $btnGenerate.Size = New-Object System.Drawing.Size(160, 40) $btnGenerate.Text = "生成授权码" $btnGenerate.Font = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold) $btnGenerate.FlatStyle = "Flat" $btnGenerate.BackColor = $brandBlue $btnGenerate.ForeColor = [System.Drawing.Color]::White $btnGenerate.FlatAppearance.BorderSize = 0 $btnGenerate.Cursor = [System.Windows.Forms.Cursors]::Hand $form.Controls.Add($btnGenerate) $btnClear = New-Object System.Windows.Forms.Button $btnClear.Location = New-Object System.Drawing.Point(320, $y) $btnClear.Size = New-Object System.Drawing.Size(80, 40) $btnClear.Text = "清空" $btnClear.FlatStyle = "Flat" $btnClear.FlatAppearance.BorderColor = [System.Drawing.Color]::LightGray $form.Controls.Add($btnClear) $y += 54 # ─── 分隔线 ─────────────────────────────────────────── $sep = New-Object System.Windows.Forms.Label $sep.Location = New-Object System.Drawing.Point(20, $y) $sep.Size = New-Object System.Drawing.Size(510, 1) $sep.BorderStyle = "Fixed3D" $form.Controls.Add($sep) $y += 10 # ─── 结果区 ─────────────────────────────────────────── $lblResultTitle = New-Object System.Windows.Forms.Label $lblResultTitle.Location = New-Object System.Drawing.Point(24, $y) $lblResultTitle.Size = New-Object System.Drawing.Size(200, 22) $lblResultTitle.Text = "生成结果" $lblResultTitle.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold) $lblResultTitle.ForeColor = $brandDark $form.Controls.Add($lblResultTitle) $y += 26 # 授权码 (大字 Consolas, 可选中复制) $txtAuthCode = New-Object System.Windows.Forms.TextBox $txtAuthCode.Location = New-Object System.Drawing.Point(24, $y) $txtAuthCode.Size = New-Object System.Drawing.Size(400, 32) $txtAuthCode.Font = New-Object System.Drawing.Font("Consolas", 13, [System.Drawing.FontStyle]::Bold) $txtAuthCode.ReadOnly = $true $txtAuthCode.BackColor = $brandLight $txtAuthCode.ForeColor = $brandDark $txtAuthCode.Text = "" $form.Controls.Add($txtAuthCode) $btnCopy = New-Object System.Windows.Forms.Button $btnCopy.Location = New-Object System.Drawing.Point(430, $y) $btnCopy.Size = New-Object System.Drawing.Size(90, 32) $btnCopy.Text = "复制" $btnCopy.FlatStyle = "Flat" $btnCopy.BackColor = $brandLight $btnCopy.FlatAppearance.BorderColor = [System.Drawing.Color]::LightGray $btnCopy.Enabled = $false $form.Controls.Add($btnCopy) $y += 40 # 详细信息 $lblDetails = New-Object System.Windows.Forms.Label $lblDetails.Location = New-Object System.Drawing.Point(24, $y) $lblDetails.Size = New-Object System.Drawing.Size(500, 66) $lblDetails.Text = "" $lblDetails.ForeColor = [System.Drawing.Color]::FromArgb(100, 100, 120) $form.Controls.Add($lblDetails) $y += 70 # 状态栏 $statusBar = New-Object System.Windows.Forms.Label $statusBar.Location = New-Object System.Drawing.Point(0, $y) $statusBar.Size = New-Object System.Drawing.Size(560, 24) $statusBar.BackColor = $brandLight $statusBar.ForeColor = [System.Drawing.Color]::Gray $statusBar.Text = " 就绪 | $ScriptDir" $statusBar.Font = New-Object System.Drawing.Font("Segoe UI", 8) $statusBar.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft $form.Controls.Add($statusBar) # ─── 事件处理 ───────────────────────────────────────── $btnCopy.Add_Click({ if ($txtAuthCode.Text) { [System.Windows.Forms.Clipboard]::SetText($txtAuthCode.Text) $statusBar.Text = " 已复制到剪贴板" $statusBar.ForeColor = $successGreen } }) $btnClear.Add_Click({ $txtUser.Text = "" $txtKey.Text = "" $txtAuthCode.Text = "" $lblDetails.Text = "" $btnCopy.Enabled = $false $statusBar.Text = " 已清空" $statusBar.ForeColor = [System.Drawing.Color]::Gray }) $btnGenerate.Add_Click({ # 校验 $user = $txtUser.Text.Trim() $key = $txtKey.Text.Trim() $days = $cmbDays.SelectedItem.ToString() if (-not $user) { [System.Windows.Forms.MessageBox]::Show("请输入用户标识", "缺少用户名", "OK", "Warning") $txtUser.Focus(); return } if (-not $key) { [System.Windows.Forms.MessageBox]::Show("请输入 Relay Sub-Key`n(从中转站后台为用户创建)", "缺少 Sub-Key", "OK", "Warning") $txtKey.Focus(); return } if (-not (Test-Path $SecretsTxt)) { [System.Windows.Forms.MessageBox]::Show("secrets.txt 不存在:`n$SecretsTxt`n`n请先创建 secrets.txt (每行 KEY=VALUE)", "缺少凭证文件", "OK", "Error") return } if (-not $nodeOK) { [System.Windows.Forms.MessageBox]::Show("Node.js 未安装。`n请先安装: https://nodejs.org", "缺少 Node.js", "OK", "Error") return } $statusBar.Text = " 生成中..." $statusBar.ForeColor = $warningOrange $btnGenerate.Enabled = $false [System.Windows.Forms.Application]::DoEvents() try { $args = @($GenScript, $days, "-k", $key, "-u", $user) $proc = Start-Process node -ArgumentList $args -NoNewWindow -PassThru ` -RedirectStandardOutput "$env:TEMP\bw-gen-out.tmp" ` -RedirectStandardError "$env:TEMP\bw-gen-err.tmp" ` -WorkingDirectory $ScriptDir $sw = [System.Diagnostics.Stopwatch]::StartNew() while (-not $proc.HasExited -and $sw.ElapsedMilliseconds -lt 30000) { [System.Windows.Forms.Application]::DoEvents() Start-Sleep -Milliseconds 100 } if (-not $proc.HasExited) { $proc.Kill(); throw "超时 (30s)" } $stdout = Get-Content "$env:TEMP\bw-gen-out.tmp" -Raw -ErrorAction SilentlyContinue $stderr = Get-Content "$env:TEMP\bw-gen-err.tmp" -Raw -ErrorAction SilentlyContinue Remove-Item "$env:TEMP\bw-gen-out.tmp", "$env:TEMP\bw-gen-err.tmp" -Force -ErrorAction SilentlyContinue if ($proc.ExitCode -ne 0) { throw "gen-authcode.js 退出码 $($proc.ExitCode)`n$stderr" } # 解析输出 $authCode = if ($stdout -match '授权码:\s*(BW-\d{8}-[A-F0-9]{24})') { $Matches[1] } else { "" } $fileId = if ($stdout -match '文件 ID:\s*([a-f0-9]{8})\s*→\s*(secrets-[a-f0-9]{8}\.enc)') { $Matches[2] } else { "" } $expiry = if ($stdout -match '有效期:\s*\d+\s*天\s*\(至\s*(\d{4}-\d{2}-\d{2})\)') { $Matches[1] } else { "" } if ($authCode) { $txtAuthCode.Text = $authCode $lblDetails.Text = "用户: $user`n加密文件: $fileId`n有效期: $days 天 (至 $expiry)`n路径: $ScriptDir\$fileId" $btnCopy.Enabled = $true $statusBar.Text = " 生成成功 — $fileId | 请 git add + push 后将授权码发给 $user" $statusBar.ForeColor = $successGreen } else { throw "无法解析授权码输出:`n$stdout" } } catch { $txtAuthCode.Text = "" $lblDetails.Text = "错误: $_" $btnCopy.Enabled = $false $statusBar.Text = " 生成失败" $statusBar.ForeColor = [System.Drawing.Color]::Red [System.Windows.Forms.MessageBox]::Show("生成失败:`n$_", "错误", "OK", "Error") } finally { $btnGenerate.Enabled = $true } }) # ─── 启动 ───────────────────────────────────────────── $form.Add_Shown({ $txtUser.Focus() }) [System.Windows.Forms.Application]::Run($form)