mirror of
https://github.com/jackwener/wx-cli.git
synced 2026-10-08 21:55:46 +00:00
feat: Rust 完整重写 wx-cli(单一二进制,支持 macOS/Linux/Windows)
实现所有核心模块: - src/crypto/: SQLCipher 4 页解密 + WAL 应用(AES-256-CBC) - src/scanner/: 三平台内存扫描(macOS Mach VM / Linux /proc/mem / Windows ReadProcessMemory) - src/daemon/: tokio 异步 daemon,Unix socket IPC,mtime-aware DB 缓存,WAL 监听推送 - src/cli/: clap CLI,自动启动 daemon,完整命令实现 - src/config.rs: 跨平台配置加载,兼容 Python 版 config.json 格式 - src/ipc.rs: 换行符分隔 JSON 协议,与 Python 版兼容 - .github/workflows/release.yml: 四平台自动构建发布 cargo build --release 验证通过,生成 4.8MB macOS arm64 单一二进制
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::config;
|
||||
use crate::scanner;
|
||||
|
||||
pub fn cmd_init(force: bool) -> Result<()> {
|
||||
// 查找 config.json
|
||||
let config_path = find_or_create_config_path();
|
||||
|
||||
// 检查是否已初始化
|
||||
if !force && config_path.exists() {
|
||||
if let Ok(content) = std::fs::read_to_string(&config_path) {
|
||||
if let Ok(cfg) = serde_json::from_str::<serde_json::Value>(&content) {
|
||||
let db_dir = cfg.get("db_dir").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let keys_file = cfg.get("keys_file").and_then(|v| v.as_str()).unwrap_or("all_keys.json");
|
||||
let keys_path = if std::path::Path::new(keys_file).is_absolute() {
|
||||
std::path::PathBuf::from(keys_file)
|
||||
} else {
|
||||
config_path.parent().unwrap_or(std::path::Path::new("."))
|
||||
.join(keys_file)
|
||||
};
|
||||
if !db_dir.is_empty() && !db_dir.contains("your_wxid")
|
||||
&& std::path::Path::new(db_dir).exists()
|
||||
&& keys_path.exists()
|
||||
{
|
||||
println!("已初始化,数据目录: {}", db_dir);
|
||||
println!("如需重新扫描密钥,使用 --force");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1: 检测 db_dir
|
||||
println!("检测微信数据目录...");
|
||||
let db_dir = config::auto_detect_db_dir()
|
||||
.context("未能自动检测到微信数据目录\n请手动编辑 config.json 中的 db_dir 字段")?;
|
||||
println!("找到数据目录: {}", db_dir.display());
|
||||
|
||||
// Step 2: 扫描密钥(需要 root/sudo)
|
||||
println!("扫描加密密钥(需要 root 权限)...");
|
||||
let entries = scanner::scan_keys(&db_dir)?;
|
||||
|
||||
// Step 3: 保存 all_keys.json
|
||||
let keys_file_path = config_path.parent()
|
||||
.unwrap_or(std::path::Path::new("."))
|
||||
.join("all_keys.json");
|
||||
|
||||
let mut keys_json = serde_json::Map::new();
|
||||
for entry in &entries {
|
||||
keys_json.insert(entry.db_name.clone(), json!({
|
||||
"enc_key": entry.enc_key,
|
||||
}));
|
||||
}
|
||||
std::fs::write(&keys_file_path, serde_json::to_string_pretty(&keys_json)?)
|
||||
.context("写入 all_keys.json 失败")?;
|
||||
println!("成功提取 {} 个数据库密钥", entries.len());
|
||||
println!("密钥已保存: {}", keys_file_path.display());
|
||||
|
||||
// Step 4: 保存 config.json
|
||||
let mut cfg = HashMap::new();
|
||||
// 读取已有配置
|
||||
if config_path.exists() {
|
||||
if let Ok(c) = std::fs::read_to_string(&config_path) {
|
||||
if let Ok(v) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&c) {
|
||||
for (k, val) in v {
|
||||
cfg.insert(k, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cfg.insert("db_dir".into(), json!(db_dir.to_string_lossy()));
|
||||
cfg.entry("keys_file".into()).or_insert_with(|| json!("all_keys.json"));
|
||||
cfg.entry("decrypted_dir".into()).or_insert_with(|| json!("decrypted"));
|
||||
|
||||
std::fs::write(&config_path, serde_json::to_string_pretty(&cfg)?)
|
||||
.context("写入 config.json 失败")?;
|
||||
println!("配置已保存: {}", config_path.display());
|
||||
println!("初始化完成,可以使用 wx sessions / wx history 等命令了");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_or_create_config_path() -> std::path::PathBuf {
|
||||
// 优先使用可执行文件同目录
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(dir) = exe.parent() {
|
||||
return dir.join("config.json");
|
||||
}
|
||||
}
|
||||
std::env::current_dir()
|
||||
.unwrap_or_default()
|
||||
.join("config.json")
|
||||
}
|
||||
Reference in New Issue
Block a user