OpenClaw 完整备份 - 2026-03-21
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
# 🤖 WeCom OpenClaw Plugin
|
||||
|
||||
**WeCom channel plugin for [OpenClaw](https://github.com/openclaw)** — by the Tencent WeCom team.
|
||||
|
||||
> A bot plugin powered by WeCom AI Bot WebSocket persistent connections. Supports direct messages & group chats, streaming replies, and proactive messaging.
|
||||
|
||||
---
|
||||
|
||||
📖 [WeCom AI Bot Official Documentation](https://open.work.weixin.qq.com/help?doc_id=21657)
|
||||
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🔗 WebSocket persistent connection for stable communication
|
||||
- 💬 Supports both direct messages (DM) and group chat
|
||||
- 📤 Proactive messaging to specific users or groups
|
||||
- 🖼️ Receives and processes image and file messages with automatic downloading
|
||||
- ⏳ Streaming replies with "thinking" placeholder messages
|
||||
- 📝 Markdown formatting support for replies
|
||||
- 🔒 Built-in access control: DM Policy (pairing / open / allowlist / disabled) and Group Policy (open / allowlist / disabled)
|
||||
- ⚡ Auto heartbeat keep-alive and reconnection (up to 100 reconnect attempts)
|
||||
- 🧙 Interactive CLI setup wizard
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Requirements
|
||||
|
||||
- OpenClaw `>= 2026.2.13`
|
||||
|
||||
### Installation
|
||||
|
||||
```shell
|
||||
openclaw plugins install @wecom/wecom-openclaw-plugin
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
#### Option 1: Interactive Setup
|
||||
|
||||
```shell
|
||||
openclaw channels add
|
||||
```
|
||||
|
||||
Follow the prompts to enter your WeCom bot's **Bot ID** and **Secret**.
|
||||
|
||||
#### Option 2: CLI Quick Setup
|
||||
|
||||
```shell
|
||||
openclaw config set channels.wecom.botId <YOUR_BOT_ID>
|
||||
openclaw config set channels.wecom.secret <YOUR_BOT_SECRET>
|
||||
openclaw config set channels.wecom.enabled true
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
### Configuration Reference
|
||||
|
||||
| Config Path | Description | Options | Default |
|
||||
|---|---|---|---|
|
||||
| `channels.wecom.botId` | WeCom bot ID | — | — |
|
||||
| `channels.wecom.secret` | WeCom bot secret | — | — |
|
||||
| `channels.wecom.enabled` | Enable the channel | `true` / `false` | `false` |
|
||||
| `channels.wecom.websocketUrl` | WebSocket endpoint | — | `wss://openws.work.weixin.qq.com` |
|
||||
| `channels.wecom.dmPolicy` | DM access policy | `pairing` / `open` / `allowlist` / `disabled` | `open` |
|
||||
| `channels.wecom.allowFrom` | DM allowlist (user IDs) | — | `[]` |
|
||||
| `channels.wecom.groupPolicy` | Group chat access policy | `open` / `allowlist` / `disabled` | `open` |
|
||||
| `channels.wecom.groupAllowFrom` | Group allowlist (group IDs) | — | `[]` |
|
||||
| `channels.wecom.sendThinkingMessage` | Send "thinking" placeholder | `true` / `false` | `true` |
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Access Control
|
||||
|
||||
### DM (Direct Message) Access
|
||||
|
||||
**Default**: `dmPolicy: "open"` — all users can send direct messages without approval.
|
||||
|
||||
#### Approve Pairing
|
||||
|
||||
```shell
|
||||
openclaw pairing list wecom # View pending pairing requests
|
||||
openclaw pairing approve wecom <CODE> # Approve a pairing request
|
||||
```
|
||||
|
||||
#### Allowlist Mode
|
||||
|
||||
Configure allowed user IDs via `channels.wecom.allowFrom`:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"dmPolicy": "allowlist",
|
||||
"allowFrom": ["user_id_1", "user_id_2"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Open Mode
|
||||
|
||||
Set `dmPolicy: "open"` to allow all users to send direct messages without approval.
|
||||
|
||||
#### Disabled Mode
|
||||
|
||||
Set `dmPolicy: "disabled"` to completely block all direct messages.
|
||||
|
||||
### Group Access
|
||||
|
||||
#### Group Policy (`channels.wecom.groupPolicy`)
|
||||
|
||||
- `"open"` — Allow messages from all groups (default)
|
||||
- `"allowlist"` — Only allow groups listed in `groupAllowFrom`
|
||||
- `"disabled"` — Disable all group messages
|
||||
|
||||
### Group Configuration Examples
|
||||
|
||||
#### Allow All Groups (Default Behavior)
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"groupPolicy": "open"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Allow Only Specific Groups
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"groupPolicy": "allowlist",
|
||||
"groupAllowFrom": ["group_id_1", "group_id_2"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Allow Only Specific Senders Within a Group (Sender Allowlist)
|
||||
|
||||
In addition to the group allowlist, you can restrict which members within a group are allowed to interact with the bot. Only messages from users listed in `groups.<chatId>.allowFrom` will be processed; messages from other members will be silently ignored. This is a sender-level allowlist that applies to **all messages**.
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"wecom": {
|
||||
"groupPolicy": "allowlist",
|
||||
"groupAllowFrom": ["group_id_1"],
|
||||
"groups": {
|
||||
"group_id_1": {
|
||||
"allowFrom": ["user_id_1", "user_id_2"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Update
|
||||
|
||||
```shell
|
||||
openclaw plugins update wecom-openclaw-plugin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
|
||||
import { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
||||
|
||||
declare const plugin: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
configSchema: any;
|
||||
register(api: OpenClawPluginApi): void;
|
||||
};
|
||||
|
||||
export { plugin as default };
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
import { type ChannelPlugin } from "openclaw/plugin-sdk";
|
||||
import type { ResolvedWeComAccount } from "./utils.js";
|
||||
export declare const wecomPlugin: ChannelPlugin<ResolvedWeComAccount>;
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 企业微信渠道常量定义
|
||||
*/
|
||||
/**
|
||||
* 企业微信渠道 ID
|
||||
*/
|
||||
export declare const CHANNEL_ID: "wecom";
|
||||
/**
|
||||
* 企业微信 WebSocket 命令枚举
|
||||
*/
|
||||
export declare enum WeComCommand {
|
||||
/** 认证订阅 */
|
||||
SUBSCRIBE = "aibot_subscribe",
|
||||
/** 心跳 */
|
||||
PING = "ping",
|
||||
/** 企业微信推送消息 */
|
||||
AIBOT_CALLBACK = "aibot_callback",
|
||||
/** clawdbot 响应消息 */
|
||||
AIBOT_RESPONSE = "aibot_response"
|
||||
}
|
||||
/** 图片下载超时时间(毫秒) */
|
||||
export declare const IMAGE_DOWNLOAD_TIMEOUT_MS = 30000;
|
||||
/** 文件下载超时时间(毫秒) */
|
||||
export declare const FILE_DOWNLOAD_TIMEOUT_MS = 60000;
|
||||
/** 消息发送超时时间(毫秒) */
|
||||
export declare const REPLY_SEND_TIMEOUT_MS = 15000;
|
||||
/** 消息处理总超时时间(毫秒) */
|
||||
export declare const MESSAGE_PROCESS_TIMEOUT_MS: number;
|
||||
/** WebSocket 心跳间隔(毫秒) */
|
||||
export declare const WS_HEARTBEAT_INTERVAL_MS = 30000;
|
||||
/** WebSocket 最大重连次数 */
|
||||
export declare const WS_MAX_RECONNECT_ATTEMPTS = 100;
|
||||
/** messageStates Map 条目的最大 TTL(毫秒),防止内存泄漏 */
|
||||
export declare const MESSAGE_STATE_TTL_MS: number;
|
||||
/** messageStates Map 清理间隔(毫秒) */
|
||||
export declare const MESSAGE_STATE_CLEANUP_INTERVAL_MS = 60000;
|
||||
/** messageStates Map 最大条目数 */
|
||||
export declare const MESSAGE_STATE_MAX_SIZE = 500;
|
||||
/** "思考中"流式消息占位内容 */
|
||||
export declare const THINKING_MESSAGE = "<think></think>";
|
||||
/** 仅包含图片时的消息占位符 */
|
||||
export declare const MEDIA_IMAGE_PLACEHOLDER = "<media:image>";
|
||||
/** 仅包含文件时的消息占位符 */
|
||||
export declare const MEDIA_DOCUMENT_PLACEHOLDER = "<media:document>";
|
||||
/** 获取 MCP 配置的 WebSocket 命令 */
|
||||
export declare const MCP_GET_CONFIG_CMD = "aibot_get_mcp_config";
|
||||
/** MCP 配置拉取超时时间(毫秒) */
|
||||
export declare const MCP_CONFIG_FETCH_TIMEOUT_MS = 15000;
|
||||
export declare const PLUGIN_JSON_FILENAME = "openclaw.plugin.json";
|
||||
export declare const PLUGIN_JSON_PATH: string;
|
||||
/** 默认媒体大小上限(MB) */
|
||||
export declare const DEFAULT_MEDIA_MAX_MB = 5;
|
||||
/** 文本分块大小上限 */
|
||||
export declare const TEXT_CHUNK_LIMIT = 4000;
|
||||
/** 图片大小上限(字节):10MB */
|
||||
export declare const IMAGE_MAX_BYTES: number;
|
||||
/** 视频大小上限(字节):10MB */
|
||||
export declare const VIDEO_MAX_BYTES: number;
|
||||
/** 语音大小上限(字节):2MB */
|
||||
export declare const VOICE_MAX_BYTES: number;
|
||||
/** 文件大小上限(字节):20MB */
|
||||
export declare const FILE_MAX_BYTES: number;
|
||||
/** 文件绝对上限(字节):超过此值无法发送,等于 FILE_MAX_BYTES */
|
||||
export declare const ABSOLUTE_MAX_BYTES: number;
|
||||
/** 上传分片大小(字节,Base64 编码前):512KB */
|
||||
export declare const UPLOAD_CHUNK_SIZE: number;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 企业微信 DM(私聊)访问控制模块
|
||||
*
|
||||
* 负责私聊策略检查、配对流程
|
||||
*/
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import type { WSClient, WsFrame } from "@wecom/aibot-node-sdk";
|
||||
import type { ResolvedWeComAccount } from "./utils.js";
|
||||
/**
|
||||
* DM Policy 检查结果
|
||||
*/
|
||||
export interface DmPolicyCheckResult {
|
||||
/** 是否允许继续处理消息 */
|
||||
allowed: boolean;
|
||||
/** 是否已发送配对消息(仅在 pairing 模式下) */
|
||||
pairingSent?: boolean;
|
||||
}
|
||||
/**
|
||||
* 检查 DM Policy 访问控制
|
||||
* @returns 检查结果,包含是否允许继续处理
|
||||
*/
|
||||
export declare function checkDmPolicy(params: {
|
||||
senderId: string;
|
||||
isGroup: boolean;
|
||||
account: ResolvedWeComAccount;
|
||||
wsClient: WSClient;
|
||||
frame: WsFrame;
|
||||
runtime: RuntimeEnv;
|
||||
}): Promise<DmPolicyCheckResult>;
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 企业微信群组访问控制模块
|
||||
*
|
||||
* 负责群组策略检查(groupPolicy、群组白名单、群内发送者白名单)
|
||||
*/
|
||||
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import type { ResolvedWeComAccount } from "./utils.js";
|
||||
/**
|
||||
* 群组策略检查结果
|
||||
*/
|
||||
export interface GroupPolicyCheckResult {
|
||||
/** 是否允许继续处理消息 */
|
||||
allowed: boolean;
|
||||
}
|
||||
/**
|
||||
* 检查群组策略访问控制
|
||||
* @returns 检查结果,包含是否允许继续处理
|
||||
*/
|
||||
export declare function checkGroupPolicy(params: {
|
||||
chatId: string;
|
||||
senderId: string;
|
||||
account: ResolvedWeComAccount;
|
||||
config: OpenClawConfig;
|
||||
runtime: RuntimeEnv;
|
||||
}): GroupPolicyCheckResult;
|
||||
/**
|
||||
* 检查发送者是否在允许列表中(通用)
|
||||
*/
|
||||
export declare function isSenderAllowed(senderId: string, allowFrom: string[]): boolean;
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* 企业微信渠道类型定义
|
||||
*/
|
||||
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import type { ResolvedWeComAccount } from "./utils.js";
|
||||
import { WeComCommand } from "./const.js";
|
||||
/**
|
||||
* Monitor 配置选项
|
||||
*/
|
||||
export type WeComMonitorOptions = {
|
||||
account: ResolvedWeComAccount;
|
||||
config: OpenClawConfig;
|
||||
runtime: RuntimeEnv;
|
||||
abortSignal?: AbortSignal;
|
||||
};
|
||||
/**
|
||||
* 消息状态
|
||||
*/
|
||||
export interface MessageState {
|
||||
accumulatedText: string;
|
||||
/** 流式回复的 streamId,用于保持同一个流式回复使用相同的 streamId */
|
||||
streamId?: string;
|
||||
/** 是否有用户可见的文本内容(不包括 <think>...</think> 标签) */
|
||||
hasText?: boolean;
|
||||
/** 是否已成功发送过媒体文件 */
|
||||
hasMedia?: boolean;
|
||||
/** 是否有媒体发送失败(权限不足、文件过大等) */
|
||||
hasMediaFailed?: boolean;
|
||||
/** 媒体发送失败时的纯文本错误摘要(用于替换 thinking 流展示给用户) */
|
||||
mediaErrorSummary?: string;
|
||||
/** deliver 回调是否被调用过(用于区分"核心无回复"和"核心回复了空内容") */
|
||||
deliverCalled?: boolean;
|
||||
}
|
||||
/**
|
||||
* MCP 配置响应体
|
||||
*/
|
||||
export interface McpConfigBody {
|
||||
/** MCP Server 的 StreamableHttp URL */
|
||||
url: string;
|
||||
/** 连接类型,如 "streamable-http" */
|
||||
type?: string;
|
||||
/** 是否已授权 */
|
||||
is_authed?: boolean;
|
||||
/** mcp业务类型 */
|
||||
biz_type?: string;
|
||||
}
|
||||
/**
|
||||
* WebSocket 请求消息基础格式
|
||||
*/
|
||||
export interface WeComRequest {
|
||||
cmd: string;
|
||||
headers: {
|
||||
req_id: string;
|
||||
};
|
||||
body: any;
|
||||
}
|
||||
/**
|
||||
* WebSocket 响应消息格式
|
||||
*/
|
||||
export interface WeComResponse {
|
||||
headers: {
|
||||
req_id: string;
|
||||
};
|
||||
errcode: number;
|
||||
errmsg: string;
|
||||
}
|
||||
/**
|
||||
* 企业微信认证请求
|
||||
*/
|
||||
export interface WeComSubscribeRequest extends WeComRequest {
|
||||
cmd: WeComCommand.SUBSCRIBE;
|
||||
body: {
|
||||
secret: string;
|
||||
bot_id: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 企业微信推送消息格式
|
||||
*/
|
||||
export interface WeComCallbackMessage {
|
||||
cmd: WeComCommand.AIBOT_CALLBACK;
|
||||
headers: {
|
||||
req_id: string;
|
||||
};
|
||||
body: {
|
||||
msgid: string;
|
||||
aibotid: string;
|
||||
chatid: string;
|
||||
chattype: "single" | "group";
|
||||
from: {
|
||||
userid: string;
|
||||
};
|
||||
response_url: string;
|
||||
msgtype: "text" | "image" | "voice" | "video" | "file" | "stream" | "mixed";
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
/** 图片 URL(通过 URL 方式接收图片时) */
|
||||
url?: string;
|
||||
/** 图片 base64 数据(直接传输时) */
|
||||
base64?: string;
|
||||
md5?: string;
|
||||
};
|
||||
/** 图文混排消息 */
|
||||
mixed?: {
|
||||
msg_item: Array<{
|
||||
msgtype: "text" | "image";
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
url?: string;
|
||||
base64?: string;
|
||||
md5?: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
quote?: {
|
||||
msgtype: string;
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
file?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
};
|
||||
stream?: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 企业微信响应消息格式
|
||||
*/
|
||||
export interface WeComResponseMessage extends WeComRequest {
|
||||
cmd: WeComCommand.AIBOT_RESPONSE;
|
||||
body: {
|
||||
msgtype: "stream" | "text" | "markdown";
|
||||
stream?: {
|
||||
id: string;
|
||||
finish: boolean;
|
||||
content: string;
|
||||
msg_item?: Array<{
|
||||
msgtype: "image" | "file";
|
||||
image?: {
|
||||
base64: string;
|
||||
md5: string;
|
||||
};
|
||||
}>;
|
||||
feedback?: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
markdown?: {
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* MCP 配置拉取与持久化模块
|
||||
*
|
||||
* 负责:
|
||||
* - 通过 WSClient 发送 aibot_get_mcp_config 请求
|
||||
* - 解析服务端响应,提取 MCP 配置(url、type、is_authed)
|
||||
* - 将配置写入 openclaw.plugin.json 的 wecomMcp 字段
|
||||
*/
|
||||
import { type WSClient } from "@wecom/aibot-node-sdk";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import type { McpConfigBody } from "./interface.js";
|
||||
/**
|
||||
* 通过 WSClient 发送 aibot_get_mcp_config 命令,获取 MCP 配置
|
||||
*
|
||||
* @param wsClient - 已认证的 WSClient 实例
|
||||
* @returns MCP 配置(url、type、is_authed)
|
||||
* @throws 响应错误码非 0 或缺少 url 字段时抛出错误
|
||||
*/
|
||||
export declare function fetchMcpConfig(wsClient: WSClient): Promise<McpConfigBody>;
|
||||
/**
|
||||
* 拉取 MCP 配置并持久化到 openclaw.plugin.json
|
||||
*
|
||||
* 认证成功后调用。失败仅记录日志,不影响 WebSocket 消息正常收发。
|
||||
*
|
||||
* @param wsClient - 已认证的 WSClient 实例
|
||||
* @param accountId - 账户 ID(用于日志)
|
||||
* @param runtime - 运行时环境(用于日志)
|
||||
*/
|
||||
export declare function fetchAndSaveMcpConfig(wsClient: WSClient, accountId: string, runtime: RuntimeEnv): Promise<void>;
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 企业微信媒体(图片)下载和保存模块
|
||||
*
|
||||
* 负责下载、检测格式、保存图片到本地,包含超时保护
|
||||
*/
|
||||
import type { OpenClawConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import type { WSClient } from "@wecom/aibot-node-sdk";
|
||||
import type { ResolvedWeComAccount } from "./utils.js";
|
||||
/**
|
||||
* 下载并保存所有图片到本地,每张图片的下载带超时保护
|
||||
*/
|
||||
export declare function downloadAndSaveImages(params: {
|
||||
imageUrls: string[];
|
||||
imageAesKeys?: Map<string, string>;
|
||||
account: ResolvedWeComAccount;
|
||||
config: OpenClawConfig;
|
||||
runtime: RuntimeEnv;
|
||||
wsClient: WSClient;
|
||||
}): Promise<Array<{
|
||||
path: string;
|
||||
contentType?: string;
|
||||
}>>;
|
||||
/**
|
||||
* 下载并保存所有文件到本地,每个文件的下载带超时保护
|
||||
*/
|
||||
export declare function downloadAndSaveFiles(params: {
|
||||
fileUrls: string[];
|
||||
fileAesKeys?: Map<string, string>;
|
||||
account: ResolvedWeComAccount;
|
||||
config: OpenClawConfig;
|
||||
runtime: RuntimeEnv;
|
||||
wsClient: WSClient;
|
||||
}): Promise<Array<{
|
||||
path: string;
|
||||
contentType?: string;
|
||||
}>>;
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* 企业微信出站媒体上传工具模块
|
||||
*
|
||||
* 负责:
|
||||
* - 从 mediaUrl 加载文件 buffer(远程 URL 或本地路径均支持)
|
||||
* - 检测 MIME 类型并映射为企微媒体类型
|
||||
* - 文件大小检查与降级策略
|
||||
*/
|
||||
import type { WeComMediaType, WSClient, WsFrameHeaders } from "@wecom/aibot-node-sdk";
|
||||
/** 媒体文件解析结果 */
|
||||
export interface ResolvedMedia {
|
||||
/** 文件数据 */
|
||||
buffer: Buffer;
|
||||
/** 检测到的 MIME 类型 */
|
||||
contentType: string;
|
||||
/** 文件名(从 URL 提取或默认生成) */
|
||||
fileName: string;
|
||||
}
|
||||
/** 文件大小检查结果 */
|
||||
export interface FileSizeCheckResult {
|
||||
/** 最终确定的企微媒体类型(可能被降级) */
|
||||
finalType: WeComMediaType;
|
||||
/** 是否需要拒绝(超过绝对限制) */
|
||||
shouldReject: boolean;
|
||||
/** 拒绝原因(仅 shouldReject=true 时有值) */
|
||||
rejectReason?: string;
|
||||
/** 是否发生了降级 */
|
||||
downgraded: boolean;
|
||||
/** 降级说明(仅 downgraded=true 时有值) */
|
||||
downgradeNote?: string;
|
||||
}
|
||||
/**
|
||||
* 根据 MIME 类型检测企微媒体类型
|
||||
*
|
||||
* @param mimeType - MIME 类型字符串
|
||||
* @returns 企微媒体类型
|
||||
*/
|
||||
export declare function detectWeComMediaType(mimeType: string): WeComMediaType;
|
||||
/**
|
||||
* 从 mediaUrl 加载媒体文件
|
||||
*
|
||||
* 支持远程 URL(http/https)和本地路径(file:// 或绝对路径),
|
||||
* 利用 openclaw plugin-sdk 的 loadOutboundMediaFromUrl 统一处理。
|
||||
*
|
||||
* @param mediaUrl - 媒体文件的 URL 或本地路径
|
||||
* @param mediaLocalRoots - 允许读取本地文件的安全白名单目录
|
||||
* @returns 解析后的媒体文件信息
|
||||
*/
|
||||
export declare function resolveMediaFile(mediaUrl: string, mediaLocalRoots?: readonly string[]): Promise<ResolvedMedia>;
|
||||
/**
|
||||
* 检查文件大小并执行降级策略
|
||||
*
|
||||
* 降级规则:
|
||||
* - voice 非 AMR 格式 → 降级为 file(企微后台仅支持 AMR)
|
||||
* - image 超过 10MB → 降级为 file
|
||||
* - video 超过 10MB → 降级为 file
|
||||
* - voice 超过 2MB → 降级为 file
|
||||
* - file 超过 20MB → 拒绝发送
|
||||
*
|
||||
* @param fileSize - 文件大小(字节)
|
||||
* @param detectedType - 检测到的企微媒体类型
|
||||
* @param contentType - 文件的 MIME 类型(用于语音格式校验)
|
||||
* @returns 大小检查结果
|
||||
*/
|
||||
export declare function applyFileSizeLimits(fileSize: number, detectedType: WeComMediaType, contentType?: string): FileSizeCheckResult;
|
||||
/** uploadAndSendMedia 的参数 */
|
||||
export interface UploadAndSendMediaOptions {
|
||||
/** WSClient 实例 */
|
||||
wsClient: WSClient;
|
||||
/** 媒体文件的 URL 或本地路径 */
|
||||
mediaUrl: string;
|
||||
/** 目标会话 ID(用于 aibot_send_msg 主动发送) */
|
||||
chatId: string;
|
||||
/** 允许读取本地文件的安全白名单目录 */
|
||||
mediaLocalRoots?: readonly string[];
|
||||
/** 日志函数 */
|
||||
log?: (...args: any[]) => void;
|
||||
/** 错误日志函数 */
|
||||
errorLog?: (...args: any[]) => void;
|
||||
}
|
||||
/** uploadAndSendMedia 的返回结果 */
|
||||
export interface UploadAndSendMediaResult {
|
||||
/** 是否发送成功 */
|
||||
ok: boolean;
|
||||
/** 发送后返回的 messageId */
|
||||
messageId?: string;
|
||||
/** 最终的企微媒体类型 */
|
||||
finalType?: WeComMediaType;
|
||||
/** 是否被拒绝(文件过大) */
|
||||
rejected?: boolean;
|
||||
/** 拒绝原因 */
|
||||
rejectReason?: string;
|
||||
/** 是否发生了降级 */
|
||||
downgraded?: boolean;
|
||||
/** 降级说明 */
|
||||
downgradeNote?: string;
|
||||
/** 错误信息 */
|
||||
error?: string;
|
||||
}
|
||||
/**
|
||||
* 公共媒体上传+发送流程
|
||||
*
|
||||
* 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → sendMediaMessage
|
||||
* 媒体消息统一走 aibot_send_msg 主动发送,避免多文件场景下 reqId 只能用一次的问题。
|
||||
* channel.ts 的 sendMedia 和 monitor.ts 的 deliver 回调都使用此函数。
|
||||
*/
|
||||
export declare function uploadAndSendMedia(options: UploadAndSendMediaOptions): Promise<UploadAndSendMediaResult>;
|
||||
/** uploadAndReplyMedia 的参数 */
|
||||
export interface UploadAndReplyMediaOptions {
|
||||
/** WSClient 实例 */
|
||||
wsClient: WSClient;
|
||||
/** 媒体文件的 URL 或本地路径 */
|
||||
mediaUrl: string;
|
||||
/** 原始 WebSocket 帧(用于 aibot_respond_msg 被动回复,携带 req_id) */
|
||||
frame: WsFrameHeaders;
|
||||
/** 允许读取本地文件的安全白名单目录 */
|
||||
mediaLocalRoots?: readonly string[];
|
||||
/** 日志函数 */
|
||||
log?: (...args: any[]) => void;
|
||||
/** 错误日志函数 */
|
||||
errorLog?: (...args: any[]) => void;
|
||||
}
|
||||
/**
|
||||
* 被动回复媒体上传+发送流程
|
||||
*
|
||||
* 统一处理:resolveMediaFile → detectType → sizeCheck → uploadMedia → replyMedia
|
||||
* 通过 aibot_respond_msg 被动回复通道发送媒体消息,可以覆盖之前的 THINKING_MESSAGE。
|
||||
*
|
||||
* 适用场景:回包只有媒体没有文本时,第一个媒体文件用此方法发送以清理 thinking 状态。
|
||||
*/
|
||||
export declare function uploadAndReplyMedia(options: UploadAndReplyMediaOptions): Promise<UploadAndSendMediaResult>;
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 企业微信消息内容解析模块
|
||||
*
|
||||
* 负责从 WsFrame 中提取文本、图片、引用等内容
|
||||
*/
|
||||
export interface MessageBody {
|
||||
msgid: string;
|
||||
aibotid?: string;
|
||||
chatid?: string;
|
||||
chattype: "single" | "group";
|
||||
from: {
|
||||
userid: string;
|
||||
};
|
||||
response_url?: string;
|
||||
msgtype: string;
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
voice?: {
|
||||
content?: string;
|
||||
};
|
||||
mixed?: {
|
||||
msg_item: Array<{
|
||||
msgtype: "text" | "image";
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
file?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
quote?: {
|
||||
msgtype: string;
|
||||
text?: {
|
||||
content: string;
|
||||
};
|
||||
voice?: {
|
||||
content: string;
|
||||
};
|
||||
image?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
file?: {
|
||||
url?: string;
|
||||
aeskey?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
export interface ParsedMessageContent {
|
||||
textParts: string[];
|
||||
imageUrls: string[];
|
||||
imageAesKeys: Map<string, string>;
|
||||
fileUrls: string[];
|
||||
fileAesKeys: Map<string, string>;
|
||||
quoteContent: string | undefined;
|
||||
}
|
||||
/**
|
||||
* 解析消息内容(支持单条消息、图文混排和引用消息)
|
||||
* @returns 提取的文本数组、图片URL数组和引用消息内容
|
||||
*/
|
||||
export declare function parseMessageContent(body: MessageBody): ParsedMessageContent;
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 企业微信消息发送模块
|
||||
*
|
||||
* 负责通过 WSClient 发送回复消息,包含超时保护
|
||||
*/
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import { type WSClient, type WsFrame } from "@wecom/aibot-node-sdk";
|
||||
/**
|
||||
* 发送企业微信回复消息
|
||||
* 供 monitor 内部和 channel outbound 使用
|
||||
*
|
||||
* @returns messageId (streamId)
|
||||
*/
|
||||
export declare function sendWeComReply(params: {
|
||||
wsClient: WSClient;
|
||||
frame: WsFrame;
|
||||
text?: string;
|
||||
runtime: RuntimeEnv;
|
||||
/** 是否为流式回复的最终消息,默认为 true */
|
||||
finish?: boolean;
|
||||
/** 指定 streamId,用于流式回复时保持相同的 streamId */
|
||||
streamId?: string;
|
||||
}): Promise<string>;
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 企业微信 WebSocket 监控器主模块
|
||||
*
|
||||
* 负责:
|
||||
* - 建立和管理 WebSocket 连接
|
||||
* - 协调消息处理流程(解析→策略检查→下载图片→路由回复)
|
||||
* - 资源生命周期管理
|
||||
*
|
||||
* 子模块:
|
||||
* - message-parser.ts : 消息内容解析
|
||||
* - message-sender.ts : 消息发送(带超时保护)
|
||||
* - media-handler.ts : 图片下载和保存(带超时保护)
|
||||
* - group-policy.ts : 群组访问控制
|
||||
* - dm-policy.ts : 私聊访问控制
|
||||
* - state-manager.ts : 全局状态管理(带 TTL 清理)
|
||||
* - timeout.ts : 超时工具
|
||||
*/
|
||||
import type { WeComMonitorOptions } from "./interface.js";
|
||||
export type { WeComMonitorOptions } from "./interface.js";
|
||||
export { WeComCommand } from "./const.js";
|
||||
export { getWeComWebSocket, setReqIdForChat, getReqIdForChatAsync, getReqIdForChat, deleteReqIdForChat, warmupReqIdStore, flushReqIdStore, } from "./state-manager.js";
|
||||
export { sendWeComReply } from "./message-sender.js";
|
||||
/**
|
||||
* 监听企业微信 WebSocket 连接
|
||||
* 使用 aibot-node-sdk 简化连接管理
|
||||
*/
|
||||
export declare function monitorWeComProvider(options: WeComMonitorOptions): Promise<void>;
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 企业微信 onboarding adapter for CLI setup wizard.
|
||||
*/
|
||||
import { type ChannelOnboardingAdapter } from "openclaw/plugin-sdk";
|
||||
export declare const wecomOnboardingAdapter: ChannelOnboardingAdapter;
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* openclaw plugin-sdk 高版本方法兼容层
|
||||
*
|
||||
* 部分方法(如 loadOutboundMediaFromUrl、detectMime、getDefaultMediaLocalRoots)
|
||||
* 仅在较新版本的 openclaw plugin-sdk 中才导出。
|
||||
*
|
||||
* 本模块在加载时一次性探测 SDK 导出,存在则直接 re-export SDK 版本,
|
||||
* 不存在则导出 fallback 实现。其他模块统一从本文件导入,无需关心底层兼容细节。
|
||||
*/
|
||||
/** 与 openclaw plugin-sdk 中 WebMediaResult 兼容的类型 */
|
||||
export type WebMediaResult = {
|
||||
buffer: Buffer;
|
||||
contentType?: string;
|
||||
kind?: string;
|
||||
fileName?: string;
|
||||
};
|
||||
export type OutboundMediaLoadOptions = {
|
||||
maxBytes?: number;
|
||||
mediaLocalRoots?: readonly string[];
|
||||
};
|
||||
export type DetectMimeOptions = {
|
||||
buffer?: Buffer;
|
||||
headerMime?: string | null;
|
||||
filePath?: string;
|
||||
};
|
||||
/**
|
||||
* 检测 MIME 类型(兼容入口)
|
||||
*
|
||||
* 支持两种调用签名以兼容不同使用场景:
|
||||
* - detectMime(buffer) → 旧式调用
|
||||
* - detectMime({ buffer, headerMime, filePath }) → 完整参数
|
||||
*
|
||||
* 优先使用 SDK 版本,不可用时使用 fallback。
|
||||
*/
|
||||
export declare function detectMime(bufferOrOpts: Buffer | DetectMimeOptions): Promise<string | undefined>;
|
||||
/**
|
||||
* 从 URL 或本地路径加载媒体文件(兼容入口)
|
||||
*
|
||||
* 优先使用 SDK 版本,不可用时使用 fallback。
|
||||
* SDK 版本抛出的业务异常(如 LocalMediaAccessError)会直接透传。
|
||||
*/
|
||||
export declare function loadOutboundMediaFromUrl(mediaUrl: string, options?: OutboundMediaLoadOptions): Promise<WebMediaResult>;
|
||||
/**
|
||||
* 获取默认媒体本地路径白名单(兼容入口)
|
||||
*
|
||||
* 优先使用 SDK 版本,不可用时手动构建白名单(与 weclaw/src/media/local-roots.ts 逻辑一致)。
|
||||
*/
|
||||
export declare function getDefaultMediaLocalRoots(): Promise<readonly string[]>;
|
||||
@@ -0,0 +1,31 @@
|
||||
/** Store 配置 */
|
||||
interface ReqIdStoreOptions {
|
||||
/** TTL 毫秒数,超时的 reqId 视为过期(默认 24 小时) */
|
||||
ttlMs?: number;
|
||||
/** 内存最大条目数(默认 200) */
|
||||
memoryMaxSize?: number;
|
||||
/** 磁盘最大条目数(默认 500) */
|
||||
fileMaxEntries?: number;
|
||||
/** 磁盘写入防抖时间(毫秒),默认 1000ms */
|
||||
flushDebounceMs?: number;
|
||||
}
|
||||
export interface PersistentReqIdStore {
|
||||
/** 设置 chatId 对应的 reqId(写入内存 + 防抖写磁盘) */
|
||||
set(chatId: string, reqId: string): void;
|
||||
/** 获取 chatId 对应的 reqId(异步:优先内存,miss 时查磁盘并回填内存) */
|
||||
get(chatId: string): Promise<string | undefined>;
|
||||
/** 同步获取 chatId 对应的 reqId(仅内存) */
|
||||
getSync(chatId: string): string | undefined;
|
||||
/** 删除 chatId 对应的 reqId */
|
||||
delete(chatId: string): void;
|
||||
/** 启动时从磁盘预热内存,返回加载条目数 */
|
||||
warmup(onError?: (error: unknown) => void): Promise<number>;
|
||||
/** 立即将内存数据刷写到磁盘(用于优雅退出) */
|
||||
flush(): Promise<void>;
|
||||
/** 清空内存缓存 */
|
||||
clearMemory(): void;
|
||||
/** 返回内存中的条目数 */
|
||||
memorySize(): number;
|
||||
}
|
||||
export declare function createPersistentReqIdStore(accountId: string, options?: ReqIdStoreOptions): PersistentReqIdStore;
|
||||
export {};
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
||||
export declare function setWeComRuntime(r: PluginRuntime): void;
|
||||
export declare function getWeComRuntime(): PluginRuntime;
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 企业微信全局状态管理模块
|
||||
*
|
||||
* 负责管理 WSClient 实例、消息状态(带 TTL 清理)、ReqId 存储
|
||||
* 解决全局 Map 的内存泄漏问题
|
||||
*/
|
||||
import type { WSClient } from "@wecom/aibot-node-sdk";
|
||||
import type { MessageState } from "./interface.js";
|
||||
/**
|
||||
* 获取指定账户的 WSClient 实例
|
||||
*/
|
||||
export declare function getWeComWebSocket(accountId: string): WSClient | null;
|
||||
/**
|
||||
* 设置指定账户的 WSClient 实例
|
||||
*/
|
||||
export declare function setWeComWebSocket(accountId: string, client: WSClient): void;
|
||||
/**
|
||||
* 删除指定账户的 WSClient 实例
|
||||
*/
|
||||
export declare function deleteWeComWebSocket(accountId: string): void;
|
||||
/**
|
||||
* 启动消息状态定期清理(自动 TTL 清理 + 容量限制)
|
||||
*/
|
||||
export declare function startMessageStateCleanup(): void;
|
||||
/**
|
||||
* 停止消息状态定期清理
|
||||
*/
|
||||
export declare function stopMessageStateCleanup(): void;
|
||||
/**
|
||||
* 设置消息状态
|
||||
*/
|
||||
export declare function setMessageState(messageId: string, state: MessageState): void;
|
||||
/**
|
||||
* 获取消息状态
|
||||
*/
|
||||
export declare function getMessageState(messageId: string): MessageState | undefined;
|
||||
/**
|
||||
* 删除消息状态
|
||||
*/
|
||||
export declare function deleteMessageState(messageId: string): void;
|
||||
/**
|
||||
* 清空所有消息状态
|
||||
*/
|
||||
export declare function clearAllMessageStates(): void;
|
||||
/**
|
||||
* 设置 chatId 对应的 reqId(写入内存 + 防抖写磁盘)
|
||||
*/
|
||||
export declare function setReqIdForChat(chatId: string, reqId: string, accountId?: string): void;
|
||||
/**
|
||||
* 获取 chatId 对应的 reqId(异步:优先内存,miss 时查磁盘并回填内存)
|
||||
*/
|
||||
export declare function getReqIdForChatAsync(chatId: string, accountId?: string): Promise<string | undefined>;
|
||||
/**
|
||||
* 获取 chatId 对应的 reqId(同步:仅内存,保留向后兼容)
|
||||
*/
|
||||
export declare function getReqIdForChat(chatId: string, accountId?: string): string | undefined;
|
||||
/**
|
||||
* 删除 chatId 对应的 reqId
|
||||
*/
|
||||
export declare function deleteReqIdForChat(chatId: string, accountId?: string): void;
|
||||
/**
|
||||
* 启动时预热 reqId 缓存(从磁盘加载到内存)
|
||||
*/
|
||||
export declare function warmupReqIdStore(accountId?: string, log?: (...args: unknown[]) => void): Promise<number>;
|
||||
/**
|
||||
* 立即将 reqId 数据刷写到磁盘(用于优雅退出)
|
||||
*/
|
||||
export declare function flushReqIdStore(accountId?: string): Promise<void>;
|
||||
/**
|
||||
* 清理指定账户的所有资源
|
||||
*/
|
||||
export declare function cleanupAccount(accountId: string): Promise<void>;
|
||||
/**
|
||||
* 清理所有资源(用于进程退出)
|
||||
*/
|
||||
export declare function cleanupAll(): Promise<void>;
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 超时控制工具模块
|
||||
*
|
||||
* 为异步操作提供统一的超时保护机制
|
||||
*/
|
||||
/**
|
||||
* 为 Promise 添加超时保护
|
||||
*
|
||||
* @param promise - 原始 Promise
|
||||
* @param timeoutMs - 超时时间(毫秒)
|
||||
* @param message - 超时错误消息
|
||||
* @returns 带超时保护的 Promise
|
||||
*/
|
||||
export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message?: string): Promise<T>;
|
||||
/**
|
||||
* 超时错误类型
|
||||
*/
|
||||
export declare class TimeoutError extends Error {
|
||||
constructor(message: string);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 企业微信公共工具函数
|
||||
*/
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
||||
/**
|
||||
* 企业微信群组配置
|
||||
*/
|
||||
export interface WeComGroupConfig {
|
||||
/** 群组内发送者白名单(仅列表中的成员消息会被处理) */
|
||||
allowFrom?: Array<string | number>;
|
||||
}
|
||||
/**
|
||||
* 企业微信配置类型
|
||||
*/
|
||||
export interface WeComConfig {
|
||||
enabled?: boolean;
|
||||
websocketUrl?: string;
|
||||
botId?: string;
|
||||
secret?: string;
|
||||
name?: string;
|
||||
allowFrom?: Array<string | number>;
|
||||
dmPolicy?: "open" | "allowlist" | "pairing" | "disabled";
|
||||
/** 群组访问策略:"open" = 允许所有群组(默认),"allowlist" = 仅允许 groupAllowFrom 中的群组,"disabled" = 禁用群组消息 */
|
||||
groupPolicy?: "open" | "allowlist" | "disabled";
|
||||
/** 群组白名单(仅 groupPolicy="allowlist" 时生效) */
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
/** 每个群组的详细配置(如群组内发送者白名单) */
|
||||
groups?: Record<string, WeComGroupConfig>;
|
||||
/** 是否发送"思考中"消息,默认为 true */
|
||||
sendThinkingMessage?: boolean;
|
||||
/** 额外允许访问的本地媒体路径白名单(支持 ~ 表示 home 目录),如 ["~/Downloads", "~/Documents"] */
|
||||
mediaLocalRoots?: string[];
|
||||
}
|
||||
export declare const DefaultWsUrl = "wss://openws.work.weixin.qq.com";
|
||||
export interface ResolvedWeComAccount {
|
||||
accountId: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
websocketUrl: string;
|
||||
botId: string;
|
||||
secret: string;
|
||||
/** 是否发送"思考中"消息,默认为 true */
|
||||
sendThinkingMessage: boolean;
|
||||
config: WeComConfig;
|
||||
}
|
||||
/**
|
||||
* 解析企业微信账户配置
|
||||
*/
|
||||
export declare function resolveWeComAccount(cfg: OpenClawConfig): ResolvedWeComAccount;
|
||||
/**
|
||||
* 设置企业微信账户配置
|
||||
*/
|
||||
export declare function setWeComAccount(cfg: OpenClawConfig, account: Partial<WeComConfig>): OpenClawConfig;
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"id": "wecom-openclaw-plugin",
|
||||
"channels": [
|
||||
"wecom"
|
||||
],
|
||||
"skills": [
|
||||
"./skills"
|
||||
],
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
{
|
||||
"name": "@wecom/wecom-openclaw-plugin",
|
||||
"version": "1.0.11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@wecom/wecom-openclaw-plugin",
|
||||
"version": "1.0.11",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@wecom/aibot-node-sdk": "^1.0.2",
|
||||
"file-type": "^21.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@borewit/text-codec": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz",
|
||||
"integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz",
|
||||
"integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.3",
|
||||
"token-types": "^6.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/token": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@wecom/aibot-node-sdk": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@wecom/aibot-node-sdk/-/aibot-node-sdk-1.0.2.tgz",
|
||||
"integrity": "sha512-azClUIMWWF5vs8K1YWBiNykTFUawej0Z1ooN0ZMGX/PlLB/BK0dQfwbLc1a5Wj3bLRLaFb8HuCTuBrxLnJKJ7g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.7",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/file-type": {
|
||||
"version": "21.3.3",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.3.tgz",
|
||||
"integrity": "sha512-pNwbwz8c3aZ+GvbJnIsCnDjKvgCZLHxkFWLEFxU3RMa+Ey++ZSEfisvsWQMcdys6PpxQjWUOIDi1fifXsW3YRg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/inflate": "^0.4.1",
|
||||
"strtok3": "^10.3.4",
|
||||
"token-types": "^6.1.1",
|
||||
"uint8array-extras": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/strtok3": {
|
||||
"version": "10.3.4",
|
||||
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz",
|
||||
"integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/token-types": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz",
|
||||
"integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@borewit/text-codec": "^0.2.1",
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"ieee754": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/uint8array-extras": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
|
||||
"integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@wecom/wecom-openclaw-plugin",
|
||||
"version": "1.0.11",
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"skills",
|
||||
"openclaw.plugin.json",
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"clean": "rm -rf dist",
|
||||
"prebuild": "npm run clean",
|
||||
"release": "node scripts/publish-all.mjs",
|
||||
"release:dry": "node scripts/publish-all.mjs --dry-run"
|
||||
},
|
||||
"keywords": [
|
||||
"wecom",
|
||||
"openclaw",
|
||||
"wecom-openclaw-plugin"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/WecomTeam/wecom-openclaw-plugin.git"
|
||||
},
|
||||
"homepage": "https://github.com/WecomTeam/wecom-openclaw-plugin#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/WecomTeam/wecom-openclaw-plugin/issues"
|
||||
},
|
||||
"description": "OpenClaw WeCom (企业微信) channel plugin (official by Tencent WeCom team)",
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./dist/index.esm.js"
|
||||
],
|
||||
"channel": {
|
||||
"id": "wecom",
|
||||
"label": "企业微信",
|
||||
"selectionLabel": "企业微信 (WeCom)",
|
||||
"docsPath": "/channels/wecom",
|
||||
"docsLabel": "wecom-openclaw-plugin",
|
||||
"blurb": "企业微信机器人接入插件",
|
||||
"order": 80,
|
||||
"quickstartAllowFrom": true
|
||||
},
|
||||
"install": {
|
||||
"npmSpec": "@wecom/wecom-openclaw-plugin",
|
||||
"localPath": "extensions/wecom-openclaw-plugin",
|
||||
"defaultChoice": "npm"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wecom/aibot-node-sdk": "^1.0.2",
|
||||
"file-type": "^21.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
---
|
||||
name: wecom-doc
|
||||
description: 文档与智能表格操作。当用户提到企业微信文档、创建文档、编辑文档、新建文档、写文档、智能表格时激活。支持文档创建/写入和智能表格的创建及子表/字段/记录写入。注意:所有文档创建和编辑请求都应使用此 skill,不要尝试用其他方式处理文档操作。
|
||||
metadata:
|
||||
{
|
||||
"openclaw":
|
||||
{
|
||||
"emoji": "📄",
|
||||
"always": true,
|
||||
"requires":
|
||||
{
|
||||
"bins": ["mcporter"],
|
||||
},
|
||||
"install":
|
||||
[
|
||||
{
|
||||
"id": "mcporter",
|
||||
"kind": "node",
|
||||
"package": "mcporter",
|
||||
"bins": ["mcporter"],
|
||||
"label": "Install mcporter (npm)",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
---
|
||||
|
||||
# 企业微信文档与智能表格工具
|
||||
|
||||
通过 mcporter 调用 wecom-doc MCP server 操作企业微信文档和智能表格。
|
||||
|
||||
## 意图处理
|
||||
|
||||
当用户说"创建文档"、"新建文档"、"帮我写个文档"等**不指定平台**的请求时,默认使用企业微信文档,无需询问用户使用什么平台。
|
||||
|
||||
## 前置检查(每次会话首次操作前必须执行)
|
||||
|
||||
> **自动续接原则**:前置检查中的任何环节如果需要等待用户确认或操作(安装、配置、授权等),在该操作完成后必须**自动继续执行后续步骤**,最终完成用户的原始请求。不要在中间环节完成后停下来等用户重复发起请求。
|
||||
|
||||
### 1. 检查 mcporter 是否安装
|
||||
|
||||
```bash
|
||||
which mcporter
|
||||
```
|
||||
|
||||
如果 `mcporter` **不在 PATH 中**(命令返回空或 not found),**必须原样输出以下提示**(不要修改、不要摘要):
|
||||
|
||||
> 企业微信文档能力依赖 mcporter 工具,当前尚未安装。需要我帮你安装吗?
|
||||
|
||||
输出提示后**停止执行并等待用户回复**。
|
||||
|
||||
- 如果用户**确认安装**(回复"好"、"是"、"装吧"、"yes"、"ok" 等肯定语义),**立即执行**:
|
||||
|
||||
```bash
|
||||
npm install -g mcporter
|
||||
```
|
||||
|
||||
安装完成后**不要停下来**,直接继续执行后续的「检查 MCP Server 是否配置」步骤,无缝衔接用户原始请求。
|
||||
|
||||
- 如果用户**拒绝安装**,回复:
|
||||
|
||||
> 好的,你也可以手动执行 `npm install -g mcporter` 安装后再找我。
|
||||
|
||||
### 2. 确保 MCP Server 已配置
|
||||
|
||||
确认 mcporter 存在后,先检查配置是否已存在,仅在未配置时才尝试自动配置。
|
||||
|
||||
**验证配置:**
|
||||
|
||||
```bash
|
||||
mcporter list wecom-doc --output json
|
||||
```
|
||||
|
||||
如果返回正常(包含 tool 列表),说明已配置,**跳过自动配置,直接进入步骤 3**。
|
||||
|
||||
如果返回 **server not found**、**unknown server** 或类似错误,执行下方自动配置。
|
||||
|
||||
**自动配置(仅在未配置时执行):**
|
||||
|
||||
读取 wecom 运行时配置文件,该文件由 wecom channel 长连接建立时写入:
|
||||
|
||||
```bash
|
||||
cat ~/.openclaw/wecomConfig/config.json
|
||||
```
|
||||
|
||||
检查 JSON 中是否存在 `mcpConfig.doc` 字段(含 `type` 和 `url`),如果存在则执行:
|
||||
|
||||
```bash
|
||||
mcporter config add wecom-doc \
|
||||
--type "<mcpConfig.doc.type的值>" \
|
||||
--url "<mcpConfig.doc.url的值>"
|
||||
```
|
||||
|
||||
配置完成后**再次执行** `mcporter list wecom-doc --output json` 验证。如果仍然失败,按下方"MCP Server 未配置"章节处理。
|
||||
|
||||
> ⚠️ 配置文件不存在或缺少 `mcpConfig.doc` 字段,说明 wecom channel 长连接尚未建立,应引导用户检查 wecom channel 是否正常运行。自动配置失败不应阻断流程,继续引导用户手动配置。
|
||||
|
||||
### 3. 获取 Tool 列表
|
||||
|
||||
`mcporter list` 成功后,返回的每个 tool 包含 `name`、`description`、`inputSchema`。
|
||||
|
||||
**不要硬编码 tool name 和参数**,据此构造 `mcporter call wecom-doc.<tool> --args '{...}' --output json` 调用。
|
||||
|
||||
## docid 管理规则(重要)
|
||||
|
||||
**仅支持对通过本 skill 创建的文档或智能表格进行编辑。**
|
||||
|
||||
### docid 的获取方式
|
||||
|
||||
docid **只能**通过 `create_doc` 的返回结果获取。创建成功后需要**保存返回的 docid**,后续编辑操作依赖此 ID。
|
||||
|
||||
### 不支持从 URL 解析 docid
|
||||
|
||||
从文档 URL(如 `https://doc.weixin.qq.com/doc/...`)中**无法解析到可用的 docid**。如果用户提供了文档 URL 并要求编辑,**不要尝试从 URL 中提取 docid**。
|
||||
|
||||
### 编辑操作的 docid 校验
|
||||
|
||||
当用户请求编辑文档或智能表格时,如果当前会话中**没有**通过 `create_doc` 获取到的 docid,**必须原样输出以下提示**(不要修改、不要摘要):
|
||||
|
||||
> 仅支持对机器人创建的文档进行编辑
|
||||
|
||||
### docid 类型判断
|
||||
|
||||
| doc_id 前缀 | 类型 | doc_type |
|
||||
|-------------|------|----------|
|
||||
| `w3_` | 文档 | 3 |
|
||||
| `s3_` | 智能表格 | 10 |
|
||||
|
||||
## 工作流
|
||||
|
||||
### 文档操作流
|
||||
|
||||
1. 如需新建文档 → `create_doc`(`doc_type: 3`)→ **保存返回的 `docid`**
|
||||
2. 如需编辑内容 → 先确认当前会话中有通过 `create_doc` 获取的 `docid`,若无则提示"仅支持对机器人创建的文档进行编辑" → `edit_doc_content`(`content_type: 1` 使用 markdown,全量覆写)
|
||||
|
||||
> `edit_doc_content` 是**全量覆写**操作。如需追加内容,应先了解原有内容再拼接。
|
||||
|
||||
### 智能表格操作流
|
||||
|
||||
操作层级:**文档(docid)→ 子表(sheet_id)→ 字段(field_id)/ 记录(record_id)**
|
||||
|
||||
1. 如需新建智能表 → `create_doc`(`doc_type: 10`)→ **保存返回的 `docid`**
|
||||
2. 如需编辑已有智能表 → 先确认当前会话中有通过 `create_doc` 获取的 `docid`,若无则提示"仅支持对机器人创建的文档进行编辑"
|
||||
3. 查询已有子表 → `smartsheet_get_sheet` → 获取 `sheet_id`
|
||||
4. 如需新建子表 → `smartsheet_add_sheet` → 获取新的 `sheet_id`
|
||||
5. 查询已有字段 → `smartsheet_get_fields` → 获取 `field_id`、`field_title`、`field_type`
|
||||
6. 如需添加字段 → `wedoc_smartsheet_add_fields`
|
||||
7. 如需更新字段 → `wedoc_smartsheet_update_fields`(**不能改变字段类型**)
|
||||
8. 添加数据记录 → `smartsheet_add_records`(values 的 key **必须**使用字段标题 field_title,不能用 field_id)
|
||||
|
||||
### 从零创建智能表完整流程
|
||||
|
||||
```
|
||||
create_doc(doc_type=10) → docid
|
||||
└→ smartsheet_add_sheet(docid) → sheet_id
|
||||
└→ wedoc_smartsheet_add_fields(docid, sheet_id, fields) → field_ids
|
||||
└→ smartsheet_add_records(docid, sheet_id, records)
|
||||
```
|
||||
|
||||
### 向已有智能表添加数据流程
|
||||
|
||||
```
|
||||
smartsheet_get_sheet(docid) → sheet_id
|
||||
└→ smartsheet_get_fields(docid, sheet_id) → field_ids + field_titles + field_types
|
||||
└→ smartsheet_add_records(docid, sheet_id, records)
|
||||
```
|
||||
|
||||
> **重要**:添加记录前**必须**先通过 `smartsheet_get_fields` 获取字段信息,确保 `values` 中的 key 和 value 格式正确。
|
||||
|
||||
## 业务知识(MCP Schema 中缺失的上下文)
|
||||
|
||||
以下信息是 MCP tool 的 inputSchema 中没有的,Agent 构造参数时必须参考。
|
||||
|
||||
### FieldType 枚举(16 种)
|
||||
|
||||
| 类型 | 说明 | 使用场景建议 |
|
||||
|------|------|-------------|
|
||||
| `FIELD_TYPE_TEXT` | 文本 | 通用文本内容;当用户只提供了成员**姓名**(而非 user_id)时,也应使用 TEXT 而非 USER |
|
||||
| `FIELD_TYPE_NUMBER` | 数字 | 数值型数据(金额、数量、评分等) |
|
||||
| `FIELD_TYPE_CHECKBOX` | 复选框 | 是/否、完成/未完成等布尔状态 |
|
||||
| `FIELD_TYPE_DATE_TIME` | 日期时间 | 日期、截止时间、创建时间等 |
|
||||
| `FIELD_TYPE_IMAGE` | 图片 | 需要展示图片的场景 |
|
||||
| `FIELD_TYPE_USER` | 成员 | **仅**在明确知道成员 user_id 时使用;若用户只提供了姓名,应使用 TEXT 代替 |
|
||||
| `FIELD_TYPE_URL` | 链接 | 网址、外部链接 |
|
||||
| `FIELD_TYPE_SELECT` | 多选 | 标签、多分类等允许多选的场景 |
|
||||
| `FIELD_TYPE_SINGLE_SELECT` | 单选 | 状态、优先级、严重程度、分类等有固定选项的字段 |
|
||||
| `FIELD_TYPE_PROGRESS` | 进度 | 完成进度、完成百分比(值为 0-100 整数) |
|
||||
| `FIELD_TYPE_PHONE_NUMBER` | 手机号 | 手机号码 |
|
||||
| `FIELD_TYPE_EMAIL` | 邮箱 | 邮箱地址 |
|
||||
| `FIELD_TYPE_LOCATION` | 位置 | 地理位置信息 |
|
||||
| `FIELD_TYPE_CURRENCY` | 货币 | 金额(带货币符号) |
|
||||
| `FIELD_TYPE_PERCENTAGE` | 百分比 | 百分比数值(值为 0~1) |
|
||||
| `FIELD_TYPE_BARCODE` | 条码 | 条形码、ISBN 等 |
|
||||
|
||||
### FieldType ↔ CellValue 对照表
|
||||
|
||||
添加记录(`smartsheet_add_records`)时,`values` 中每个字段的 **key 必须使用字段标题(field_title),不能使用 field_id**。value 必须匹配其字段类型:
|
||||
|
||||
| 字段类型 | CellValue 格式 | 示例 |
|
||||
|---------|---------------|------|
|
||||
| `TEXT` | CellTextValue 数组 | `[{"type": "text", "text": "内容"}]` |
|
||||
| `NUMBER` | number | `85` |
|
||||
| `CHECKBOX` | boolean | `true` |
|
||||
| `DATE_TIME` | 日期时间**字符串** | `"2023-01-01 12:00:00"`、`"2023-01-01 12:00"`、`"2023-01-01"` |
|
||||
| `URL` | CellUrlValue 数组(限 1 个) | `[{"type": "url", "text": "百度", "link": "https://baidu.com"}]` |
|
||||
| `USER` | CellUserValue 数组 | `[{"user_id": "zhangsan"}]` |
|
||||
| `IMAGE` | CellImageValue 数组 | `[{"image_url": "https://..."}]`(`id`、`title` 可选) |
|
||||
| `SELECT` | Option 数组(多选) | `[{"text": "选项A"}, {"text": "选项B"}]` |
|
||||
| `SINGLE_SELECT` | Option 数组(限 1 个) | `[{"text": "选项A"}]` |
|
||||
| `PROGRESS` | number(0~100 整数) | `85`(表示 85%) |
|
||||
| `CURRENCY` | number | `99.5` |
|
||||
| `PERCENTAGE` | number(0~1) | `0.85` |
|
||||
| `PHONE_NUMBER` | string | `"13800138000"` |
|
||||
| `EMAIL` | string | `"user@example.com"` |
|
||||
| `BARCODE` | string | `"978-3-16-148410-0"` |
|
||||
| `LOCATION` | CellLocationValue 数组(限 1 个) | `[{"source_type": 1, "id": "xxx", "latitude": "39.9", "longitude": "116.3", "title": "北京"}]` |
|
||||
|
||||
> **Option 格式说明**:`SINGLE_SELECT`/`SELECT` 的选项支持 `style` 字段(1~27 对应不同颜色),如 `[{"text": "紧急", "style": 1}]`。`style` 为可选字段,不传则使用默认颜色。
|
||||
|
||||
### 易错点
|
||||
|
||||
- `DATE_TIME` 的值是**日期时间字符串**,支持 `"YYYY-MM-DD HH:MM:SS"`(精确到秒)、`"YYYY-MM-DD HH:MM"`(精确到分)、`"YYYY-MM-DD"`(精确到天),系统自动按东八区转换为时间戳,无需手动计算
|
||||
- `CellUrlValue` 的链接字段名是 **`link`**,不是 `url`
|
||||
- `TEXT` 类型的值**必须**使用数组格式 `[{"type": "text", "text": "内容"}]`,外层方括号不可省略,不能传单个对象 `{"type":"text","text":"内容"}`
|
||||
- `SINGLE_SELECT`/`SELECT` 类型的值**必须**使用数组格式 `[{"text": "选项内容"}]`,不能直接传字符串
|
||||
- `PROGRESS` 的值范围是 **0~100 整数**(85 = 85%);`PERCENTAGE` 的值范围是 **0~1**(0.85 = 85%),两者不同注意区分
|
||||
- `wedoc_smartsheet_update_fields` **不能更改字段类型**,只能改标题和属性
|
||||
- `values` 的 key **必须**使用**字段标题**(field_title),**不能**使用 field_id
|
||||
- 不可写入的字段类型:创建时间、最后编辑时间、创建人、最后编辑人
|
||||
|
||||
## 错误处理
|
||||
|
||||
### MCP 错误响应处理(重要)
|
||||
|
||||
当 `mcporter call` 返回结果中 `errcode` 不为 0 且 `help_message` 有值时,将 `help_message` 中面向用户的内容输出给用户。
|
||||
|
||||
**输出原则:**
|
||||
|
||||
- **完整性**:不截断、不摘要
|
||||
- **原样性**:不改写、不重新排版
|
||||
- **用户可读性**:只输出面向用户的内容,过滤掉给 Agent 的元指令(如"将以下内容告知用户"等引导语);直接以 markdown 形式输出,不要包裹在代码块中
|
||||
|
||||
### 错误码 850001:需要用户提供配置
|
||||
|
||||
当 `errcode` 为 `850001` 时:
|
||||
|
||||
1. 将 `help_message` 中面向用户的内容**原样输出**(遵循上方输出原则)
|
||||
2. 输出后**等待用户回复**,后续流程与「MCP Server 未配置」的场景 A / B / C 一致。配置成功后**不要停下来**,立即回到「前置检查」的步骤 2 重新验证配置,验证通过后继续执行用户的原始请求。
|
||||
|
||||
### MCP Server 未配置
|
||||
|
||||
如果 `mcporter call wecom-doc.*` 或 `mcporter list wecom-doc` 返回 **server not found**、**unknown server** 或类似错误,说明 wecom-doc MCP server 尚未配置。
|
||||
|
||||
此时需要**检测运行环境**以提供针对性的提示。按以下步骤执行:
|
||||
|
||||
#### 步骤一:检测是否在 OpenClaw 运行时中
|
||||
|
||||
通过检测 `OPENCLAW_SHELL` 环境变量判断当前是否运行在 OpenClaw 中。该变量是 OpenClaw **运行时自动注入到子进程**中的标记,不会因为机器上安装了 openclaw 就存在——只有 skill 确实在 OpenClaw 中被调用时,exec 执行的命令才会携带此变量。
|
||||
|
||||
```bash
|
||||
echo "OPENCLAW_SHELL=${OPENCLAW_SHELL:-}" && command -v openclaw 2>/dev/null && echo "OPENCLAW_CLI=FOUND" || echo "OPENCLAW_CLI=NOT_FOUND"
|
||||
```
|
||||
|
||||
判断规则:
|
||||
- **`OPENCLAW_SHELL` 为空**(输出 `OPENCLAW_SHELL=`)→ 当前不在 OpenClaw 运行时中,跳到**「通用提示」**。
|
||||
- **`OPENCLAW_SHELL` 非空**(如 `exec`、`tui-local` 等)**且** `OPENCLAW_CLI=FOUND` → 确认在 OpenClaw 中且 CLI 可用,继续步骤二。
|
||||
- **`OPENCLAW_SHELL` 非空但 `OPENCLAW_CLI=NOT_FOUND`** → 虽然在 OpenClaw 中但 CLI 不可用,跳到**「通用提示」**。
|
||||
|
||||
> 为什么不能仅用 `command -v openclaw`:同一台机器可能同时安装了 openclaw 和 claude 等其他 AI 工具。仅检测 CLI 是否存在无法区分"装了 openclaw"和"正在 openclaw 中运行"。`OPENCLAW_SHELL` 是进程级的运行时标记,从根本上解决此问题。
|
||||
|
||||
#### 步骤二:查询 wecom channel 的 botId 配置
|
||||
|
||||
```bash
|
||||
openclaw config get channels.wecom.botId 2>&1
|
||||
```
|
||||
|
||||
- 如果命令返回了**具体的 botId 值**(非空、非报错),保存该值,跳到**「OpenClaw 场景 + 有 botId 提示」**。
|
||||
- 如果返回 `Config path not found`、报错或为空,跳到**「通用提示」**。
|
||||
|
||||
#### 异常容错
|
||||
|
||||
如果上述任何命令执行失败(如 exec 工具不可用、命令超时等),一律跳到**「通用提示」**。
|
||||
|
||||
---
|
||||
|
||||
#### OpenClaw 场景 + 有 botId 提示
|
||||
|
||||
当确认是 OpenClaw 环境且获取到了 botId 时,**必须原样输出以下提示**(不要修改、不要摘要),将 `XXXX` 替换为实际获取到的 botId 值:
|
||||
|
||||
> 若你是智能机器人创建者,可以[点击这里](https://work.weixin.qq.com/ai/aiHelper/authorizationPage?str_aibotid=XXXX&from=chat&forceInnerBrowser=1)授权当前机器人文档使用权限;
|
||||
> 若你不是机器人创建者,可联系该机器人创建者,前往企业微信「工作台-智能机器人」找到对应机器人进行授权
|
||||
> 若你已授权,可将对应机器人文档MCP的接入配置(StreamableHttp URL或者JSON Config)发送给我
|
||||
|
||||
输出提示后**等待用户回复**,后续流程与下方「通用提示」的后续流程一致(场景 A / B / C)。配置成功后**不要停下来**,立即回到「前置检查」的步骤 2 重新验证配置,验证通过后继续执行用户的原始请求。
|
||||
|
||||
---
|
||||
|
||||
#### 通用提示
|
||||
|
||||
当非 OpenClaw 环境、无法判断环境、或 OpenClaw 环境但未配置 wecom channel / botId 时,**必须原样输出以下提示文案**(不要修改、不要摘要):
|
||||
|
||||
> 机器人可通过MCP方式调用文档相关能力,当前暂未完成所需配置。请参考以下配置指引:
|
||||
>
|
||||
> 1. 请前往「企业微信-工作台-智能机器人应用」,以API模式创建机器人(如已创建,可忽略该步骤)
|
||||
>
|
||||
> 2. 授权该机器人「文档」使用权限。授权后,可自行选择StreamableHttp URL 或 JSON Config 进行配置。
|
||||
|
||||
输出提示后**等待用户回复**。用户可能:
|
||||
|
||||
**场景 A:用户提供了 StreamableHttp URL**
|
||||
|
||||
从用户消息中提取 URL,执行:
|
||||
|
||||
```bash
|
||||
mcporter config add wecom-doc \
|
||||
--type streamable-http \
|
||||
--url "<用户提供的URL>"
|
||||
```
|
||||
|
||||
配置完成后**不要停下来**,立即回到「前置检查」的步骤 2 重新验证配置,验证通过后继续执行用户的原始请求。
|
||||
|
||||
**场景 B:用户提供了 JSON 配置**
|
||||
|
||||
如果用户提供了类似以下格式的 JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "wecom-doc",
|
||||
"type": "streamable-http",
|
||||
"url": "http://xxx"
|
||||
}
|
||||
```
|
||||
|
||||
从 JSON 中提取 `url` 字段,执行:
|
||||
|
||||
```bash
|
||||
mcporter config add wecom-doc \
|
||||
--type streamable-http \
|
||||
--url "<从JSON提取的url>"
|
||||
```
|
||||
|
||||
配置完成后**不要停下来**,立即回到「前置检查」的步骤 2 重新验证配置,验证通过后继续执行用户的原始请求。
|
||||
|
||||
**场景 C:用户自行完成了配置**
|
||||
|
||||
用户可能在管理后台或其他途径自行完成配置后告知已配置好,此时直接继续执行原来的操作。
|
||||
|
||||
> **配置检测**:当用户输入的内容包含 URL(如 `http://...`)或 JSON(含 `"type": "streamable-http"`),应判断用户意图是在提供 MCP server 配置信息,自动执行配置命令。
|
||||
|
||||
### Daemon 未启动
|
||||
|
||||
如果返回 **connection refused** 或 **daemon not running** 错误,提示用户:
|
||||
|
||||
```bash
|
||||
mcporter daemon start
|
||||
```
|
||||
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 所有调用通过 `mcporter call wecom-doc.<tool>` 执行,不要直接调用企业微信 API
|
||||
- `create_doc` 返回的 `docid` 需要保存,后续操作依赖此 ID
|
||||
- 添加记录前**必须**先 `smartsheet_get_fields` 获取字段元信息
|
||||
@@ -0,0 +1,224 @@
|
||||
# 企业微信文档 API 参考
|
||||
|
||||
## 文档类型
|
||||
|
||||
| doc_type | 类型 | doc_id 前缀 | URL 路径 |
|
||||
|----------|------|------------|---------|
|
||||
| 3 | 文档 | `w3_` | `/doc/` |
|
||||
| 10 | 智能表格 | `s3_` | `/smartsheet/` |
|
||||
|
||||
## URL 格式
|
||||
|
||||
### 文档
|
||||
|
||||
```
|
||||
https://doc.weixin.qq.com/doc/{doc_id}?scode=xxx
|
||||
```
|
||||
|
||||
示例:
|
||||
```
|
||||
https://doc.weixin.qq.com/doc/w3_AMEA4QYkACkCNN7hNRzRzQkaElHbQ?scode=AJEAIQdfAAodYknI73AMEA4QYkACk
|
||||
→ doc_id = w3_AMEA4QYkACkCNN7hNRzRzQkaElHbQ
|
||||
```
|
||||
|
||||
### 智能表格
|
||||
|
||||
```
|
||||
https://doc.weixin.qq.com/smartsheet/{doc_id}
|
||||
```
|
||||
|
||||
示例:
|
||||
```
|
||||
https://doc.weixin.qq.com/smartsheet/s3_ATAA_QaoAKQCNIQ6XYeEYQ3q5Rv05
|
||||
→ doc_id = s3_ATAA_QaoAKQCNIQ6XYeEYQ3q5Rv05
|
||||
```
|
||||
|
||||
> 始终忽略 `?` 之后的查询参数。
|
||||
|
||||
## 智能表格字段类型(FieldType)
|
||||
|
||||
完整 16 种类型:
|
||||
|
||||
| 枚举值 | 说明 |
|
||||
|--------|------|
|
||||
| `FIELD_TYPE_TEXT` | 文本 |
|
||||
| `FIELD_TYPE_NUMBER` | 数字 |
|
||||
| `FIELD_TYPE_CHECKBOX` | 复选框 |
|
||||
| `FIELD_TYPE_DATE_TIME` | 日期时间 |
|
||||
| `FIELD_TYPE_IMAGE` | 图片 |
|
||||
| `FIELD_TYPE_USER` | 成员 |
|
||||
| `FIELD_TYPE_URL` | 链接 |
|
||||
| `FIELD_TYPE_SELECT` | 多选 |
|
||||
| `FIELD_TYPE_SINGLE_SELECT` | 单选 |
|
||||
| `FIELD_TYPE_PROGRESS` | 进度 |
|
||||
| `FIELD_TYPE_PHONE_NUMBER` | 手机号 |
|
||||
| `FIELD_TYPE_EMAIL` | 邮箱 |
|
||||
| `FIELD_TYPE_LOCATION` | 位置 |
|
||||
| `FIELD_TYPE_CURRENCY` | 货币 |
|
||||
| `FIELD_TYPE_PERCENTAGE` | 百分比 |
|
||||
| `FIELD_TYPE_BARCODE` | 条码 |
|
||||
|
||||
## CellValue 类型完整对照
|
||||
|
||||
### CellTextValue — 文本字段
|
||||
|
||||
```json
|
||||
[
|
||||
{"type": "text", "text": "普通文本"},
|
||||
{"type": "url", "text": "链接文本", "link": "https://example.com"}
|
||||
]
|
||||
```
|
||||
|
||||
- `type`(必填):`"text"` 或 `"url"`
|
||||
- `text`(必填):文本内容
|
||||
- `link`(当 type 为 url 时):链接跳转 URL
|
||||
|
||||
适用:`FIELD_TYPE_TEXT`
|
||||
|
||||
### 数字类 — number
|
||||
|
||||
直接传 number 值。
|
||||
|
||||
```json
|
||||
85
|
||||
```
|
||||
|
||||
适用:`FIELD_TYPE_NUMBER`、`FIELD_TYPE_PROGRESS`、`FIELD_TYPE_CURRENCY`、`FIELD_TYPE_PERCENTAGE`
|
||||
|
||||
### 布尔值 — boolean
|
||||
|
||||
```json
|
||||
true
|
||||
```
|
||||
|
||||
适用:`FIELD_TYPE_CHECKBOX`
|
||||
|
||||
### 字符串类 — string
|
||||
|
||||
直接传字符串。
|
||||
|
||||
适用场景:
|
||||
- `FIELD_TYPE_DATE_TIME`:毫秒 unix 时间戳字符串,如 `"1672531200000"`
|
||||
- `FIELD_TYPE_PHONE_NUMBER`:手机号字符串,如 `"13800138000"`
|
||||
- `FIELD_TYPE_EMAIL`:邮箱字符串,如 `"user@example.com"`
|
||||
- `FIELD_TYPE_BARCODE`:条码字符串,如 `"978-3-16-148410-0"`
|
||||
|
||||
### CellUrlValue — 链接字段
|
||||
|
||||
```json
|
||||
[{"type": "url", "text": "显示文本", "link": "https://example.com"}]
|
||||
```
|
||||
|
||||
- `type`(必填):固定 `"url"`
|
||||
- `link`(必填):链接跳转 URL
|
||||
- `text`(可选):链接显示文本
|
||||
|
||||
> 注意:字段名是 **`link`** 不是 `url`。数组为预留能力,目前只支持 1 个链接。
|
||||
|
||||
适用:`FIELD_TYPE_URL`
|
||||
|
||||
### CellUserValue — 成员字段
|
||||
|
||||
```json
|
||||
[{"user_id": "zhangsan"}]
|
||||
```
|
||||
|
||||
- `user_id`(必填):成员 ID
|
||||
|
||||
适用:`FIELD_TYPE_USER`
|
||||
|
||||
### CellImageValue — 图片字段
|
||||
|
||||
```json
|
||||
[{
|
||||
"id": "img1",
|
||||
"title": "截图",
|
||||
"image_url": "https://...",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
}]
|
||||
```
|
||||
|
||||
- `id`:图片 ID(自定义)
|
||||
- `title`:图片标题
|
||||
- `image_url`:图片链接(通过上传图片接口获取)
|
||||
- `width` / `height`:图片尺寸
|
||||
|
||||
适用:`FIELD_TYPE_IMAGE`
|
||||
|
||||
### CellAttachmentValue — 文件字段
|
||||
|
||||
```json
|
||||
[{
|
||||
"name": "文件名",
|
||||
"size": 1024,
|
||||
"file_ext": "DOC",
|
||||
"file_id": "xxx",
|
||||
"file_url": "https://...",
|
||||
"file_type": "50"
|
||||
}]
|
||||
```
|
||||
|
||||
- `file_ext` 取值:`DOC`、`SHEET`、`SLIDE`、`MIND`、`FLOWCHART`、`SMARTSHEET`、`FORM`,或文件扩展名
|
||||
- `file_type` 取值:`Folder`(文件夹)、`Wedrive`(微盘文件)、`30`(收集表)、`50`(文档)、`51`(表格)、`52`(幻灯片)、`54`(思维导图)、`55`(流程图)、`70`(智能表)
|
||||
|
||||
### Option — 选项(单选/多选字段)
|
||||
|
||||
```json
|
||||
[{"text": "选项A", "style": 1}, {"text": "选项B", "style": 5}]
|
||||
```
|
||||
|
||||
- `text`:选项内容。新增选项时填写,已存在时优先匹配
|
||||
- `id`(可选):选项 ID,已存在的选项通过 ID 识别
|
||||
- `style`(可选):选项颜色,1-27
|
||||
|
||||
适用:`FIELD_TYPE_SELECT`(多选,可传多个)、`FIELD_TYPE_SINGLE_SELECT`(单选,建议传 1 个)
|
||||
|
||||
### CellLocationValue — 位置字段
|
||||
|
||||
```json
|
||||
[{
|
||||
"source_type": 1,
|
||||
"id": "地点ID",
|
||||
"latitude": "39.9042",
|
||||
"longitude": "116.4074",
|
||||
"title": "北京天安门"
|
||||
}]
|
||||
```
|
||||
|
||||
- `source_type`(必填):固定 `1`(腾讯地图)
|
||||
- `id`(必填):地点 ID
|
||||
- `latitude`(必填):纬度(字符串)
|
||||
- `longitude`(必填):经度(字符串)
|
||||
- `title`(必填):地点名称
|
||||
|
||||
> 数组长度不大于 1。
|
||||
|
||||
适用:`FIELD_TYPE_LOCATION`
|
||||
|
||||
## 选项样式(Style)
|
||||
|
||||
取值 1-27 对应颜色:
|
||||
|
||||
| 值 | 颜色 | 值 | 颜色 | 值 | 颜色 |
|
||||
|----|------|----|------|----|------|
|
||||
| 1 | 浅红 | 10 | 浅蓝 | 19 | 浅橙 |
|
||||
| 2 | 浅橙 | 11 | 浅蓝 | 20 | 橙 |
|
||||
| 3 | 浅天蓝 | 12 | 蓝 | 21 | 浅黄 |
|
||||
| 4 | 浅绿 | 13 | 浅天蓝 | 22 | 浅黄 |
|
||||
| 5 | 浅紫 | 14 | 天蓝 | 23 | 黄 |
|
||||
| 6 | 浅粉红 | 15 | 浅绿 | 24 | 浅紫 |
|
||||
| 7 | 浅灰 | 16 | 绿 | 25 | 紫 |
|
||||
| 8 | 白 | 17 | 浅红 | 26 | 浅粉红 |
|
||||
| 9 | 灰 | 18 | 红 | 27 | 粉红 |
|
||||
|
||||
## 限制
|
||||
|
||||
| 维度 | 限制 |
|
||||
|------|------|
|
||||
| 文档名称 | 最多 255 字符 |
|
||||
| 子表字段数 | 单表最多 150 个 |
|
||||
| 记录数 | 单表最多 100,000 行 |
|
||||
| 单元格数 | 单表最多 15,000,000 个 |
|
||||
| 单次添加记录 | 建议 500 行内 |
|
||||
| 不可写入字段 | 创建时间、最后编辑时间、创建人、最后编辑人 |
|
||||
Reference in New Issue
Block a user