@xmanrui/dsh-im 0.1.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.
Files changed (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/THIRD_PARTY_NOTICES.md +15 -0
  4. package/bin/dsh-im.mjs +99 -0
  5. package/cordis.patch.yml +3 -0
  6. package/lib/client.js +6211 -0
  7. package/lib/index.js +13339 -0
  8. package/package.json +73 -0
  9. package/plugin-src/client/build.mjs +38 -0
  10. package/plugin-src/client/channel-logos.js +107 -0
  11. package/plugin-src/client/channels/dingtalk/api.js +203 -0
  12. package/plugin-src/client/channels/dingtalk/index.js +709 -0
  13. package/plugin-src/client/channels/dingtalk/styles.js +140 -0
  14. package/plugin-src/client/channels/discord/api.js +11 -0
  15. package/plugin-src/client/channels/discord/index.js +25 -0
  16. package/plugin-src/client/channels/discord/styles.js +19 -0
  17. package/plugin-src/client/channels/feishu/api.js +325 -0
  18. package/plugin-src/client/channels/feishu/index.js +986 -0
  19. package/plugin-src/client/channels/feishu/styles.js +505 -0
  20. package/plugin-src/client/channels/qq/api.js +119 -0
  21. package/plugin-src/client/channels/qq/index.js +402 -0
  22. package/plugin-src/client/channels/qq/styles.js +19 -0
  23. package/plugin-src/client/channels/shared/token-api.js +88 -0
  24. package/plugin-src/client/channels/shared/token-channel.js +277 -0
  25. package/plugin-src/client/channels/telegram/api.js +11 -0
  26. package/plugin-src/client/channels/telegram/index.js +25 -0
  27. package/plugin-src/client/channels/telegram/styles.js +19 -0
  28. package/plugin-src/client/channels/wecom/api.js +119 -0
  29. package/plugin-src/client/channels/wecom/index.js +402 -0
  30. package/plugin-src/client/channels/wecom/styles.js +19 -0
  31. package/plugin-src/client/channels/weixin/api.js +156 -0
  32. package/plugin-src/client/channels/weixin/index.js +577 -0
  33. package/plugin-src/client/channels/weixin/styles.js +120 -0
  34. package/plugin-src/client/credential-binding.js +117 -0
  35. package/plugin-src/client/index.js +192 -0
  36. package/plugin-src/client/lifecycle.js +86 -0
  37. package/plugin-src/client/styles.js +192 -0
  38. package/plugin-src/host/build.mjs +24 -0
  39. package/plugin-src/host/channels/dingtalk/connection-supervisor.mjs +130 -0
  40. package/plugin-src/host/channels/dingtalk/index.mjs +39 -0
  41. package/plugin-src/host/channels/dingtalk/production.mjs +122 -0
  42. package/plugin-src/host/channels/dingtalk/rpc.mjs +237 -0
  43. package/plugin-src/host/channels/discord/index.mjs +30 -0
  44. package/plugin-src/host/channels/discord/production.mjs +17 -0
  45. package/plugin-src/host/channels/discord/rpc.mjs +21 -0
  46. package/plugin-src/host/channels/feishu/connection-supervisor.mjs +167 -0
  47. package/plugin-src/host/channels/feishu/controller.mjs +172 -0
  48. package/plugin-src/host/channels/feishu/credential-store.mjs +84 -0
  49. package/plugin-src/host/channels/feishu/index.mjs +73 -0
  50. package/plugin-src/host/channels/feishu/production.mjs +138 -0
  51. package/plugin-src/host/channels/feishu/rpc.mjs +454 -0
  52. package/plugin-src/host/channels/qq/connection-supervisor.mjs +124 -0
  53. package/plugin-src/host/channels/qq/index.mjs +30 -0
  54. package/plugin-src/host/channels/qq/production.mjs +109 -0
  55. package/plugin-src/host/channels/qq/rpc.mjs +146 -0
  56. package/plugin-src/host/channels/shared/connection-supervisor.mjs +133 -0
  57. package/plugin-src/host/channels/shared/production.mjs +104 -0
  58. package/plugin-src/host/channels/shared/rpc.mjs +119 -0
  59. package/plugin-src/host/channels/telegram/index.mjs +30 -0
  60. package/plugin-src/host/channels/telegram/production.mjs +17 -0
  61. package/plugin-src/host/channels/telegram/rpc.mjs +21 -0
  62. package/plugin-src/host/channels/wecom/connection-supervisor.mjs +124 -0
  63. package/plugin-src/host/channels/wecom/index.mjs +36 -0
  64. package/plugin-src/host/channels/wecom/production.mjs +113 -0
  65. package/plugin-src/host/channels/wecom/rpc.mjs +151 -0
  66. package/plugin-src/host/channels/weixin/connection-supervisor.mjs +127 -0
  67. package/plugin-src/host/channels/weixin/index.mjs +39 -0
  68. package/plugin-src/host/channels/weixin/production.mjs +119 -0
  69. package/plugin-src/host/channels/weixin/rpc.mjs +178 -0
  70. package/plugin-src/host/index.mjs +44 -0
  71. package/plugin-src/host/rpc-authority.mjs +11 -0
  72. package/scripts/verify-package.mjs +97 -0
  73. package/src/channels/dingtalk/config-store.mjs +282 -0
  74. package/src/channels/dingtalk/device-auth.mjs +237 -0
  75. package/src/channels/dingtalk/dingtalk-api.mjs +579 -0
  76. package/src/channels/dingtalk/dingtalk-bridge.mjs +281 -0
  77. package/src/channels/dingtalk/dingtalk-card-stream.mjs +233 -0
  78. package/src/channels/dingtalk/dingtalk-controller.mjs +752 -0
  79. package/src/channels/dingtalk/dingtalk-runtime.mjs +358 -0
  80. package/src/channels/dingtalk/harness-client.mjs +299 -0
  81. package/src/channels/dingtalk/state-store.mjs +212 -0
  82. package/src/channels/discord/config-store.mjs +24 -0
  83. package/src/channels/discord/discord-api.mjs +153 -0
  84. package/src/channels/discord/discord-bridge.mjs +15 -0
  85. package/src/channels/discord/discord-controller.mjs +16 -0
  86. package/src/channels/discord/discord-runtime.mjs +460 -0
  87. package/src/channels/discord/harness-client.mjs +3 -0
  88. package/src/channels/discord/state-store.mjs +3 -0
  89. package/src/channels/feishu/bridge.mjs +216 -0
  90. package/src/channels/feishu/config.mjs +71 -0
  91. package/src/channels/feishu/feishu-app.mjs +51 -0
  92. package/src/channels/feishu/feishu-channel.mjs +153 -0
  93. package/src/channels/feishu/feishu-runtime.mjs +222 -0
  94. package/src/channels/feishu/harness-client.mjs +268 -0
  95. package/src/channels/feishu/message-utils.mjs +51 -0
  96. package/src/channels/feishu/multi-bot-controller.mjs +686 -0
  97. package/src/channels/feishu/plugin-config-store.mjs +204 -0
  98. package/src/channels/feishu/plugin-controller.mjs +248 -0
  99. package/src/channels/feishu/registration-manager.mjs +345 -0
  100. package/src/channels/feishu/state-store.mjs +71 -0
  101. package/src/channels/qq/config-store.mjs +171 -0
  102. package/src/channels/qq/harness-client.mjs +3 -0
  103. package/src/channels/qq/qq-bridge.mjs +177 -0
  104. package/src/channels/qq/qq-controller.mjs +452 -0
  105. package/src/channels/qq/qq-runtime.mjs +208 -0
  106. package/src/channels/qq/qr-auth.mjs +24 -0
  107. package/src/channels/qq/state-store.mjs +96 -0
  108. package/src/channels/shared/conversation-state-store.mjs +107 -0
  109. package/src/channels/shared/editable-message-stream.mjs +81 -0
  110. package/src/channels/shared/text-harness-bridge.mjs +180 -0
  111. package/src/channels/shared/token-bot-controller.mjs +298 -0
  112. package/src/channels/shared/token-config-store.mjs +180 -0
  113. package/src/channels/telegram/config-store.mjs +24 -0
  114. package/src/channels/telegram/harness-client.mjs +3 -0
  115. package/src/channels/telegram/state-store.mjs +3 -0
  116. package/src/channels/telegram/telegram-api.mjs +120 -0
  117. package/src/channels/telegram/telegram-bridge.mjs +15 -0
  118. package/src/channels/telegram/telegram-controller.mjs +16 -0
  119. package/src/channels/telegram/telegram-runtime.mjs +278 -0
  120. package/src/channels/wecom/config-store.mjs +170 -0
  121. package/src/channels/wecom/harness-client.mjs +3 -0
  122. package/src/channels/wecom/qr-auth.mjs +95 -0
  123. package/src/channels/wecom/state-store.mjs +90 -0
  124. package/src/channels/wecom/wecom-bridge.mjs +243 -0
  125. package/src/channels/wecom/wecom-controller.mjs +474 -0
  126. package/src/channels/wecom/wecom-runtime.mjs +209 -0
  127. package/src/channels/weixin/config-store.mjs +182 -0
  128. package/src/channels/weixin/harness-client.mjs +259 -0
  129. package/src/channels/weixin/state-store.mjs +112 -0
  130. package/src/channels/weixin/weixin-api.mjs +319 -0
  131. package/src/channels/weixin/weixin-bridge.mjs +173 -0
  132. package/src/channels/weixin/weixin-controller.mjs +545 -0
  133. package/src/channels/weixin/weixin-runtime.mjs +204 -0
@@ -0,0 +1,204 @@
1
+ import { WeixinApiError } from './weixin-api.mjs';
2
+ import { createWeixinBridgeStatus, WeixinHarnessBridge } from './weixin-bridge.mjs';
3
+
4
+ function delay(ms, signal) {
5
+ return new Promise((resolve, reject) => {
6
+ if (signal?.aborted) {
7
+ reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
8
+ return;
9
+ }
10
+ const finish = () => {
11
+ signal?.removeEventListener('abort', onAbort);
12
+ resolve();
13
+ };
14
+ const timer = setTimeout(finish, ms);
15
+ const onAbort = () => {
16
+ clearTimeout(timer);
17
+ signal?.removeEventListener('abort', onAbort);
18
+ reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
19
+ };
20
+ signal?.addEventListener('abort', onAbort, { once: true });
21
+ });
22
+ }
23
+
24
+ export function createWeixinRuntimeStatus() {
25
+ return {
26
+ startedAt: null,
27
+ ready: false,
28
+ weixinConnectionState: 'idle',
29
+ harnessReachable: false,
30
+ lastCheckedAt: null,
31
+ lastError: null,
32
+ ...createWeixinBridgeStatus(),
33
+ };
34
+ }
35
+
36
+ export class WeixinRuntime {
37
+ #api;
38
+ #config;
39
+ #token;
40
+ #harness;
41
+ #state;
42
+ #logger;
43
+ #replyTimeoutMs;
44
+ #maxMessageChars;
45
+ #status = createWeixinRuntimeStatus();
46
+ #bridge = null;
47
+ #abortController = null;
48
+ #monitor = null;
49
+ #starting = null;
50
+
51
+ constructor({
52
+ api,
53
+ config,
54
+ token,
55
+ harness,
56
+ state,
57
+ logger = console,
58
+ replyTimeoutMs = 600_000,
59
+ maxMessageChars = 4_000,
60
+ }) {
61
+ if (!api || !config || !token || !harness || !state) {
62
+ throw new TypeError('WeixinRuntime requires API, account, token, Harness, and state');
63
+ }
64
+ this.#api = api;
65
+ this.#config = config;
66
+ this.#token = token;
67
+ this.#harness = harness;
68
+ this.#state = state;
69
+ this.#logger = logger;
70
+ this.#replyTimeoutMs = replyTimeoutMs;
71
+ this.#maxMessageChars = maxMessageChars;
72
+ }
73
+
74
+ get status() {
75
+ return structuredClone(this.#status);
76
+ }
77
+
78
+ async start() {
79
+ if (this.#status.ready && this.#monitor) return this.status;
80
+ if (this.#starting) return this.#starting;
81
+ this.#starting = this.#start().finally(() => {
82
+ this.#starting = null;
83
+ });
84
+ return this.#starting;
85
+ }
86
+
87
+ async #start() {
88
+ await this.stop();
89
+ this.#status.startedAt = new Date().toISOString();
90
+ this.#status.weixinConnectionState = 'connecting';
91
+ this.#status.lastError = null;
92
+ try {
93
+ await this.#harness.ensureRunning();
94
+ this.#status.harnessReachable = true;
95
+ await this.#api.notifyStart({
96
+ baseUrl: this.#config.baseUrl,
97
+ token: this.#token,
98
+ });
99
+ this.#bridge = new WeixinHarnessBridge({
100
+ api: this.#api,
101
+ baseUrl: this.#config.baseUrl,
102
+ token: this.#token,
103
+ ownerUserId: this.#config.ownerUserId,
104
+ harness: this.#harness,
105
+ state: this.#state,
106
+ status: this.#status,
107
+ logger: this.#logger,
108
+ replyTimeoutMs: this.#replyTimeoutMs,
109
+ maxMessageChars: this.#maxMessageChars,
110
+ });
111
+ this.#abortController = new AbortController();
112
+ this.#status.ready = true;
113
+ this.#status.weixinConnectionState = 'connected';
114
+ this.#status.lastCheckedAt = Date.now();
115
+ const signal = this.#abortController.signal;
116
+ this.#monitor = this.#runMonitor(signal).catch((error) => {
117
+ if (signal.aborted) return;
118
+ this.#status.ready = false;
119
+ this.#status.weixinConnectionState = 'failed';
120
+ this.#status.lastError = error?.message ?? String(error);
121
+ this.#logger.error?.(`[dsh-weixin] account ${this.#config.botId} monitor stopped:`, error);
122
+ });
123
+ return this.status;
124
+ } catch (error) {
125
+ this.#status.ready = false;
126
+ this.#status.weixinConnectionState = 'failed';
127
+ this.#status.lastError = error?.message ?? String(error);
128
+ throw error;
129
+ }
130
+ }
131
+
132
+ async #runMonitor(signal) {
133
+ let consecutiveFailures = 0;
134
+ while (!signal.aborted) {
135
+ try {
136
+ const response = await this.#api.getUpdates({
137
+ baseUrl: this.#config.baseUrl,
138
+ token: this.#token,
139
+ getUpdatesBuf: this.#state.getUpdatesBuf(),
140
+ signal,
141
+ });
142
+ if (signal.aborted) return;
143
+ const rejected = (response?.ret !== undefined && response.ret !== 0)
144
+ || (response?.errcode !== undefined && response.errcode !== 0);
145
+ if (rejected) {
146
+ const code = response.errcode ?? response.ret;
147
+ throw new WeixinApiError(
148
+ code === -14 ? 'stale-token' : 'updates-rejected',
149
+ code === -14 ? '微信登录凭据已失效,请移除账号后重新扫码。' : '微信消息同步请求被拒绝。',
150
+ );
151
+ }
152
+ consecutiveFailures = 0;
153
+ this.#status.ready = true;
154
+ this.#status.weixinConnectionState = 'connected';
155
+ this.#status.lastCheckedAt = Date.now();
156
+ this.#status.lastError = null;
157
+
158
+ for (const message of response?.msgs ?? []) {
159
+ await this.#bridge.accept(message);
160
+ }
161
+ if (typeof response?.get_updates_buf === 'string' && response.get_updates_buf) {
162
+ await this.#state.setGetUpdatesBuf(response.get_updates_buf);
163
+ }
164
+ } catch (error) {
165
+ if (signal.aborted) return;
166
+ consecutiveFailures += 1;
167
+ this.#status.lastError = error?.message ?? String(error);
168
+ this.#logger.warn?.(
169
+ `[dsh-weixin] account ${this.#config.botId} poll failed (${consecutiveFailures}/3):`,
170
+ error,
171
+ );
172
+ if (error instanceof WeixinApiError && error.code === 'stale-token') throw error;
173
+ if (consecutiveFailures >= 3) throw error;
174
+ await delay(Math.min(2_000 * (2 ** (consecutiveFailures - 1)), 10_000), signal);
175
+ }
176
+ }
177
+ }
178
+
179
+ async stop() {
180
+ const monitor = this.#monitor;
181
+ const bridge = this.#bridge;
182
+ const wasStarted = Boolean(this.#abortController || monitor || this.#status.ready);
183
+ this.#abortController?.abort();
184
+ this.#abortController = null;
185
+ this.#monitor = null;
186
+ await monitor?.catch(() => undefined);
187
+ await bridge?.waitForIdle();
188
+ this.#bridge = null;
189
+ if (wasStarted) {
190
+ try {
191
+ await this.#api.notifyStop({
192
+ baseUrl: this.#config.baseUrl,
193
+ token: this.#token,
194
+ signal: AbortSignal.timeout(10_000),
195
+ });
196
+ } catch (error) {
197
+ this.#logger.warn?.(`[dsh-weixin] account ${this.#config.botId} stop notification failed:`, error);
198
+ }
199
+ }
200
+ this.#status.ready = false;
201
+ this.#status.weixinConnectionState = 'idle';
202
+ return this.status;
203
+ }
204
+ }