mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 03:03:24 +08:00
feat: send test messages during bot connection checks
This commit is contained in:
parent
94da24538f
commit
fb61c4235f
78 changed files with 3460 additions and 210 deletions
|
|
@ -13,6 +13,7 @@ export const QQ_ENDPOINTS = Object.freeze({
|
|||
|
||||
const PROVISION_STATES = new Set(['starting', 'pending', 'refreshing', 'connecting', 'connected', 'failed', 'cancelled']);
|
||||
const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
|
||||
const TEST_MESSAGE_CODES = new Set(['test-target-unavailable', 'test-message-failed']);
|
||||
const QR_DATA_URL = /^data:image\/(?:png|webp);base64,[a-z\d+/]+={0,2}$/i;
|
||||
|
||||
function isRecord(value) {
|
||||
|
|
@ -96,18 +97,38 @@ function normalizeBot(value) {
|
|||
};
|
||||
}
|
||||
|
||||
function normalizeTestMessage(value) {
|
||||
if (!isRecord(value) || typeof value.sent !== 'boolean') return undefined;
|
||||
if (value.sent) return { sent: true };
|
||||
const code = text(value.code, 'test-message-failed', 80);
|
||||
return {
|
||||
sent: false,
|
||||
code: TEST_MESSAGE_CODES.has(code) ? code : 'test-message-failed',
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeSnapshot(value) {
|
||||
const source = isRecord(value?.snapshot) ? value.snapshot : value;
|
||||
if (!isRecord(source) || !Array.isArray(source.bots)) throw new Error('QQ 服务没有返回有效的机器人列表');
|
||||
const bots = source.bots.map(normalizeBot).filter(Boolean);
|
||||
const testMessage = normalizeTestMessage(source.testMessage);
|
||||
return {
|
||||
revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
|
||||
bots,
|
||||
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
|
||||
provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
|
||||
...(testMessage ? { testMessage } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function connectionTestFeedback(result) {
|
||||
if (result?.sent === true) return '测试消息已发送,请到对应机器人会话中确认。';
|
||||
if (result?.code === 'test-target-unavailable') {
|
||||
return '连接检查完成。请先私聊机器人发送 /status,再重新检查。';
|
||||
}
|
||||
return result ? '连接检查完成,但测试消息发送失败。' : null;
|
||||
}
|
||||
|
||||
export function presentError(error) {
|
||||
return {
|
||||
code: text(error?.code, 'QQ_ERROR', 80),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { installDingtalkStyles } from '../dingtalk/styles.js';
|
|||
import {
|
||||
QQ_ENDPOINTS,
|
||||
QQ_RPC_CHANNEL,
|
||||
connectionTestFeedback,
|
||||
formatRemaining,
|
||||
normalizeProvisioning,
|
||||
normalizeSnapshot,
|
||||
|
|
@ -151,6 +152,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) {
|
|||
export function AccountCard({
|
||||
account,
|
||||
busy,
|
||||
feedback,
|
||||
removing,
|
||||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
|
|
@ -180,6 +182,11 @@ export function AccountCard({
|
|||
}),
|
||||
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
||||
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
||||
feedback ? h('div', {
|
||||
className: 'ddt-summary dim-cardSummary',
|
||||
role: 'status',
|
||||
'aria-live': 'polite',
|
||||
}, feedback) : null,
|
||||
h('div', { className: 'ddt-actions dim-cardActions' },
|
||||
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
||||
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))),
|
||||
|
|
@ -193,6 +200,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|||
const [provision, setProvision] = React.useState(null);
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [busyByBot, setBusyByBot] = React.useState({});
|
||||
const [feedbackByBot, setFeedbackByBot] = React.useState({});
|
||||
const [removeTarget, setRemoveTarget] = React.useState(null);
|
||||
const [credentialOpen, setCredentialOpen] = React.useState(false);
|
||||
const [credentialError, setCredentialError] = React.useState(null);
|
||||
|
|
@ -361,6 +369,35 @@ export function QqSettingsTab({ rpcCall }) {
|
|||
}
|
||||
}, [invoke, loadStatus, workspaceFence]);
|
||||
|
||||
const reconnect = React.useCallback(async (account) => {
|
||||
setFeedbackByBot((current) => {
|
||||
const next = { ...current };
|
||||
delete next[account.botId];
|
||||
return next;
|
||||
});
|
||||
try {
|
||||
const snapshot = await botAction(
|
||||
account,
|
||||
'reconnect',
|
||||
QQ_ENDPOINTS.reconnectBot,
|
||||
{ botId: account.botId, sendTest: true },
|
||||
);
|
||||
if (mounted.current) {
|
||||
setFeedbackByBot((current) => ({
|
||||
...current,
|
||||
[account.botId]: connectionTestFeedback(snapshot?.testMessage),
|
||||
}));
|
||||
}
|
||||
} catch {
|
||||
if (mounted.current) {
|
||||
setFeedbackByBot((current) => ({
|
||||
...current,
|
||||
[account.botId]: '连接检查失败,请稍后重试。',
|
||||
}));
|
||||
}
|
||||
}
|
||||
}, [botAction]);
|
||||
|
||||
let provisionView = null;
|
||||
if (provision?.status === 'starting') provisionView = h('div', { className: 'ddt-card ddt-loading dim-surfaceCard' }, h('div', { className: 'ddt-spinner' }), '正在申请 QQ 二维码…');
|
||||
else if (['pending', 'refreshing'].includes(provision?.status)) provisionView = h(QrPanel, {
|
||||
|
|
@ -378,8 +415,9 @@ export function QqSettingsTab({ rpcCall }) {
|
|||
h('li', { key: account.botId }, h(AccountCard, {
|
||||
account,
|
||||
busy: busyByBot[account.botId],
|
||||
feedback: feedbackByBot[account.botId],
|
||||
removing: removeTarget === account.botId,
|
||||
onReconnect: () => void botAction(account, 'reconnect', QQ_ENDPOINTS.reconnectBot, { botId: account.botId }),
|
||||
onReconnect: () => void reconnect(account),
|
||||
onWorkspaceSave: (workspace) => botAction(
|
||||
account,
|
||||
'workspace',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue