@aster110/cc2wechat 1.0.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +8 -4
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.js +292 -0
- package/dist/daemon.js.map +1 -0
- package/dist/reply-cli.d.ts +2 -0
- package/dist/reply-cli.js +42 -0
- package/dist/reply-cli.js.map +1 -0
- package/package.json +2 -1
- package/src/cli.ts +8 -4
- package/src/daemon.ts +331 -0
- package/src/reply-cli.ts +50 -0
package/dist/cli.js
CHANGED
|
@@ -12,10 +12,11 @@ function printUsage() {
|
|
|
12
12
|
🦞 wechat-claude — WeChat channel for Claude Code
|
|
13
13
|
|
|
14
14
|
Usage:
|
|
15
|
-
npx
|
|
16
|
-
npx
|
|
17
|
-
npx
|
|
18
|
-
npx
|
|
15
|
+
npx cc2wechat start v2 Pipe Mode: listen & auto-reply via claude -p
|
|
16
|
+
npx cc2wechat install Setup: register MCP + scan QR login (v1 MCP mode)
|
|
17
|
+
npx cc2wechat login Re-login (scan QR code)
|
|
18
|
+
npx cc2wechat status Check connection status
|
|
19
|
+
npx cc2wechat help Show this help
|
|
19
20
|
`);
|
|
20
21
|
}
|
|
21
22
|
async function install() {
|
|
@@ -99,6 +100,9 @@ function status() {
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
switch (command) {
|
|
103
|
+
case 'start':
|
|
104
|
+
import('./daemon.js');
|
|
105
|
+
break;
|
|
102
106
|
case 'install':
|
|
103
107
|
case 'setup':
|
|
104
108
|
install().catch(console.error);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;AAErD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAEhC,SAAS,UAAU;IACjB,OAAO,CAAC,GAAG,CAAC
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon.d.ts
ADDED
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const replyCli = path.join(__dirname, 'reply-cli.js');
|
|
9
|
+
const IS_MACOS = process.platform === 'darwin';
|
|
10
|
+
import { loginWithQRWeb } from './auth.js';
|
|
11
|
+
import { getActiveAccount, saveAccount, loadSyncBuf, saveSyncBuf } from './store.js';
|
|
12
|
+
import { getUpdates, sendTyping, getConfig } from './wechat-api.js';
|
|
13
|
+
import { MessageItemType } from './types.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Constants
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const SESSION_EXPIRED_ERRCODE = -14;
|
|
18
|
+
const MAX_CONSECUTIVE_FAILURES = 3;
|
|
19
|
+
const BACKOFF_DELAY_MS = 30_000;
|
|
20
|
+
const RETRY_DELAY_MS = 2_000;
|
|
21
|
+
const SESSION_PAUSE_MS = 5 * 60_000;
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
function userIdToSessionUUID(userId) {
|
|
26
|
+
const hash = createHash('md5').update(`cc2wechat:${userId}`).digest('hex');
|
|
27
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
28
|
+
}
|
|
29
|
+
function extractText(msg) {
|
|
30
|
+
const parts = [];
|
|
31
|
+
for (const item of msg.item_list ?? []) {
|
|
32
|
+
if (item.type === MessageItemType.TEXT && item.text_item?.text) {
|
|
33
|
+
parts.push(item.text_item.text);
|
|
34
|
+
}
|
|
35
|
+
else if (item.type === MessageItemType.IMAGE) {
|
|
36
|
+
parts.push('[Image]');
|
|
37
|
+
}
|
|
38
|
+
else if (item.type === MessageItemType.VOICE && item.voice_item?.text) {
|
|
39
|
+
parts.push(`[Voice] ${item.voice_item.text}`);
|
|
40
|
+
}
|
|
41
|
+
else if (item.type === MessageItemType.FILE && item.file_item?.file_name) {
|
|
42
|
+
parts.push(`[File: ${item.file_item.file_name}]`);
|
|
43
|
+
}
|
|
44
|
+
else if (item.type === MessageItemType.VIDEO) {
|
|
45
|
+
parts.push('[Video]');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return parts.join('\n') || '[Empty message]';
|
|
49
|
+
}
|
|
50
|
+
function sleep(ms) {
|
|
51
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// iTerm Tab Management
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Maintain tab state per WeChat user
|
|
57
|
+
const userTabs = new Map(); // userId -> tabName
|
|
58
|
+
// Track tab IDs - persisted to file so it survives daemon restart
|
|
59
|
+
const TAB_REGISTRY_PATH = '/tmp/cc2wechat-tabs.json';
|
|
60
|
+
const tabSessionIds = new Map(); // tabName -> iTerm session id
|
|
61
|
+
// Load persisted tab registry on startup
|
|
62
|
+
try {
|
|
63
|
+
if (fs.existsSync(TAB_REGISTRY_PATH)) {
|
|
64
|
+
const data = JSON.parse(fs.readFileSync(TAB_REGISTRY_PATH, 'utf-8'));
|
|
65
|
+
for (const [k, v] of Object.entries(data)) {
|
|
66
|
+
tabSessionIds.set(k, v);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
function saveTabRegistry() {
|
|
72
|
+
fs.writeFileSync(TAB_REGISTRY_PATH, JSON.stringify(Object.fromEntries(tabSessionIds)));
|
|
73
|
+
}
|
|
74
|
+
function tabExists(tabName) {
|
|
75
|
+
const windowId = tabSessionIds.get(tabName);
|
|
76
|
+
if (!windowId)
|
|
77
|
+
return false;
|
|
78
|
+
try {
|
|
79
|
+
const result = execSync(`osascript -e '
|
|
80
|
+
tell application "iTerm2"
|
|
81
|
+
try
|
|
82
|
+
set w to (first window whose id is ${windowId})
|
|
83
|
+
return "found"
|
|
84
|
+
on error
|
|
85
|
+
return "not_found"
|
|
86
|
+
end try
|
|
87
|
+
end tell
|
|
88
|
+
'`, { encoding: 'utf-8' }).trim();
|
|
89
|
+
return result === 'found';
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function createTabAndStartCC(tabName, ccSessionId, cwd) {
|
|
96
|
+
// Create NEW WINDOW and capture window ID (same approach as cc-mesh)
|
|
97
|
+
const windowId = execSync(`osascript -e '
|
|
98
|
+
tell application "iTerm2"
|
|
99
|
+
set w to (create window with default profile)
|
|
100
|
+
tell current session of w
|
|
101
|
+
write text "cd ${cwd} && claude --resume ${ccSessionId} --dangerously-skip-permissions"
|
|
102
|
+
end tell
|
|
103
|
+
return id of w
|
|
104
|
+
end tell
|
|
105
|
+
'`, { encoding: 'utf-8' }).trim();
|
|
106
|
+
tabSessionIds.set(tabName, windowId);
|
|
107
|
+
saveTabRegistry();
|
|
108
|
+
console.log(`[cc2wechat] window created: ${tabName} -> window id: ${windowId}`);
|
|
109
|
+
}
|
|
110
|
+
function injectMessage(tabName, message) {
|
|
111
|
+
const windowId = tabSessionIds.get(tabName);
|
|
112
|
+
if (!windowId)
|
|
113
|
+
return;
|
|
114
|
+
const escaped = message.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
115
|
+
execSync(`osascript -e '
|
|
116
|
+
tell application "iTerm2"
|
|
117
|
+
tell current session of (first window whose id is ${windowId})
|
|
118
|
+
write text "${escaped}"
|
|
119
|
+
end tell
|
|
120
|
+
end tell
|
|
121
|
+
'`);
|
|
122
|
+
}
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Core message handler
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
async function handleMessage(msg, account) {
|
|
127
|
+
const text = extractText(msg);
|
|
128
|
+
const userId = msg.from_user_id ?? '';
|
|
129
|
+
const contextToken = msg.context_token ?? '';
|
|
130
|
+
const sessionId = userIdToSessionUUID(userId);
|
|
131
|
+
const tabName = `wechat-${userId.slice(0, 8)}`;
|
|
132
|
+
const cwd = process.cwd();
|
|
133
|
+
console.log(`[cc2wechat] <- ${userId.slice(0, 10)}...: ${text.slice(0, 50)}`);
|
|
134
|
+
// Write context for reply-cli
|
|
135
|
+
fs.writeFileSync('/tmp/cc2wechat-context.json', JSON.stringify({
|
|
136
|
+
token: account.token,
|
|
137
|
+
baseUrl: account.baseUrl,
|
|
138
|
+
userId,
|
|
139
|
+
contextToken,
|
|
140
|
+
}));
|
|
141
|
+
// Send typing indicator
|
|
142
|
+
try {
|
|
143
|
+
const cfg = await getConfig(account.token, userId, contextToken, account.baseUrl);
|
|
144
|
+
if (cfg.typing_ticket) {
|
|
145
|
+
await sendTyping(account.token, userId, cfg.typing_ticket, 1, account.baseUrl).catch(() => { });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// non-critical
|
|
150
|
+
}
|
|
151
|
+
if (IS_MACOS) {
|
|
152
|
+
// v3: Interactive Terminal Mode (macOS + iTerm2)
|
|
153
|
+
if (tabExists(tabName)) {
|
|
154
|
+
console.log(`[cc2wechat] -> inject to existing window: ${tabName}`);
|
|
155
|
+
injectMessage(tabName, `[微信] ${text}`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.log(`[cc2wechat] -> creating window: ${tabName} (session: ${sessionId})`);
|
|
159
|
+
createTabAndStartCC(tabName, sessionId, cwd);
|
|
160
|
+
await sleep(5000);
|
|
161
|
+
injectMessage(tabName, `[微信] ${text}`);
|
|
162
|
+
}
|
|
163
|
+
userTabs.set(userId, tabName);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// v2: Pipe Mode fallback (Windows/Linux)
|
|
167
|
+
console.log(`[cc2wechat] -> pipe mode: ${text.slice(0, 30)}...`);
|
|
168
|
+
const prompt = JSON.stringify(text);
|
|
169
|
+
const systemPrompt = JSON.stringify(`You are responding to a WeChat message. Keep replies concise. Use this to reply: node ${replyCli} --text "reply" or node ${replyCli} --image /path/to/file`);
|
|
170
|
+
let result;
|
|
171
|
+
try {
|
|
172
|
+
result = execSync(`claude -p ${prompt} --resume ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${systemPrompt}`, { encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd }).trim();
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
const execErr = err;
|
|
176
|
+
if (!execErr.stdout?.trim()) {
|
|
177
|
+
try {
|
|
178
|
+
result = execSync(`claude -p ${prompt} --session-id ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${systemPrompt}`, { encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd }).trim();
|
|
179
|
+
}
|
|
180
|
+
catch (err2) {
|
|
181
|
+
const execErr2 = err2;
|
|
182
|
+
result = execErr2.stdout?.trim() || `Error: ${execErr2.message ?? 'unknown'}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
result = execErr.stdout.trim();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Auto-send text reply
|
|
190
|
+
const { sendMessage: sendMsg } = await import('./wechat-api.js');
|
|
191
|
+
const plain = result.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_, c) => c.trim())
|
|
192
|
+
.replace(/\*\*(.+?)\*\*/g, '$1').replace(/^#{1,6}\s+/gm, '').trim();
|
|
193
|
+
const chunks = plain.length <= 3900 ? [plain] : [plain.slice(0, 3900), plain.slice(3900)];
|
|
194
|
+
for (const chunk of chunks) {
|
|
195
|
+
await sendMsg(account.token, userId, chunk, contextToken, account.baseUrl);
|
|
196
|
+
}
|
|
197
|
+
console.log(`[cc2wechat] -> replied (${chunks.length} chunk)`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Long-polling loop
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
async function pollLoop(account) {
|
|
204
|
+
let buf = loadSyncBuf(account.accountId);
|
|
205
|
+
let consecutiveFailures = 0;
|
|
206
|
+
let nextTimeoutMs = 35_000;
|
|
207
|
+
console.log(`[cc2wechat] Polling started for account ${account.accountId}`);
|
|
208
|
+
// eslint-disable-next-line no-constant-condition
|
|
209
|
+
while (true) {
|
|
210
|
+
try {
|
|
211
|
+
const resp = await getUpdates(account.token, buf, account.baseUrl, nextTimeoutMs);
|
|
212
|
+
// Update timeout if server suggests one
|
|
213
|
+
if (resp.longpolling_timeout_ms != null && resp.longpolling_timeout_ms > 0) {
|
|
214
|
+
nextTimeoutMs = resp.longpolling_timeout_ms;
|
|
215
|
+
}
|
|
216
|
+
// Check for API errors
|
|
217
|
+
const isApiError = (resp.ret !== undefined && resp.ret !== 0) ||
|
|
218
|
+
(resp.errcode !== undefined && resp.errcode !== 0);
|
|
219
|
+
if (isApiError) {
|
|
220
|
+
const isSessionExpired = resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
|
|
221
|
+
if (isSessionExpired) {
|
|
222
|
+
console.log(`[cc2wechat] Session expired (errcode ${SESSION_EXPIRED_ERRCODE}), pausing ${Math.ceil(SESSION_PAUSE_MS / 60_000)} min`);
|
|
223
|
+
consecutiveFailures = 0;
|
|
224
|
+
await sleep(SESSION_PAUSE_MS);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
consecutiveFailures++;
|
|
228
|
+
console.error(`[cc2wechat] getUpdates error: ret=${resp.ret} errcode=${resp.errcode} errmsg=${resp.errmsg ?? ''} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`);
|
|
229
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
230
|
+
consecutiveFailures = 0;
|
|
231
|
+
await sleep(BACKOFF_DELAY_MS);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
await sleep(RETRY_DELAY_MS);
|
|
235
|
+
}
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
consecutiveFailures = 0;
|
|
239
|
+
// Save sync buf
|
|
240
|
+
if (resp.get_updates_buf != null && resp.get_updates_buf !== '') {
|
|
241
|
+
saveSyncBuf(account.accountId, resp.get_updates_buf);
|
|
242
|
+
buf = resp.get_updates_buf;
|
|
243
|
+
}
|
|
244
|
+
// Process messages
|
|
245
|
+
const msgs = resp.msgs ?? [];
|
|
246
|
+
for (const msg of msgs) {
|
|
247
|
+
// Only process user messages (message_type === 1)
|
|
248
|
+
if (msg.message_type !== 1)
|
|
249
|
+
continue;
|
|
250
|
+
// Handle each message (sequential to avoid race conditions)
|
|
251
|
+
await handleMessage(msg, account);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
consecutiveFailures++;
|
|
256
|
+
console.error(`[cc2wechat] Poll error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)}`);
|
|
257
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
258
|
+
consecutiveFailures = 0;
|
|
259
|
+
await sleep(BACKOFF_DELAY_MS);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
await sleep(RETRY_DELAY_MS);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// Main
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
async function main() {
|
|
271
|
+
console.log('\n cc2wechat v3 — Interactive Terminal Mode\n');
|
|
272
|
+
let account = getActiveAccount();
|
|
273
|
+
if (!account) {
|
|
274
|
+
console.log(' No saved credentials. Starting login...');
|
|
275
|
+
const result = await loginWithQRWeb();
|
|
276
|
+
saveAccount({
|
|
277
|
+
accountId: result.accountId.replace(/@/g, '-').replace(/\./g, '-'),
|
|
278
|
+
token: result.token,
|
|
279
|
+
baseUrl: result.baseUrl,
|
|
280
|
+
savedAt: new Date().toISOString(),
|
|
281
|
+
});
|
|
282
|
+
account = getActiveAccount();
|
|
283
|
+
}
|
|
284
|
+
console.log(` Account: ${account.accountId}`);
|
|
285
|
+
console.log(' Listening for WeChat messages...\n');
|
|
286
|
+
await pollLoop(account);
|
|
287
|
+
}
|
|
288
|
+
main().catch((err) => {
|
|
289
|
+
console.error(`Fatal: ${String(err)}`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
});
|
|
292
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CC calls this from Bash: cc2wechat-reply --image /path/to/file
|
|
3
|
+
// Or: cc2wechat-reply --text "hello"
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { sendMessage, uploadAndSendMedia } from './wechat-api.js';
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const contextPath = '/tmp/cc2wechat-context.json';
|
|
8
|
+
if (!fs.existsSync(contextPath)) {
|
|
9
|
+
console.error('No active WeChat context. cc2wechat must be running.');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf-8'));
|
|
13
|
+
async function main() {
|
|
14
|
+
if (args[0] === '--image' || args[0] === '--file') {
|
|
15
|
+
const filePath = args[1];
|
|
16
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
17
|
+
console.error(`File not found: ${filePath}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
await uploadAndSendMedia({
|
|
21
|
+
token: ctx.token,
|
|
22
|
+
toUser: ctx.userId,
|
|
23
|
+
contextToken: ctx.contextToken,
|
|
24
|
+
filePath,
|
|
25
|
+
baseUrl: ctx.baseUrl,
|
|
26
|
+
});
|
|
27
|
+
console.log(`Sent: ${filePath}`);
|
|
28
|
+
}
|
|
29
|
+
else if (args[0] === '--text') {
|
|
30
|
+
const text = args.slice(1).join(' ');
|
|
31
|
+
await sendMessage(ctx.token, ctx.userId, text, ctx.contextToken, ctx.baseUrl);
|
|
32
|
+
console.log(`Sent: ${text.slice(0, 50)}...`);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.log('Usage: cc2wechat-reply --image <path> | --text <message>');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
main().catch((err) => {
|
|
39
|
+
console.error(String(err));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
42
|
+
//# sourceMappingURL=reply-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reply-cli.js","sourceRoot":"","sources":["../src/reply-cli.ts"],"names":[],"mappings":";AACA,iEAAiE;AACjE,qCAAqC;AAErC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAElE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,WAAW,GAAG,6BAA6B,CAAC;AAElD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;IACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAK3D,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,kBAAkB,CAAC;YACvB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,YAAY,EAAE,GAAG,CAAC,YAAY;YAC9B,QAAQ;YACR,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;IACnC,CAAC;SAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9E,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aster110/cc2wechat",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "WeChat channel for Claude Code — chat with Claude Code from WeChat via iLink Bot API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cc2wechat": "dist/cli.js",
|
|
8
|
+
"cc2wechat-reply": "dist/reply-cli.js",
|
|
8
9
|
"wechat-cc-channel": "dist/server.js"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
package/src/cli.ts
CHANGED
|
@@ -16,10 +16,11 @@ function printUsage(): void {
|
|
|
16
16
|
🦞 wechat-claude — WeChat channel for Claude Code
|
|
17
17
|
|
|
18
18
|
Usage:
|
|
19
|
-
npx
|
|
20
|
-
npx
|
|
21
|
-
npx
|
|
22
|
-
npx
|
|
19
|
+
npx cc2wechat start v2 Pipe Mode: listen & auto-reply via claude -p
|
|
20
|
+
npx cc2wechat install Setup: register MCP + scan QR login (v1 MCP mode)
|
|
21
|
+
npx cc2wechat login Re-login (scan QR code)
|
|
22
|
+
npx cc2wechat status Check connection status
|
|
23
|
+
npx cc2wechat help Show this help
|
|
23
24
|
`);
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -110,6 +111,9 @@ function status(): void {
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
switch (command) {
|
|
114
|
+
case 'start':
|
|
115
|
+
import('./daemon.js');
|
|
116
|
+
break;
|
|
113
117
|
case 'install':
|
|
114
118
|
case 'setup':
|
|
115
119
|
install().catch(console.error);
|
package/src/daemon.ts
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const replyCli = path.join(__dirname, 'reply-cli.js');
|
|
11
|
+
const IS_MACOS = process.platform === 'darwin';
|
|
12
|
+
|
|
13
|
+
import { loginWithQRWeb } from './auth.js';
|
|
14
|
+
import { getActiveAccount, saveAccount, loadSyncBuf, saveSyncBuf } from './store.js';
|
|
15
|
+
import { getUpdates, sendTyping, getConfig } from './wechat-api.js';
|
|
16
|
+
import type { WeixinMessage } from './types.js';
|
|
17
|
+
import { MessageItemType } from './types.js';
|
|
18
|
+
import type { AccountData } from './store.js';
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Constants
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
const SESSION_EXPIRED_ERRCODE = -14;
|
|
25
|
+
const MAX_CONSECUTIVE_FAILURES = 3;
|
|
26
|
+
const BACKOFF_DELAY_MS = 30_000;
|
|
27
|
+
const RETRY_DELAY_MS = 2_000;
|
|
28
|
+
const SESSION_PAUSE_MS = 5 * 60_000;
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Helpers
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
function userIdToSessionUUID(userId: string): string {
|
|
35
|
+
const hash = createHash('md5').update(`cc2wechat:${userId}`).digest('hex');
|
|
36
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractText(msg: WeixinMessage): string {
|
|
40
|
+
const parts: string[] = [];
|
|
41
|
+
for (const item of msg.item_list ?? []) {
|
|
42
|
+
if (item.type === MessageItemType.TEXT && item.text_item?.text) {
|
|
43
|
+
parts.push(item.text_item.text);
|
|
44
|
+
} else if (item.type === MessageItemType.IMAGE) {
|
|
45
|
+
parts.push('[Image]');
|
|
46
|
+
} else if (item.type === MessageItemType.VOICE && item.voice_item?.text) {
|
|
47
|
+
parts.push(`[Voice] ${item.voice_item.text}`);
|
|
48
|
+
} else if (item.type === MessageItemType.FILE && item.file_item?.file_name) {
|
|
49
|
+
parts.push(`[File: ${item.file_item.file_name}]`);
|
|
50
|
+
} else if (item.type === MessageItemType.VIDEO) {
|
|
51
|
+
parts.push('[Video]');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return parts.join('\n') || '[Empty message]';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sleep(ms: number): Promise<void> {
|
|
58
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// iTerm Tab Management
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
// Maintain tab state per WeChat user
|
|
66
|
+
const userTabs = new Map<string, string>(); // userId -> tabName
|
|
67
|
+
|
|
68
|
+
// Track tab IDs - persisted to file so it survives daemon restart
|
|
69
|
+
const TAB_REGISTRY_PATH = '/tmp/cc2wechat-tabs.json';
|
|
70
|
+
const tabSessionIds = new Map<string, string>(); // tabName -> iTerm session id
|
|
71
|
+
|
|
72
|
+
// Load persisted tab registry on startup
|
|
73
|
+
try {
|
|
74
|
+
if (fs.existsSync(TAB_REGISTRY_PATH)) {
|
|
75
|
+
const data = JSON.parse(fs.readFileSync(TAB_REGISTRY_PATH, 'utf-8'));
|
|
76
|
+
for (const [k, v] of Object.entries(data)) {
|
|
77
|
+
tabSessionIds.set(k, v as string);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
81
|
+
|
|
82
|
+
function saveTabRegistry(): void {
|
|
83
|
+
fs.writeFileSync(TAB_REGISTRY_PATH, JSON.stringify(Object.fromEntries(tabSessionIds)));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function tabExists(tabName: string): boolean {
|
|
87
|
+
const windowId = tabSessionIds.get(tabName);
|
|
88
|
+
if (!windowId) return false;
|
|
89
|
+
try {
|
|
90
|
+
const result = execSync(`osascript -e '
|
|
91
|
+
tell application "iTerm2"
|
|
92
|
+
try
|
|
93
|
+
set w to (first window whose id is ${windowId})
|
|
94
|
+
return "found"
|
|
95
|
+
on error
|
|
96
|
+
return "not_found"
|
|
97
|
+
end try
|
|
98
|
+
end tell
|
|
99
|
+
'`, { encoding: 'utf-8' }).trim();
|
|
100
|
+
return result === 'found';
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createTabAndStartCC(tabName: string, ccSessionId: string, cwd: string): void {
|
|
107
|
+
// Create NEW WINDOW and capture window ID (same approach as cc-mesh)
|
|
108
|
+
const windowId = execSync(`osascript -e '
|
|
109
|
+
tell application "iTerm2"
|
|
110
|
+
set w to (create window with default profile)
|
|
111
|
+
tell current session of w
|
|
112
|
+
write text "cd ${cwd} && claude --resume ${ccSessionId} --dangerously-skip-permissions"
|
|
113
|
+
end tell
|
|
114
|
+
return id of w
|
|
115
|
+
end tell
|
|
116
|
+
'`, { encoding: 'utf-8' }).trim();
|
|
117
|
+
tabSessionIds.set(tabName, windowId);
|
|
118
|
+
saveTabRegistry();
|
|
119
|
+
console.log(`[cc2wechat] window created: ${tabName} -> window id: ${windowId}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function injectMessage(tabName: string, message: string): void {
|
|
123
|
+
const windowId = tabSessionIds.get(tabName);
|
|
124
|
+
if (!windowId) return;
|
|
125
|
+
const escaped = message.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
126
|
+
execSync(`osascript -e '
|
|
127
|
+
tell application "iTerm2"
|
|
128
|
+
tell current session of (first window whose id is ${windowId})
|
|
129
|
+
write text "${escaped}"
|
|
130
|
+
end tell
|
|
131
|
+
end tell
|
|
132
|
+
'`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Core message handler
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
async function handleMessage(msg: WeixinMessage, account: AccountData): Promise<void> {
|
|
140
|
+
const text = extractText(msg);
|
|
141
|
+
const userId = msg.from_user_id ?? '';
|
|
142
|
+
const contextToken = msg.context_token ?? '';
|
|
143
|
+
const sessionId = userIdToSessionUUID(userId);
|
|
144
|
+
const tabName = `wechat-${userId.slice(0, 8)}`;
|
|
145
|
+
const cwd = process.cwd();
|
|
146
|
+
|
|
147
|
+
console.log(`[cc2wechat] <- ${userId.slice(0, 10)}...: ${text.slice(0, 50)}`);
|
|
148
|
+
|
|
149
|
+
// Write context for reply-cli
|
|
150
|
+
fs.writeFileSync('/tmp/cc2wechat-context.json', JSON.stringify({
|
|
151
|
+
token: account.token,
|
|
152
|
+
baseUrl: account.baseUrl,
|
|
153
|
+
userId,
|
|
154
|
+
contextToken,
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
// Send typing indicator
|
|
158
|
+
try {
|
|
159
|
+
const cfg = await getConfig(account.token, userId, contextToken, account.baseUrl);
|
|
160
|
+
if (cfg.typing_ticket) {
|
|
161
|
+
await sendTyping(account.token, userId, cfg.typing_ticket, 1, account.baseUrl).catch(() => {});
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// non-critical
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (IS_MACOS) {
|
|
168
|
+
// v3: Interactive Terminal Mode (macOS + iTerm2)
|
|
169
|
+
if (tabExists(tabName)) {
|
|
170
|
+
console.log(`[cc2wechat] -> inject to existing window: ${tabName}`);
|
|
171
|
+
injectMessage(tabName, `[微信] ${text}`);
|
|
172
|
+
} else {
|
|
173
|
+
console.log(`[cc2wechat] -> creating window: ${tabName} (session: ${sessionId})`);
|
|
174
|
+
createTabAndStartCC(tabName, sessionId, cwd);
|
|
175
|
+
await sleep(5000);
|
|
176
|
+
injectMessage(tabName, `[微信] ${text}`);
|
|
177
|
+
}
|
|
178
|
+
userTabs.set(userId, tabName);
|
|
179
|
+
} else {
|
|
180
|
+
// v2: Pipe Mode fallback (Windows/Linux)
|
|
181
|
+
console.log(`[cc2wechat] -> pipe mode: ${text.slice(0, 30)}...`);
|
|
182
|
+
const prompt = JSON.stringify(text);
|
|
183
|
+
const systemPrompt = JSON.stringify(`You are responding to a WeChat message. Keep replies concise. Use this to reply: node ${replyCli} --text "reply" or node ${replyCli} --image /path/to/file`);
|
|
184
|
+
let result: string;
|
|
185
|
+
try {
|
|
186
|
+
result = execSync(
|
|
187
|
+
`claude -p ${prompt} --resume ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${systemPrompt}`,
|
|
188
|
+
{ encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd },
|
|
189
|
+
).trim();
|
|
190
|
+
} catch (err: unknown) {
|
|
191
|
+
const execErr = err as { stdout?: string; stderr?: string; message?: string };
|
|
192
|
+
if (!execErr.stdout?.trim()) {
|
|
193
|
+
try {
|
|
194
|
+
result = execSync(
|
|
195
|
+
`claude -p ${prompt} --session-id ${sessionId} --output-format text --permission-mode bypassPermissions --system-prompt ${systemPrompt}`,
|
|
196
|
+
{ encoding: 'utf-8', timeout: 120_000, maxBuffer: 10 * 1024 * 1024, cwd },
|
|
197
|
+
).trim();
|
|
198
|
+
} catch (err2: unknown) {
|
|
199
|
+
const execErr2 = err2 as { stdout?: string; message?: string };
|
|
200
|
+
result = execErr2.stdout?.trim() || `Error: ${execErr2.message ?? 'unknown'}`;
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
result = execErr.stdout.trim();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Auto-send text reply
|
|
207
|
+
const { sendMessage: sendMsg } = await import('./wechat-api.js');
|
|
208
|
+
const plain = result.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_, c: string) => c.trim())
|
|
209
|
+
.replace(/\*\*(.+?)\*\*/g, '$1').replace(/^#{1,6}\s+/gm, '').trim();
|
|
210
|
+
const chunks = plain.length <= 3900 ? [plain] : [plain.slice(0, 3900), plain.slice(3900)];
|
|
211
|
+
for (const chunk of chunks) {
|
|
212
|
+
await sendMsg(account.token, userId, chunk, contextToken, account.baseUrl);
|
|
213
|
+
}
|
|
214
|
+
console.log(`[cc2wechat] -> replied (${chunks.length} chunk)`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Long-polling loop
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
async function pollLoop(account: AccountData): Promise<void> {
|
|
223
|
+
let buf = loadSyncBuf(account.accountId);
|
|
224
|
+
let consecutiveFailures = 0;
|
|
225
|
+
let nextTimeoutMs = 35_000;
|
|
226
|
+
|
|
227
|
+
console.log(`[cc2wechat] Polling started for account ${account.accountId}`);
|
|
228
|
+
|
|
229
|
+
// eslint-disable-next-line no-constant-condition
|
|
230
|
+
while (true) {
|
|
231
|
+
try {
|
|
232
|
+
const resp = await getUpdates(account.token, buf, account.baseUrl, nextTimeoutMs);
|
|
233
|
+
|
|
234
|
+
// Update timeout if server suggests one
|
|
235
|
+
if (resp.longpolling_timeout_ms != null && resp.longpolling_timeout_ms > 0) {
|
|
236
|
+
nextTimeoutMs = resp.longpolling_timeout_ms;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check for API errors
|
|
240
|
+
const isApiError =
|
|
241
|
+
(resp.ret !== undefined && resp.ret !== 0) ||
|
|
242
|
+
(resp.errcode !== undefined && resp.errcode !== 0);
|
|
243
|
+
|
|
244
|
+
if (isApiError) {
|
|
245
|
+
const isSessionExpired =
|
|
246
|
+
resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
|
|
247
|
+
|
|
248
|
+
if (isSessionExpired) {
|
|
249
|
+
console.log(
|
|
250
|
+
`[cc2wechat] Session expired (errcode ${SESSION_EXPIRED_ERRCODE}), pausing ${Math.ceil(SESSION_PAUSE_MS / 60_000)} min`,
|
|
251
|
+
);
|
|
252
|
+
consecutiveFailures = 0;
|
|
253
|
+
await sleep(SESSION_PAUSE_MS);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
consecutiveFailures++;
|
|
258
|
+
console.error(
|
|
259
|
+
`[cc2wechat] getUpdates error: ret=${resp.ret} errcode=${resp.errcode} errmsg=${resp.errmsg ?? ''} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`,
|
|
260
|
+
);
|
|
261
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
262
|
+
consecutiveFailures = 0;
|
|
263
|
+
await sleep(BACKOFF_DELAY_MS);
|
|
264
|
+
} else {
|
|
265
|
+
await sleep(RETRY_DELAY_MS);
|
|
266
|
+
}
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
consecutiveFailures = 0;
|
|
271
|
+
|
|
272
|
+
// Save sync buf
|
|
273
|
+
if (resp.get_updates_buf != null && resp.get_updates_buf !== '') {
|
|
274
|
+
saveSyncBuf(account.accountId, resp.get_updates_buf);
|
|
275
|
+
buf = resp.get_updates_buf;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Process messages
|
|
279
|
+
const msgs = resp.msgs ?? [];
|
|
280
|
+
for (const msg of msgs) {
|
|
281
|
+
// Only process user messages (message_type === 1)
|
|
282
|
+
if (msg.message_type !== 1) continue;
|
|
283
|
+
|
|
284
|
+
// Handle each message (sequential to avoid race conditions)
|
|
285
|
+
await handleMessage(msg, account);
|
|
286
|
+
}
|
|
287
|
+
} catch (err) {
|
|
288
|
+
consecutiveFailures++;
|
|
289
|
+
console.error(
|
|
290
|
+
`[cc2wechat] Poll error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)}`,
|
|
291
|
+
);
|
|
292
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
293
|
+
consecutiveFailures = 0;
|
|
294
|
+
await sleep(BACKOFF_DELAY_MS);
|
|
295
|
+
} else {
|
|
296
|
+
await sleep(RETRY_DELAY_MS);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Main
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
async function main(): Promise<void> {
|
|
307
|
+
console.log('\n cc2wechat v3 — Interactive Terminal Mode\n');
|
|
308
|
+
|
|
309
|
+
let account = getActiveAccount();
|
|
310
|
+
if (!account) {
|
|
311
|
+
console.log(' No saved credentials. Starting login...');
|
|
312
|
+
const result = await loginWithQRWeb();
|
|
313
|
+
saveAccount({
|
|
314
|
+
accountId: result.accountId.replace(/@/g, '-').replace(/\./g, '-'),
|
|
315
|
+
token: result.token,
|
|
316
|
+
baseUrl: result.baseUrl,
|
|
317
|
+
savedAt: new Date().toISOString(),
|
|
318
|
+
});
|
|
319
|
+
account = getActiveAccount()!;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
console.log(` Account: ${account.accountId}`);
|
|
323
|
+
console.log(' Listening for WeChat messages...\n');
|
|
324
|
+
|
|
325
|
+
await pollLoop(account);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
main().catch((err) => {
|
|
329
|
+
console.error(`Fatal: ${String(err)}`);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
});
|
package/src/reply-cli.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CC calls this from Bash: cc2wechat-reply --image /path/to/file
|
|
3
|
+
// Or: cc2wechat-reply --text "hello"
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { sendMessage, uploadAndSendMedia } from './wechat-api.js';
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const contextPath = '/tmp/cc2wechat-context.json';
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(contextPath)) {
|
|
12
|
+
console.error('No active WeChat context. cc2wechat must be running.');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ctx = JSON.parse(fs.readFileSync(contextPath, 'utf-8')) as {
|
|
17
|
+
token: string;
|
|
18
|
+
baseUrl?: string;
|
|
19
|
+
userId: string;
|
|
20
|
+
contextToken: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
async function main(): Promise<void> {
|
|
24
|
+
if (args[0] === '--image' || args[0] === '--file') {
|
|
25
|
+
const filePath = args[1];
|
|
26
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
27
|
+
console.error(`File not found: ${filePath}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
await uploadAndSendMedia({
|
|
31
|
+
token: ctx.token,
|
|
32
|
+
toUser: ctx.userId,
|
|
33
|
+
contextToken: ctx.contextToken,
|
|
34
|
+
filePath,
|
|
35
|
+
baseUrl: ctx.baseUrl,
|
|
36
|
+
});
|
|
37
|
+
console.log(`Sent: ${filePath}`);
|
|
38
|
+
} else if (args[0] === '--text') {
|
|
39
|
+
const text = args.slice(1).join(' ');
|
|
40
|
+
await sendMessage(ctx.token, ctx.userId, text, ctx.contextToken, ctx.baseUrl);
|
|
41
|
+
console.log(`Sent: ${text.slice(0, 50)}...`);
|
|
42
|
+
} else {
|
|
43
|
+
console.log('Usage: cc2wechat-reply --image <path> | --text <message>');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
main().catch((err) => {
|
|
48
|
+
console.error(String(err));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
});
|