feat: add personal wechat wcf collector deploy
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$Root = "C:\UniDesk\personal-wechat"
|
||||
$WechatRoot = Join-Path $Root "wechat-3.9.12.51"
|
||||
$WcfRoot = Join-Path $Root "wcf\v39.5.2"
|
||||
$StateRoot = Join-Path $Root "wcf-state"
|
||||
$DownloadRoot = Join-Path $Root "downloads"
|
||||
$PrepareResult = Join-Path $StateRoot "prepare-result.json"
|
||||
$Python = "C:\ProgramData\miniconda3\python.exe"
|
||||
$PipIndex = "http://mirrors.aliyun.com/pypi/simple/"
|
||||
$ReleaseBase = "https://github.com/lich0821/WeChatFerry/releases/download/v39.5.2"
|
||||
|
||||
New-Item -ItemType Directory -Force $WechatRoot,$WcfRoot,$StateRoot,$DownloadRoot | Out-Null
|
||||
|
||||
function Download-IfMissing {
|
||||
param([string]$Url, [string]$Path)
|
||||
if (Test-Path $Path) { return }
|
||||
$tmp = "$Path.part"
|
||||
Invoke-WebRequest -Uri $Url -OutFile $tmp -UseBasicParsing
|
||||
Move-Item -Force $tmp $Path
|
||||
}
|
||||
|
||||
function Find-WeChatExe {
|
||||
$candidates = @(
|
||||
(Join-Path $WechatRoot "WeChat.exe"),
|
||||
"C:\Program Files (x86)\Tencent\WeChat\WeChat.exe",
|
||||
"C:\Program Files\Tencent\WeChat\WeChat.exe"
|
||||
)
|
||||
foreach ($candidate in $candidates) {
|
||||
if (Test-Path $candidate) { return $candidate }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
$startedAt = (Get-Date).ToUniversalTime().ToString("o")
|
||||
$installer = Join-Path $DownloadRoot "WeChatSetup-3.9.12.51.exe"
|
||||
Download-IfMissing "$ReleaseBase/WeChatSetup-3.9.12.51.exe" $installer
|
||||
Download-IfMissing "$ReleaseBase/sdk.dll" (Join-Path $WcfRoot "sdk.dll")
|
||||
Download-IfMissing "$ReleaseBase/spy.dll" (Join-Path $WcfRoot "spy.dll")
|
||||
Download-IfMissing "$ReleaseBase/spy_debug.dll" (Join-Path $WcfRoot "spy_debug.dll")
|
||||
|
||||
if (!(Test-Path $Python)) {
|
||||
throw "Expected Python not found: $Python"
|
||||
}
|
||||
|
||||
$pipOut = Join-Path $StateRoot "prepare-pip.stdout.log"
|
||||
$pipErr = Join-Path $StateRoot "prepare-pip.stderr.log"
|
||||
& $Python -m pip install --trusted-host mirrors.aliyun.com --index-url $PipIndex "wcferry==39.5.2.0" > $pipOut 2> $pipErr
|
||||
if ($LASTEXITCODE -ne 0) { throw "pip install wcferry failed" }
|
||||
|
||||
Copy-Item -Force "$PSScriptRoot\wcf_host.py" (Join-Path $WcfRoot "wcf_host.py")
|
||||
|
||||
if (-not (Get-NetFirewallRule -DisplayName "UniDesk Personal WeChat WCF 10086" -ErrorAction SilentlyContinue)) {
|
||||
New-NetFirewallRule `
|
||||
-DisplayName "UniDesk Personal WeChat WCF 10086" `
|
||||
-Direction Inbound `
|
||||
-Action Allow `
|
||||
-Protocol TCP `
|
||||
-LocalPort 10086,10087 `
|
||||
-RemoteAddress 172.26.0.0/16,10.42.0.0/16,127.0.0.1 `
|
||||
-Profile Any `
|
||||
-ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
|
||||
$wechatExe = Find-WeChatExe
|
||||
$installAttempted = $false
|
||||
$installExitCode = $null
|
||||
$installStdout = Join-Path $StateRoot "prepare-wechat-installer.stdout.log"
|
||||
$installStderr = Join-Path $StateRoot "prepare-wechat-installer.stderr.log"
|
||||
if (-not $wechatExe) {
|
||||
$installAttempted = $true
|
||||
$args = @("/S", "/D=$WechatRoot")
|
||||
$proc = Start-Process -FilePath $installer -ArgumentList $args -Wait -PassThru -RedirectStandardOutput $installStdout -RedirectStandardError $installStderr -WindowStyle Hidden
|
||||
$installExitCode = $proc.ExitCode
|
||||
Start-Sleep -Seconds 5
|
||||
$wechatExe = Find-WeChatExe
|
||||
}
|
||||
|
||||
$probePy = Join-Path $StateRoot "prepare-probe.py"
|
||||
@'
|
||||
import importlib.metadata
|
||||
import json
|
||||
|
||||
payload = {"ok": True}
|
||||
try:
|
||||
payload["wcferryVersion"] = importlib.metadata.version("wcferry")
|
||||
except Exception as exc:
|
||||
payload = {"ok": False, "error": f"{type(exc).__name__}: {exc}"}
|
||||
print(json.dumps(payload, ensure_ascii=False))
|
||||
'@ | Set-Content -Encoding utf8 $probePy
|
||||
$pyProbeJson = & $Python $probePy
|
||||
$pyProbe = $pyProbeJson | ConvertFrom-Json
|
||||
|
||||
$summary = [ordered]@{
|
||||
ok = [bool]($pyProbe.ok -and $wechatExe)
|
||||
startedAt = $startedAt
|
||||
finishedAt = (Get-Date).ToUniversalTime().ToString("o")
|
||||
root = $Root
|
||||
wechatInstaller = $installer
|
||||
wechatRoot = $WechatRoot
|
||||
wechatExe = $wechatExe
|
||||
installAttempted = $installAttempted
|
||||
installExitCode = $installExitCode
|
||||
installLogs = [ordered]@{
|
||||
stdout = $installStdout
|
||||
stderr = $installStderr
|
||||
}
|
||||
wcfRoot = $WcfRoot
|
||||
stateRoot = $StateRoot
|
||||
python = $Python
|
||||
wcferryVersion = $pyProbe.wcferryVersion
|
||||
next = if ($wechatExe) { "Run start.ps1 and scan the WeChat login QR." } else { "WeChat silent install did not produce WeChat.exe; run the installer UI from the interactive Windows session, then re-run prepare.ps1." }
|
||||
}
|
||||
$summary | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 $PrepareResult
|
||||
$summary | ConvertTo-Json -Depth 6
|
||||
@@ -0,0 +1,30 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$Root = "C:\UniDesk\personal-wechat"
|
||||
$WcfRoot = Join-Path $Root "wcf\v39.5.2"
|
||||
$StateRoot = Join-Path $Root "wcf-state"
|
||||
$Python = "C:\ProgramData\miniconda3\python.exe"
|
||||
$Script = Join-Path $WcfRoot "wcf_host.py"
|
||||
$PidFile = Join-Path $StateRoot "wcf-host.pid"
|
||||
$Stdout = Join-Path $StateRoot "wcf-host.stdout.log"
|
||||
$Stderr = Join-Path $StateRoot "wcf-host.stderr.log"
|
||||
|
||||
New-Item -ItemType Directory -Force $StateRoot | Out-Null
|
||||
|
||||
if (Test-Path $PidFile) {
|
||||
$oldPid = Get-Content $PidFile -ErrorAction SilentlyContinue
|
||||
if ($oldPid) {
|
||||
$existing = Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue
|
||||
if ($existing) {
|
||||
[pscustomobject]@{ ok = $true; alreadyRunning = $true; pid = $existing.Id; stdout = $Stdout; stderr = $Stderr } | ConvertTo-Json -Depth 4
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$env:WCF_COMMAND_PORT = "10086"
|
||||
$env:WCF_STATE_ROOT = $StateRoot
|
||||
$proc = Start-Process -FilePath $Python -ArgumentList @($Script) -WorkingDirectory $WcfRoot -WindowStyle Normal -PassThru -RedirectStandardOutput $Stdout -RedirectStandardError $Stderr
|
||||
$proc.Id | Set-Content -Encoding ascii $PidFile
|
||||
|
||||
[pscustomobject]@{ ok = $true; started = $true; pid = $proc.Id; stdout = $Stdout; stderr = $Stderr; status = (Join-Path $StateRoot "status.json") } | ConvertTo-Json -Depth 4
|
||||
@@ -0,0 +1,54 @@
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
$Root = "C:\UniDesk\personal-wechat"
|
||||
$StateRoot = Join-Path $Root "wcf-state"
|
||||
$PidFile = Join-Path $StateRoot "wcf-host.pid"
|
||||
$StatusFile = Join-Path $StateRoot "status.json"
|
||||
$PrepareResultFile = Join-Path $StateRoot "prepare-result.json"
|
||||
$Stdout = Join-Path $StateRoot "wcf-host.stdout.log"
|
||||
$Stderr = Join-Path $StateRoot "wcf-host.stderr.log"
|
||||
function Read-JsonFile {
|
||||
param([string]$Path)
|
||||
if (!(Test-Path $Path)) { return $null }
|
||||
try { return Get-Content $Path -Raw | ConvertFrom-Json } catch { return @{ parseError = $_.Exception.Message } }
|
||||
}
|
||||
function Tail-Text {
|
||||
param([string]$Path)
|
||||
if (!(Test-Path $Path)) { return "" }
|
||||
try {
|
||||
return (Get-Content $Path -Tail 80 -ErrorAction SilentlyContinue) -join "`n"
|
||||
} catch {
|
||||
return $_.Exception.Message
|
||||
}
|
||||
}
|
||||
$pidValue = $null
|
||||
$running = $false
|
||||
if (Test-Path $PidFile) {
|
||||
$pidValue = Get-Content $PidFile -ErrorAction SilentlyContinue
|
||||
if ($pidValue) {
|
||||
$running = [bool](Get-Process -Id ([int]$pidValue) -ErrorAction SilentlyContinue)
|
||||
}
|
||||
}
|
||||
$status = Read-JsonFile $StatusFile
|
||||
$prepare = Read-JsonFile $PrepareResultFile
|
||||
[pscustomobject]@{
|
||||
ok = $true
|
||||
pid = $pidValue
|
||||
running = $running
|
||||
prepared = if ($prepare) { [bool]$prepare.ok } else { $false }
|
||||
prepare = $prepare
|
||||
status = $status
|
||||
ports = Get-NetTCPConnection -LocalPort 10086,10087 -ErrorAction SilentlyContinue | Select-Object LocalAddress,LocalPort,State,OwningProcess
|
||||
logs = [ordered]@{
|
||||
stdout = $Stdout
|
||||
stderr = $Stderr
|
||||
stdoutTail = Tail-Text $Stdout
|
||||
stderrTail = Tail-Text $Stderr
|
||||
}
|
||||
paths = [ordered]@{
|
||||
root = $Root
|
||||
stateRoot = $StateRoot
|
||||
statusFile = $StatusFile
|
||||
prepareResultFile = $PrepareResultFile
|
||||
}
|
||||
} | ConvertTo-Json -Depth 8
|
||||
@@ -0,0 +1,76 @@
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from wcferry import Wcf
|
||||
|
||||
|
||||
PORT = int(os.environ.get("WCF_COMMAND_PORT", "10086"))
|
||||
STATE_ROOT = os.environ.get("WCF_STATE_ROOT", r"C:\UniDesk\personal-wechat\wcf-state")
|
||||
DEBUG = os.environ.get("WCF_DEBUG", "false").lower() == "true"
|
||||
|
||||
STOP = False
|
||||
|
||||
|
||||
def now_iso():
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def log(event, **fields):
|
||||
os.makedirs(STATE_ROOT, exist_ok=True)
|
||||
payload = {"ts": now_iso(), "event": event, **fields}
|
||||
line = json.dumps(payload, ensure_ascii=False, sort_keys=True)
|
||||
print(line, flush=True)
|
||||
with open(os.path.join(STATE_ROOT, "wcf-host.log"), "a", encoding="utf-8") as fp:
|
||||
fp.write(line + "\n")
|
||||
|
||||
|
||||
def handle_signal(_signum, _frame):
|
||||
global STOP
|
||||
STOP = True
|
||||
|
||||
|
||||
signal.signal(signal.SIGTERM, handle_signal)
|
||||
signal.signal(signal.SIGINT, handle_signal)
|
||||
|
||||
|
||||
def main():
|
||||
log("wcf-host-start", port=PORT, debug=DEBUG)
|
||||
wcf = Wcf(port=PORT, debug=DEBUG, block=False)
|
||||
while not STOP:
|
||||
try:
|
||||
logged_in = bool(wcf.is_login())
|
||||
qrcode = ""
|
||||
if not logged_in:
|
||||
try:
|
||||
qrcode = wcf.get_qrcode()
|
||||
except Exception:
|
||||
qrcode = ""
|
||||
state = {
|
||||
"ts": now_iso(),
|
||||
"isLogin": logged_in,
|
||||
"qrcodePresent": bool(qrcode),
|
||||
"commandPort": PORT,
|
||||
"messagePort": PORT + 1,
|
||||
}
|
||||
if qrcode:
|
||||
state["qrcode"] = qrcode
|
||||
with open(os.path.join(STATE_ROOT, "status.json"), "w", encoding="utf-8") as fp:
|
||||
json.dump(state, fp, ensure_ascii=False, indent=2, sort_keys=True)
|
||||
log("wcf-host-status", isLogin=logged_in, qrcodePresent=bool(qrcode))
|
||||
time.sleep(5)
|
||||
except Exception as exc:
|
||||
log("wcf-host-error", error=f"{type(exc).__name__}: {exc}"[:1000])
|
||||
time.sleep(3)
|
||||
try:
|
||||
wcf.cleanup()
|
||||
except Exception:
|
||||
pass
|
||||
log("wcf-host-stop")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user