@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.
- package/LICENSE +21 -0
- package/README.md +143 -0
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/bin/dsh-im.mjs +99 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +6211 -0
- package/lib/index.js +13339 -0
- package/package.json +73 -0
- package/plugin-src/client/build.mjs +38 -0
- package/plugin-src/client/channel-logos.js +107 -0
- package/plugin-src/client/channels/dingtalk/api.js +203 -0
- package/plugin-src/client/channels/dingtalk/index.js +709 -0
- package/plugin-src/client/channels/dingtalk/styles.js +140 -0
- package/plugin-src/client/channels/discord/api.js +11 -0
- package/plugin-src/client/channels/discord/index.js +25 -0
- package/plugin-src/client/channels/discord/styles.js +19 -0
- package/plugin-src/client/channels/feishu/api.js +325 -0
- package/plugin-src/client/channels/feishu/index.js +986 -0
- package/plugin-src/client/channels/feishu/styles.js +505 -0
- package/plugin-src/client/channels/qq/api.js +119 -0
- package/plugin-src/client/channels/qq/index.js +402 -0
- package/plugin-src/client/channels/qq/styles.js +19 -0
- package/plugin-src/client/channels/shared/token-api.js +88 -0
- package/plugin-src/client/channels/shared/token-channel.js +277 -0
- package/plugin-src/client/channels/telegram/api.js +11 -0
- package/plugin-src/client/channels/telegram/index.js +25 -0
- package/plugin-src/client/channels/telegram/styles.js +19 -0
- package/plugin-src/client/channels/wecom/api.js +119 -0
- package/plugin-src/client/channels/wecom/index.js +402 -0
- package/plugin-src/client/channels/wecom/styles.js +19 -0
- package/plugin-src/client/channels/weixin/api.js +156 -0
- package/plugin-src/client/channels/weixin/index.js +577 -0
- package/plugin-src/client/channels/weixin/styles.js +120 -0
- package/plugin-src/client/credential-binding.js +117 -0
- package/plugin-src/client/index.js +192 -0
- package/plugin-src/client/lifecycle.js +86 -0
- package/plugin-src/client/styles.js +192 -0
- package/plugin-src/host/build.mjs +24 -0
- package/plugin-src/host/channels/dingtalk/connection-supervisor.mjs +130 -0
- package/plugin-src/host/channels/dingtalk/index.mjs +39 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +122 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +237 -0
- package/plugin-src/host/channels/discord/index.mjs +30 -0
- package/plugin-src/host/channels/discord/production.mjs +17 -0
- package/plugin-src/host/channels/discord/rpc.mjs +21 -0
- package/plugin-src/host/channels/feishu/connection-supervisor.mjs +167 -0
- package/plugin-src/host/channels/feishu/controller.mjs +172 -0
- package/plugin-src/host/channels/feishu/credential-store.mjs +84 -0
- package/plugin-src/host/channels/feishu/index.mjs +73 -0
- package/plugin-src/host/channels/feishu/production.mjs +138 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +454 -0
- package/plugin-src/host/channels/qq/connection-supervisor.mjs +124 -0
- package/plugin-src/host/channels/qq/index.mjs +30 -0
- package/plugin-src/host/channels/qq/production.mjs +109 -0
- package/plugin-src/host/channels/qq/rpc.mjs +146 -0
- package/plugin-src/host/channels/shared/connection-supervisor.mjs +133 -0
- package/plugin-src/host/channels/shared/production.mjs +104 -0
- package/plugin-src/host/channels/shared/rpc.mjs +119 -0
- package/plugin-src/host/channels/telegram/index.mjs +30 -0
- package/plugin-src/host/channels/telegram/production.mjs +17 -0
- package/plugin-src/host/channels/telegram/rpc.mjs +21 -0
- package/plugin-src/host/channels/wecom/connection-supervisor.mjs +124 -0
- package/plugin-src/host/channels/wecom/index.mjs +36 -0
- package/plugin-src/host/channels/wecom/production.mjs +113 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +151 -0
- package/plugin-src/host/channels/weixin/connection-supervisor.mjs +127 -0
- package/plugin-src/host/channels/weixin/index.mjs +39 -0
- package/plugin-src/host/channels/weixin/production.mjs +119 -0
- package/plugin-src/host/channels/weixin/rpc.mjs +178 -0
- package/plugin-src/host/index.mjs +44 -0
- package/plugin-src/host/rpc-authority.mjs +11 -0
- package/scripts/verify-package.mjs +97 -0
- package/src/channels/dingtalk/config-store.mjs +282 -0
- package/src/channels/dingtalk/device-auth.mjs +237 -0
- package/src/channels/dingtalk/dingtalk-api.mjs +579 -0
- package/src/channels/dingtalk/dingtalk-bridge.mjs +281 -0
- package/src/channels/dingtalk/dingtalk-card-stream.mjs +233 -0
- package/src/channels/dingtalk/dingtalk-controller.mjs +752 -0
- package/src/channels/dingtalk/dingtalk-runtime.mjs +358 -0
- package/src/channels/dingtalk/harness-client.mjs +299 -0
- package/src/channels/dingtalk/state-store.mjs +212 -0
- package/src/channels/discord/config-store.mjs +24 -0
- package/src/channels/discord/discord-api.mjs +153 -0
- package/src/channels/discord/discord-bridge.mjs +15 -0
- package/src/channels/discord/discord-controller.mjs +16 -0
- package/src/channels/discord/discord-runtime.mjs +460 -0
- package/src/channels/discord/harness-client.mjs +3 -0
- package/src/channels/discord/state-store.mjs +3 -0
- package/src/channels/feishu/bridge.mjs +216 -0
- package/src/channels/feishu/config.mjs +71 -0
- package/src/channels/feishu/feishu-app.mjs +51 -0
- package/src/channels/feishu/feishu-channel.mjs +153 -0
- package/src/channels/feishu/feishu-runtime.mjs +222 -0
- package/src/channels/feishu/harness-client.mjs +268 -0
- package/src/channels/feishu/message-utils.mjs +51 -0
- package/src/channels/feishu/multi-bot-controller.mjs +686 -0
- package/src/channels/feishu/plugin-config-store.mjs +204 -0
- package/src/channels/feishu/plugin-controller.mjs +248 -0
- package/src/channels/feishu/registration-manager.mjs +345 -0
- package/src/channels/feishu/state-store.mjs +71 -0
- package/src/channels/qq/config-store.mjs +171 -0
- package/src/channels/qq/harness-client.mjs +3 -0
- package/src/channels/qq/qq-bridge.mjs +177 -0
- package/src/channels/qq/qq-controller.mjs +452 -0
- package/src/channels/qq/qq-runtime.mjs +208 -0
- package/src/channels/qq/qr-auth.mjs +24 -0
- package/src/channels/qq/state-store.mjs +96 -0
- package/src/channels/shared/conversation-state-store.mjs +107 -0
- package/src/channels/shared/editable-message-stream.mjs +81 -0
- package/src/channels/shared/text-harness-bridge.mjs +180 -0
- package/src/channels/shared/token-bot-controller.mjs +298 -0
- package/src/channels/shared/token-config-store.mjs +180 -0
- package/src/channels/telegram/config-store.mjs +24 -0
- package/src/channels/telegram/harness-client.mjs +3 -0
- package/src/channels/telegram/state-store.mjs +3 -0
- package/src/channels/telegram/telegram-api.mjs +120 -0
- package/src/channels/telegram/telegram-bridge.mjs +15 -0
- package/src/channels/telegram/telegram-controller.mjs +16 -0
- package/src/channels/telegram/telegram-runtime.mjs +278 -0
- package/src/channels/wecom/config-store.mjs +170 -0
- package/src/channels/wecom/harness-client.mjs +3 -0
- package/src/channels/wecom/qr-auth.mjs +95 -0
- package/src/channels/wecom/state-store.mjs +90 -0
- package/src/channels/wecom/wecom-bridge.mjs +243 -0
- package/src/channels/wecom/wecom-controller.mjs +474 -0
- package/src/channels/wecom/wecom-runtime.mjs +209 -0
- package/src/channels/weixin/config-store.mjs +182 -0
- package/src/channels/weixin/harness-client.mjs +259 -0
- package/src/channels/weixin/state-store.mjs +112 -0
- package/src/channels/weixin/weixin-api.mjs +319 -0
- package/src/channels/weixin/weixin-bridge.mjs +173 -0
- package/src/channels/weixin/weixin-controller.mjs +545 -0
- package/src/channels/weixin/weixin-runtime.mjs +204 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export const DINGTALK_STYLE_ID = 'xmanrui-dsh-dingtalk-settings';
|
|
2
|
+
|
|
3
|
+
const CSS = String.raw`
|
|
4
|
+
.ddt-page {
|
|
5
|
+
--ddt-accent: #1677ff;
|
|
6
|
+
--ddt-accent-deep: #0958d9;
|
|
7
|
+
--ddt-accent-wash: #eaf3ff;
|
|
8
|
+
--ddt-success: var(--dsw-alias-state-success-primary, #20a162);
|
|
9
|
+
--ddt-warning: var(--dsw-alias-state-warning-primary, #d97706);
|
|
10
|
+
--ddt-error: var(--dsw-alias-state-error-primary, #d54941);
|
|
11
|
+
width: 100%;
|
|
12
|
+
max-width: 880px;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
gap: 18px;
|
|
16
|
+
padding: 2px 0 28px;
|
|
17
|
+
container-type: inline-size;
|
|
18
|
+
color: var(--dsw-alias-label-primary, #1f2329);
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
}
|
|
21
|
+
.ddt-page *, .ddt-page *::before, .ddt-page *::after { box-sizing: border-box; }
|
|
22
|
+
.ddt-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }
|
|
23
|
+
.ddt-headingCopy { min-width: 0; }
|
|
24
|
+
.ddt-heading h2, .ddt-heading p, .ddt-card h3, .ddt-card h4, .ddt-card p { margin: 0; }
|
|
25
|
+
.ddt-eyebrow { margin-bottom: 3px; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 12px; font-weight: 650; letter-spacing: .08em; text-transform: uppercase; }
|
|
26
|
+
.ddt-heading h2 { font-size: 20px; line-height: 28px; font-weight: 680; }
|
|
27
|
+
.ddt-heading p { margin-top: 5px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; white-space: nowrap; }
|
|
28
|
+
.ddt-tools, .ddt-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
|
|
29
|
+
.ddt-tools { width: 100%; justify-content: space-between; flex-wrap: nowrap; }
|
|
30
|
+
.ddt-badge { min-height: 30px; display: inline-flex; align-items: center; gap: 7px; 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; }
|
|
31
|
+
.ddt-dot { width: 8px; height: 8px; flex: none; border-radius: 50%; background: #aeb3bb; }
|
|
32
|
+
.ddt-dot[data-tone="success"] { background: var(--ddt-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ddt-success) 14%, transparent); }
|
|
33
|
+
.ddt-dot[data-tone="warning"] { background: var(--ddt-warning); }
|
|
34
|
+
.ddt-dot[data-tone="error"] { background: var(--ddt-error); }
|
|
35
|
+
.ddt-button { min-height: 34px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; padding: 0 13px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); font: inherit; font-size: 13px; font-weight: 560; text-decoration: none; cursor: pointer; transition: border-color .15s ease, background .15s ease, transform .15s ease; }
|
|
36
|
+
.ddt-button:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-fill-tertiary, #f7f8fa); }
|
|
37
|
+
.ddt-button:active:not(:disabled) { transform: translateY(1px); }
|
|
38
|
+
.ddt-button:focus-visible { outline: 2px solid color-mix(in srgb, var(--ddt-accent) 70%, white); outline-offset: 2px; }
|
|
39
|
+
.ddt-button:disabled { cursor: not-allowed; opacity: .55; }
|
|
40
|
+
.ddt-button[data-kind="primary"] { color: #fff; border-color: var(--ddt-accent); background: var(--ddt-accent); }
|
|
41
|
+
.ddt-button[data-kind="primary"]:hover:not(:disabled) { border-color: var(--ddt-accent-deep); background: var(--ddt-accent-deep); }
|
|
42
|
+
.ddt-button[data-kind="danger"] { color: var(--ddt-error); }
|
|
43
|
+
.ddt-button[data-kind="quiet"] { min-height: 30px; padding: 0 10px; border-color: transparent; background: transparent; }
|
|
44
|
+
.ddt-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%); }
|
|
45
|
+
.ddt-cardBody { padding: 24px; }
|
|
46
|
+
.ddt-empty { min-height: 230px; display: grid; grid-template-columns: minmax(0, 1fr) 180px; align-items: center; gap: 30px; }
|
|
47
|
+
.ddt-empty h3 { margin: 8px 0; font-size: 18px; }
|
|
48
|
+
.ddt-empty p { max-width: 560px; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; }
|
|
49
|
+
.ddt-empty .ddt-actions { margin-top: 20px; }
|
|
50
|
+
.ddt-brandMark { width: 110px; height: 110px; display: grid; place-items: center; justify-self: center; border-radius: 28px; color: #fff; background: linear-gradient(145deg, #2997ff, var(--ddt-accent)); box-shadow: 0 18px 45px rgb(22 119 255 / 23%); }
|
|
51
|
+
.ddt-brandMark svg { filter: drop-shadow(0 3px 8px rgb(0 35 96 / 16%)); }
|
|
52
|
+
.ddt-qrLayout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 34px; align-items: start; }
|
|
53
|
+
.ddt-qrColumn { display: flex; flex-direction: column; align-items: center; gap: 12px; }
|
|
54
|
+
.ddt-qrFrame { position: relative; width: min(270px, 100%); 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: #fff; }
|
|
55
|
+
.ddt-qrFrame::before { content: ''; position: absolute; inset: 6px; border: 1px solid rgb(22 119 255 / 10%); border-radius: 11px; pointer-events: none; }
|
|
56
|
+
.ddt-qrFrame img { display: block; width: 100%; height: 100%; object-fit: contain; }
|
|
57
|
+
.ddt-qrFallback { padding: 24px; color: #646a73; text-align: center; }
|
|
58
|
+
.ddt-expired { position: absolute; inset: 0; display: grid; place-items: center; padding: 30px; color: #fff; text-align: center; font-weight: 650; white-space: pre-line; background: rgb(31 35 41 / 76%); backdrop-filter: blur(3px); }
|
|
59
|
+
.ddt-countdown { width: min(270px, 100%); color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; }
|
|
60
|
+
.ddt-countdownTop { display: flex; justify-content: space-between; margin-bottom: 6px; }
|
|
61
|
+
.ddt-countdown strong { color: var(--dsw-alias-label-primary, #1f2329); font-variant-numeric: tabular-nums; }
|
|
62
|
+
.ddt-progress { height: 4px; overflow: hidden; border-radius: 99px; background: #eef0f3; }
|
|
63
|
+
.ddt-progress span { display: block; width: var(--ddt-progress); height: 100%; background: var(--ddt-accent); transition: width .2s linear; }
|
|
64
|
+
.ddt-stateLabel { display: inline-flex; align-items: center; gap: 8px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; font-weight: 600; }
|
|
65
|
+
.ddt-qrCopy { min-width: 0; overflow-wrap: anywhere; }
|
|
66
|
+
.ddt-qrCopy h3 { margin: 9px 0 8px; font-size: 18px; }
|
|
67
|
+
.ddt-qrCopy > p { color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.65; }
|
|
68
|
+
.ddt-steps { margin: 18px 0 16px; padding: 0; list-style: none; counter-reset: ddt-step; }
|
|
69
|
+
.ddt-steps li { position: relative; min-height: 28px; padding: 3px 0 3px 36px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 22px; counter-increment: ddt-step; }
|
|
70
|
+
.ddt-steps li::before { content: counter(ddt-step); position: absolute; left: 0; top: 1px; width: 26px; height: 26px; display: grid; place-items: center; border-radius: 8px; color: var(--ddt-accent-deep); background: var(--ddt-accent-wash); font-size: 12px; font-weight: 700; }
|
|
71
|
+
.ddt-loading { padding: 38px; color: var(--dsw-alias-label-secondary, #646a73); text-align: center; }
|
|
72
|
+
.ddt-loading h3 { margin: 0 0 7px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 17px; }
|
|
73
|
+
.ddt-loading p { line-height: 1.6; }
|
|
74
|
+
.ddt-spinner { width: 24px; height: 24px; margin: 0 auto 13px; border: 3px solid #e6e8eb; border-top-color: var(--ddt-accent); border-radius: 50%; animation: ddt-spin .8s linear infinite; }
|
|
75
|
+
.ddt-statusNotice, .ddt-inlineError { display: flex; align-items: flex-start; gap: 10px; padding: 13px 15px; border: 1px solid color-mix(in srgb, var(--ddt-error) 28%, transparent); border-radius: 10px; color: var(--ddt-error); background: color-mix(in srgb, var(--ddt-error) 7%, transparent); font-size: 13px; }
|
|
76
|
+
.ddt-inlineError { flex-direction: column; padding: 22px; }
|
|
77
|
+
.ddt-inlineError h3 { font-size: 17px; }
|
|
78
|
+
.ddt-inlineError p { line-height: 1.55; }
|
|
79
|
+
.ddt-errorCode { font: 11px ui-monospace, SFMono-Regular, monospace; opacity: .8; }
|
|
80
|
+
.ddt-listHeading { display: flex; align-items: center; justify-content: space-between; margin: 2px 0 9px; }
|
|
81
|
+
.ddt-listHeading h3 { margin: 0; font-size: 14px; }
|
|
82
|
+
.ddt-list { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; }
|
|
83
|
+
.ddt-accountTop { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
|
|
84
|
+
.ddt-accountIdentity { min-width: 0; display: flex; align-items: center; gap: 12px; }
|
|
85
|
+
.ddt-avatar { width: 42px; height: 42px; display: grid; place-items: center; flex: none; border-radius: 12px; color: #fff; background: linear-gradient(145deg, #2997ff, var(--ddt-accent)); }
|
|
86
|
+
.ddt-accountIdentity h3 { overflow: hidden; font-size: 15px; text-overflow: ellipsis; white-space: nowrap; }
|
|
87
|
+
.ddt-accountIdentity p { margin-top: 4px; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; }
|
|
88
|
+
.ddt-health { display: inline-flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; white-space: nowrap; }
|
|
89
|
+
.ddt-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 20px 0; }
|
|
90
|
+
.ddt-metric { min-width: 0; padding: 12px; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); }
|
|
91
|
+
.ddt-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; }
|
|
92
|
+
.ddt-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
|
|
93
|
+
.ddt-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); }
|
|
94
|
+
.ddt-accountFooter .ddt-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; }
|
|
95
|
+
.ddt-accountFooter .ddt-button { flex: none; white-space: nowrap; }
|
|
96
|
+
.ddt-summary { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; }
|
|
97
|
+
.ddt-confirm { padding: 18px 24px; border-top: 1px solid color-mix(in srgb, var(--ddt-error) 25%, transparent); background: color-mix(in srgb, var(--ddt-error) 5%, transparent); }
|
|
98
|
+
.ddt-confirm strong { display: block; margin-bottom: 6px; font-size: 14px; }
|
|
99
|
+
.ddt-confirm p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 1.55; }
|
|
100
|
+
.ddt-confirm .ddt-actions { margin-top: 13px; }
|
|
101
|
+
.ddt-visuallyHidden { position: absolute !important; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
102
|
+
@keyframes ddt-spin { to { transform: rotate(360deg); } }
|
|
103
|
+
@container (max-width: 680px) {
|
|
104
|
+
.ddt-heading { flex-direction: column; align-items: stretch; }
|
|
105
|
+
.ddt-tools { width: 100%; flex-wrap: nowrap; gap: 6px; }
|
|
106
|
+
.ddt-tools .ddt-badge { min-height: 34px; padding-inline: 8px; }
|
|
107
|
+
.ddt-tools .ddt-button { flex: none; padding-inline: 10px; white-space: nowrap; }
|
|
108
|
+
.ddt-empty { grid-template-columns: minmax(0, 1fr); }
|
|
109
|
+
.ddt-brandMark { display: none; }
|
|
110
|
+
.ddt-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; gap: 24px; }
|
|
111
|
+
.ddt-qrColumn { width: 100%; min-width: 0; }
|
|
112
|
+
.ddt-qrCopy { width: 100%; }
|
|
113
|
+
.ddt-metrics { gap: 8px; }
|
|
114
|
+
.ddt-metric { padding: 10px; }
|
|
115
|
+
}
|
|
116
|
+
@media (max-width: 720px) {
|
|
117
|
+
.ddt-heading, .ddt-accountTop { flex-direction: column; align-items: stretch; }
|
|
118
|
+
.ddt-heading p { white-space: normal; }
|
|
119
|
+
.ddt-empty { grid-template-columns: minmax(0, 1fr); }
|
|
120
|
+
.ddt-brandMark { display: none; }
|
|
121
|
+
.ddt-qrLayout { grid-template-columns: minmax(0, 1fr); justify-items: center; }
|
|
122
|
+
.ddt-qrCopy { width: 100%; }
|
|
123
|
+
.ddt-cardBody { padding: 20px; }
|
|
124
|
+
}
|
|
125
|
+
@media (prefers-reduced-motion: reduce) {
|
|
126
|
+
.ddt-page *, .ddt-page *::before, .ddt-page *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
export function installDingtalkStyles() {
|
|
131
|
+
if (typeof document === 'undefined') return () => {};
|
|
132
|
+
const existing = document.querySelector(`style[data-plugin-css="${DINGTALK_STYLE_ID}"]`);
|
|
133
|
+
if (existing) return () => {};
|
|
134
|
+
const style = document.createElement('style');
|
|
135
|
+
style.dataset.plugin = '@xmanrui/dsh-dingtalk';
|
|
136
|
+
style.dataset.pluginCss = DINGTALK_STYLE_ID;
|
|
137
|
+
style.textContent = CSS;
|
|
138
|
+
document.head.appendChild(style);
|
|
139
|
+
return () => style.remove();
|
|
140
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TOKEN_BOT_ENDPOINTS, createTokenChannelApi } from '../shared/token-api.js';
|
|
2
|
+
|
|
3
|
+
export const DISCORD_RPC_CHANNEL = '/discord';
|
|
4
|
+
export const DISCORD_ENDPOINTS = TOKEN_BOT_ENDPOINTS;
|
|
5
|
+
|
|
6
|
+
const api = createTokenChannelApi('Discord', ' Gateway 长连接');
|
|
7
|
+
|
|
8
|
+
export const unwrapRpcResult = api.unwrapRpcResult;
|
|
9
|
+
export const normalizeSnapshot = api.normalizeSnapshot;
|
|
10
|
+
export const presentError = api.presentError;
|
|
11
|
+
export { api as discordClientApi };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DiscordLogoGlyph } from '../../channel-logos.js';
|
|
2
|
+
import { createTokenChannelSettings } from '../shared/token-channel.js';
|
|
3
|
+
import {
|
|
4
|
+
DISCORD_ENDPOINTS,
|
|
5
|
+
discordClientApi,
|
|
6
|
+
} from './api.js';
|
|
7
|
+
import { installDiscordStyles } from './styles.js';
|
|
8
|
+
|
|
9
|
+
const channel = createTokenChannelSettings({
|
|
10
|
+
channel: 'Discord',
|
|
11
|
+
endpoints: DISCORD_ENDPOINTS,
|
|
12
|
+
api: discordClientApi,
|
|
13
|
+
LogoGlyph: DiscordLogoGlyph,
|
|
14
|
+
installStyles: installDiscordStyles,
|
|
15
|
+
pageClass: 'ddc-page',
|
|
16
|
+
avatarClass: 'ddc-avatar',
|
|
17
|
+
connectionLabel: 'Gateway 长连接',
|
|
18
|
+
tokenPlaceholder: '填写 Discord Developer Portal 的 Bot Token',
|
|
19
|
+
emptyTitle: '接入 Discord 机器人',
|
|
20
|
+
emptyDescription: '先在 Developer Portal 创建 Bot 并邀请到服务器,再在这里完成接入。',
|
|
21
|
+
platformLabel: 'Discord Developer Portal',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const DiscordSettingsTab = channel.SettingsTab;
|
|
25
|
+
export const DiscordAccountCard = channel.AccountCard;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const DISCORD_STYLE_ID = 'xmanrui-dsh-im-discord-settings';
|
|
2
|
+
|
|
3
|
+
const CSS = String.raw`
|
|
4
|
+
.ddc-page { --ddt-accent: #5865f2; --ddt-accent-deep: #4752c4; --ddt-accent-wash: #eef0ff; }
|
|
5
|
+
.ddc-avatar { color: #fff; background: #5865f2; }
|
|
6
|
+
.ddc-avatar svg { display: block; }
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
export function installDiscordStyles() {
|
|
10
|
+
if (typeof document === 'undefined') return () => {};
|
|
11
|
+
const existing = document.querySelector(`style[data-plugin-css="${DISCORD_STYLE_ID}"]`);
|
|
12
|
+
if (existing) return () => {};
|
|
13
|
+
const style = document.createElement('style');
|
|
14
|
+
style.dataset.plugin = '@xmanrui/dsh-im';
|
|
15
|
+
style.dataset.pluginCss = DISCORD_STYLE_ID;
|
|
16
|
+
style.textContent = CSS;
|
|
17
|
+
document.head.appendChild(style);
|
|
18
|
+
return () => style.remove();
|
|
19
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe contract for the Feishu Host plugin.
|
|
3
|
+
*
|
|
4
|
+
* The Host owns app registration and credentials. This module deliberately
|
|
5
|
+
* models only redacted presentation data; app secrets and credential refs
|
|
6
|
+
* must never be returned by any endpoint on this channel.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const FEISHU_RPC_CHANNEL = "/feishu";
|
|
10
|
+
|
|
11
|
+
export const FEISHU_ENDPOINTS = Object.freeze({
|
|
12
|
+
status: "connection.status",
|
|
13
|
+
beginProvisioning: "provision.begin",
|
|
14
|
+
pollProvisioning: "provision.poll",
|
|
15
|
+
cancelProvisioning: "provision.cancel",
|
|
16
|
+
bindCredentials: "bot.bind-credentials",
|
|
17
|
+
reconnectBot: "bot.reconnect",
|
|
18
|
+
disconnectBot: "bot.disconnect",
|
|
19
|
+
deleteBot: "bot.delete",
|
|
20
|
+
// Kept for rolling upgrades. The multi-bot UI never calls these endpoints.
|
|
21
|
+
testConnection: "connection.test",
|
|
22
|
+
disconnect: "connection.disconnect",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const CONNECTION_STATES = new Set([
|
|
26
|
+
"disconnected",
|
|
27
|
+
"offline",
|
|
28
|
+
"provisioning",
|
|
29
|
+
"connecting",
|
|
30
|
+
"reconnecting",
|
|
31
|
+
"connected",
|
|
32
|
+
"error",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const POLL_STATES = new Set([
|
|
36
|
+
"pending",
|
|
37
|
+
"scanned",
|
|
38
|
+
"connecting",
|
|
39
|
+
"connected",
|
|
40
|
+
"expired",
|
|
41
|
+
"failed",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
function isRecord(value) {
|
|
45
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function optionalString(value) {
|
|
49
|
+
return typeof value === "string" && value.trim().length > 0
|
|
50
|
+
? value.trim()
|
|
51
|
+
: undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function optionalTimestamp(value) {
|
|
55
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
56
|
+
if (typeof value === "string" && value.length > 0) {
|
|
57
|
+
const parsed = Date.parse(value);
|
|
58
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function clamp(value, min, max, fallback) {
|
|
64
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
65
|
+
? Math.min(max, Math.max(min, value))
|
|
66
|
+
: fallback;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function unwrapRpcResult(result) {
|
|
70
|
+
if (!isRecord(result) || typeof result.ok !== "boolean") {
|
|
71
|
+
throw new Error("飞书服务返回了无法识别的响应");
|
|
72
|
+
}
|
|
73
|
+
if (!result.ok) {
|
|
74
|
+
const message = optionalString(result.error?.message) ?? "飞书服务请求失败";
|
|
75
|
+
const error = new Error(message);
|
|
76
|
+
error.code = optionalString(result.error?.code) ?? "FEISHU_RPC_ERROR";
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
return result.value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function normalizeProvisioning(value, now = Date.now()) {
|
|
83
|
+
const source = isRecord(value?.provisioning) ? value.provisioning : value;
|
|
84
|
+
if (!isRecord(source)) throw new Error("飞书服务没有返回二维码信息");
|
|
85
|
+
|
|
86
|
+
const attemptId = optionalString(source.attemptId)
|
|
87
|
+
?? optionalString(source.provisioningId);
|
|
88
|
+
const verificationUrl = optionalString(source.verificationUrl);
|
|
89
|
+
const qrCodeDataUrl = optionalString(source.qrCodeDataUrl);
|
|
90
|
+
if (!attemptId || (!verificationUrl && !qrCodeDataUrl)) {
|
|
91
|
+
throw new Error("飞书服务返回的二维码信息不完整");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const explicitExpiry = optionalTimestamp(source.expiresAt);
|
|
95
|
+
const expireIn = clamp(source.expireIn, 1, 60 * 60, 5 * 60);
|
|
96
|
+
return {
|
|
97
|
+
attemptId,
|
|
98
|
+
verificationUrl,
|
|
99
|
+
qrCodeDataUrl,
|
|
100
|
+
expiresAt: explicitExpiry ?? now + expireIn * 1000,
|
|
101
|
+
pollIntervalMs: clamp(source.pollIntervalMs, 800, 10_000, 1_800),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizeBot(value) {
|
|
106
|
+
const source = isRecord(value) ? value : {};
|
|
107
|
+
return {
|
|
108
|
+
name: optionalString(source.name) ?? "飞书机器人",
|
|
109
|
+
avatarUrl: optionalString(source.avatarUrl),
|
|
110
|
+
appIdMasked: optionalString(source.appIdMasked),
|
|
111
|
+
tenantName: optionalString(source.tenantName),
|
|
112
|
+
domain: source.domain === "lark" ? "lark" : "feishu",
|
|
113
|
+
activated: typeof source.activated === "boolean" || typeof source.activated === "number"
|
|
114
|
+
? source.activated
|
|
115
|
+
: undefined,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function normalizeHealth(value, connected = false) {
|
|
120
|
+
const source = isRecord(value) ? value : {};
|
|
121
|
+
const fallbackStatus = connected ? "healthy" : "offline";
|
|
122
|
+
const status = ["healthy", "degraded", "offline", "checking"].includes(source.status)
|
|
123
|
+
? source.status
|
|
124
|
+
: fallbackStatus;
|
|
125
|
+
return {
|
|
126
|
+
status,
|
|
127
|
+
summary: optionalString(source.summary)
|
|
128
|
+
?? (connected ? "长连接运行正常" : "机器人尚未连接"),
|
|
129
|
+
lastCheckedAt: optionalTimestamp(source.lastCheckedAt),
|
|
130
|
+
lastConnectedAt: optionalTimestamp(source.lastConnectedAt),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function normalizeError(value) {
|
|
135
|
+
if (!isRecord(value)) return undefined;
|
|
136
|
+
const message = optionalString(value.message);
|
|
137
|
+
if (!message) return undefined;
|
|
138
|
+
return { message, code: optionalString(value.code) };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function authoritativeState(value, connected) {
|
|
142
|
+
if (connected) return "connected";
|
|
143
|
+
const reported = CONNECTION_STATES.has(value) ? value : "disconnected";
|
|
144
|
+
if (reported === "connected" || reported === "connecting" || reported === "reconnecting") {
|
|
145
|
+
return "connecting";
|
|
146
|
+
}
|
|
147
|
+
if (reported === "error") return "error";
|
|
148
|
+
return "offline";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Normalize one redacted bot connection. `connected` is authoritative. */
|
|
152
|
+
export function normalizeBotConnection(value, fallbackBotId) {
|
|
153
|
+
if (!isRecord(value)) throw new Error("飞书服务返回了无效的机器人状态");
|
|
154
|
+
const botId = optionalString(value.botId) ?? optionalString(fallbackBotId);
|
|
155
|
+
if (!botId) throw new Error("飞书服务返回的机器人缺少 botId");
|
|
156
|
+
const connected = value.connected === true;
|
|
157
|
+
return {
|
|
158
|
+
botId,
|
|
159
|
+
state: authoritativeState(value.state, connected),
|
|
160
|
+
connected,
|
|
161
|
+
configured: value.configured !== false,
|
|
162
|
+
bot: normalizeBot(value.bot),
|
|
163
|
+
health: normalizeHealth(value.health, connected),
|
|
164
|
+
error: normalizeError(value.error),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Normalize the v2 multi-bot list. A singleton fallback is accepted only so a
|
|
170
|
+
* browser/Host rolling upgrade does not strand an existing connection.
|
|
171
|
+
*/
|
|
172
|
+
export function normalizeBotsSnapshot(value) {
|
|
173
|
+
if (!isRecord(value)) throw new Error("飞书服务没有返回连接状态");
|
|
174
|
+
|
|
175
|
+
let sourceBots = Array.isArray(value.bots) ? value.bots : [];
|
|
176
|
+
if (sourceBots.length === 0 && value.configured === true) {
|
|
177
|
+
sourceBots = [{
|
|
178
|
+
botId: optionalString(value.botId) ?? "legacy-default",
|
|
179
|
+
state: value.state,
|
|
180
|
+
connected: value.connected,
|
|
181
|
+
configured: true,
|
|
182
|
+
bot: value.bot,
|
|
183
|
+
health: value.health,
|
|
184
|
+
error: value.error,
|
|
185
|
+
}];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const seen = new Set();
|
|
189
|
+
const bots = [];
|
|
190
|
+
for (const source of sourceBots) {
|
|
191
|
+
const bot = normalizeBotConnection(source);
|
|
192
|
+
if (seen.has(bot.botId)) continue;
|
|
193
|
+
seen.add(bot.botId);
|
|
194
|
+
bots.push(bot);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const configured = bots.filter((bot) => bot.configured).length;
|
|
198
|
+
const connected = bots.filter((bot) => bot.connected).length;
|
|
199
|
+
const revision = Number.isSafeInteger(value.revision) && value.revision >= 0
|
|
200
|
+
? value.revision
|
|
201
|
+
: 0;
|
|
202
|
+
const state = CONNECTION_STATES.has(value.state) ? value.state : "disconnected";
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
schemaVersion: value.schemaVersion === 2 ? 2 : 1,
|
|
206
|
+
revision,
|
|
207
|
+
state,
|
|
208
|
+
bots,
|
|
209
|
+
// Derive counts from the authoritative list so stale summary fields never
|
|
210
|
+
// make the UI claim that an unavailable bot is online.
|
|
211
|
+
totals: { configured, connected },
|
|
212
|
+
provisioning: value.provisioning
|
|
213
|
+
? normalizeProvisioning(value.provisioning)
|
|
214
|
+
: undefined,
|
|
215
|
+
error: normalizeError(value.error),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Legacy single-bot normalizer retained for the compatibility surface. */
|
|
220
|
+
export function normalizeConnectionSnapshot(value) {
|
|
221
|
+
if (!isRecord(value)) throw new Error("飞书服务没有返回连接状态");
|
|
222
|
+
const connected = value.connected === true;
|
|
223
|
+
const reportedState = CONNECTION_STATES.has(value.state)
|
|
224
|
+
? value.state
|
|
225
|
+
: "disconnected";
|
|
226
|
+
const state = connected
|
|
227
|
+
? "connected"
|
|
228
|
+
: reportedState === "connected"
|
|
229
|
+
? "connecting"
|
|
230
|
+
: reportedState;
|
|
231
|
+
const snapshot = {
|
|
232
|
+
state,
|
|
233
|
+
configured: value.configured === true,
|
|
234
|
+
bot: normalizeBot(value.bot),
|
|
235
|
+
health: normalizeHealth(value.health, connected),
|
|
236
|
+
provisioning: undefined,
|
|
237
|
+
errorMessage: optionalString(value.error?.message) ?? optionalString(value.message),
|
|
238
|
+
};
|
|
239
|
+
if (value.provisioning) snapshot.provisioning = normalizeProvisioning(value.provisioning);
|
|
240
|
+
return snapshot;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Legacy UI projection retained for tests and rolling upgrades. */
|
|
244
|
+
export function screenFromSnapshot(snapshot) {
|
|
245
|
+
switch (snapshot.state) {
|
|
246
|
+
case "connected":
|
|
247
|
+
return { phase: "connected", configured: snapshot.configured, bot: snapshot.bot, health: snapshot.health };
|
|
248
|
+
case "provisioning":
|
|
249
|
+
return snapshot.provisioning
|
|
250
|
+
? { phase: "qr", configured: snapshot.configured, provision: snapshot.provisioning, expired: false }
|
|
251
|
+
: { phase: "creating", configured: snapshot.configured };
|
|
252
|
+
case "connecting":
|
|
253
|
+
return { phase: "connecting", configured: snapshot.configured, provision: snapshot.provisioning };
|
|
254
|
+
case "error":
|
|
255
|
+
return {
|
|
256
|
+
phase: "error",
|
|
257
|
+
configured: snapshot.configured,
|
|
258
|
+
bot: snapshot.bot,
|
|
259
|
+
health: snapshot.health,
|
|
260
|
+
error: { message: snapshot.errorMessage ?? "飞书连接遇到问题", code: "FEISHU_CONNECTION_ERROR" },
|
|
261
|
+
retry: snapshot.configured ? "test" : "begin",
|
|
262
|
+
};
|
|
263
|
+
default:
|
|
264
|
+
return snapshot.configured
|
|
265
|
+
? { phase: "offline", configured: true, bot: snapshot.bot, health: snapshot.health }
|
|
266
|
+
: { phase: "disconnected", configured: false };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Legacy helper. The new UI uses reconnectBot with an explicit botId. */
|
|
271
|
+
export async function retryConnection(invoke, signal) {
|
|
272
|
+
if (typeof invoke !== "function") throw new TypeError("retryConnection requires an RPC caller");
|
|
273
|
+
await invoke(FEISHU_ENDPOINTS.testConnection, {}, signal);
|
|
274
|
+
return invoke(FEISHU_ENDPOINTS.status, {}, signal);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Reconnect exactly one bot, then fetch the authoritative list once. */
|
|
278
|
+
export async function reconnectBot(invoke, botId, signal) {
|
|
279
|
+
if (typeof invoke !== "function") throw new TypeError("reconnectBot requires an RPC caller");
|
|
280
|
+
const id = optionalString(botId);
|
|
281
|
+
if (!id) throw new TypeError("reconnectBot requires a botId");
|
|
282
|
+
await invoke(FEISHU_ENDPOINTS.reconnectBot, { botId: id }, signal);
|
|
283
|
+
return invoke(FEISHU_ENDPOINTS.status, {}, signal);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function normalizePollResult(value) {
|
|
287
|
+
if (!isRecord(value)) throw new Error("飞书服务没有返回创建进度");
|
|
288
|
+
const status = POLL_STATES.has(value.status)
|
|
289
|
+
? value.status
|
|
290
|
+
: POLL_STATES.has(value.state)
|
|
291
|
+
? value.state
|
|
292
|
+
: undefined;
|
|
293
|
+
if (!status) throw new Error("飞书服务返回了未知的创建状态");
|
|
294
|
+
|
|
295
|
+
const normalized = {
|
|
296
|
+
status,
|
|
297
|
+
botId: optionalString(value.botId),
|
|
298
|
+
message: optionalString(value.error?.message) ?? optionalString(value.message),
|
|
299
|
+
connection: undefined,
|
|
300
|
+
provisioning: undefined,
|
|
301
|
+
};
|
|
302
|
+
if (value.provisioning) normalized.provisioning = normalizeProvisioning(value.provisioning);
|
|
303
|
+
if (status === "connected" && isRecord(value.connection)) {
|
|
304
|
+
normalized.connection = value.connection.botId
|
|
305
|
+
? normalizeBotConnection(value.connection)
|
|
306
|
+
: normalizeConnectionSnapshot(value.connection);
|
|
307
|
+
}
|
|
308
|
+
return normalized;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Keep transport and Host details out of the user-facing alert. */
|
|
312
|
+
export function presentError(error) {
|
|
313
|
+
const raw = optionalString(error?.message) ?? "操作失败,请稍后重试";
|
|
314
|
+
const message = raw
|
|
315
|
+
.replace(/(client[_-]?secret|app[_-]?secret|secret|token)\s*[:=]\s*[^\s,;]+/gi, "$1=••••••")
|
|
316
|
+
.slice(0, 240);
|
|
317
|
+
return { message, code: optionalString(error?.code) };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function formatRemaining(milliseconds) {
|
|
321
|
+
const totalSeconds = Math.max(0, Math.ceil(milliseconds / 1000));
|
|
322
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
323
|
+
const seconds = totalSeconds % 60;
|
|
324
|
+
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
325
|
+
}
|