-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwechat_send.sh
More file actions
executable file
·200 lines (169 loc) · 7.6 KB
/
wechat_send.sh
File metadata and controls
executable file
·200 lines (169 loc) · 7.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#!/bin/bash
# wechat_send.sh — 通过 AppleScript 操控 macOS 微信客户端发送文字消息
# 用法: wechat_send.sh "联系人名称" "消息内容"
#
# 实现原理:
# Cmd+F 搜索 → Enter 选中 → cliclick 点击输入框 → 粘贴消息 → 发送
#
# 安全机制:
# - 截屏存证: 发消息前自动截屏,事后可追溯(/tmp/wechat_send_audit/)
# - 环境清理: 开始前按 Esc 关闭残留弹窗
# - 并发锁: 防止多实例同时操控微信
# - 剪贴板恢复: trap EXIT 确保异常退出也能恢复
# - 焦点恢复: 发完后自动切回调用方应用
#
# 配置:
# WECHAT_SEND_KEY 发送快捷键,默认 "cmd" (Cmd+Enter),设为 "enter" 则用纯 Enter
# CLICLICK cliclick 路径,默认 /opt/homebrew/bin/cliclick
set -euo pipefail
CLICLICK="${CLICLICK:-/opt/homebrew/bin/cliclick}"
SEND_KEY="${WECHAT_SEND_KEY:-cmd}" # "cmd" = Cmd+Enter, "enter" = Enter
LOCKFILE="/tmp/wechat_send.lockdir"
# ── 帮助信息 ──────────────────────────────────────────────
show_help() {
cat <<'EOF'
用法: wechat_send.sh "联系人名称" "消息内容"
通过 AppleScript 操控 macOS 微信客户端,给指定联系人发送文字消息。
参数:
联系人名称 微信通讯录中的联系人名字或备注名
消息内容 要发送的文字消息
选项:
--help, -h 显示帮助信息
示例:
wechat_send.sh "张三" "明天下午3点开会"
wechat_send.sh "工作群" "收到,已确认"
注意:
- 需要微信客户端已登录且正在运行
- 首次使用需授予终端 Accessibility 权限
- 联系人名称需要能在搜索中唯一匹配到目标
- 默认发送快捷键为 Cmd+Enter,如需改为 Enter: export WECHAT_SEND_KEY=enter
- 脚本运行期间请勿操作键盘/鼠标(约 5 秒)
EOF
exit 0
}
# ── 参数解析 ──────────────────────────────────────────────
[[ "${1:-}" == "--help" || "${1:-}" == "-h" ]] && show_help
if [[ $# -lt 2 ]]; then
echo "错误: 需要两个参数(联系人名称 和 消息内容)" >&2
echo "用法: wechat_send.sh \"联系人名称\" \"消息内容\"" >&2
exit 1
fi
CONTACT="$1"
MESSAGE="$2"
CONTACT_ESCAPED="${CONTACT//\"/\\\"}"
MESSAGE_ESCAPED="${MESSAGE//\"/\\\"}"
[[ -z "$CONTACT" ]] && { echo "错误: 联系人名称不能为空" >&2; exit 1; }
[[ -z "$MESSAGE" ]] && { echo "错误: 消息内容不能为空" >&2; exit 1; }
# ── 检查依赖 ─────────────────────────────────────────────
if ! pgrep -x "WeChat" > /dev/null 2>&1; then
echo "错误: 微信未运行,请先启动微信并登录" >&2
exit 1
fi
[[ ! -x "$CLICLICK" ]] && { echo "错误: cliclick 未安装 (brew install cliclick)" >&2; exit 1; }
# ── 并发锁(mkdir 原子操作)─────────────────────────────
if ! mkdir "$LOCKFILE" 2>/dev/null; then
# 检查锁是否超过 30 秒(可能是残留锁)
if [[ -d "$LOCKFILE" ]] && (( $(date +%s) - $(stat -f %m "$LOCKFILE") > 30 )); then
rm -rf "$LOCKFILE"
mkdir "$LOCKFILE" 2>/dev/null || { echo "错误: 无法获取锁" >&2; exit 1; }
else
echo "错误: 另一个 wechat_send 正在运行,请等待完成" >&2
exit 1
fi
fi
# ── 保存/恢复剪贴板 ──────────────────────────────────────
ORIGINAL_CLIPBOARD=$(pbpaste 2>/dev/null || true)
cleanup() {
echo -n "$ORIGINAL_CLIPBOARD" | pbcopy 2>/dev/null || true
rm -rf "$LOCKFILE" 2>/dev/null || true
}
trap cleanup EXIT
# ── 获取微信窗口位置 ─────────────────────────────────────
read -r WIN_X WIN_Y WIN_W WIN_H < <(osascript -e '
tell application "System Events"
tell process "WeChat"
set winPos to position of window "微信"
set winSize to size of window "微信"
return (item 1 of winPos as text) & " " & (item 2 of winPos as text) & " " & (item 1 of winSize as text) & " " & (item 2 of winSize as text)
end tell
end tell
')
# 窗口尺寸校验
if (( WIN_W < 400 || WIN_H < 400 )); then
echo "错误: 微信窗口太小 (${WIN_W}x${WIN_H}),请调大窗口再试" >&2
exit 1
fi
CLICK_X=$(( WIN_X + WIN_W * 2 / 3 ))
CLICK_Y=$(( WIN_Y + WIN_H - 40 ))
# ── 记住调用方应用,发完后切回 ─────────────────────────
CALLER_APP=$(osascript -e 'tell application "System Events" to return name of first application process whose frontmost is true' 2>/dev/null || echo "Terminal")
# ── 步骤 0: 通知用户,请勿操作 ───────────────────────────
osascript -e 'display notification "正在发送微信消息,请勿操作键鼠(约5秒)" with title "wechat_send"' 2>/dev/null || true
sleep 1.5
# ── 步骤 1: 激活微信 + 清理残留弹窗 ─────────────────────
osascript <<'APPLESCRIPT'
tell application "WeChat" to activate
delay 0.3
tell application "System Events"
tell process "WeChat"
set frontmost to true
delay 0.2
-- 按 3 次 Esc 清理可能残留的弹窗/搜索/选择框
key code 53
delay 0.1
key code 53
delay 0.1
key code 53
delay 0.3
end tell
end tell
APPLESCRIPT
# ── 步骤 2: 搜索联系人(可被干扰,但不致命)─────────────
osascript <<APPLESCRIPT
tell application "System Events"
tell process "WeChat"
keystroke "1" using command down
delay 0.3
keystroke "f" using command down
delay 0.5
set the clipboard to "$CONTACT_ESCAPED"
keystroke "v" using command down
delay 1.5
end tell
end tell
APPLESCRIPT
# 准备好消息剪贴板(在 shell 侧提前准备,减少 AppleScript 内耗时)
echo -n "$MESSAGE" | pbcopy
# ── 步骤 3: 选中→点击输入框→粘贴→发送(原子化,最小化干扰窗口)──
# Enter 选中搜索结果后,立刻 cliclick 点输入框 + 粘贴 + 发送,一气呵成
osascript <<APPLESCRIPT
tell application "System Events"
tell process "WeChat"
-- 选中第一个搜索结果
key code 36
delay 0.5
end tell
end tell
APPLESCRIPT
# cliclick 点击输入框(必须在 shell 侧执行)
"$CLICLICK" c:"$CLICK_X","$CLICK_Y"
# 立刻粘贴+发送,不给干扰留时间
osascript <<APPLESCRIPT
tell application "System Events"
tell process "WeChat"
keystroke "v" using command down
delay 0.1
$( [[ "$SEND_KEY" == "enter" ]] && echo 'key code 36' || echo 'keystroke return using command down' )
end tell
end tell
APPLESCRIPT
# ── 步骤 4: 截屏存证(发送后拍,记录实际发到了哪个对话)──
AUDIT_DIR="/tmp/wechat_send_audit"
mkdir -p "$AUDIT_DIR"
AUDIT_FILE="$AUDIT_DIR/$(date +%Y%m%d_%H%M%S)_${CONTACT}.png"
screencapture -x "$AUDIT_FILE" 2>/dev/null || true
ls -t "$AUDIT_DIR"/*.png 2>/dev/null | tail -n +21 | xargs rm -f 2>/dev/null || true
# ── 步骤 5: 切回终端 ─────────────────────────────────────
osascript -e "tell application \"$CALLER_APP\" to activate" 2>/dev/null || true
echo "✓ 已发送消息给 \"$CONTACT\""
echo " 截屏存证: $AUDIT_FILE"