weixin-mcp 1.4.2 → 1.5.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/README.en.md +67 -29
- package/README.md +67 -25
- package/dist/index.js +13 -2
- package/dist/messaging.js +24 -3
- package/package.json +1 -1
- package/src/index.ts +13 -3
- package/src/messaging.ts +21 -3
package/README.en.md
CHANGED
|
@@ -1,28 +1,54 @@
|
|
|
1
1
|
# weixin-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server for WeChat messaging — expose WeChat capabilities as MCP tools for Claude Desktop, Cursor, and other MCP clients.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[中文](./README.md)
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
9
|
-
### Step 1 — Login (first time only)
|
|
10
|
-
|
|
11
9
|
```bash
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
# 1. Login (scan QR code)
|
|
11
|
+
npx weixin-mcp login
|
|
12
|
+
|
|
13
|
+
# 2. Check status
|
|
14
|
+
npx weixin-mcp status
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
# 3. Start MCP server (stdio mode for Claude Desktop)
|
|
17
|
+
npx weixin-mcp
|
|
18
|
+
```
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
## CLI Commands
|
|
21
|
+
|
|
22
|
+
| Command | Description |
|
|
23
|
+
|---------|-------------|
|
|
24
|
+
| `npx weixin-mcp login` | QR code login |
|
|
25
|
+
| `npx weixin-mcp status` | Show account and daemon status |
|
|
26
|
+
| `npx weixin-mcp` | Start stdio MCP server (Claude Desktop) |
|
|
27
|
+
| `npx weixin-mcp start [--port n]` | Start HTTP daemon (background, default 3001) |
|
|
28
|
+
| `npx weixin-mcp stop` | Stop daemon |
|
|
29
|
+
| `npx weixin-mcp restart` | Restart daemon |
|
|
30
|
+
| `npx weixin-mcp logs [-f]` | View daemon logs (-f for follow) |
|
|
31
|
+
| `npx weixin-mcp send <userId> <text>` | Send message (supports short ID prefix) |
|
|
32
|
+
| `npx weixin-mcp poll [--watch] [--reset]` | Poll messages (--watch for continuous) |
|
|
33
|
+
| `npx weixin-mcp contacts` | List contacts (users who messaged the bot) |
|
|
34
|
+
| `npx weixin-mcp accounts [list]` | List all accounts |
|
|
35
|
+
| `npx weixin-mcp accounts remove <id>` | Remove an account |
|
|
36
|
+
| `npx weixin-mcp accounts clean` | Remove duplicates (keep newest per userId) |
|
|
37
|
+
| `npx weixin-mcp update` | Check and install latest version |
|
|
38
|
+
| `npx weixin-mcp --version` | Print version |
|
|
39
|
+
|
|
40
|
+
### Short ID Matching
|
|
41
|
+
|
|
42
|
+
When sending messages, you can use a prefix of the user ID if it uniquely matches a contact:
|
|
18
43
|
|
|
19
44
|
```bash
|
|
20
|
-
npx weixin-mcp
|
|
45
|
+
npx weixin-mcp send o9cq8 "hello"
|
|
46
|
+
# Resolved "o9cq8" → o9cq80x8ou646cs3Tt5EQgfsZRtI@im.wechat
|
|
21
47
|
```
|
|
22
48
|
|
|
23
49
|
## Claude Desktop Integration
|
|
24
50
|
|
|
25
|
-
Add to
|
|
51
|
+
Add to `claude_desktop_config.json`:
|
|
26
52
|
|
|
27
53
|
```json
|
|
28
54
|
{
|
|
@@ -35,34 +61,46 @@ Add to your `claude_desktop_config.json`:
|
|
|
35
61
|
}
|
|
36
62
|
```
|
|
37
63
|
|
|
38
|
-
|
|
64
|
+
## HTTP Daemon Mode
|
|
39
65
|
|
|
40
|
-
|
|
66
|
+
Start an HTTP daemon for multi-client connections:
|
|
41
67
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
| `weixin_poll` | Poll for new messages (cursor-based, no duplicates) | `reset_cursor` (optional boolean) |
|
|
46
|
-
| `weixin_get_config` | Get user config (typing ticket, etc.) | `user_id`, `context_token` (optional) |
|
|
68
|
+
```bash
|
|
69
|
+
npx weixin-mcp start --port 3001
|
|
70
|
+
```
|
|
47
71
|
|
|
48
|
-
|
|
72
|
+
- MCP endpoint: `http://localhost:3001/mcp` (StreamableHTTP)
|
|
73
|
+
- Health check: `http://localhost:3001/health`
|
|
49
74
|
|
|
50
|
-
|
|
75
|
+
## MCP Tools
|
|
51
76
|
|
|
52
|
-
|
|
77
|
+
| Tool | Description | Parameters |
|
|
78
|
+
|------|-------------|------------|
|
|
79
|
+
| `weixin_send` | Send text message | `to`, `text`, `context_token` (optional) |
|
|
80
|
+
| `weixin_poll` | Poll new messages | `reset_cursor` (optional) |
|
|
81
|
+
| `weixin_contacts` | List contacts | none |
|
|
82
|
+
| `weixin_get_config` | Get user config | `user_id`, `context_token` (optional) |
|
|
53
83
|
|
|
54
|
-
|
|
55
|
-
|----------|---------|-------------|
|
|
56
|
-
| `OPENCLAW_STATE_DIR` | `~/.openclaw` | State directory; accounts are read from `$OPENCLAW_STATE_DIR/openclaw-weixin/accounts/` |
|
|
57
|
-
| `WEIXIN_ACCOUNT_ID` | first account found | Specify which account to use (filename without `.json`) |
|
|
84
|
+
## Data Storage
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
Priority:
|
|
87
|
+
1. `WEIXIN_MCP_DIR` environment variable
|
|
88
|
+
2. `~/.openclaw/openclaw-weixin/` (if OpenClaw installed)
|
|
89
|
+
3. `~/.weixin-mcp/` (default)
|
|
60
90
|
|
|
61
|
-
|
|
91
|
+
Files:
|
|
92
|
+
- `accounts/<accountId>.json` — account token
|
|
93
|
+
- `accounts/<accountId>.cursor.json` — message cursor
|
|
94
|
+
- `contacts.json` — contact book
|
|
95
|
+
- `daemon.json` — daemon PID (HTTP mode only)
|
|
96
|
+
- `daemon.log` — daemon logs
|
|
62
97
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
98
|
+
## Environment Variables
|
|
99
|
+
|
|
100
|
+
| Variable | Description |
|
|
101
|
+
|----------|-------------|
|
|
102
|
+
| `WEIXIN_MCP_DIR` | Custom data directory |
|
|
103
|
+
| `WEIXIN_ACCOUNT_ID` | Specify which account to use |
|
|
66
104
|
|
|
67
105
|
## License
|
|
68
106
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# weixin-mcp
|
|
2
2
|
|
|
3
|
-
基于 MCP 协议的微信消息服务端——将微信能力暴露为 MCP 工具,供 Claude Desktop 及其他 MCP 客户端使用。
|
|
3
|
+
基于 MCP 协议的微信消息服务端——将微信能力暴露为 MCP 工具,供 Claude Desktop、Cursor 及其他 MCP 客户端使用。
|
|
4
4
|
|
|
5
5
|
支持复用 [OpenClaw weixin 插件](https://www.npmjs.com/package/@tencent-weixin/openclaw-weixin) 的已有登录态,或独立扫码登录。
|
|
6
6
|
|
|
@@ -8,18 +8,44 @@
|
|
|
8
8
|
|
|
9
9
|
## 快速开始
|
|
10
10
|
|
|
11
|
-
### 第一步 — 登录(首次使用)
|
|
12
|
-
|
|
13
11
|
```bash
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
# 1. 登录(首次使用,扫码)
|
|
13
|
+
npx weixin-mcp login
|
|
14
|
+
|
|
15
|
+
# 2. 查看状态
|
|
16
|
+
npx weixin-mcp status
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
# 3. 启动 MCP server(Claude Desktop 模式)
|
|
19
|
+
npx weixin-mcp
|
|
20
|
+
```
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
## CLI 命令一览
|
|
23
|
+
|
|
24
|
+
| 命令 | 说明 |
|
|
25
|
+
|------|------|
|
|
26
|
+
| `npx weixin-mcp login` | 扫码登录微信 |
|
|
27
|
+
| `npx weixin-mcp status` | 查看账号和 daemon 状态 |
|
|
28
|
+
| `npx weixin-mcp` | 启动 stdio MCP server(Claude Desktop) |
|
|
29
|
+
| `npx weixin-mcp start [--port n]` | 启动 HTTP daemon(后台,默认端口 3001) |
|
|
30
|
+
| `npx weixin-mcp stop` | 停止 daemon |
|
|
31
|
+
| `npx weixin-mcp restart` | 重启 daemon |
|
|
32
|
+
| `npx weixin-mcp logs [-f]` | 查看 daemon 日志(-f 实时跟踪) |
|
|
33
|
+
| `npx weixin-mcp send <userId> <text>` | 发送消息(支持短 ID 前缀匹配) |
|
|
34
|
+
| `npx weixin-mcp poll [--watch] [--reset]` | 拉取消息(--watch 持续监听) |
|
|
35
|
+
| `npx weixin-mcp contacts` | 查看联系人(给 bot 发过消息的用户) |
|
|
36
|
+
| `npx weixin-mcp accounts [list]` | 列出所有账号 |
|
|
37
|
+
| `npx weixin-mcp accounts remove <id>` | 删除账号 |
|
|
38
|
+
| `npx weixin-mcp accounts clean` | 清理重复账号(同 userId 保留最新) |
|
|
39
|
+
| `npx weixin-mcp update` | 检查并更新到最新版 |
|
|
40
|
+
| `npx weixin-mcp --version` | 查看版本 |
|
|
41
|
+
|
|
42
|
+
### 短 ID 匹配
|
|
43
|
+
|
|
44
|
+
发送消息时可以用用户 ID 的前缀,只要在联系人中唯一匹配即可:
|
|
20
45
|
|
|
21
46
|
```bash
|
|
22
|
-
npx weixin-mcp
|
|
47
|
+
npx weixin-mcp send o9cq8 "hello"
|
|
48
|
+
# Resolved "o9cq8" → o9cq80x8ou646cs3Tt5EQgfsZRtI@im.wechat
|
|
23
49
|
```
|
|
24
50
|
|
|
25
51
|
## Claude Desktop 集成
|
|
@@ -37,32 +63,48 @@ npx weixin-mcp
|
|
|
37
63
|
}
|
|
38
64
|
```
|
|
39
65
|
|
|
40
|
-
重启 Claude Desktop
|
|
66
|
+
重启 Claude Desktop 后即可使用。
|
|
67
|
+
|
|
68
|
+
## HTTP Daemon 模式
|
|
41
69
|
|
|
42
|
-
|
|
70
|
+
除了 stdio 模式,也可以启动 HTTP daemon 供多客户端连接:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx weixin-mcp start --port 3001
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
MCP 端点:`http://localhost:3001/mcp`(StreamableHTTP)
|
|
77
|
+
健康检查:`http://localhost:3001/health`
|
|
78
|
+
|
|
79
|
+
## MCP 工具列表
|
|
43
80
|
|
|
44
81
|
| 工具名 | 说明 | 参数 |
|
|
45
82
|
|--------|------|------|
|
|
46
|
-
| `weixin_send` | 发送文本消息 | `to
|
|
47
|
-
| `weixin_poll` |
|
|
48
|
-
| `
|
|
49
|
-
|
|
50
|
-
## 已有 OpenClaw weixin 插件?
|
|
83
|
+
| `weixin_send` | 发送文本消息 | `to`、`text`、`context_token`(可选) |
|
|
84
|
+
| `weixin_poll` | 拉取新消息 | `reset_cursor`(可选) |
|
|
85
|
+
| `weixin_contacts` | 列出联系人 | 无 |
|
|
86
|
+
| `weixin_get_config` | 获取用户配置 | `user_id`、`context_token`(可选) |
|
|
51
87
|
|
|
52
|
-
|
|
88
|
+
## 数据存储路径
|
|
53
89
|
|
|
54
|
-
|
|
90
|
+
优先级:
|
|
91
|
+
1. `WEIXIN_MCP_DIR` 环境变量
|
|
92
|
+
2. `~/.openclaw/openclaw-weixin/`(已装 OpenClaw)
|
|
93
|
+
3. `~/.weixin-mcp/`(默认)
|
|
55
94
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
95
|
+
文件:
|
|
96
|
+
- `accounts/<accountId>.json` — 账号 token
|
|
97
|
+
- `accounts/<accountId>.cursor.json` — 消息游标
|
|
98
|
+
- `contacts.json` — 联系人
|
|
99
|
+
- `daemon.json` — daemon PID(仅 HTTP 模式)
|
|
100
|
+
- `daemon.log` — daemon 日志
|
|
60
101
|
|
|
61
|
-
##
|
|
102
|
+
## 环境变量
|
|
62
103
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
104
|
+
| 变量名 | 说明 |
|
|
105
|
+
|--------|------|
|
|
106
|
+
| `WEIXIN_MCP_DIR` | 自定义数据目录 |
|
|
107
|
+
| `WEIXIN_ACCOUNT_ID` | 指定使用哪个账号 |
|
|
66
108
|
|
|
67
109
|
## License
|
|
68
110
|
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,16 @@ import path from "node:path";
|
|
|
11
11
|
import { DEFAULT_BASE_URL, getUpdates, getConfig, sendTextMessage, loadCursor, saveCursor, WeixinAuthError, WeixinNetworkError, } from "./api.js";
|
|
12
12
|
import { ACCOUNTS_DIR } from "./paths.js";
|
|
13
13
|
import { updateContactsFromMsgs, loadContacts } from "./contacts.js";
|
|
14
|
+
/** Resolve short userId prefix to full ID from contacts. */
|
|
15
|
+
function resolveUserId(input, contacts) {
|
|
16
|
+
if (!input || input.includes("@"))
|
|
17
|
+
return input;
|
|
18
|
+
const ids = Object.keys(contacts);
|
|
19
|
+
const matches = ids.filter((id) => id.startsWith(input) || id.includes(input));
|
|
20
|
+
if (matches.length === 1)
|
|
21
|
+
return matches[0];
|
|
22
|
+
return input; // ambiguous or not found — use as-is
|
|
23
|
+
}
|
|
14
24
|
// ── Auth / config ──────────────────────────────────────────────────────────
|
|
15
25
|
const WEIXIN_DIR = ACCOUNTS_DIR;
|
|
16
26
|
function loadAccount() {
|
|
@@ -52,7 +62,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
52
62
|
inputSchema: {
|
|
53
63
|
type: "object",
|
|
54
64
|
properties: {
|
|
55
|
-
to: { type: "string", description: "Recipient user ID
|
|
65
|
+
to: { type: "string", description: "Recipient user ID (full or short prefix if unique in contacts)" },
|
|
56
66
|
text: { type: "string", description: "Message text to send" },
|
|
57
67
|
context_token: {
|
|
58
68
|
type: "string",
|
|
@@ -103,8 +113,9 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
103
113
|
if (name === "weixin_send") {
|
|
104
114
|
const { to, text, context_token } = (args ?? {});
|
|
105
115
|
const validatedTo = assertNonEmptyString(to, "to");
|
|
116
|
+
const resolvedTo = resolveUserId(validatedTo, loadContacts());
|
|
106
117
|
const validatedText = assertNonEmptyString(text, "text");
|
|
107
|
-
result = await sendTextMessage(
|
|
118
|
+
result = await sendTextMessage(resolvedTo, validatedText, token, baseUrl, context_token);
|
|
108
119
|
}
|
|
109
120
|
else if (name === "weixin_poll") {
|
|
110
121
|
const { reset_cursor } = (args ?? {});
|
package/dist/messaging.js
CHANGED
|
@@ -7,7 +7,25 @@ import fs from "node:fs";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { ACCOUNTS_DIR } from "./paths.js";
|
|
9
9
|
import { DEFAULT_BASE_URL, sendTextMessage, getUpdates, loadCursor, saveCursor, } from "./api.js";
|
|
10
|
-
import { updateContactsFromMsgs } from "./contacts.js";
|
|
10
|
+
import { updateContactsFromMsgs, loadContacts } from "./contacts.js";
|
|
11
|
+
/** Resolve a short/partial userId to a full one from contacts. */
|
|
12
|
+
function resolveUserId(input) {
|
|
13
|
+
if (!input)
|
|
14
|
+
return input;
|
|
15
|
+
// Already looks like a full id? return as-is
|
|
16
|
+
if (input.includes("@"))
|
|
17
|
+
return input;
|
|
18
|
+
const contacts = Object.keys(loadContacts());
|
|
19
|
+
const matches = contacts.filter((id) => id.startsWith(input) || id.includes(input));
|
|
20
|
+
if (matches.length === 1)
|
|
21
|
+
return matches[0];
|
|
22
|
+
if (matches.length > 1) {
|
|
23
|
+
console.error(`Ambiguous user "${input}", matches:\n${matches.map((m) => ` ${m}`).join("\n")}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
// Not found in contacts — treat as literal
|
|
27
|
+
return input;
|
|
28
|
+
}
|
|
11
29
|
function loadAccount() {
|
|
12
30
|
const files = fs.readdirSync(ACCOUNTS_DIR).filter((f) => f.endsWith(".json") && !f.endsWith(".sync.json") && !f.endsWith(".cursor.json"));
|
|
13
31
|
if (files.length === 0)
|
|
@@ -41,9 +59,12 @@ export async function cliSend(args) {
|
|
|
41
59
|
process.exit(1);
|
|
42
60
|
}
|
|
43
61
|
const text = textParts.join(" ");
|
|
62
|
+
const resolvedTo = resolveUserId(to);
|
|
63
|
+
if (resolvedTo !== to)
|
|
64
|
+
console.log(`Resolved "${to}" → ${resolvedTo}`);
|
|
44
65
|
const { token, baseUrl = DEFAULT_BASE_URL } = loadAccount();
|
|
45
|
-
process.stdout.write(`Sending to ${
|
|
46
|
-
const result = await sendTextMessage(
|
|
66
|
+
process.stdout.write(`Sending to ${resolvedTo}... `);
|
|
67
|
+
const result = await sendTextMessage(resolvedTo, text, token, baseUrl);
|
|
47
68
|
const ret = result?.ret ?? result?.errcode;
|
|
48
69
|
if (ret === 0 || ret === undefined) {
|
|
49
70
|
console.log("✅ Sent");
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -23,7 +23,16 @@ import {
|
|
|
23
23
|
WeixinNetworkError,
|
|
24
24
|
} from "./api.js";
|
|
25
25
|
import { ACCOUNTS_DIR } from "./paths.js";
|
|
26
|
-
import { updateContactsFromMsgs, loadContacts } from "./contacts.js";
|
|
26
|
+
import { updateContactsFromMsgs, loadContacts, type ContactBook } from "./contacts.js";
|
|
27
|
+
|
|
28
|
+
/** Resolve short userId prefix to full ID from contacts. */
|
|
29
|
+
function resolveUserId(input: string, contacts: ContactBook): string {
|
|
30
|
+
if (!input || input.includes("@")) return input;
|
|
31
|
+
const ids = Object.keys(contacts);
|
|
32
|
+
const matches = ids.filter((id) => id.startsWith(input) || id.includes(input));
|
|
33
|
+
if (matches.length === 1) return matches[0];
|
|
34
|
+
return input; // ambiguous or not found — use as-is
|
|
35
|
+
}
|
|
27
36
|
|
|
28
37
|
// ── Auth / config ──────────────────────────────────────────────────────────
|
|
29
38
|
|
|
@@ -85,7 +94,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
85
94
|
inputSchema: {
|
|
86
95
|
type: "object",
|
|
87
96
|
properties: {
|
|
88
|
-
to: { type: "string", description: "Recipient user ID
|
|
97
|
+
to: { type: "string", description: "Recipient user ID (full or short prefix if unique in contacts)" },
|
|
89
98
|
text: { type: "string", description: "Message text to send" },
|
|
90
99
|
context_token: {
|
|
91
100
|
type: "string",
|
|
@@ -149,9 +158,10 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
149
158
|
context_token?: string;
|
|
150
159
|
};
|
|
151
160
|
const validatedTo = assertNonEmptyString(to, "to");
|
|
161
|
+
const resolvedTo = resolveUserId(validatedTo, loadContacts());
|
|
152
162
|
const validatedText = assertNonEmptyString(text, "text");
|
|
153
163
|
result = await sendTextMessage(
|
|
154
|
-
|
|
164
|
+
resolvedTo,
|
|
155
165
|
validatedText,
|
|
156
166
|
token!,
|
|
157
167
|
baseUrl,
|
package/src/messaging.ts
CHANGED
|
@@ -14,7 +14,23 @@ import {
|
|
|
14
14
|
loadCursor,
|
|
15
15
|
saveCursor,
|
|
16
16
|
} from "./api.js";
|
|
17
|
-
import { updateContactsFromMsgs } from "./contacts.js";
|
|
17
|
+
import { updateContactsFromMsgs, loadContacts } from "./contacts.js";
|
|
18
|
+
|
|
19
|
+
/** Resolve a short/partial userId to a full one from contacts. */
|
|
20
|
+
function resolveUserId(input: string): string {
|
|
21
|
+
if (!input) return input;
|
|
22
|
+
// Already looks like a full id? return as-is
|
|
23
|
+
if (input.includes("@")) return input;
|
|
24
|
+
const contacts = Object.keys(loadContacts());
|
|
25
|
+
const matches = contacts.filter((id) => id.startsWith(input) || id.includes(input));
|
|
26
|
+
if (matches.length === 1) return matches[0];
|
|
27
|
+
if (matches.length > 1) {
|
|
28
|
+
console.error(`Ambiguous user "${input}", matches:\n${matches.map((m) => ` ${m}`).join("\n")}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
// Not found in contacts — treat as literal
|
|
32
|
+
return input;
|
|
33
|
+
}
|
|
18
34
|
|
|
19
35
|
interface AccountData { token?: string; baseUrl?: string; userId?: string }
|
|
20
36
|
|
|
@@ -50,10 +66,12 @@ export async function cliSend(args: string[]) {
|
|
|
50
66
|
process.exit(1);
|
|
51
67
|
}
|
|
52
68
|
const text = textParts.join(" ");
|
|
69
|
+
const resolvedTo = resolveUserId(to);
|
|
70
|
+
if (resolvedTo !== to) console.log(`Resolved "${to}" → ${resolvedTo}`);
|
|
53
71
|
const { token, baseUrl = DEFAULT_BASE_URL } = loadAccount();
|
|
54
72
|
|
|
55
|
-
process.stdout.write(`Sending to ${
|
|
56
|
-
const result = await sendTextMessage(
|
|
73
|
+
process.stdout.write(`Sending to ${resolvedTo}... `);
|
|
74
|
+
const result = await sendTextMessage(resolvedTo, text, token!, baseUrl) as Record<string, unknown>;
|
|
57
75
|
const ret = result?.ret ?? result?.errcode;
|
|
58
76
|
if (ret === 0 || ret === undefined) {
|
|
59
77
|
console.log("✅ Sent");
|