@xmanrui/dsh-im 4.22.0 → 4.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/lib/client.js +2505 -1152
- package/lib/index.js +290 -299
- package/package.json +8 -1
- package/plugin-src/client/channel-card-meta.js +2 -0
- package/plugin-src/client/channel-logos.js +11 -0
- package/plugin-src/client/channels/dingtalk/api.js +5 -1
- package/plugin-src/client/channels/dingtalk/index.js +15 -13
- package/plugin-src/client/channels/email/api.js +52 -0
- package/plugin-src/client/channels/email/index.js +728 -0
- package/plugin-src/client/channels/email/styles.js +51 -0
- package/plugin-src/client/channels/feishu/api.js +4 -2
- package/plugin-src/client/channels/feishu/index.js +16 -3
- package/plugin-src/client/channels/imessage/index.js +2 -1
- package/plugin-src/client/channels/office/api.js +2 -0
- package/plugin-src/client/channels/office/index.js +6 -5
- package/plugin-src/client/channels/qq/api.js +7 -0
- package/plugin-src/client/channels/qq/index.js +16 -3
- package/plugin-src/client/channels/shared/token-api.js +5 -1
- package/plugin-src/client/channels/shared/token-channel.js +28 -3
- package/plugin-src/client/channels/slack/index.js +2 -1
- package/plugin-src/client/channels/wecom/api.js +7 -1
- package/plugin-src/client/channels/wecom/index.js +16 -3
- package/plugin-src/client/channels/wecom-app/api.js +6 -1
- package/plugin-src/client/channels/wecom-app/index.js +16 -3
- package/plugin-src/client/channels/weixin/api.js +2 -1
- package/plugin-src/client/channels/weixin/connection-error.js +1 -71
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +16 -3
- package/plugin-src/client/connection-error.js +87 -0
- package/plugin-src/client/global-settings.js +81 -1
- package/plugin-src/client/i18n.js +61 -0
- package/plugin-src/client/index.js +30 -2
- package/plugin-src/client/last-message-error.js +2 -1
- package/plugin-src/client/styles.js +6 -0
- package/plugin-src/host/build.mjs +4 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +2 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +7 -4
- package/plugin-src/host/channels/email/availability.mjs +60 -0
- package/plugin-src/host/channels/email/index.mjs +36 -0
- package/plugin-src/host/channels/email/production.mjs +58 -0
- package/plugin-src/host/channels/email/rpc.mjs +163 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +7 -4
- package/plugin-src/host/channels/imessage/rpc.mjs +13 -6
- package/plugin-src/host/channels/office/rpc.mjs +3 -1
- package/plugin-src/host/channels/qq/production.mjs +2 -0
- package/plugin-src/host/channels/qq/rpc.mjs +5 -3
- package/plugin-src/host/channels/shared/production.mjs +52 -5
- package/plugin-src/host/channels/shared/rpc.mjs +5 -3
- package/plugin-src/host/channels/shared/startup-error.mjs +4 -3
- package/plugin-src/host/channels/shared/startup.mjs +11 -6
- package/plugin-src/host/channels/slack/production.mjs +2 -0
- package/plugin-src/host/channels/slack/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom/production.mjs +2 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom-app/production.mjs +2 -0
- package/plugin-src/host/channels/wecom-app/rpc.mjs +5 -3
- package/plugin-src/host/channels/weixin/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +5 -3
- package/plugin-src/host/delivery-adapter.mjs +11 -0
- package/plugin-src/host/image-input-rpc.mjs +24 -0
- package/plugin-src/host/inbound-ttl-rpc.mjs +6 -1
- package/plugin-src/host/index.mjs +3 -0
- package/plugin-src/management-rpc.mjs +12 -2
- package/scripts/verify-package.mjs +3 -1
- package/src/channels/dingtalk/connection-error.mjs +5 -12
- package/src/channels/dingtalk/device-auth.mjs +4 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +6 -4
- package/src/channels/dingtalk/dingtalk-controller.mjs +60 -45
- package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -4
- package/src/channels/discord/discord-api.mjs +3 -3
- package/src/channels/discord/discord-runtime.mjs +18 -12
- package/src/channels/email/config-store.mjs +194 -0
- package/src/channels/email/email-api.mjs +20 -0
- package/src/channels/email/email-bridge.mjs +17 -0
- package/src/channels/email/email-controller.mjs +824 -0
- package/src/channels/email/email-runtime.mjs +692 -0
- package/src/channels/email/harness-client.mjs +7 -0
- package/src/channels/email/mail-format.mjs +91 -0
- package/src/channels/email/state-store.mjs +176 -0
- package/src/channels/email/transport.mjs +64 -0
- package/src/channels/email/transports/agent-mail.mjs +593 -0
- package/src/channels/email/transports/agently-cli.mjs +288 -0
- package/src/channels/email/transports/imap-smtp.mjs +165 -0
- package/src/channels/feishu/bridge.mjs +8 -7
- package/src/channels/feishu/feishu-runtime.mjs +15 -8
- package/src/channels/feishu/group-message-permission-manager.mjs +2 -1
- package/src/channels/feishu/message-utils.mjs +3 -2
- package/src/channels/feishu/multi-bot-controller.mjs +56 -47
- package/src/channels/feishu/registration-manager.mjs +8 -4
- package/src/channels/feishu/repair-manager.mjs +2 -1
- package/src/channels/imessage/runtime.mjs +9 -5
- package/src/channels/office/office-controller.mjs +23 -12
- package/src/channels/office/office-runtime.mjs +6 -3
- package/src/channels/office/office-transport.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +7 -5
- package/src/channels/qq/qq-controller.mjs +56 -42
- package/src/channels/qq/qq-runtime.mjs +16 -11
- package/src/channels/shared/bot-workspace-store.mjs +29 -10
- package/src/channels/shared/connection-error.mjs +191 -0
- package/src/channels/shared/connection-test.mjs +3 -1
- package/src/channels/shared/conversation-state-store.mjs +46 -1
- package/src/channels/shared/diagnostic-details.mjs +95 -0
- package/src/channels/shared/harness-client.mjs +63 -18
- package/src/channels/shared/i18n-en/diagnostics.mjs +55 -0
- package/src/channels/shared/i18n-en/email.mjs +25 -0
- package/src/channels/shared/i18n-en/image-input.mjs +17 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +1 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +1 -1
- package/src/channels/shared/i18n-en.mjs +6 -0
- package/src/channels/shared/image-input-policy.mjs +35 -0
- package/src/channels/shared/image-input-settings-store.mjs +60 -0
- package/src/channels/shared/image-input.mjs +124 -0
- package/src/channels/shared/image-prompt.mjs +15 -7
- package/src/channels/shared/message-failure.mjs +12 -0
- package/src/channels/shared/semantic/reply-reference.mjs +3 -3
- package/src/channels/shared/text-harness-bridge.mjs +90 -11
- package/src/channels/shared/token-bot-controller.mjs +52 -47
- package/src/channels/slack/slack-api.mjs +3 -3
- package/src/channels/slack/slack-controller.mjs +59 -51
- package/src/channels/slack/slack-runtime.mjs +18 -12
- package/src/channels/telegram/telegram-api.mjs +2 -2
- package/src/channels/telegram/telegram-runtime.mjs +17 -13
- package/src/channels/wecom/wecom-bridge.mjs +15 -6
- package/src/channels/wecom/wecom-controller.mjs +51 -37
- package/src/channels/wecom/wecom-runtime.mjs +10 -6
- package/src/channels/wecom-app/wecom-app-bridge.mjs +7 -5
- package/src/channels/wecom-app/wecom-app-controller.mjs +50 -37
- package/src/channels/wecom-app/wecom-app-runtime.mjs +2 -1
- package/src/channels/weixin/connection-error.mjs +26 -92
- package/src/channels/weixin/diagnostic-details.mjs +1 -63
- package/src/channels/weixin/weixin-bridge.mjs +7 -5
- package/src/channels/whatsapp/whatsapp-controller.mjs +34 -17
- package/src/channels/whatsapp/whatsapp-runtime.mjs +35 -8
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { t } from '../shared/i18n.mjs';
|
|
2
|
+
import {
|
|
3
|
+
deriveTokenBotIdentity,
|
|
4
|
+
maskPlatformId,
|
|
5
|
+
TokenBotConfigStore,
|
|
6
|
+
} from '../shared/token-config-store.mjs';
|
|
7
|
+
|
|
8
|
+
const IDENTITY_OPTIONS = Object.freeze({
|
|
9
|
+
botPrefix: 'email',
|
|
10
|
+
tokenRefPrefix: 'DSH_EMAIL_PASSWORD',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const EMAIL_ADDRESS = /^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/;
|
|
14
|
+
const HOSTNAME = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* How the mailbox is reached. Each value maps to one transport implementation,
|
|
18
|
+
* so adding a mail protocol adds a transport rather than another channel.
|
|
19
|
+
*/
|
|
20
|
+
export const EMAIL_TRANSPORTS = Object.freeze({
|
|
21
|
+
'imap-smtp': Object.freeze({
|
|
22
|
+
key: 'imap-smtp',
|
|
23
|
+
label: 'IMAP / SMTP(任意邮箱)',
|
|
24
|
+
// Needs the address plus an app password, and knows its server hosts.
|
|
25
|
+
fields: Object.freeze(['address', 'password', 'provider', 'hosts', 'allowedSenders']),
|
|
26
|
+
}),
|
|
27
|
+
'agent-mail': Object.freeze({
|
|
28
|
+
key: 'agent-mail',
|
|
29
|
+
label: '腾讯 Agent 邮箱',
|
|
30
|
+
// Authorizes by QR code, so no password and no server hosts.
|
|
31
|
+
fields: Object.freeze(['address', 'allowedSenders']),
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const DEFAULT_EMAIL_TRANSPORT = 'imap-smtp';
|
|
36
|
+
|
|
37
|
+
/** Normalize a transport key, defaulting to the standard IMAP/SMTP one. */
|
|
38
|
+
export function normalizeEmailTransport(value) {
|
|
39
|
+
const key = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
40
|
+
if (!key) return DEFAULT_EMAIL_TRANSPORT;
|
|
41
|
+
if (!Object.hasOwn(EMAIL_TRANSPORTS, key)) {
|
|
42
|
+
throw new TypeError(`Unsupported email transport: ${value}`);
|
|
43
|
+
}
|
|
44
|
+
return key;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Well-known provider presets so users do not have to know IMAP/SMTP hosts. */
|
|
48
|
+
export const EMAIL_PROVIDERS = Object.freeze({
|
|
49
|
+
qq: Object.freeze({
|
|
50
|
+
key: 'qq', label: 'QQ 邮箱',
|
|
51
|
+
imapHost: 'imap.qq.com', imapPort: 993,
|
|
52
|
+
smtpHost: 'smtp.qq.com', smtpPort: 465,
|
|
53
|
+
}),
|
|
54
|
+
'163': Object.freeze({
|
|
55
|
+
key: '163', label: '163 邮箱',
|
|
56
|
+
imapHost: 'imap.163.com', imapPort: 993,
|
|
57
|
+
smtpHost: 'smtp.163.com', smtpPort: 465,
|
|
58
|
+
}),
|
|
59
|
+
gmail: Object.freeze({
|
|
60
|
+
key: 'gmail', label: 'Gmail',
|
|
61
|
+
imapHost: 'imap.gmail.com', imapPort: 993,
|
|
62
|
+
smtpHost: 'smtp.gmail.com', smtpPort: 465,
|
|
63
|
+
}),
|
|
64
|
+
custom: Object.freeze({ key: 'custom', label: '自定义' }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Email access is allowlist-only: a mail address is trivially forgeable, so an
|
|
69
|
+
* open mailbox would let anyone drive the Harness. Unlike other channels the
|
|
70
|
+
* policy is therefore not optional — `allowedSenders` must be non-empty.
|
|
71
|
+
*/
|
|
72
|
+
export function normalizeEmailAllowedSenders(value) {
|
|
73
|
+
if (value === undefined) return Object.freeze([]);
|
|
74
|
+
if (!Array.isArray(value)) throw new TypeError('allowedSenders must be an array of email addresses');
|
|
75
|
+
const normalized = value.map((entry) => {
|
|
76
|
+
const address = typeof entry === 'string' ? entry.trim().toLowerCase() : '';
|
|
77
|
+
if (!EMAIL_ADDRESS.test(address)) {
|
|
78
|
+
throw new TypeError('allowedSenders contains an invalid email address');
|
|
79
|
+
}
|
|
80
|
+
return address;
|
|
81
|
+
});
|
|
82
|
+
return Object.freeze([...new Set(normalized)]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function normalizeEmailAccessPolicy(value = {}) {
|
|
86
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
87
|
+
throw new TypeError('Email access policy must be an object');
|
|
88
|
+
}
|
|
89
|
+
return Object.freeze({ allowedSenders: normalizeEmailAllowedSenders(value.allowedSenders) });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizePort(value, fallback, label) {
|
|
93
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
94
|
+
const port = typeof value === 'number' ? value : Number.parseInt(String(value).trim(), 10);
|
|
95
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
|
|
96
|
+
throw new TypeError(`${label} must be a valid TCP port`);
|
|
97
|
+
}
|
|
98
|
+
return port;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeHost(value, label) {
|
|
102
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
103
|
+
const host = String(value).trim();
|
|
104
|
+
if (!HOSTNAME.test(host)) throw new TypeError(`${label} must be a valid hostname`);
|
|
105
|
+
return host;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Channel-specific fields persisted alongside the shared token identity.
|
|
110
|
+
* Only keys the caller actually supplied are written, so a later partial save
|
|
111
|
+
* spreads over the previous record (see `save`).
|
|
112
|
+
*/
|
|
113
|
+
function normalizeEmailBotExtension(value) {
|
|
114
|
+
const provider = typeof value.provider === 'string' ? value.provider.trim() : undefined;
|
|
115
|
+
const present = [
|
|
116
|
+
'transport', 'provider', 'imapHost', 'imapPort', 'smtpHost', 'smtpPort',
|
|
117
|
+
'allowedSenders',
|
|
118
|
+
];
|
|
119
|
+
if (!present.some((key) => Object.hasOwn(value, key))) return {};
|
|
120
|
+
const preset = EMAIL_PROVIDERS[provider] ?? null;
|
|
121
|
+
try {
|
|
122
|
+
const policy = normalizeEmailAccessPolicy({ allowedSenders: value.allowedSenders ?? [] });
|
|
123
|
+
const transport = Object.hasOwn(value, 'transport')
|
|
124
|
+
? normalizeEmailTransport(value.transport)
|
|
125
|
+
: undefined;
|
|
126
|
+
return {
|
|
127
|
+
...(transport ? { transport } : {}),
|
|
128
|
+
...(provider ? { provider } : {}),
|
|
129
|
+
imapHost: normalizeHost(value.imapHost, 'imapHost') ?? preset?.imapHost,
|
|
130
|
+
imapPort: normalizePort(value.imapPort, preset?.imapPort ?? 993, 'imapPort'),
|
|
131
|
+
smtpHost: normalizeHost(value.smtpHost, 'smtpHost') ?? preset?.smtpHost,
|
|
132
|
+
smtpPort: normalizePort(value.smtpPort, preset?.smtpPort ?? 465, 'smtpPort'),
|
|
133
|
+
allowedSenders: policy.allowedSenders,
|
|
134
|
+
};
|
|
135
|
+
} catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function deriveEmailBotIdentity(address) {
|
|
141
|
+
return deriveTokenBotIdentity(address, IDENTITY_OPTIONS);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function maskEmailBotId(address) {
|
|
145
|
+
const value = String(address ?? '');
|
|
146
|
+
const at = value.indexOf('@');
|
|
147
|
+
if (at <= 0) return maskPlatformId(value, t('邮箱'));
|
|
148
|
+
const name = value.slice(0, at);
|
|
149
|
+
const domain = value.slice(at);
|
|
150
|
+
const head = name.slice(0, Math.min(2, name.length));
|
|
151
|
+
return `${head}${'*'.repeat(Math.max(1, name.length - head.length))}${domain}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The mailbox address is the platform identity; the password lives in the
|
|
156
|
+
* credential store behind `tokenRef`.
|
|
157
|
+
*/
|
|
158
|
+
export function normalizeEmailAddress(value) {
|
|
159
|
+
const address = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
160
|
+
if (!EMAIL_ADDRESS.test(address)) throw new TypeError(t('邮箱地址格式不正确'));
|
|
161
|
+
return address;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const EMAIL_CLIENT_DEFAULTS = Object.freeze({
|
|
165
|
+
mailbox: 'INBOX',
|
|
166
|
+
pollIntervalMs: 20_000,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
export class EmailConfigStore extends TokenBotConfigStore {
|
|
171
|
+
constructor(path) {
|
|
172
|
+
super(path, {
|
|
173
|
+
channel: 'Email',
|
|
174
|
+
...IDENTITY_OPTIONS,
|
|
175
|
+
normalizeBotExtension: normalizeEmailBotExtension,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async save(value) {
|
|
180
|
+
// Merge over the stored record so a settings dialog can patch one field
|
|
181
|
+
// without resending hosts and the allowlist. The bot id and credential ref
|
|
182
|
+
// are derived from the address here, so callers only supply the mailbox.
|
|
183
|
+
const previous = value?.platformId ? this.getByPlatformId(String(value.platformId)) : null;
|
|
184
|
+
const merged = { ...previous, ...value };
|
|
185
|
+
const platformId = typeof merged.platformId === 'string' ? merged.platformId.trim().toLowerCase() : '';
|
|
186
|
+
if (platformId) {
|
|
187
|
+
const identity = deriveEmailBotIdentity(platformId);
|
|
188
|
+
merged.botId = identity.botId;
|
|
189
|
+
merged.tokenRef = identity.tokenRef;
|
|
190
|
+
merged.name = merged.name || platformId;
|
|
191
|
+
}
|
|
192
|
+
return super.save(merged);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility surface for the mailbox channel.
|
|
3
|
+
*
|
|
4
|
+
* The implementation moved to a transport seam so a second mail protocol can
|
|
5
|
+
* be added without another channel:
|
|
6
|
+
* mail-format.mjs transport-independent helpers
|
|
7
|
+
* transports/imap-smtp.mjs standard IMAP/SMTP mailbox
|
|
8
|
+
*
|
|
9
|
+
* This module re-exports those so existing imports keep working.
|
|
10
|
+
*/
|
|
11
|
+
export {
|
|
12
|
+
MAX_REPLY_CHARS,
|
|
13
|
+
imapSecurity,
|
|
14
|
+
normalizeAddress,
|
|
15
|
+
parseMessageIds,
|
|
16
|
+
resolveThreadKey,
|
|
17
|
+
smtpSecurity,
|
|
18
|
+
stripQuotedHistory,
|
|
19
|
+
} from './mail-format.mjs';
|
|
20
|
+
export { ImapSmtpTransport, ImapSmtpTransport as EmailApi } from './transports/imap-smtp.mjs';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { TextHarnessBridge, createTextBridgeStatus } from '../shared/text-harness-bridge.mjs';
|
|
2
|
+
|
|
3
|
+
export const EMAIL_DESCRIPTOR = Object.freeze({
|
|
4
|
+
key: 'email',
|
|
5
|
+
label: 'Email',
|
|
6
|
+
connectionLabel: ' IMAP/SMTP 邮箱',
|
|
7
|
+
// Email has no reaction concept; the fields stay for descriptor parity.
|
|
8
|
+
reactions: Object.freeze({ processing: '', success: '', error: '' }),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export class EmailHarnessBridge extends TextHarnessBridge {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super({ ...options, descriptor: EMAIL_DESCRIPTOR });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { createTextBridgeStatus as createEmailBridgeStatus };
|