mirror of https://github.com/jackwener/wx-cli.git
feat: add macos send command
parent
6659f48984
commit
def157272e
|
|
@ -8,7 +8,7 @@
|
|||
[](#安装)
|
||||
[](https://www.rust-lang.org)
|
||||
|
||||
会话 · 聊天记录 · 搜索 · 联系人 · 群成员 · 收藏 · 统计 · 导出
|
||||
会话 · 聊天记录 · 搜索 · 发送 · 联系人 · 群成员 · 收藏 · 统计 · 导出
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -38,6 +38,7 @@ npx skills add jackwener/wx-cli -g
|
|||
- **毫秒级响应** — 后台 daemon 持久缓存解密数据库,mtime 不变则复用
|
||||
- **AI 友好** — 默认 YAML 输出,更省 token & 易读;`--json` 可切换为 JSON(方便 `jq` 处理等)
|
||||
- **完全本地** — 数据不出本机,实时解密,无需全量预解密
|
||||
- **macOS 发送** — 通过微信搜索快捷键打开聊天并发送文本消息(需辅助功能权限)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -150,10 +151,13 @@ wx history "AI群" --since 2026-04-01 --until 2026-04-15
|
|||
wx search "关键词" # 全库搜索
|
||||
wx search "关键词" -n 500 # 放宽搜索结果条数
|
||||
wx search "会议" --in "工作群" --since 2026-01-01
|
||||
wx send "张三" "你好,今晚 8 点见" # 发送消息(macOS)
|
||||
```
|
||||
|
||||
`history` / `search` / `export` 都支持 `-n` / `--limit` 指定条数。默认值只是为了避免一次性输出过多消息,不是硬上限。
|
||||
|
||||
`send` 是 macOS 屏幕自动化命令:它会激活微信,用 `⌘F` 聚焦搜索框,打开目标聊天后发送文本。使用前需保持微信已登录,并给当前终端或 agent 应用开启"辅助功能"权限;发送过程中会临时使用剪贴板。
|
||||
|
||||
会话/消息输出里都带 `chat_type` 字段,取值为 `private` / `group` / `official_account` / `folded`。`official_account` 涵盖公众号、订阅号、服务号及 `mphelper` / `qqsafe` 等系统通知;`folded` 对应微信里的"订阅号折叠"和"折叠群聊"两个聚合入口。
|
||||
|
||||
### 朋友圈(SNS)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ mod init;
|
|||
pub mod sessions;
|
||||
pub mod history;
|
||||
pub mod search;
|
||||
pub mod send;
|
||||
pub mod contacts;
|
||||
pub mod export;
|
||||
pub mod daemon_cmd;
|
||||
|
|
@ -92,6 +93,14 @@ enum Commands {
|
|||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// 发送微信消息(macOS 屏幕自动化)
|
||||
Send {
|
||||
/// 聊天对象名称
|
||||
chat: String,
|
||||
/// 要发送的消息
|
||||
#[arg(allow_hyphen_values = true)]
|
||||
message: String,
|
||||
},
|
||||
/// 查看联系人
|
||||
Contacts {
|
||||
/// 按名字过滤
|
||||
|
|
@ -282,6 +291,7 @@ fn dispatch(cli: Cli) -> Result<()> {
|
|||
Commands::Search { keyword, chats, limit, since, until, msg_type, json } => {
|
||||
search::cmd_search(keyword, chats, limit, since, until, msg_type, json)
|
||||
}
|
||||
Commands::Send { chat, message } => send::cmd_send(chat, message),
|
||||
Commands::Contacts { query, limit, json } => contacts::cmd_contacts(query, limit, json),
|
||||
Commands::Export { chat, since, until, limit, format, output } => {
|
||||
export::cmd_export(chat, since, until, limit, format, output)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
use anyhow::Result;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use anyhow::{bail, Context};
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::process::Command;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
const SEND_SCRIPT: &str = r#"
|
||||
on run argv
|
||||
if (count of argv) < 2 then error "chat and message are required"
|
||||
set chatName to item 1 of argv
|
||||
set messageText to item 2 of argv
|
||||
set previousClipboard to missing value
|
||||
try
|
||||
set previousClipboard to the clipboard as text
|
||||
end try
|
||||
try
|
||||
tell application id "com.tencent.xinWeChat" to activate
|
||||
delay 0.3
|
||||
tell application "System Events"
|
||||
set wxProc to first application process whose bundle identifier is "com.tencent.xinWeChat"
|
||||
set frontmost of wxProc to true
|
||||
delay 0.2
|
||||
keystroke "f" using command down
|
||||
delay 0.1
|
||||
keystroke "a" using command down
|
||||
delay 0.05
|
||||
my pasteText(chatName)
|
||||
delay 1.5
|
||||
key code 36
|
||||
delay 0.8
|
||||
my pasteText(messageText)
|
||||
delay 0.1
|
||||
key code 36
|
||||
end tell
|
||||
my restoreClipboard(previousClipboard)
|
||||
on error errorMessage number errorNumber
|
||||
my restoreClipboard(previousClipboard)
|
||||
error errorMessage number errorNumber
|
||||
end try
|
||||
end run
|
||||
|
||||
on pasteText(textValue)
|
||||
tell application "System Events"
|
||||
set the clipboard to textValue
|
||||
delay 0.05
|
||||
keystroke "v" using command down
|
||||
end tell
|
||||
end pasteText
|
||||
|
||||
on restoreClipboard(previousClipboard)
|
||||
if previousClipboard is not missing value then set the clipboard to previousClipboard
|
||||
end restoreClipboard
|
||||
"#;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn cmd_send(chat: String, message: String) -> Result<()> {
|
||||
if chat.trim().is_empty() {
|
||||
bail!("聊天对象名称不能为空");
|
||||
}
|
||||
if message.is_empty() {
|
||||
bail!("消息不能为空");
|
||||
}
|
||||
|
||||
let output = Command::new("osascript")
|
||||
.arg("-e")
|
||||
.arg(SEND_SCRIPT)
|
||||
.arg(&chat)
|
||||
.arg(&message)
|
||||
.output()
|
||||
.context("无法运行 osascript")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
let reason = if stderr.is_empty() {
|
||||
format!("osascript exited with status {}", output.status)
|
||||
} else {
|
||||
stderr
|
||||
};
|
||||
bail!(
|
||||
"发送微信消息失败:{}。请确认微信已登录,并已给当前终端/应用开启“辅助功能”权限",
|
||||
reason
|
||||
);
|
||||
}
|
||||
|
||||
println!("已发送到 {}", chat);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn cmd_send(_chat: String, _message: String) -> Result<()> {
|
||||
anyhow::bail!("send 命令目前只支持 macOS");
|
||||
}
|
||||
Loading…
Reference in New Issue