fix: isolate personal wechat wcf host

This commit is contained in:
Codex
2026-06-13 16:17:25 +00:00
parent 5d484d1ac4
commit 3b6d22d817
5 changed files with 359 additions and 106 deletions
+172 -73
View File
@@ -2,6 +2,9 @@ $ErrorActionPreference = "Stop"
$Root = "C:\UniDesk\personal-wechat"
$WechatRoot = Join-Path $Root "wechat-3.9.12.51"
$ExtractRoot = Join-Path $Root "wechat-3.9.12.51-extracted"
$VersionRoot = Join-Path $ExtractRoot "[3.9.12.51]"
$DataRoot = Join-Path $Root "data\profile"
$WcfRoot = Join-Path $Root "wcf\v39.5.2"
$StateRoot = Join-Path $Root "wcf-state"
$DownloadRoot = Join-Path $Root "downloads"
@@ -10,8 +13,9 @@ $ProgressLog = Join-Path $StateRoot "prepare-progress.log"
$Python = "C:\ProgramData\miniconda3\python.exe"
$PipIndex = "http://mirrors.aliyun.com/pypi/simple/"
$ReleaseBase = "https://github.com/lich0821/WeChatFerry/releases/download/v39.5.2"
$RegistryPath = "HKCU:\Software\Tencent\WeChat"
New-Item -ItemType Directory -Force $WechatRoot,$WcfRoot,$StateRoot,$DownloadRoot | Out-Null
New-Item -ItemType Directory -Force $WechatRoot,$ExtractRoot,$VersionRoot,$DataRoot,$WcfRoot,$StateRoot,$DownloadRoot | Out-Null
$StartedAt = (Get-Date).ToUniversalTime().ToString("o")
function Write-PrepareProgress {
@@ -32,13 +36,13 @@ trap {
error = $message
valuesPrinted = $false
}
$failed | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 $PrepareResult
$failed | ConvertTo-Json -Depth 8 | Set-Content -Encoding utf8 $PrepareResult
exit 1
}
function Download-IfMissing {
param([string]$Url, [string]$Path)
if (Test-Path $Path) { return }
if (Test-Path -LiteralPath $Path) { return }
Write-PrepareProgress "download-start $Url"
$tmp = "$Path.part"
Invoke-WebRequest -Uri $Url -OutFile $tmp -UseBasicParsing
@@ -46,18 +50,155 @@ function Download-IfMissing {
Write-PrepareProgress "download-done $Path"
}
function Find-WeChatExe {
function Get-SevenZipPath {
$candidates = @(
(Join-Path $WechatRoot "WeChat.exe"),
"C:\Program Files (x86)\Tencent\WeChat\WeChat.exe",
"C:\Program Files\Tencent\WeChat\WeChat.exe"
"C:\Program Files\7-Zip\7z.exe",
"C:\Program Files (x86)\7-Zip\7z.exe",
"7z.exe"
)
foreach ($candidate in $candidates) {
if (Test-Path $candidate) { return $candidate }
$cmd = Get-Command $candidate -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
if (Test-Path -LiteralPath $candidate) { return $candidate }
}
return $null
}
function Find-DedicatedWeChatExe {
$candidates = @(
(Join-Path $VersionRoot "WeChat.exe"),
(Join-Path $ExtractRoot "WeChat.exe"),
(Join-Path $WechatRoot "WeChat.exe")
)
foreach ($candidate in $candidates) {
if (!(Test-Path -LiteralPath $candidate)) { continue }
$dir = Split-Path -Parent $candidate
if (Test-Path -LiteralPath (Join-Path $dir "WeChatWin.dll")) { return $candidate }
}
return $null
}
function Ensure-WeChatExtracted {
param([string]$Installer)
$wechatExe = Find-DedicatedWeChatExe
if ($wechatExe) { return [ordered]@{ mode = "dedicated-existing"; wechatExe = $wechatExe } }
$sevenZip = Get-SevenZipPath
if (!$sevenZip) { throw "7-Zip is required to extract the WeChat installer without administrator elevation" }
$extractLog = Join-Path $StateRoot "7z-extract.log"
Write-PrepareProgress "wechat-extract-start $Installer"
& $sevenZip x -y "-o$ExtractRoot" $Installer > $extractLog 2>&1
if ($LASTEXITCODE -ne 0) { throw "7-Zip extract failed; see $extractLog" }
$wechatExe = Find-DedicatedWeChatExe
if (!$wechatExe) { throw "Extracted installer did not produce dedicated WeChat.exe with WeChatWin.dll" }
Write-PrepareProgress "wechat-extract-done $wechatExe"
return [ordered]@{ mode = "nsis-extracted-hkcu-registry-shim"; wechatExe = $wechatExe; extractLog = $extractLog }
}
function Ensure-IsolatedProfile {
$appData = Join-Path $DataRoot "AppData\Roaming"
$localAppData = Join-Path $DataRoot "AppData\Local"
$documents = Join-Path $DataRoot "Documents"
$temp = Join-Path $localAppData "Temp"
New-Item -ItemType Directory -Force $appData,$localAppData,$documents,$temp | Out-Null
return [ordered]@{
dataRoot = $DataRoot
appData = $appData
localAppData = $localAppData
documents = $documents
temp = $temp
}
}
function Ensure-WeChatRegistryShim {
param([string]$WechatExe)
$installPath = Split-Path -Parent $WechatExe
New-Item -Path $RegistryPath -Force | Out-Null
Set-ItemProperty -Path $RegistryPath -Name InstallPath -Value ($installPath + "\")
Set-ItemProperty -Path $RegistryPath -Name Version -Value 0x03090c33 -Type DWord
return $installPath + "\"
}
function Install-Wcferry {
if (!(Test-Path -LiteralPath $Python)) { throw "Expected Python not found: $Python" }
$pipOut = Join-Path $StateRoot "prepare-pip.stdout.log"
$pipErr = Join-Path $StateRoot "prepare-pip.stderr.log"
Write-PrepareProgress "pip-install-start"
& $Python -m pip install --trusted-host mirrors.aliyun.com --index-url $PipIndex --timeout 120 --retries 8 "wcferry==39.5.2.0" > $pipOut 2> $pipErr
if ($LASTEXITCODE -ne 0) { throw "pip install wcferry failed; see $pipErr" }
Write-PrepareProgress "pip-install-done"
$probePy = Join-Path $StateRoot "prepare-probe.py"
@'
import importlib.metadata
import json
import os
import wcferry
payload = {
"ok": True,
"wcferryVersion": importlib.metadata.version("wcferry"),
"packageRoot": os.path.abspath(os.path.dirname(wcferry.__file__)),
}
print(json.dumps(payload, ensure_ascii=False))
'@ | Set-Content -Encoding utf8 $probePy
$pyProbe = (& $Python $probePy) | ConvertFrom-Json
return [ordered]@{
ok = [bool]$pyProbe.ok
version = $pyProbe.wcferryVersion
packageRoot = $pyProbe.packageRoot
stdout = $pipOut
stderr = $pipErr
}
}
function Sync-WcfDlls {
param([string]$PackageRoot)
$dlls = @()
foreach ($name in @("sdk.dll","spy.dll","spy_debug.dll")) {
$src = Join-Path $WcfRoot $name
$dst = Join-Path $PackageRoot $name
if (!(Test-Path -LiteralPath $src)) { throw "Missing WCF release asset: $src" }
if (!(Test-Path -LiteralPath $dst)) { throw "Missing wcferry package dll target: $dst" }
$backup = "$dst.unidesk-bak"
if (!(Test-Path -LiteralPath $backup)) {
Copy-Item -LiteralPath $dst -Destination $backup -Force
}
Copy-Item -LiteralPath $src -Destination $dst -Force
$srcItem = Get-Item -LiteralPath $src
$dstItem = Get-Item -LiteralPath $dst
$dlls += [ordered]@{
name = $name
sourceBytes = $srcItem.Length
targetBytes = $dstItem.Length
targetVersion = $dstItem.VersionInfo.FileVersion
backup = $backup
}
}
return $dlls
}
function Ensure-Firewall {
try {
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 Stop | Out-Null
return [ordered]@{ ok = $true; applied = $true }
}
return [ordered]@{ ok = $true; applied = $false; alreadyPresent = $true }
} catch {
Write-PrepareProgress "firewall-skip $($_.Exception.Message)"
return [ordered]@{ ok = $false; skipped = $true; reason = $_.Exception.Message }
}
}
Write-PrepareProgress "prepare-start"
$installer = Join-Path $DownloadRoot "WeChatSetup-3.9.12.51.exe"
Download-IfMissing "$ReleaseBase/WeChatSetup-3.9.12.51.exe" $installer
@@ -65,82 +206,40 @@ 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"
Write-PrepareProgress "pip-install-start"
& $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" }
Write-PrepareProgress "pip-install-done"
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")
Write-PrepareProgress "wechat-install-start $installer"
$proc = Start-Process -FilePath $installer -ArgumentList $args -Wait -PassThru -RedirectStandardOutput $installStdout -RedirectStandardError $installStderr -WindowStyle Hidden
$installExitCode = $proc.ExitCode
Write-PrepareProgress "wechat-install-exit $installExitCode"
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
$wechat = Ensure-WeChatExtracted $installer
$profile = Ensure-IsolatedProfile
$installPath = Ensure-WeChatRegistryShim $wechat.wechatExe
$wcferry = Install-Wcferry
$dlls = Sync-WcfDlls $wcferry.packageRoot
$firewall = Ensure-Firewall
$summary = [ordered]@{
ok = [bool]($pyProbe.ok -and $wechatExe)
ok = [bool]($wechat.wechatExe -and $wcferry.ok)
startedAt = $StartedAt
finishedAt = (Get-Date).ToUniversalTime().ToString("o")
root = $Root
mode = $wechat.mode
wechatInstaller = $installer
wechatRoot = $WechatRoot
wechatExe = $wechatExe
installAttempted = $installAttempted
installExitCode = $installExitCode
installLogs = [ordered]@{
stdout = $installStdout
stderr = $installStderr
}
extractRoot = $ExtractRoot
versionRoot = $VersionRoot
wechatExe = $wechat.wechatExe
installPath = $installPath
registryKey = $RegistryPath
requiredVersion = "3.9.12.51"
profile = $profile
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." }
wcferryVersion = $wcferry.version
wcferryPackageRoot = $wcferry.packageRoot
dllOverride = $dlls
firewall = $firewall
valuesPrinted = $false
next = "Run start.ps1 and scan the isolated WeChat login QR."
}
Write-PrepareProgress "prepare-done ok=$($summary.ok)"
$summary | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 $PrepareResult
$summary | ConvertTo-Json -Depth 6
$summary | ConvertTo-Json -Depth 10 | Set-Content -Encoding utf8 $PrepareResult
$summary | ConvertTo-Json -Depth 10
+78 -15
View File
@@ -3,28 +3,91 @@ $ErrorActionPreference = "Stop"
$Root = "C:\UniDesk\personal-wechat"
$WcfRoot = Join-Path $Root "wcf\v39.5.2"
$StateRoot = Join-Path $Root "wcf-state"
$DataRoot = Join-Path $Root "data\profile"
$AppData = Join-Path $DataRoot "AppData\Roaming"
$LocalAppData = Join-Path $DataRoot "AppData\Local"
$Documents = Join-Path $DataRoot "Documents"
$TempRoot = Join-Path $LocalAppData "Temp"
$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"
$Runner = Join-Path $StateRoot "wcf-host-runner.cmd"
New-Item -ItemType Directory -Force $StateRoot | Out-Null
New-Item -ItemType Directory -Force $StateRoot,$DataRoot,$AppData,$LocalAppData,$Documents,$TempRoot | 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
}
}
function Get-WcfProcessByPidFile {
if (!(Test-Path -LiteralPath $PidFile)) { return $null }
$oldPid = (Get-Content $PidFile -ErrorAction SilentlyContinue | Select-Object -First 1)
if (!$oldPid) { return $null }
return Get-Process -Id ([int]$oldPid) -ErrorAction SilentlyContinue
}
$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
$existing = Get-WcfProcessByPidFile
if ($existing) {
[pscustomobject]@{
ok = $true
alreadyRunning = $true
pid = $existing.Id
stdout = $Stdout
stderr = $Stderr
status = (Join-Path $StateRoot "status.json")
dataRoot = $DataRoot
} | ConvertTo-Json -Depth 6
exit 0
}
[pscustomobject]@{ ok = $true; started = $true; pid = $proc.Id; stdout = $Stdout; stderr = $Stderr; status = (Join-Path $StateRoot "status.json") } | ConvertTo-Json -Depth 4
# Keep this sandbox isolated from the daily Weixin/WeChat install. Only stop
# processes that were launched from the UniDesk personal-wechat tree/profile.
Get-CimInstance Win32_Process |
Where-Object {
($_.ExecutablePath -like "$Root\*") -or
($_.CommandLine -like "*$WcfRoot\wcf_host.py*") -or
($_.Name -eq "WeChatAppEx.exe" -and $_.CommandLine -like "*$DataRoot*")
} |
ForEach-Object {
try { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } catch {}
}
Start-Sleep -Seconds 2
Remove-Item -Force (Join-Path $StateRoot "status.json") -ErrorAction SilentlyContinue
Remove-Item -Force (Join-Path $WcfRoot "injector.log") -ErrorAction SilentlyContinue
@"
@echo off
set WCF_COMMAND_PORT=10086
set WCF_STATE_ROOT=$StateRoot
set USERPROFILE=$DataRoot
set APPDATA=$AppData
set LOCALAPPDATA=$LocalAppData
set HOMEDRIVE=C:
set HOMEPATH=\UniDesk\personal-wechat\data\profile
set TEMP=$TempRoot
set TMP=$TempRoot
mkdir "$TempRoot" 2>nul
cd /d "$WcfRoot"
"$Python" "$Script" > "$Stdout" 2> "$Stderr"
"@ | Set-Content -Encoding ASCII $Runner
$cmd = '/c start "" /min "' + $Runner + '"'
$launcher = Start-Process -FilePath "cmd.exe" -ArgumentList $cmd -WindowStyle Hidden -PassThru
Start-Sleep -Seconds 3
$pidValue = $null
if (Test-Path -LiteralPath $PidFile) {
$pidValue = (Get-Content $PidFile -ErrorAction SilentlyContinue | Select-Object -First 1)
}
[pscustomobject]@{
ok = $true
started = $true
launcherPid = $launcher.Id
pid = $pidValue
runner = $Runner
stdout = $Stdout
stderr = $Stderr
status = (Join-Path $StateRoot "status.json")
dataRoot = $DataRoot
appData = $AppData
localAppData = $LocalAppData
} | ConvertTo-Json -Depth 6
+29 -11
View File
@@ -1,7 +1,9 @@
$ErrorActionPreference = "Continue"
$Root = "C:\UniDesk\personal-wechat"
$WcfRoot = Join-Path $Root "wcf\v39.5.2"
$StateRoot = Join-Path $Root "wcf-state"
$DataRoot = Join-Path $Root "data\profile"
$PidFile = Join-Path $StateRoot "wcf-host.pid"
$StatusFile = Join-Path $StateRoot "status.json"
$PrepareResultFile = Join-Path $StateRoot "prepare-result.json"
@@ -10,34 +12,45 @@ $PrepareStderr = Join-Path $StateRoot "prepare.stderr.log"
$PrepareProgress = Join-Path $StateRoot "prepare-progress.log"
$Stdout = Join-Path $StateRoot "wcf-host.stdout.log"
$Stderr = Join-Path $StateRoot "wcf-host.stderr.log"
$HostLog = Join-Path $StateRoot "wcf-host.log"
$InjectorLog = Join-Path $WcfRoot "injector.log"
function Read-JsonFile {
param([string]$Path)
if (!(Test-Path $Path)) { return $null }
if (!(Test-Path -LiteralPath $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 "" }
if (!(Test-Path -LiteralPath $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 (Test-Path -LiteralPath $PidFile) {
$pidValue = (Get-Content $PidFile -ErrorAction SilentlyContinue | Select-Object -First 1)
if ($pidValue) {
$running = [bool](Get-Process -Id ([int]$pidValue) -ErrorAction SilentlyContinue)
}
}
$status = Read-JsonFile $StatusFile
$prepare = Read-JsonFile $PrepareResultFile
$prepareProc = Get-Process -Name powershell -ErrorAction SilentlyContinue |
Where-Object { $_.Path -like "*powershell*" -or $_.ProcessName -like "*powershell*" } |
Sort-Object StartTime -Descending |
Select-Object -First 5 Id,ProcessName,StartTime
$ports = Get-NetTCPConnection -LocalPort 10086,10087 -ErrorAction SilentlyContinue | Select-Object LocalAddress,LocalPort,State,OwningProcess
$personalProcesses = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
Where-Object {
($_.ExecutablePath -like "$Root\*") -or
($_.CommandLine -like "*$WcfRoot\wcf_host.py*") -or
($_.Name -eq "WeChatAppEx.exe" -and $_.CommandLine -like "*$DataRoot*")
} |
Select-Object ProcessId,ParentProcessId,Name,ExecutablePath,CommandLine
[pscustomobject]@{
ok = $true
pid = $pidValue
@@ -45,7 +58,6 @@ $prepareProc = Get-Process -Name powershell -ErrorAction SilentlyContinue |
prepared = if ($prepare) { [bool]$prepare.ok } else { $false }
prepare = $prepare
prepareRuntime = [ordered]@{
candidateProcesses = $prepareProc
stdout = $PrepareStdout
stderr = $PrepareStderr
progress = $PrepareProgress
@@ -54,17 +66,23 @@ $prepareProc = Get-Process -Name powershell -ErrorAction SilentlyContinue |
progressTail = Tail-Text $PrepareProgress
}
status = $status
ports = Get-NetTCPConnection -LocalPort 10086,10087 -ErrorAction SilentlyContinue | Select-Object LocalAddress,LocalPort,State,OwningProcess
ports = $ports
personalProcesses = $personalProcesses
logs = [ordered]@{
stdout = $Stdout
stderr = $Stderr
host = $HostLog
injector = $InjectorLog
stdoutTail = Tail-Text $Stdout
stderrTail = Tail-Text $Stderr
hostTail = Tail-Text $HostLog
injectorTail = Tail-Text $InjectorLog
}
paths = [ordered]@{
root = $Root
dataRoot = $DataRoot
stateRoot = $StateRoot
statusFile = $StatusFile
prepareResultFile = $PrepareResultFile
}
} | ConvertTo-Json -Depth 8
} | ConvertTo-Json -Depth 10
+79 -6
View File
@@ -5,6 +5,7 @@ import sys
import time
from datetime import datetime, timezone
import wcferry.client as wcf_client
from wcferry import Wcf
@@ -15,10 +16,23 @@ DEBUG = os.environ.get("WCF_DEBUG", "false").lower() == "true"
STOP = False
class WcfProcessExit(Exception):
def __init__(self, code):
super().__init__(f"wcferry requested process exit: {code}")
self.code = code
def now_iso():
return datetime.now(timezone.utc).isoformat()
def write_json(path, payload):
tmp = f"{path}.tmp"
with open(tmp, "w", encoding="utf-8") as fp:
json.dump(payload, fp, ensure_ascii=False, indent=2, sort_keys=True)
os.replace(tmp, path)
def log(event, **fields):
os.makedirs(STATE_ROOT, exist_ok=True)
payload = {"ts": now_iso(), "event": event, **fields}
@@ -26,6 +40,20 @@ def log(event, **fields):
print(line, flush=True)
with open(os.path.join(STATE_ROOT, "wcf-host.log"), "a", encoding="utf-8") as fp:
fp.write(line + "\n")
fp.flush()
def write_state(**fields):
os.makedirs(STATE_ROOT, exist_ok=True)
payload = {
"ts": now_iso(),
"isLogin": None,
"qrcodePresent": False,
"commandPort": PORT,
"messagePort": PORT + 1,
**fields,
}
write_json(os.path.join(STATE_ROOT, "status.json"), payload)
def handle_signal(_signum, _frame):
@@ -33,13 +61,58 @@ def handle_signal(_signum, _frame):
STOP = True
def patch_wcferry_exit():
def raise_exit(code=0):
raise WcfProcessExit(code)
wcf_client.os._exit = raise_exit
signal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)
def connect_wcf():
patch_wcferry_exit()
attempt = 0
while not STOP:
attempt += 1
local_init = attempt == 1 or attempt % 15 == 0
mode = "local-init" if local_init else "remote-dial"
try:
write_state(phase="connecting", connectAttempt=attempt, connectMode=mode)
log("wcf-host-connect-attempt", attempt=attempt, mode=mode, port=PORT)
if local_init:
wcf = Wcf(port=PORT, debug=DEBUG, block=False)
else:
wcf = Wcf(host="127.0.0.1", port=PORT, debug=DEBUG, block=False)
log("wcf-host-connected", attempt=attempt, mode=mode, port=PORT)
return wcf
except WcfProcessExit as exc:
log("wcf-host-connect-retry", attempt=attempt, mode=mode, requestedExit=exc.code)
except Exception as exc:
log("wcf-host-connect-error", attempt=attempt, mode=mode, error=f"{type(exc).__name__}: {exc}"[:1000])
time.sleep(min(2 + attempt, 10))
return None
def main():
log("wcf-host-start", port=PORT, debug=DEBUG)
wcf = Wcf(port=PORT, debug=DEBUG, block=False)
os.makedirs(STATE_ROOT, exist_ok=True)
with open(os.path.join(STATE_ROOT, "wcf-host.pid"), "w", encoding="ascii") as fp:
fp.write(str(os.getpid()))
log(
"wcf-host-start",
port=PORT,
debug=DEBUG,
pid=os.getpid(),
appData=os.environ.get("APPDATA", ""),
userProfile=os.environ.get("USERPROFILE", ""),
)
wcf = connect_wcf()
if wcf is None:
log("wcf-host-stop-before-connect")
return
while not STOP:
try:
logged_in = bool(wcf.is_login())
@@ -47,10 +120,11 @@ def main():
if not logged_in:
try:
qrcode = wcf.get_qrcode()
except Exception:
except Exception as exc:
log("wcf-host-qrcode-error", error=f"{type(exc).__name__}: {exc}"[:1000])
qrcode = ""
state = {
"ts": now_iso(),
"phase": "ready",
"isLogin": logged_in,
"qrcodePresent": bool(qrcode),
"commandPort": PORT,
@@ -58,8 +132,7 @@ def main():
}
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)
write_state(**state)
log("wcf-host-status", isLogin=logged_in, qrcodePresent=bool(qrcode))
time.sleep(5)
except Exception as exc: