diff --git a/Cargo.toml b/Cargo.toml index b0b7986..d9e97c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ meval = "0.2" rusqlite = { version = "0.32", features = ["bundled"] } rustls = { version = "0.23", features = ["ring"] } wechatbot = { path = "vendor/wechatbot" } +encoding_rs = "0.8" diff --git a/src/tools/bash.rs b/src/tools/bash.rs index 7c5ed5f..8810f87 100644 --- a/src/tools/bash.rs +++ b/src/tools/bash.rs @@ -5,7 +5,7 @@ use std::time::Duration; use async_trait::async_trait; use serde_json::json; -use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, BufReader}; +use tokio::io::{AsyncRead, AsyncReadExt, BufReader}; use tokio::process::Command; use tokio::sync::{Mutex, mpsc}; use tokio::time::{Instant, sleep_until}; @@ -392,25 +392,51 @@ where R: AsyncRead + Unpin + Send + 'static, { let mut reader = BufReader::new(stream); - let mut line = String::new(); + let mut buffer = Vec::new(); loop { - line.clear(); - match reader.read_line(&mut line).await { + let mut chunk = [0u8; 4096]; + match reader.read(&mut chunk).await { Ok(0) => break, - Ok(_) => { - let _ = tx.send((is_stderr, line.clone())); + Ok(n) => { + buffer.extend_from_slice(&chunk[..n]); + + // 处理完整的行 + while let Some(pos) = buffer.iter().position(|&b| b == b'\n') { + let line_bytes = &buffer[..pos + 1]; + let line = decode_bytes(line_bytes); + let _ = tx.send((is_stderr, line)); + buffer.drain(..pos + 1); + } } Err(_) => break, } } - let mut remainder = String::new(); - if reader.read_to_string(&mut remainder).await.is_ok() && !remainder.is_empty() { + // 处理剩余的字节 + if !buffer.is_empty() { + let remainder = decode_bytes(&buffer); let _ = tx.send((is_stderr, remainder)); } } +/// 尝试 UTF-8 解码,失败则尝试 GBK 解码 +fn decode_bytes(bytes: &[u8]) -> String { + // 首先尝试 UTF-8 + if let Ok(s) = std::str::from_utf8(bytes) { + return s.to_string(); + } + + // 尝试 GBK 解码 + let (cow, _, had_errors) = encoding_rs::GBK.decode(bytes); + if !had_errors { + return cow.to_string(); + } + + // 如果 GBK 也失败,使用 lossy 转换 + String::from_utf8_lossy(bytes).to_string() +} + fn format_command_output(stdout: &str, stderr: &str, exit_code: Option) -> String { let mut output = String::new();