























@@ -1049,9 +1049,35 @@ function sshArgs(inspect: CrabboxInspect) {
10491049};
10501050}
105110511052+function isTransientSshFailure(error: unknown) {
1053+const message = error instanceof Error ? error.message : String(error);
1054+return /Connection (?:closed|reset)|Operation timed out|Connection timed out/u.test(message);
1055+}
1056+1057+async function runRemoteCommand(params: {
1058+ args: string[];
1059+ command: string;
1060+ cwd: string;
1061+stdio?: "inherit" | "pipe";
1062+}) {
1063+ let lastError: unknown;
1064+for (let attempt = 1; attempt <= 4; attempt += 1) {
1065+try {
1066+return await runCommand(params);
1067+} catch (error) {
1068+lastError = error;
1069+if (attempt === 4 || !isTransientSshFailure(error)) {
1070+throw error;
1071+}
1072+await new Promise((resolve) => setTimeout(resolve, attempt * 3000));
1073+}
1074+}
1075+throw lastError;
1076+}
1077+10521078async function scpToRemote(root: string, inspect: CrabboxInspect, local: string, remote: string) {
10531079const ssh = sshArgs(inspect);
1054-await runCommand({
1080+await runRemoteCommand({
10551081command: "scp",
10561082args: [...ssh.scpBase, local, `${ssh.target}:${remote}`],
10571083cwd: root,
@@ -1061,7 +1087,7 @@ async function scpToRemote(root: string, inspect: CrabboxInspect, local: string,
1061108710621088async function scpFromRemote(root: string, inspect: CrabboxInspect, remote: string, local: string) {
10631089const ssh = sshArgs(inspect);
1064-await runCommand({
1090+await runRemoteCommand({
10651091command: "scp",
10661092args: [...ssh.scpBase, `${ssh.target}:${remote}`, local],
10671093cwd: root,
@@ -1071,7 +1097,7 @@ async function scpFromRemote(root: string, inspect: CrabboxInspect, remote: stri
1071109710721098async function sshRun(root: string, inspect: CrabboxInspect, remoteCommand: string) {
10731099const ssh = sshArgs(inspect);
1074-return await runCommand({
1100+return await runRemoteCommand({
10751101command: "ssh",
10761102args: [...ssh.base, ssh.target, remoteCommand],
10771103cwd: root,
@@ -1090,7 +1116,7 @@ tdlib_url=${tdlibUrl}
10901116mkdir -p "$root"
10911117tar -xzf "$root/state.tgz" -C "$root"
10921118sudo apt-get update -y
1093-sudo DEBIAN_FRONTEND=noninteractive apt-get install -y curl git cmake g++ make zlib1g-dev libssl-dev python3 ffmpeg scrot xz-utils tar wmctrl xdotool x11-utils libopengl0 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 libxcb-xinerama0 libxkbcommon-x11-0 >/tmp/openclaw-telegram-apt.log
1119+sudo DEBIAN_FRONTEND=noninteractive apt-get install -y curl git cmake g++ make zlib1g-dev libssl-dev python3 ffmpeg scrot xz-utils tar wmctrl xdotool x11-utils zbar-tools libopengl0 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 libxcb-xinerama0 libxkbcommon-x11-0 >/tmp/openclaw-telegram-apt.log
10941120if ! command -v python3 >/dev/null 2>&1; then
10951121 echo "python3 is required" >&2
10961122 exit 127
@@ -1122,6 +1148,7 @@ if ! ldconfig -p | grep -q libtdjson.so; then
11221148 sudo ldconfig
11231149fi
11241150TELEGRAM_USER_DRIVER_STATE_DIR="$root/user-driver" python3 "$root/user-driver.py" status --json --timeout-ms 60000 >"$root/status.json"
1151+TELEGRAM_USER_DRIVER_STATE_DIR="$root/user-driver" python3 "$root/user-driver.py" terminate-desktop-sessions --json --timeout-ms 60000 --output "$root/desktop-sessions-cleanup.json"
11251152`;
11261153}
11271154@@ -1131,6 +1158,7 @@ set -euo pipefail
11311158root=${REMOTE_ROOT}
11321159export DISPLAY="\${DISPLAY:-:99}"
11331160pkill -f "$root/Telegram/Telegram" >/dev/null 2>&1 || true
1161+rm -rf "$root/desktop/tdata"
11341162nohup "$root/Telegram/Telegram" -workdir "$root/desktop" >"$root/telegram-desktop.log" 2>&1 &
11351163pid=$!
11361164sleep 8
@@ -1145,6 +1173,60 @@ fi
11451173`;
11461174}
114711751176+function renderAuthorizeDesktop() {
1177+return `#!/usr/bin/env bash
1178+set -euo pipefail
1179+root=${REMOTE_ROOT}
1180+export DISPLAY="\${DISPLAY:-:99}"
1181+win="$(wmctrl -l | awk 'tolower($0) ~ /telegram/ {print $1; exit}')"
1182+test -n "$win"
1183+xdotool windowactivate "$win"
1184+sleep 5
1185+click_window_ratio() {
1186+ eval "$(xdotool getwindowgeometry --shell "$win")"
1187+ xdotool windowactivate "$win"
1188+ sleep 0.2
1189+ xdotool mousemove "$((X + WIDTH / 2))" "$((Y + HEIGHT * $1 / 100))"
1190+ sleep 0.2
1191+ xdotool click 1
1192+ sleep 1
1193+}
1194+read_qr_link() {
1195+ scrot "$root/telegram-login-qr.png"
1196+ { zbarimg --raw "$root/telegram-login-qr.png" 2>/dev/null || true; } | awk 'index($0, "tg://login?token=") == 1 {print; exit}'
1197+}
1198+wait_for_qr_link() {
1199+ for _ in $(seq 1 25); do
1200+ link="$(read_qr_link)"
1201+ if [ -n "$link" ]; then
1202+ printf '%s\\n' "$link"
1203+ return 0
1204+ fi
1205+ sleep 1
1206+ done
1207+ return 1
1208+}
1209+click_window_ratio 69
1210+sleep 3
1211+click_window_ratio 80
1212+link="$(wait_for_qr_link)" || {
1213+ echo "Telegram Desktop QR login code was not found." >&2
1214+ exit 1
1215+}
1216+export TELEGRAM_USER_DRIVER_STATE_DIR="$root/user-driver"
1217+python3 "$root/user-driver.py" confirm-qr --link "$link" --json --output "$root/desktop-session.json"
1218+python3 - "$root/desktop-session.json" <<'PY'
1219+import json
1220+import sys
1221+payload = json.loads(open(sys.argv[1]).read())
1222+session = payload.get("session") or {}
1223+if session.get("isPasswordPending"):
1224+ raise SystemExit("Telegram Desktop QR login requires a 2FA password.")
1225+PY
1226+sleep 6
1227+`;
1228+}
1229+11481230function renderSelectDesktopChat(params: { chatTitle: string }) {
11491231return `#!/usr/bin/env bash
11501232set -euo pipefail
@@ -1414,12 +1496,14 @@ async function writeRemoteSessionScripts(params: {
14141496}) {
14151497const setupScript = path.join(params.localRoot, "remote-setup.sh");
14161498const launchScript = path.join(params.localRoot, "launch-desktop.sh");
1499+const authorizeScript = path.join(params.localRoot, "authorize-desktop.sh");
14171500const selectChatScript = path.join(params.localRoot, "select-desktop-chat.sh");
14181501await writeExecutable(
14191502setupScript,
14201503renderRemoteSetup({ tdlibSha256: params.opts.tdlibSha256, tdlibUrl: params.opts.tdlibUrl }),
14211504);
14221505await writeExecutable(launchScript, renderLaunchDesktop());
1506+await writeExecutable(authorizeScript, renderAuthorizeDesktop());
14231507await writeExecutable(
14241508selectChatScript,
14251509renderSelectDesktopChat({ chatTitle: params.opts.desktopChatTitle }),
@@ -1429,6 +1513,12 @@ async function writeRemoteSessionScripts(params: {
14291513await scpToRemote(params.root, params.inspect, params.stateArchive, `${REMOTE_ROOT}/state.tgz`);
14301514await scpToRemote(params.root, params.inspect, setupScript, `${REMOTE_ROOT}/remote-setup.sh`);
14311515await scpToRemote(params.root, params.inspect, launchScript, `${REMOTE_ROOT}/launch-desktop.sh`);
1516+await scpToRemote(
1517+params.root,
1518+params.inspect,
1519+authorizeScript,
1520+`${REMOTE_ROOT}/authorize-desktop.sh`,
1521+);
14321522await scpToRemote(
14331523params.root,
14341524params.inspect,
@@ -1437,6 +1527,7 @@ async function writeRemoteSessionScripts(params: {
14371527);
14381528await sshRun(params.root, params.inspect, `bash ${REMOTE_ROOT}/remote-setup.sh`);
14391529await sshRun(params.root, params.inspect, `bash ${REMOTE_ROOT}/launch-desktop.sh`);
1530+await sshRun(params.root, params.inspect, `bash ${REMOTE_ROOT}/authorize-desktop.sh`);
14401531await sshRun(params.root, params.inspect, `bash ${REMOTE_ROOT}/select-desktop-chat.sh`);
14411532await sshRun(
14421533params.root,
@@ -1486,6 +1577,30 @@ fi`,
14861577);
14871578}
148815791580+async function terminateRemoteDesktopSession(root: string, inspect: CrabboxInspect) {
1581+await sshRun(
1582+root,
1583+inspect,
1584+`set -euo pipefail
1585+root=${REMOTE_ROOT}
1586+if [ ! -s "$root/desktop-session.json" ]; then
1587+ exit 0
1588+fi
1589+session_id="$(python3 - "$root/desktop-session.json" <<'PY'
1590+import json
1591+import sys
1592+payload = json.loads(open(sys.argv[1]).read())
1593+print((payload.get("session") or {}).get("id") or "")
1594+PY
1595+)"
1596+if [ -z "$session_id" ]; then
1597+ exit 0
1598+fi
1599+export TELEGRAM_USER_DRIVER_STATE_DIR="$root/user-driver"
1600+python3 "$root/user-driver.py" terminate-session --session-id "$session_id" --json --output "$root/desktop-session-terminated.json"`,
1601+);
1602+}
1603+14891604async function startSession(root: string, opts: Options, outputDir: string) {
14901605const localRoot = path.join(outputDir, ".session");
14911606fs.rmSync(localRoot, { force: true, recursive: true });
@@ -1756,6 +1871,16 @@ async function finishSession(root: string, opts: Options, outputDir: string) {
17561871 const statusPath = path.join(session.outputDir, "status.json");
17571872 const ffmpegLogPath = path.join(session.outputDir, "ffmpeg.log");
17581873 const crop = previewCrop(opts);
1874+ let desktopSessionTerminationAttempted = false;
1875+ const terminateDesktopSession = async () => {
1876+ if (opts.keepBox || desktopSessionTerminationAttempted) {
1877+ return;
1878+ }
1879+ desktopSessionTerminationAttempted = true;
1880+ await terminateRemoteDesktopSession(root, session.crabbox.inspect).catch((error: unknown) => {
1881+ summary.desktopSessionTerminateError = error instanceof Error ? error.message : String(error);
1882+ });
1883+ };
17591884 try {
17601885 await stopRemoteRecording(root, session.crabbox.inspect, session);
17611886 await scpFromRemote(root, session.crabbox.inspect, session.recorder.remoteVideo, videoPath);
@@ -1774,6 +1899,23 @@ async function finishSession(root: string, opts: Options, outputDir: string) {
17741899 await scpFromRemote(root, session.crabbox.inspect, session.recorder.log, ffmpegLogPath).catch(
17751900 () => {},
17761901 );
1902+ await runCommand({
1903+ command: opts.crabboxBin,
1904+ args: [
1905+ "screenshot",
1906+ "--provider",
1907+ session.crabbox.provider,
1908+ "--target",
1909+ session.crabbox.target,
1910+ "--id",
1911+ session.crabbox.id,
1912+ "--output",
1913+ screenshotPath,
1914+ ],
1915+ cwd: root,
1916+ stdio: "inherit",
1917+ });
1918+ await terminateDesktopSession();
17771919 summary.mediaPreview = await createMotionPreview({
17781920 motionGifPath,
17791921 motionVideoPath,
@@ -1791,22 +1933,6 @@ async function finishSession(root: string, opts: Options, outputDir: string) {
17911933 videoPath: motionVideoPath,
17921934 });
17931935 }
1794- await runCommand({
1795- command: opts.crabboxBin,
1796- args: [
1797- "screenshot",
1798- "--provider",
1799- session.crabbox.provider,
1800- "--target",
1801- session.crabbox.target,
1802- "--id",
1803- session.crabbox.id,
1804- "--output",
1805- screenshotPath,
1806- ],
1807- cwd: root,
1808- stdio: "inherit",
1809- });
18101936 summary.artifacts = {
18111937 desktopLog: path.relative(root, desktopLogPath),
18121938 ffmpegLog: path.relative(root, ffmpegLogPath),
@@ -1826,6 +1952,7 @@ async function finishSession(root: string, opts: Options, outputDir: string) {
18261952 } finally {
18271953 killPidTree(session.localSut.gatewayPid);
18281954 killPidTree(session.localSut.mockPid);
1955+ await terminateDesktopSession();
18291956 await releaseCredential(root, opts, session.credential.leaseFile).catch((error: unknown) => {
18301957 summary.credentialReleaseError = error instanceof Error ? error.message : String(error);
18311958 });
@@ -2038,13 +2165,15 @@ async function main() {
2038216520392166const setupScript = path.join(localRoot, "remote-setup.sh");
20402167const launchScript = path.join(localRoot, "launch-desktop.sh");
2168+const authorizeScript = path.join(localRoot, "authorize-desktop.sh");
20412169const selectChatScript = path.join(localRoot, "select-desktop-chat.sh");
20422170const probeScript = path.join(localRoot, "remote-probe.sh");
20432171await writeExecutable(
20442172setupScript,
20452173renderRemoteSetup({ tdlibSha256: opts.tdlibSha256, tdlibUrl: opts.tdlibUrl }),
20462174);
20472175await writeExecutable(launchScript, renderLaunchDesktop());
2176+await writeExecutable(authorizeScript, renderAuthorizeDesktop());
20482177await writeExecutable(
20492178selectChatScript,
20502179renderSelectDesktopChat({ chatTitle: opts.desktopChatTitle }),
@@ -2063,6 +2192,7 @@ async function main() {
20632192await scpToRemote(root, inspect, stateArchive, `${REMOTE_ROOT}/state.tgz`);
20642193await scpToRemote(root, inspect, setupScript, `${REMOTE_ROOT}/remote-setup.sh`);
20652194await scpToRemote(root, inspect, launchScript, `${REMOTE_ROOT}/launch-desktop.sh`);
2195+await scpToRemote(root, inspect, authorizeScript, `${REMOTE_ROOT}/authorize-desktop.sh`);
20662196await scpToRemote(root, inspect, selectChatScript, `${REMOTE_ROOT}/select-desktop-chat.sh`);
20672197await scpToRemote(root, inspect, probeScript, `${REMOTE_ROOT}/remote-probe.sh`);
20682198await sshRun(root, inspect, `bash ${REMOTE_ROOT}/remote-setup.sh`);
@@ -2086,6 +2216,7 @@ async function main() {
20862216};
2087221720882218await sshRun(root, inspect, `bash ${REMOTE_ROOT}/launch-desktop.sh`);
2219+await sshRun(root, inspect, `bash ${REMOTE_ROOT}/authorize-desktop.sh`);
20892220await sshRun(root, inspect, `bash ${REMOTE_ROOT}/select-desktop-chat.sh`);
20902221const videoPath = path.join(outputDir, "telegram-user-crabbox-proof.mp4");
20912222const recording = spawn(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。