@amaster.ai/pi-channels 0.1.2-beta.5 → 0.1.2-beta.51
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.md +90 -26
- package/dist/adapters/dingtalk.d.ts +5 -0
- package/dist/adapters/dingtalk.d.ts.map +1 -0
- package/dist/adapters/dingtalk.js +269 -0
- package/dist/adapters/dingtalk.js.map +1 -0
- package/dist/adapters/feishu.d.ts.map +1 -1
- package/dist/adapters/feishu.js +122 -5
- package/dist/adapters/feishu.js.map +1 -1
- package/dist/adapters/wecom/adapter.d.ts +3 -1
- package/dist/adapters/wecom/adapter.d.ts.map +1 -1
- package/dist/adapters/wecom/adapter.js +194 -157
- package/dist/adapters/wecom/adapter.js.map +1 -1
- package/dist/adapters/wecom.d.ts +0 -2
- package/dist/adapters/wecom.d.ts.map +1 -1
- package/dist/adapters/wecom.js +0 -1
- package/dist/adapters/wecom.js.map +1 -1
- package/dist/bridge-provider.d.ts +3 -0
- package/dist/bridge-provider.d.ts.map +1 -0
- package/dist/bridge-provider.js +79 -0
- package/dist/bridge-provider.js.map +1 -0
- package/dist/bridge.d.ts +1 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +318 -8
- package/dist/bridge.js.map +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +96 -40
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +562 -27
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts +12 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +93 -22
- package/dist/registry.js.map +1 -1
- package/dist/types.d.ts +38 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -5
- package/preview.png +0 -0
- package/dist/adapters/wecom/client.d.ts +0 -13
- package/dist/adapters/wecom/client.d.ts.map +0 -1
- package/dist/adapters/wecom/client.js +0 -116
- package/dist/adapters/wecom/client.js.map +0 -1
- package/dist/adapters/wecom/crypto.d.ts +0 -14
- package/dist/adapters/wecom/crypto.d.ts.map +0 -1
- package/dist/adapters/wecom/crypto.js +0 -37
- package/dist/adapters/wecom/crypto.js.map +0 -1
- package/dist/adapters/wecom/errors.d.ts +0 -25
- package/dist/adapters/wecom/errors.d.ts.map +0 -1
- package/dist/adapters/wecom/errors.js +0 -56
- package/dist/adapters/wecom/errors.js.map +0 -1
- package/dist/adapters/wecom/types.d.ts +0 -52
- package/dist/adapters/wecom/types.d.ts.map +0 -1
- package/dist/adapters/wecom/types.js +0 -2
- package/dist/adapters/wecom/types.js.map +0 -1
- package/dist/adapters/wecom/xml.d.ts +0 -4
- package/dist/adapters/wecom/xml.d.ts.map +0 -1
- package/dist/adapters/wecom/xml.js +0 -16
- package/dist/adapters/wecom/xml.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @amaster.ai/pi-channels
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
Pi extension for native messaging channels.
|
|
4
6
|
|
|
5
7
|
This package follows the same extension shape as the open-source `@e9n/pi-channels`
|
|
@@ -10,21 +12,37 @@ It does not depend on `@e9n/pi-channels` at runtime. That package is published a
|
|
|
10
12
|
a standalone extension source package with the older `@mariozechner/*` peer
|
|
11
13
|
namespace and Slack/Telegram dependencies. This package keeps the same channel
|
|
12
14
|
contract while targeting this repo's `@earendil-works/*` API surface and native
|
|
13
|
-
Feishu/WeCom adapters.
|
|
15
|
+
Feishu/DingTalk/WeCom adapters.
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
Put the `pi-channels` section in `~/.pi/agent/settings.json`, the configured
|
|
20
|
+
agent directory's `settings.json`, or a project/ancestor `.pi/settings.json`.
|
|
21
|
+
Project-local files are read only after project trust is accepted. `${ENV_VAR}`
|
|
22
|
+
interpolation is supported only in user and agent settings, not in project-local
|
|
23
|
+
files. The direct credential environment variables documented in the examples
|
|
24
|
+
still override loaded adapter values.
|
|
14
25
|
|
|
15
26
|
## Channels
|
|
16
27
|
|
|
17
28
|
- `feishu` - Native Feishu/Lark app messaging backed by the official
|
|
18
29
|
`@larksuiteoapi/node-sdk`. Supports outgoing text messages, WebSocket events
|
|
19
30
|
by default, and HTTP event callbacks as a fallback.
|
|
20
|
-
- `wecom` - Native WeCom
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
- `wecom` - Native WeCom intelligent bot messaging backed by the official
|
|
32
|
+
`@wecom/aibot-node-sdk`. Supports WebSocket long connection events and active
|
|
33
|
+
Markdown pushes with Bot ID / Secret credentials.
|
|
34
|
+
- `dingtalk` - Native DingTalk app bot messaging backed by the official
|
|
35
|
+
`dingtalk-stream` SDK. Supports Stream mode incoming bot messages, reply
|
|
36
|
+
messages through `sessionWebhook`, and active group text pushes through the
|
|
37
|
+
DingTalk OpenAPI.
|
|
24
38
|
- `webhook` - Generic outgoing HTTP requests.
|
|
25
39
|
|
|
26
40
|
## Example
|
|
27
41
|
|
|
42
|
+
The `${...}` placeholders below assume this section is stored in user or agent
|
|
43
|
+
settings. For trusted project settings, use the corresponding direct credential
|
|
44
|
+
environment variables or literal non-secret values.
|
|
45
|
+
|
|
28
46
|
```json
|
|
29
47
|
{
|
|
30
48
|
"pi-channels": {
|
|
@@ -38,17 +56,18 @@ Feishu/WeCom adapters.
|
|
|
38
56
|
},
|
|
39
57
|
"wecom": {
|
|
40
58
|
"type": "wecom",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
"botId": "${WECOM_BOT_ID}",
|
|
60
|
+
"secret": "${WECOM_BOT_SECRET}",
|
|
61
|
+
"eventMode": "websocket",
|
|
62
|
+
"respondToMentionsOnly": true,
|
|
63
|
+
"timeoutMs": 15000
|
|
64
|
+
},
|
|
65
|
+
"dingtalk": {
|
|
66
|
+
"type": "dingtalk",
|
|
67
|
+
"clientId": "${DINGTALK_CLIENT_ID}",
|
|
68
|
+
"clientSecret": "${DINGTALK_CLIENT_SECRET}",
|
|
69
|
+
"eventMode": "stream",
|
|
70
|
+
"respondToMentionsOnly": true
|
|
52
71
|
}
|
|
53
72
|
},
|
|
54
73
|
"routes": {
|
|
@@ -102,6 +121,46 @@ payloads:
|
|
|
102
121
|
|
|
103
122
|
Set `"eventMode": "off"` for outgoing-only usage.
|
|
104
123
|
|
|
124
|
+
### DingTalk modes
|
|
125
|
+
|
|
126
|
+
Create a DingTalk internal app, add the bot capability, select Stream mode, and
|
|
127
|
+
publish it. Copy the Client ID and Client Secret into the adapter config:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"type": "dingtalk",
|
|
132
|
+
"clientId": "${DINGTALK_CLIENT_ID}",
|
|
133
|
+
"clientSecret": "${DINGTALK_CLIENT_SECRET}",
|
|
134
|
+
"robotCode": "${DINGTALK_ROBOT_CODE}",
|
|
135
|
+
"eventMode": "stream",
|
|
136
|
+
"respondToMentionsOnly": true,
|
|
137
|
+
"allowedConversationIds": ["cid_xxx"]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Incoming replies use the per-message `sessionWebhook` when available. Active
|
|
142
|
+
route sends use DingTalk's group message OpenAPI with the route recipient as the
|
|
143
|
+
conversation/openConversationId. `robotCode` defaults to `clientId` when omitted.
|
|
144
|
+
|
|
145
|
+
Set `"eventMode": "off"` when the bot should only send messages.
|
|
146
|
+
|
|
147
|
+
### WeCom modes
|
|
148
|
+
|
|
149
|
+
Create an intelligent bot in the WeCom admin console, choose API mode, and select
|
|
150
|
+
the long connection option. Copy the Bot ID and Secret into the adapter config:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"type": "wecom",
|
|
155
|
+
"botId": "${WECOM_BOT_ID}",
|
|
156
|
+
"secret": "${WECOM_BOT_SECRET}",
|
|
157
|
+
"eventMode": "websocket",
|
|
158
|
+
"allowedChatIds": ["wr_xxx"]
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Set `"eventMode": "off"` when the bot should only send messages.
|
|
163
|
+
|
|
105
164
|
## Tool
|
|
106
165
|
|
|
107
166
|
```text
|
|
@@ -120,20 +179,24 @@ user_id:abc123
|
|
|
120
179
|
email:name@example.com
|
|
121
180
|
```
|
|
122
181
|
|
|
123
|
-
For WeCom, recipients
|
|
124
|
-
|
|
182
|
+
For WeCom intelligent bots, recipients are the conversation ids used by the Bot
|
|
183
|
+
API. In a group, send one message to the bot and use route capture to fill the
|
|
184
|
+
group `chatid`; for one-to-one pushes, use the target user's userid:
|
|
125
185
|
|
|
126
186
|
```text
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
tag:7
|
|
130
|
-
appchat:chat-id
|
|
187
|
+
wr_xxx
|
|
188
|
+
zhangsan
|
|
131
189
|
```
|
|
132
190
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
191
|
+
The intelligent bot adapter uses the WebSocket Bot API, so long connection mode
|
|
192
|
+
does not require a public callback URL.
|
|
193
|
+
|
|
194
|
+
For DingTalk app bots, send one message to the bot and use route capture to fill
|
|
195
|
+
the group conversation id:
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
cid_xxx
|
|
199
|
+
```
|
|
137
200
|
|
|
138
201
|
## Events
|
|
139
202
|
|
|
@@ -147,6 +210,7 @@ enterprise.
|
|
|
147
210
|
|
|
148
211
|
```text
|
|
149
212
|
/channel list
|
|
213
|
+
/channel reload
|
|
150
214
|
/channel bridge on
|
|
151
215
|
/channel bridge off
|
|
152
216
|
/channel bridge status
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AdapterConfig, ChannelAdapter } from '../types.js';
|
|
2
|
+
export declare function createDingTalkAdapter(config: AdapterConfig, context?: {
|
|
3
|
+
log?: (event: string, data?: Record<string, unknown>, level?: string) => void;
|
|
4
|
+
}): ChannelAdapter;
|
|
5
|
+
//# sourceMappingURL=dingtalk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dingtalk.d.ts","sourceRoot":"","sources":["../../src/adapters/dingtalk.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EAIf,MAAM,aAAa,CAAC;AAsMrB,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1E,GACL,cAAc,CA+IhB"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
const DEFAULT_OPEN_API_BASE_URL = 'https://api.dingtalk.com';
|
|
2
|
+
const DEFAULT_TOKEN_URL = 'https://oapi.dingtalk.com/gettoken';
|
|
3
|
+
const DINGTALK_TEXT_MSG_KEY = 'sampleText';
|
|
4
|
+
function asConfig(config) {
|
|
5
|
+
return config;
|
|
6
|
+
}
|
|
7
|
+
function requireString(value, name) {
|
|
8
|
+
if (typeof value === 'string' && value.trim())
|
|
9
|
+
return value.trim();
|
|
10
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
11
|
+
return String(value);
|
|
12
|
+
throw new Error(`DingTalk adapter requires ${name}`);
|
|
13
|
+
}
|
|
14
|
+
function optionalString(value) {
|
|
15
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
16
|
+
}
|
|
17
|
+
function normalizeConfig(config) {
|
|
18
|
+
const cfg = asConfig(config);
|
|
19
|
+
const clientId = requireString(cfg.clientId, 'clientId');
|
|
20
|
+
return {
|
|
21
|
+
...cfg,
|
|
22
|
+
clientId,
|
|
23
|
+
clientSecret: requireString(cfg.clientSecret, 'clientSecret'),
|
|
24
|
+
robotCode: optionalString(cfg.robotCode) ?? clientId,
|
|
25
|
+
eventMode: cfg.eventMode === 'off' ? 'off' : 'stream',
|
|
26
|
+
openApiBaseUrl: optionalString(cfg.openApiBaseUrl) ?? DEFAULT_OPEN_API_BASE_URL,
|
|
27
|
+
tokenUrl: optionalString(cfg.tokenUrl) ?? DEFAULT_TOKEN_URL,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function textFromMessage(message) {
|
|
31
|
+
if (!message.text)
|
|
32
|
+
throw new Error('DingTalk adapter requires text');
|
|
33
|
+
return message.source ? `[${message.source}]\n${message.text}` : message.text;
|
|
34
|
+
}
|
|
35
|
+
function shouldAcceptMessage(body, cfg) {
|
|
36
|
+
const conversationId = body.conversationId ?? '';
|
|
37
|
+
const allowedConversationIds = cfg.allowedConversationIds;
|
|
38
|
+
if (conversationId &&
|
|
39
|
+
Array.isArray(allowedConversationIds) &&
|
|
40
|
+
allowedConversationIds.length > 0 &&
|
|
41
|
+
!allowedConversationIds.includes(conversationId)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const senderId = body.senderId ?? body.senderStaffId ?? '';
|
|
45
|
+
const allowedSenderIds = cfg.allowedSenderIds;
|
|
46
|
+
if (senderId &&
|
|
47
|
+
Array.isArray(allowedSenderIds) &&
|
|
48
|
+
allowedSenderIds.length > 0 &&
|
|
49
|
+
!allowedSenderIds.includes(senderId)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if ((cfg.respondToMentionsOnly ?? true) &&
|
|
53
|
+
body.conversationType !== '1' &&
|
|
54
|
+
body.isInAtList === false) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
function senderForMessage(body) {
|
|
60
|
+
return body.conversationId ?? body.senderId ?? body.senderStaffId ?? '';
|
|
61
|
+
}
|
|
62
|
+
function metadataFromMessage(body) {
|
|
63
|
+
return {
|
|
64
|
+
messageId: body.msgId,
|
|
65
|
+
replyToMessageId: body.msgId,
|
|
66
|
+
conversationId: body.conversationId,
|
|
67
|
+
chatId: body.conversationId,
|
|
68
|
+
groupId: body.conversationId,
|
|
69
|
+
conversationTitle: body.conversationTitle,
|
|
70
|
+
chatName: body.conversationTitle,
|
|
71
|
+
groupName: body.conversationTitle,
|
|
72
|
+
conversationType: body.conversationType,
|
|
73
|
+
senderId: body.senderId,
|
|
74
|
+
senderStaffId: body.senderStaffId,
|
|
75
|
+
senderNick: body.senderNick,
|
|
76
|
+
senderName: body.senderNick,
|
|
77
|
+
sessionWebhook: body.sessionWebhook,
|
|
78
|
+
sessionWebhookExpiredTime: body.sessionWebhookExpiredTime,
|
|
79
|
+
robotCode: body.robotCode,
|
|
80
|
+
chatbotCorpId: body.chatbotCorpId,
|
|
81
|
+
chatbotUserId: body.chatbotUserId,
|
|
82
|
+
createAt: body.createAt,
|
|
83
|
+
rawMsgtype: body.msgtype,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function parseRobotMessage(downstream) {
|
|
87
|
+
if (downstream.headers.topic !== '/v1.0/im/bot/messages/get')
|
|
88
|
+
return undefined;
|
|
89
|
+
try {
|
|
90
|
+
const parsed = JSON.parse(downstream.data);
|
|
91
|
+
return parsed && typeof parsed === 'object' ? parsed : undefined;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function sessionWebhookFromMetadata(metadata) {
|
|
98
|
+
const value = metadata?.sessionWebhook;
|
|
99
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
100
|
+
}
|
|
101
|
+
function sessionWebhookIsFresh(metadata) {
|
|
102
|
+
const value = metadata?.sessionWebhookExpiredTime;
|
|
103
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
104
|
+
return true;
|
|
105
|
+
return value > Date.now();
|
|
106
|
+
}
|
|
107
|
+
function normalizeApiBaseUrl(value) {
|
|
108
|
+
return value.replace(/\/+$/, '');
|
|
109
|
+
}
|
|
110
|
+
async function readJsonResponse(response) {
|
|
111
|
+
const text = await response.text();
|
|
112
|
+
if (!text)
|
|
113
|
+
return undefined;
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(text);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return text;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function formatDingTalkApiError(body) {
|
|
122
|
+
if (!body || typeof body !== 'object' || Array.isArray(body))
|
|
123
|
+
return undefined;
|
|
124
|
+
const record = body;
|
|
125
|
+
const code = record.code ?? record.errcode;
|
|
126
|
+
if (code === undefined || code === 0 || code === '0')
|
|
127
|
+
return undefined;
|
|
128
|
+
const message = record.message ?? record.errmsg;
|
|
129
|
+
return `DingTalk API error ${String(code)}${message ? `: ${String(message)}` : ''}`;
|
|
130
|
+
}
|
|
131
|
+
export function createDingTalkAdapter(config, context = {}) {
|
|
132
|
+
const cfg = normalizeConfig(config);
|
|
133
|
+
let client = null;
|
|
134
|
+
let streamModulePromise = null;
|
|
135
|
+
let accessToken;
|
|
136
|
+
async function loadStreamModule() {
|
|
137
|
+
streamModulePromise ??= import('dingtalk-stream');
|
|
138
|
+
return streamModulePromise;
|
|
139
|
+
}
|
|
140
|
+
async function getAccessToken() {
|
|
141
|
+
if (accessToken && accessToken.expiresAt > Date.now())
|
|
142
|
+
return accessToken.token;
|
|
143
|
+
const url = new URL(cfg.tokenUrl);
|
|
144
|
+
url.searchParams.set('appkey', cfg.clientId);
|
|
145
|
+
url.searchParams.set('appsecret', cfg.clientSecret);
|
|
146
|
+
const response = await fetch(url);
|
|
147
|
+
const body = (await readJsonResponse(response));
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw new Error(`DingTalk token error ${response.status}: ${JSON.stringify(body)}`);
|
|
150
|
+
}
|
|
151
|
+
if (body?.errcode && body.errcode !== 0) {
|
|
152
|
+
throw new Error(`DingTalk token error ${body.errcode}: ${body.errmsg ?? 'unknown'}`);
|
|
153
|
+
}
|
|
154
|
+
if (!body?.access_token)
|
|
155
|
+
throw new Error('DingTalk token response missing access_token');
|
|
156
|
+
const expiresIn = Number.isFinite(body.expires_in) ? Number(body.expires_in) : 7200;
|
|
157
|
+
accessToken = {
|
|
158
|
+
token: body.access_token,
|
|
159
|
+
expiresAt: Date.now() + Math.max(60, expiresIn - 300) * 1000,
|
|
160
|
+
};
|
|
161
|
+
return accessToken.token;
|
|
162
|
+
}
|
|
163
|
+
async function sendViaSessionWebhook(url, text) {
|
|
164
|
+
const response = await fetch(url, {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
167
|
+
body: JSON.stringify({
|
|
168
|
+
msgtype: 'text',
|
|
169
|
+
text: { content: text },
|
|
170
|
+
}),
|
|
171
|
+
});
|
|
172
|
+
const body = await readJsonResponse(response);
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
throw new Error(`DingTalk session webhook error ${response.status}: ${JSON.stringify(body)}`);
|
|
175
|
+
}
|
|
176
|
+
const apiError = formatDingTalkApiError(body);
|
|
177
|
+
if (apiError)
|
|
178
|
+
throw new Error(apiError);
|
|
179
|
+
}
|
|
180
|
+
async function sendViaOpenApi(recipient, text) {
|
|
181
|
+
const conversationId = recipient.trim();
|
|
182
|
+
if (!conversationId)
|
|
183
|
+
throw new Error('DingTalk adapter requires recipient');
|
|
184
|
+
const token = await getAccessToken();
|
|
185
|
+
const response = await fetch(`${normalizeApiBaseUrl(cfg.openApiBaseUrl)}/v1.0/robot/groupMessages/send`, {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
headers: {
|
|
188
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
189
|
+
'x-acs-dingtalk-access-token': token,
|
|
190
|
+
},
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
robotCode: cfg.robotCode,
|
|
193
|
+
openConversationId: conversationId,
|
|
194
|
+
msgKey: DINGTALK_TEXT_MSG_KEY,
|
|
195
|
+
msgParam: JSON.stringify({ content: text }),
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
const body = await readJsonResponse(response);
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
throw new Error(`DingTalk send error ${response.status}: ${JSON.stringify(body)}`);
|
|
201
|
+
}
|
|
202
|
+
const apiError = formatDingTalkApiError(body);
|
|
203
|
+
if (apiError)
|
|
204
|
+
throw new Error(apiError);
|
|
205
|
+
}
|
|
206
|
+
async function sendText(message) {
|
|
207
|
+
const text = textFromMessage(message);
|
|
208
|
+
const sessionWebhook = sessionWebhookFromMetadata(message.metadata);
|
|
209
|
+
if (sessionWebhook && sessionWebhookIsFresh(message.metadata)) {
|
|
210
|
+
await sendViaSessionWebhook(sessionWebhook, text);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
await sendViaOpenApi(message.recipient, text);
|
|
214
|
+
}
|
|
215
|
+
async function start(onMessage) {
|
|
216
|
+
if (cfg.eventMode === 'off')
|
|
217
|
+
return;
|
|
218
|
+
if (client)
|
|
219
|
+
return;
|
|
220
|
+
const stream = await loadStreamModule();
|
|
221
|
+
client = new stream.DWClient({
|
|
222
|
+
clientId: cfg.clientId,
|
|
223
|
+
clientSecret: cfg.clientSecret,
|
|
224
|
+
...(cfg.ua ? { ua: cfg.ua } : { ua: 'amaster-pi-channels' }),
|
|
225
|
+
...(cfg.keepAlive !== undefined ? { keepAlive: cfg.keepAlive } : {}),
|
|
226
|
+
...(cfg.debug !== undefined ? { debug: cfg.debug } : {}),
|
|
227
|
+
});
|
|
228
|
+
client.registerCallbackListener(stream.TOPIC_ROBOT, (downstream) => {
|
|
229
|
+
try {
|
|
230
|
+
const body = parseRobotMessage(downstream);
|
|
231
|
+
if (!body)
|
|
232
|
+
return;
|
|
233
|
+
if (!shouldAcceptMessage(body, cfg))
|
|
234
|
+
return;
|
|
235
|
+
if (body.msgtype !== 'text')
|
|
236
|
+
return;
|
|
237
|
+
const text = body.text?.content?.trim();
|
|
238
|
+
const sender = senderForMessage(body);
|
|
239
|
+
if (!text || !sender)
|
|
240
|
+
return;
|
|
241
|
+
void onMessage({
|
|
242
|
+
adapter: 'dingtalk',
|
|
243
|
+
sender,
|
|
244
|
+
text,
|
|
245
|
+
metadata: metadataFromMessage(body),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
try {
|
|
250
|
+
client?.socketCallBackResponse(downstream.headers.messageId, null);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
context.log?.('dingtalk-stream-ack-failed', { error: error instanceof Error ? error.message : String(error) }, 'WARN');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
await client.connect();
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
direction: cfg.eventMode === 'off' ? 'outgoing' : 'bidirectional',
|
|
261
|
+
send: sendText,
|
|
262
|
+
start,
|
|
263
|
+
async stop() {
|
|
264
|
+
client?.disconnect();
|
|
265
|
+
client = null;
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=dingtalk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dingtalk.js","sourceRoot":"","sources":["../../src/adapters/dingtalk.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feishu.d.ts","sourceRoot":"","sources":["../../src/adapters/feishu.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EAIf,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"feishu.d.ts","sourceRoot":"","sources":["../../src/adapters/feishu.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EAIf,MAAM,aAAa,CAAC;AAyPrB,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1E,GACL,cAAc,CAoThB"}
|
package/dist/adapters/feishu.js
CHANGED
|
@@ -20,6 +20,61 @@ function parseTextContent(content) {
|
|
|
20
20
|
return { text: content };
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
function normalizeFeishuIncomingText(text, mentions, cfg, mentionedBot) {
|
|
24
|
+
let cleaned = text.replace(/\u00a0/g, ' ').trim();
|
|
25
|
+
cleaned = cleaned.replace(/^回复\s+[^::]{1,80}[::]\s*/u, '').trim();
|
|
26
|
+
if (mentions?.length) {
|
|
27
|
+
const botMentions = mentions.filter((mention) => isBotMention(mention, cfg));
|
|
28
|
+
const leadingMentions = botMentions.length > 0 ? botMentions : mentionedBot ? mentions : [];
|
|
29
|
+
cleaned = stripLeadingMentionTokens(cleaned, mentionTokens(leadingMentions));
|
|
30
|
+
}
|
|
31
|
+
if (mentionedBot) {
|
|
32
|
+
cleaned = cleaned.replace(/^@[\p{L}\p{N}_\-.·]+(?:\s+|$)/u, '').trim();
|
|
33
|
+
}
|
|
34
|
+
return cleaned;
|
|
35
|
+
}
|
|
36
|
+
function isBotMention(mention, cfg) {
|
|
37
|
+
if (mention.isBot)
|
|
38
|
+
return true;
|
|
39
|
+
const botOpenId = optionalNonEmptyString(cfg.botOpenId);
|
|
40
|
+
if (!botOpenId)
|
|
41
|
+
return false;
|
|
42
|
+
return (mention.openId === botOpenId ||
|
|
43
|
+
mention.userId === botOpenId ||
|
|
44
|
+
mention.id?.open_id === botOpenId ||
|
|
45
|
+
mention.id?.user_id === botOpenId ||
|
|
46
|
+
mention.id?.union_id === botOpenId);
|
|
47
|
+
}
|
|
48
|
+
function mentionTokens(mentions) {
|
|
49
|
+
const tokens = new Set();
|
|
50
|
+
for (const mention of mentions) {
|
|
51
|
+
if (mention.key?.trim())
|
|
52
|
+
tokens.add(mention.key.trim());
|
|
53
|
+
if (mention.name?.trim())
|
|
54
|
+
tokens.add(`@${mention.name.trim()}`);
|
|
55
|
+
}
|
|
56
|
+
return [...tokens].sort((a, b) => b.length - a.length);
|
|
57
|
+
}
|
|
58
|
+
function stripLeadingMentionTokens(text, tokens) {
|
|
59
|
+
let cleaned = text.trimStart();
|
|
60
|
+
for (let i = 0; i < 8; i++) {
|
|
61
|
+
const next = stripOneLeadingMentionToken(cleaned, tokens);
|
|
62
|
+
if (next === cleaned)
|
|
63
|
+
break;
|
|
64
|
+
cleaned = next.trimStart();
|
|
65
|
+
}
|
|
66
|
+
return cleaned.trim();
|
|
67
|
+
}
|
|
68
|
+
function stripOneLeadingMentionToken(text, tokens) {
|
|
69
|
+
for (const token of tokens) {
|
|
70
|
+
if (text === token)
|
|
71
|
+
return '';
|
|
72
|
+
if (text.startsWith(token) && /\s/u.test(text[token.length] ?? '')) {
|
|
73
|
+
return text.slice(token.length);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return text;
|
|
77
|
+
}
|
|
23
78
|
function resolveReceiveId(message, defaultType) {
|
|
24
79
|
const match = message.recipient.match(/^(chat_id|open_id|user_id|union_id|email):(.+)$/);
|
|
25
80
|
if (!match)
|
|
@@ -89,6 +144,17 @@ function shouldAcceptMessage(msg, cfg) {
|
|
|
89
144
|
function senderForMessage(chatId, threadId) {
|
|
90
145
|
return threadId ? `${chatId}:${threadId}` : chatId;
|
|
91
146
|
}
|
|
147
|
+
function objectStringField(value, key) {
|
|
148
|
+
if (!value || typeof value !== 'object')
|
|
149
|
+
return undefined;
|
|
150
|
+
const field = value[key];
|
|
151
|
+
return typeof field === 'string' && field.trim() ? field : undefined;
|
|
152
|
+
}
|
|
153
|
+
function ackReactionEmoji(cfg) {
|
|
154
|
+
if (cfg.ackReactionEmoji === false)
|
|
155
|
+
return null;
|
|
156
|
+
return optionalNonEmptyString(cfg.ackReactionEmoji) ?? 'OK';
|
|
157
|
+
}
|
|
92
158
|
export function createFeishuAdapter(config, context = {}) {
|
|
93
159
|
const cfg = asConfig(config);
|
|
94
160
|
const appId = requireString(cfg.appId, 'appId');
|
|
@@ -110,6 +176,50 @@ export function createFeishuAdapter(config, context = {}) {
|
|
|
110
176
|
});
|
|
111
177
|
let channel = null;
|
|
112
178
|
let server = null;
|
|
179
|
+
async function resolveChatName(chatId) {
|
|
180
|
+
const chatApi = client.im.chat;
|
|
181
|
+
if (!chatApi?.get)
|
|
182
|
+
return undefined;
|
|
183
|
+
try {
|
|
184
|
+
const response = await chatApi.get({ path: { chat_id: chatId } });
|
|
185
|
+
const code = response.code;
|
|
186
|
+
if (code !== undefined && code !== 0)
|
|
187
|
+
return undefined;
|
|
188
|
+
const data = response.data;
|
|
189
|
+
const chat = data && typeof data === 'object' ? data.chat : undefined;
|
|
190
|
+
return (objectStringField(data, 'name') ??
|
|
191
|
+
objectStringField(chat, 'name') ??
|
|
192
|
+
objectStringField(data, 'chat_name') ??
|
|
193
|
+
objectStringField(chat, 'chat_name'));
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
context.log?.('feishu-chat-name-fetch-failed', { chatId, error: error instanceof Error ? error.message : String(error) }, 'WARN');
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function addAckReaction(messageId) {
|
|
201
|
+
const emojiType = ackReactionEmoji(cfg);
|
|
202
|
+
if (!emojiType)
|
|
203
|
+
return;
|
|
204
|
+
try {
|
|
205
|
+
const response = (await client.request({
|
|
206
|
+
url: `/open-apis/im/v1/messages/${encodeURIComponent(messageId)}/reactions`,
|
|
207
|
+
method: 'POST',
|
|
208
|
+
data: {
|
|
209
|
+
reaction_type: {
|
|
210
|
+
emoji_type: emojiType,
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
}));
|
|
214
|
+
const body = response.data;
|
|
215
|
+
if (body?.code !== undefined && body.code !== 0) {
|
|
216
|
+
context.log?.('feishu-ack-reaction-failed', { messageId, emojiType, code: body.code, msg: body.msg }, 'WARN');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
context.log?.('feishu-ack-reaction-failed', { messageId, emojiType, error: error instanceof Error ? error.message : String(error) }, 'WARN');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
113
223
|
async function sendText(message) {
|
|
114
224
|
if (!message.text)
|
|
115
225
|
throw new Error('Feishu adapter requires text');
|
|
@@ -146,7 +256,7 @@ export function createFeishuAdapter(config, context = {}) {
|
|
|
146
256
|
throw new Error(`Feishu send error: ${response.msg ?? response.code ?? 'unknown'}`);
|
|
147
257
|
}
|
|
148
258
|
}
|
|
149
|
-
function handleNormalizedMessage(msg, onMessage) {
|
|
259
|
+
async function handleNormalizedMessage(msg, onMessage) {
|
|
150
260
|
if (!shouldAcceptMessage({
|
|
151
261
|
chatId: msg.chatId,
|
|
152
262
|
senderId: msg.senderId,
|
|
@@ -157,15 +267,19 @@ export function createFeishuAdapter(config, context = {}) {
|
|
|
157
267
|
}
|
|
158
268
|
if (msg.rawContentType !== 'text' && msg.rawContentType !== 'post')
|
|
159
269
|
return;
|
|
160
|
-
|
|
270
|
+
const text = normalizeFeishuIncomingText(msg.content, msg.mentions, cfg, msg.mentionedBot);
|
|
271
|
+
if (!text)
|
|
161
272
|
return;
|
|
273
|
+
void addAckReaction(msg.messageId);
|
|
274
|
+
const chatName = await resolveChatName(msg.chatId);
|
|
162
275
|
void onMessage({
|
|
163
276
|
adapter: 'feishu',
|
|
164
277
|
sender: senderForMessage(msg.chatId, msg.threadId),
|
|
165
|
-
text
|
|
278
|
+
text,
|
|
166
279
|
metadata: {
|
|
167
280
|
messageId: msg.messageId,
|
|
168
281
|
chatId: msg.chatId,
|
|
282
|
+
...(chatName ? { chatName } : {}),
|
|
169
283
|
chatType: msg.chatType,
|
|
170
284
|
senderId: msg.senderId,
|
|
171
285
|
senderName: msg.senderName,
|
|
@@ -197,9 +311,10 @@ export function createFeishuAdapter(config, context = {}) {
|
|
|
197
311
|
}, cfg)) {
|
|
198
312
|
return;
|
|
199
313
|
}
|
|
200
|
-
const text = parsed.text.
|
|
314
|
+
const text = normalizeFeishuIncomingText(parsed.text, data.message.mentions, cfg, mentionedBot);
|
|
201
315
|
if (!text)
|
|
202
316
|
return;
|
|
317
|
+
void addAckReaction(data.message.message_id);
|
|
203
318
|
void onMessage({
|
|
204
319
|
adapter: 'feishu',
|
|
205
320
|
sender: senderForMessage(data.message.chat_id, data.message.thread_id),
|
|
@@ -248,7 +363,9 @@ export function createFeishuAdapter(config, context = {}) {
|
|
|
248
363
|
? { wsConfig: { pingTimeout: cfg.wsPingTimeoutSeconds } }
|
|
249
364
|
: {}),
|
|
250
365
|
});
|
|
251
|
-
channel.on('message', (msg) =>
|
|
366
|
+
channel.on('message', (msg) => {
|
|
367
|
+
void handleNormalizedMessage(msg, onMessage);
|
|
368
|
+
});
|
|
252
369
|
channel.on('error', (error) => {
|
|
253
370
|
context.log?.('feishu-channel-error', { error: error.message, code: error.code }, 'ERROR');
|
|
254
371
|
});
|