@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,120 @@
1
+ export const WEIXIN_STYLE_ID = 'xmanrui-dsh-weixin-settings';
2
+
3
+ const CSS = String.raw`
4
+ .dxw-page {
5
+ --dxw-accent: #07c160;
6
+ --dxw-accent-dark: #05994c;
7
+ --dxw-success: var(--dsw-alias-state-success-primary, #20a162);
8
+ --dxw-warning: var(--dsw-alias-state-warning-primary, #d97706);
9
+ --dxw-error: var(--dsw-alias-state-error-primary, #d54941);
10
+ width: 100%;
11
+ max-width: 880px;
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: 18px;
15
+ padding: 2px 0 28px;
16
+ color: var(--dsw-alias-label-primary, #1f2329);
17
+ box-sizing: border-box;
18
+ }
19
+ .dxw-page *, .dxw-page *::before, .dxw-page *::after { box-sizing: border-box; }
20
+ .dxw-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
21
+ .dxw-heading h2, .dxw-heading p, .dxw-card h3, .dxw-card p { margin: 0; }
22
+ .dxw-eyebrow { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; font-weight: 650; letter-spacing: .08em; text-transform: uppercase; margin-bottom: 3px; }
23
+ .dxw-heading h2 { font-size: 20px; line-height: 28px; font-weight: 680; }
24
+ .dxw-heading p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; margin-top: 5px; white-space: nowrap; }
25
+ .dxw-tools, .dxw-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
26
+ .dxw-tools { width: 100%; justify-content: space-between; flex-wrap: nowrap; }
27
+ .dxw-badge { display: inline-flex; align-items: center; gap: 7px; min-height: 30px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 12px; white-space: nowrap; }
28
+ .dxw-dot { width: 8px; height: 8px; border-radius: 50%; background: #aeb3bb; flex: none; }
29
+ .dxw-dot[data-tone="success"] { background: var(--dxw-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dxw-success) 14%, transparent); }
30
+ .dxw-dot[data-tone="warning"] { background: var(--dxw-warning); }
31
+ .dxw-dot[data-tone="error"] { background: var(--dxw-error); }
32
+ .dxw-button { min-height: 34px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; padding: 0 13px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); font: inherit; font-size: 13px; font-weight: 560; cursor: pointer; text-decoration: none; transition: border-color .15s ease, background .15s ease, transform .15s ease; }
33
+ .dxw-button:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-fill-tertiary, #f7f8fa); }
34
+ .dxw-button:active:not(:disabled) { transform: translateY(1px); }
35
+ .dxw-button:focus-visible, .dxw-input:focus-visible { outline: 2px solid color-mix(in srgb, var(--dxw-accent) 70%, white); outline-offset: 2px; }
36
+ .dxw-button:disabled { cursor: not-allowed; opacity: .55; }
37
+ .dxw-button[data-kind="primary"] { color: white; border-color: var(--dxw-accent); background: var(--dxw-accent); }
38
+ .dxw-button[data-kind="primary"]:hover:not(:disabled) { border-color: var(--dxw-accent-dark); background: var(--dxw-accent-dark); }
39
+ .dxw-button[data-kind="danger"] { color: var(--dxw-error); }
40
+ .dxw-card { overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
41
+ .dxw-cardBody { padding: 24px; }
42
+ .dxw-empty { min-height: 230px; display: grid; grid-template-columns: minmax(0, 1fr) 180px; align-items: center; gap: 30px; }
43
+ .dxw-empty h3 { font-size: 18px; margin-bottom: 8px; }
44
+ .dxw-empty p { max-width: 560px; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; }
45
+ .dxw-empty .dxw-actions { margin-top: 20px; }
46
+ .dxw-logo { width: 110px; height: 110px; display: grid; place-items: center; justify-self: center; border-radius: 28px; color: white; background: var(--dxw-accent); box-shadow: 0 18px 45px rgb(7 193 96 / 22%); }
47
+ .dxw-logo svg { width: 62px; height: 62px; }
48
+ .dxw-qrLayout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 34px; align-items: center; }
49
+ .dxw-qrColumn { display: flex; flex-direction: column; align-items: center; gap: 12px; }
50
+ .dxw-qrFrame { position: relative; width: 270px; aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; padding: 10px; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 16px; background: white; }
51
+ .dxw-qrFrame img { display: block; width: 100%; height: 100%; object-fit: contain; }
52
+ .dxw-qrFallback { padding: 24px; text-align: center; color: #646a73; }
53
+ .dxw-expired { position: absolute; inset: 0; display: grid; place-items: center; padding: 30px; color: white; text-align: center; font-weight: 650; background: rgb(31 35 41 / 76%); backdrop-filter: blur(3px); }
54
+ .dxw-countdown { width: 270px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; }
55
+ .dxw-countdown div { display: flex; justify-content: space-between; margin-bottom: 6px; }
56
+ .dxw-progress { height: 4px; overflow: hidden; border-radius: 99px; background: #eef0f3; }
57
+ .dxw-progress span { display: block; width: var(--dxw-progress); height: 100%; background: var(--dxw-accent); transition: width .2s linear; }
58
+ .dxw-qrCopy h3 { margin: 9px 0 8px; font-size: 18px; }
59
+ .dxw-qrCopy > p { color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; }
60
+ .dxw-steps { margin: 18px 0 22px; padding-left: 22px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 1.9; }
61
+ .dxw-stateLabel { display: inline-flex; align-items: center; gap: 8px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; font-weight: 600; }
62
+ .dxw-verify { max-width: 560px; margin: 0 auto; padding: 32px; text-align: center; }
63
+ .dxw-verify h3 { margin: 8px 0; font-size: 19px; }
64
+ .dxw-verify p { color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.6; }
65
+ .dxw-codeRow { display: flex; justify-content: center; gap: 10px; margin: 24px 0 10px; }
66
+ .dxw-input { width: 190px; height: 42px; padding: 0 14px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 9px; background: var(--dsw-alias-bg-body, white); color: inherit; font: inherit; font-size: 18px; letter-spacing: .16em; text-align: center; }
67
+ .dxw-statusNotice, .dxw-error { display: flex; align-items: center; gap: 10px; padding: 13px 15px; border: 1px solid color-mix(in srgb, var(--dxw-error) 28%, transparent); border-radius: 10px; color: var(--dxw-error); background: color-mix(in srgb, var(--dxw-error) 7%, transparent); font-size: 13px; }
68
+ .dxw-error { align-items: flex-start; flex-direction: column; padding: 22px; }
69
+ .dxw-error h3 { font-size: 17px; }
70
+ .dxw-errorCode { font-family: ui-monospace, SFMono-Regular, monospace; font-size: 11px; opacity: .8; }
71
+ .dxw-listHeading { display: flex; justify-content: space-between; align-items: center; margin: 2px 0 9px; }
72
+ .dxw-listHeading h3 { margin: 0; font-size: 14px; }
73
+ .dxw-list { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; }
74
+ .dxw-accountTop { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
75
+ .dxw-accountIdentity { display: flex; align-items: center; gap: 12px; min-width: 0; }
76
+ .dxw-avatar { width: 42px; height: 42px; display: grid; place-items: center; flex: none; border-radius: 12px; color: white; background: var(--dxw-accent); }
77
+ .dxw-accountIdentity h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; }
78
+ .dxw-accountIdentity p { color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; margin-top: 4px; }
79
+ .dxw-health { display: inline-flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; white-space: nowrap; }
80
+ .dxw-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin: 20px 0; }
81
+ .dxw-metric { padding: 12px 14px; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); }
82
+ .dxw-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; }
83
+ .dxw-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
84
+ .dxw-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); }
85
+ .dxw-accountFooter .dxw-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; }
86
+ .dxw-accountFooter .dxw-button { flex: none; white-space: nowrap; }
87
+ .dxw-summary { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; }
88
+ .dxw-confirm { padding: 18px 24px; border-top: 1px solid color-mix(in srgb, var(--dxw-error) 25%, transparent); background: color-mix(in srgb, var(--dxw-error) 5%, transparent); }
89
+ .dxw-confirm strong { display: block; font-size: 14px; margin-bottom: 6px; }
90
+ .dxw-confirm p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 1.55; }
91
+ .dxw-confirm .dxw-actions { margin-top: 13px; }
92
+ .dxw-loading { padding: 36px; color: var(--dsw-alias-label-secondary, #646a73); text-align: center; }
93
+ .dxw-spinner { width: 24px; height: 24px; margin: 0 auto 12px; border: 3px solid #e6e8eb; border-top-color: var(--dxw-accent); border-radius: 50%; animation: dxw-spin .8s linear infinite; }
94
+ .dxw-visuallyHidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
95
+ @keyframes dxw-spin { to { transform: rotate(360deg); } }
96
+ @media (max-width: 720px) {
97
+ .dxw-heading, .dxw-accountTop { flex-direction: column; align-items: stretch; }
98
+ .dxw-empty { grid-template-columns: minmax(0, 1fr); }
99
+ .dxw-logo { display: none; }
100
+ .dxw-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; }
101
+ .dxw-qrCopy { width: 100%; }
102
+ .dxw-metrics { grid-template-columns: minmax(0, 1fr); }
103
+ .dxw-cardBody { padding: 20px; }
104
+ }
105
+ @media (prefers-reduced-motion: reduce) {
106
+ .dxw-page *, .dxw-page *::before, .dxw-page *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
107
+ }
108
+ `;
109
+
110
+ export function installWeixinStyles() {
111
+ if (typeof document === 'undefined') return () => {};
112
+ const existing = document.querySelector(`style[data-plugin-css="${WEIXIN_STYLE_ID}"]`);
113
+ if (existing) return () => {};
114
+ const style = document.createElement('style');
115
+ style.dataset.plugin = '@xmanrui/dsh-weixin';
116
+ style.dataset.pluginCss = WEIXIN_STYLE_ID;
117
+ style.textContent = CSS;
118
+ document.head.appendChild(style);
119
+ return () => style.remove();
120
+ }
@@ -0,0 +1,117 @@
1
+ import * as React from 'react';
2
+
3
+ const h = React.createElement;
4
+
5
+ function ActionIcon({ children }) {
6
+ return h('svg', {
7
+ className: 'dim-actionIcon',
8
+ width: 15,
9
+ height: 15,
10
+ viewBox: '0 0 20 20',
11
+ fill: 'none',
12
+ xmlns: 'http://www.w3.org/2000/svg',
13
+ 'aria-hidden': 'true',
14
+ focusable: 'false',
15
+ }, children);
16
+ }
17
+
18
+ export function QrActionIcon() {
19
+ return h(ActionIcon, null,
20
+ h('path', {
21
+ d: 'M2.5 2.5h5v5h-5v-5Zm10 0h5v5h-5v-5Zm-10 10h5v5h-5v-5Z',
22
+ stroke: 'currentColor', strokeWidth: '1.6', strokeLinejoin: 'round',
23
+ }),
24
+ h('path', {
25
+ d: 'M11.5 11.5h2v2h-2v-2Zm4 0h2v3h-2v-3Zm-4 4h3v2h-3v-2Zm5 1h1v1h-1v-1Z',
26
+ fill: 'currentColor',
27
+ }));
28
+ }
29
+
30
+ export function CredentialActionIcon() {
31
+ return h(ActionIcon, null,
32
+ h('circle', {
33
+ cx: '6.25', cy: '10', r: '3.5', stroke: 'currentColor', strokeWidth: '1.6',
34
+ }),
35
+ h('path', {
36
+ d: 'M9.75 10h7.75m-2.5 0v2m-2.5-2v2',
37
+ stroke: 'currentColor', strokeWidth: '1.6', strokeLinecap: 'round', strokeLinejoin: 'round',
38
+ }));
39
+ }
40
+
41
+ export function CredentialBindingPanel({
42
+ channel,
43
+ identityLabel,
44
+ identityPlaceholder,
45
+ secretLabel,
46
+ secretPlaceholder,
47
+ busy = false,
48
+ error = null,
49
+ onSubmit,
50
+ onCancel,
51
+ }) {
52
+ const [identity, setIdentity] = React.useState('');
53
+ const [secret, setSecret] = React.useState('');
54
+ const headingId = React.useId();
55
+ const hasIdentity = Boolean(identityLabel);
56
+
57
+ const submit = (event) => {
58
+ event.preventDefault();
59
+ const normalizedIdentity = identity.trim();
60
+ const normalizedSecret = secret.trim();
61
+ if ((hasIdentity && !normalizedIdentity) || !normalizedSecret || busy) return;
62
+ void onSubmit?.({ identity: normalizedIdentity, secret: normalizedSecret });
63
+ };
64
+
65
+ return h('section', {
66
+ className: 'ddt-card dim-surfaceCard dim-credentialPanel',
67
+ 'aria-labelledby': headingId,
68
+ },
69
+ h('h3', { id: headingId, className: 'dim-credentialTitle' }, `手动接入${channel}机器人`),
70
+ h('form', {
71
+ className: `dim-credentialForm${hasIdentity ? '' : ' dim-credentialFormSingle'}`,
72
+ onSubmit: submit,
73
+ },
74
+ hasIdentity ? h('label', { className: 'dim-credentialField' },
75
+ h('span', null, identityLabel),
76
+ h('input', {
77
+ value: identity,
78
+ onChange: (event) => setIdentity(event.target.value),
79
+ placeholder: identityPlaceholder,
80
+ maxLength: 512,
81
+ autoCapitalize: 'none',
82
+ autoCorrect: 'off',
83
+ spellCheck: false,
84
+ autoComplete: 'off',
85
+ disabled: busy,
86
+ required: true,
87
+ })) : null,
88
+ h('label', { className: 'dim-credentialField' },
89
+ h('span', null, secretLabel),
90
+ h('input', {
91
+ type: 'password',
92
+ value: secret,
93
+ onChange: (event) => setSecret(event.target.value),
94
+ placeholder: secretPlaceholder,
95
+ maxLength: 1024,
96
+ autoCapitalize: 'none',
97
+ autoCorrect: 'off',
98
+ spellCheck: false,
99
+ autoComplete: 'new-password',
100
+ disabled: busy,
101
+ required: true,
102
+ })),
103
+ error ? h('p', { className: 'dim-credentialError', role: 'alert' }, error.message ?? String(error)) : null,
104
+ h('div', { className: 'ddt-actions dim-viewActions dim-credentialActions' },
105
+ h('button', {
106
+ type: 'submit',
107
+ className: 'ddt-button',
108
+ 'data-kind': 'primary',
109
+ disabled: busy || (hasIdentity && !identity.trim()) || !secret.trim(),
110
+ }, busy ? '正在绑定…' : '绑定并连接'),
111
+ h('button', {
112
+ type: 'button',
113
+ className: 'ddt-button',
114
+ onClick: onCancel,
115
+ disabled: busy,
116
+ }, '取消'))));
117
+ }
@@ -0,0 +1,192 @@
1
+ import * as React from 'react';
2
+
3
+ import {
4
+ DingtalkLogoGlyph,
5
+ DiscordLogoGlyph,
6
+ FeishuLogoGlyph,
7
+ QqLogoGlyph,
8
+ TelegramLogoGlyph,
9
+ WecomLogoGlyph,
10
+ WeixinLogoGlyph,
11
+ } from './channel-logos.js';
12
+ import { DINGTALK_RPC_CHANNEL } from './channels/dingtalk/api.js';
13
+ import { DingtalkSettingsTab } from './channels/dingtalk/index.js';
14
+ import { DISCORD_RPC_CHANNEL } from './channels/discord/api.js';
15
+ import { DiscordSettingsTab } from './channels/discord/index.js';
16
+ import { installDiscordStyles } from './channels/discord/styles.js';
17
+ import { FeishuSettingsTab } from './channels/feishu/index.js';
18
+ import { FEISHU_RPC_CHANNEL } from './channels/feishu/api.js';
19
+ import { installFeishuStyles } from './channels/feishu/styles.js';
20
+ import { QQ_RPC_CHANNEL } from './channels/qq/api.js';
21
+ import { QqSettingsTab } from './channels/qq/index.js';
22
+ import { installQqStyles } from './channels/qq/styles.js';
23
+ import { TELEGRAM_RPC_CHANNEL } from './channels/telegram/api.js';
24
+ import { TelegramSettingsTab } from './channels/telegram/index.js';
25
+ import { installTelegramStyles } from './channels/telegram/styles.js';
26
+ import { WECOM_RPC_CHANNEL } from './channels/wecom/api.js';
27
+ import { WecomSettingsTab } from './channels/wecom/index.js';
28
+ import { installWecomStyles } from './channels/wecom/styles.js';
29
+ import { WeixinSettingsTab } from './channels/weixin/index.js';
30
+ import { WEIXIN_RPC_CHANNEL } from './channels/weixin/api.js';
31
+ import { installWeixinStyles } from './channels/weixin/styles.js';
32
+ import { installImStyles } from './styles.js';
33
+
34
+ const h = React.createElement;
35
+
36
+ export const name = 'im-settings';
37
+ export const inject = ['slots', 'connection'];
38
+
39
+ const CHANNELS = Object.freeze([
40
+ { id: 'weixin', label: '微信' },
41
+ { id: 'feishu', label: '飞书' },
42
+ { id: 'dingtalk', label: '钉钉' },
43
+ { id: 'wecom', label: '企业微信' },
44
+ { id: 'qq', label: 'QQ' },
45
+ { id: 'telegram', label: 'Telegram' },
46
+ { id: 'discord', label: 'Discord' },
47
+ ]);
48
+
49
+ function WeixinLogo() {
50
+ return h('span', { className: 'dim-logo dim-logoWeixin', 'aria-hidden': 'true' },
51
+ h(WeixinLogoGlyph));
52
+ }
53
+
54
+ function FeishuLogo() {
55
+ return h('span', { className: 'dim-logo dim-logoFeishu', 'aria-hidden': 'true' },
56
+ h(FeishuLogoGlyph));
57
+ }
58
+
59
+ function DingtalkLogo() {
60
+ return h('span', { className: 'dim-logo dim-logoDingtalk', 'aria-hidden': 'true' },
61
+ h(DingtalkLogoGlyph));
62
+ }
63
+
64
+ function QqLogo() {
65
+ return h('span', { className: 'dim-logo dim-logoQq', 'aria-hidden': 'true' }, h(QqLogoGlyph));
66
+ }
67
+
68
+ function WecomLogo() {
69
+ return h('span', { className: 'dim-logo dim-logoWecom', 'aria-hidden': 'true' }, h(WecomLogoGlyph));
70
+ }
71
+
72
+ function TelegramLogo() {
73
+ return h('span', { className: 'dim-logo dim-logoTelegram', 'aria-hidden': 'true' },
74
+ h(TelegramLogoGlyph));
75
+ }
76
+
77
+ function DiscordLogo() {
78
+ return h('span', { className: 'dim-logo dim-logoDiscord', 'aria-hidden': 'true' },
79
+ h(DiscordLogoGlyph));
80
+ }
81
+
82
+ function ChannelLogo({ channel }) {
83
+ if (channel === 'weixin') return h(WeixinLogo);
84
+ if (channel === 'feishu') return h(FeishuLogo);
85
+ if (channel === 'dingtalk') return h(DingtalkLogo);
86
+ if (channel === 'wecom') return h(WecomLogo);
87
+ if (channel === 'qq') return h(QqLogo);
88
+ if (channel === 'telegram') return h(TelegramLogo);
89
+ return h(DiscordLogo);
90
+ }
91
+
92
+ export function IMSettingsTab({
93
+ dingtalkRpcCall,
94
+ discordRpcCall,
95
+ feishuRpcCall,
96
+ qqRpcCall,
97
+ telegramRpcCall,
98
+ wecomRpcCall,
99
+ weixinRpcCall,
100
+ }) {
101
+ const [selected, setSelected] = React.useState('weixin');
102
+ const active = CHANNELS.find((channel) => channel.id === selected) ?? CHANNELS[0];
103
+ return h('section', { className: 'dim-page', 'aria-label': 'IM机器人设置' },
104
+ h('header', { className: 'dim-title' },
105
+ h('p', null, '把机器人接入 DeepSeek Harness'),
106
+ ),
107
+ h('div', { className: 'dim-layout' },
108
+ h('nav', { className: 'dim-rail', role: 'tablist', 'aria-label': 'IM 渠道' },
109
+ CHANNELS.map((channel) => h('button', {
110
+ key: channel.id,
111
+ type: 'button',
112
+ role: 'tab',
113
+ id: `dim-tab-${channel.id}`,
114
+ className: 'dim-channel',
115
+ 'aria-selected': channel.id === active.id,
116
+ 'aria-controls': `dim-panel-${channel.id}`,
117
+ onClick: () => setSelected(channel.id),
118
+ },
119
+ h(ChannelLogo, { channel: channel.id }),
120
+ h('span', { className: 'dim-channelCopy' },
121
+ h('strong', null, channel.label),
122
+ )))),
123
+ h('div', { className: 'dim-divider', 'aria-hidden': 'true' }),
124
+ h('main', {
125
+ className: 'dim-panel',
126
+ role: 'tabpanel',
127
+ id: `dim-panel-${active.id}`,
128
+ 'aria-labelledby': `dim-tab-${active.id}`,
129
+ }, active.id === 'weixin'
130
+ ? h(WeixinSettingsTab, { rpcCall: weixinRpcCall })
131
+ : active.id === 'feishu'
132
+ ? h(FeishuSettingsTab, { rpcCall: feishuRpcCall })
133
+ : active.id === 'dingtalk'
134
+ ? h(DingtalkSettingsTab, { rpcCall: dingtalkRpcCall })
135
+ : active.id === 'wecom'
136
+ ? h(WecomSettingsTab, { rpcCall: wecomRpcCall })
137
+ : active.id === 'qq'
138
+ ? h(QqSettingsTab, { rpcCall: qqRpcCall })
139
+ : active.id === 'telegram'
140
+ ? h(TelegramSettingsTab, { rpcCall: telegramRpcCall })
141
+ : h(DiscordSettingsTab, { rpcCall: discordRpcCall })),
142
+ ),
143
+ );
144
+ }
145
+
146
+ export function apply(ctx) {
147
+ ctx.effect(() => {
148
+ const disposers = [
149
+ installFeishuStyles(),
150
+ installWeixinStyles(),
151
+ installWecomStyles(),
152
+ installQqStyles(),
153
+ installTelegramStyles(),
154
+ installDiscordStyles(),
155
+ installImStyles(),
156
+ ];
157
+ return () => {
158
+ for (const dispose of disposers.reverse()) dispose();
159
+ };
160
+ }, 'im-settings: install combined channel styles');
161
+
162
+ const feishuRpcCall = (endpoint, payload, signal) =>
163
+ ctx.connection.rpc.call(FEISHU_RPC_CHANNEL, endpoint, payload, signal);
164
+ const weixinRpcCall = (endpoint, payload, signal) =>
165
+ ctx.connection.rpc.call(WEIXIN_RPC_CHANNEL, endpoint, payload, signal);
166
+ const dingtalkRpcCall = (endpoint, payload, signal) =>
167
+ ctx.connection.rpc.call(DINGTALK_RPC_CHANNEL, endpoint, payload, signal);
168
+ const qqRpcCall = (endpoint, payload, signal) =>
169
+ ctx.connection.rpc.call(QQ_RPC_CHANNEL, endpoint, payload, signal);
170
+ const wecomRpcCall = (endpoint, payload, signal) =>
171
+ ctx.connection.rpc.call(WECOM_RPC_CHANNEL, endpoint, payload, signal);
172
+ const telegramRpcCall = (endpoint, payload, signal) =>
173
+ ctx.connection.rpc.call(TELEGRAM_RPC_CHANNEL, endpoint, payload, signal);
174
+ const discordRpcCall = (endpoint, payload, signal) =>
175
+ ctx.connection.rpc.call(DISCORD_RPC_CHANNEL, endpoint, payload, signal);
176
+
177
+ ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
178
+ name: 'settings.plugins.tab',
179
+ id: 'im',
180
+ order: 20,
181
+ label: 'IM机器人',
182
+ inject: () => ({
183
+ dingtalkRpcCall,
184
+ discordRpcCall,
185
+ feishuRpcCall,
186
+ qqRpcCall,
187
+ telegramRpcCall,
188
+ wecomRpcCall,
189
+ weixinRpcCall,
190
+ }),
191
+ }, IMSettingsTab));
192
+ }
@@ -0,0 +1,86 @@
1
+ import * as React from 'react';
2
+
3
+ export function createPollScheduler({ setTimeoutFn, clearTimeoutFn }) {
4
+ let disposed = false;
5
+ let timer;
6
+
7
+ return {
8
+ get disposed() {
9
+ return disposed;
10
+ },
11
+ schedule(callback, delayMs) {
12
+ if (disposed) return false;
13
+ if (timer !== undefined) clearTimeoutFn(timer);
14
+ timer = setTimeoutFn(() => {
15
+ timer = undefined;
16
+ if (!disposed) void callback();
17
+ }, delayMs);
18
+ return true;
19
+ },
20
+ dispose() {
21
+ if (disposed) return;
22
+ disposed = true;
23
+ if (timer !== undefined) clearTimeoutFn(timer);
24
+ timer = undefined;
25
+ },
26
+ };
27
+ }
28
+
29
+ export function createAnimationFrameScheduler({ requestFrame, cancelFrame }) {
30
+ let disposed = false;
31
+ const frames = new Set();
32
+ const keyedFrames = new Map();
33
+
34
+ return {
35
+ schedule(callback, key) {
36
+ if (disposed) return false;
37
+ const previous = key === undefined ? undefined : keyedFrames.get(key);
38
+ if (previous !== undefined) {
39
+ keyedFrames.delete(key);
40
+ frames.delete(previous);
41
+ cancelFrame(previous);
42
+ }
43
+ let frame;
44
+ let completed = false;
45
+ frame = requestFrame(() => {
46
+ completed = true;
47
+ if (frame !== undefined) frames.delete(frame);
48
+ if (key !== undefined && keyedFrames.get(key) === frame) keyedFrames.delete(key);
49
+ if (!disposed) callback();
50
+ });
51
+ if (!completed) {
52
+ frames.add(frame);
53
+ if (key !== undefined) keyedFrames.set(key, frame);
54
+ }
55
+ return true;
56
+ },
57
+ dispose() {
58
+ if (disposed) return;
59
+ disposed = true;
60
+ for (const frame of frames) cancelFrame(frame);
61
+ frames.clear();
62
+ keyedFrames.clear();
63
+ },
64
+ };
65
+ }
66
+
67
+ export function useAnimationFrameScheduler() {
68
+ const schedulerRef = React.useRef(null);
69
+
70
+ React.useEffect(() => {
71
+ const scheduler = createAnimationFrameScheduler({
72
+ requestFrame: (callback) => window.requestAnimationFrame(callback),
73
+ cancelFrame: (frame) => window.cancelAnimationFrame(frame),
74
+ });
75
+ schedulerRef.current = scheduler;
76
+ return () => {
77
+ scheduler.dispose();
78
+ if (schedulerRef.current === scheduler) schedulerRef.current = null;
79
+ };
80
+ }, []);
81
+
82
+ return React.useCallback(
83
+ (callback, key) => schedulerRef.current?.schedule(callback, key) ?? false,
84
+ [],
85
+ );
86
+ }