@tencent-connect/dsh-qqbot 0.5.0 → 0.5.2

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 (60) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/CHANGELOG_EN.md +18 -0
  3. package/README.md +12 -1
  4. package/dist/commands/help.d.ts +1 -1
  5. package/dist/commands/help.js +1 -1
  6. package/dist/config.d.ts +2 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +10 -8
  9. package/dist/config.js.map +1 -1
  10. package/dist/gateway/bootstrap.d.ts.map +1 -1
  11. package/dist/gateway/bootstrap.js +17 -0
  12. package/dist/gateway/bootstrap.js.map +1 -1
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +41 -22
  16. package/dist/index.js.map +1 -1
  17. package/dist/setup/bind-routes.d.ts +78 -0
  18. package/dist/setup/bind-routes.d.ts.map +1 -0
  19. package/dist/setup/bind-routes.js +311 -0
  20. package/dist/setup/bind-routes.js.map +1 -0
  21. package/dist/setup/binding-service.d.ts +86 -0
  22. package/dist/setup/binding-service.d.ts.map +1 -0
  23. package/dist/setup/binding-service.js +175 -0
  24. package/dist/setup/binding-service.js.map +1 -0
  25. package/dist/setup/index.d.ts +50 -0
  26. package/dist/setup/index.d.ts.map +1 -0
  27. package/dist/setup/index.js +98 -0
  28. package/dist/setup/index.js.map +1 -0
  29. package/dist/setup/persist.d.ts +41 -0
  30. package/dist/setup/persist.d.ts.map +1 -0
  31. package/dist/setup/persist.js +201 -0
  32. package/dist/setup/persist.js.map +1 -0
  33. package/dist/setup/presenters.d.ts +40 -0
  34. package/dist/setup/presenters.d.ts.map +1 -0
  35. package/dist/setup/presenters.js +84 -0
  36. package/dist/setup/presenters.js.map +1 -0
  37. package/dist/setup/qr-svg.d.ts +8 -0
  38. package/dist/setup/qr-svg.d.ts.map +1 -0
  39. package/dist/setup/qr-svg.js +20 -0
  40. package/dist/setup/qr-svg.js.map +1 -0
  41. package/dist/shared/bind-protocol.d.ts +42 -0
  42. package/dist/shared/bind-protocol.d.ts.map +1 -0
  43. package/dist/shared/bind-protocol.js +19 -0
  44. package/dist/shared/bind-protocol.js.map +1 -0
  45. package/dist/shared/utils.d.ts +8 -3
  46. package/dist/shared/utils.d.ts.map +1 -1
  47. package/dist/shared/utils.js +45 -4
  48. package/dist/shared/utils.js.map +1 -1
  49. package/dist/transport/reply-limiter.d.ts +1 -1
  50. package/dist/transport/reply-limiter.js +1 -1
  51. package/lib/client.js +996 -0
  52. package/lib/client.js.map +1 -0
  53. package/package.json +26 -6
  54. package/pnpm-workspace.yaml +1 -0
  55. package/tsconfig.client.json +19 -0
  56. package/tsdown.client.config.ts +58 -0
  57. package/dist/setup.d.ts +0 -24
  58. package/dist/setup.d.ts.map +0 -1
  59. package/dist/setup.js +0 -170
  60. package/dist/setup.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["STYLE_ID","useState","useRef","useCallback","createElement","createElement","useState","useCallback","createElement"],"sources":["../src/shared/bind-protocol.ts","../src/client/bind-page.ts","../src/client/config-panel.ts","../src/client/index.ts"],"sourcesContent":["/**\n * 端内绑定协议 —— 宿主路由与客户端面板的共享契约\n *\n * 类型与路径常量的唯一事实源:宿主(setup/bind-routes.ts)与浏览器\n * (client/bind-page.ts)各自 import,杜绝两端协议漂移。\n * 本模块必须保持纯类型 + 纯常量(零 node 依赖、零副作用):\n * 客户端 tsdown 构建会将其内联进 bundle。\n */\n\n/** 状态查询(绑定页轮询) */\nexport const BIND_STATE_PATH = '/qqbot/im-qqbot/bind';\n\n/** 刷新二维码(绑定页按钮) */\nexport const BIND_REFRESH_PATH = '/qqbot/im-qqbot/bind/refresh';\n\n/** 手动填写/编辑凭据(绑定页表单) */\nexport const BIND_MANUAL_PATH = '/qqbot/im-qqbot/bind/manual';\n\n/** 解绑:移除已保存凭据,回到绑定流程 */\nexport const BIND_UNBIND_PATH = '/qqbot/im-qqbot/bind/unbind';\n\n/** 配置读写(设置面板表单) */\nexport const BIND_CONFIG_PATH = '/qqbot/im-qqbot/config';\n\n/** 线上协议(HTTP 路由序列化形态) */\nexport type BindStateWire =\n | { phase: 'starting' }\n | { phase: 'waiting'; url: string; qrSvg?: string; issuedAt: number }\n | { phase: 'saving' }\n | { phase: 'failed'; reason: string }\n | { phase: 'bound'; appId: string };\n\n/** manual / unbind 操作的统一响应 */\nexport interface BindActionResponse {\n ok: boolean;\n message?: string;\n error?: string;\n}\n","/**\n * 端内绑定面板 — 扫码 / 手动填写 / 已绑定编辑\n *\n * 宿主插件注册 /qqbot/im-qqbot/bind 路由(待绑定与已绑定 fiber 均常驻):\n * - 未绑定:waiting(二维码)/ starting / saving / failed 相位 + manual 提交\n * - 已绑定:bound 相位(回显脱敏 appId,secret 不回传)+ manual 更换 + 解绑\n * 扫码成功或手动保存均写入 profile patch 并经 HMR 重载生效。\n *\n * 样式为注入式 CSS 类(imqb-* 命名空间,随面板挂载注入一次):\n * hover/过渡/focus 样式无法用 inline style 表达,统一走类;\n * 颜色尽量取 DSW 主题变量(--dsw-alias-*),缺省值兜底。\n *\n * 同一组件复用于两种展示位(侧栏绑定页 / 设置页 QQ Bot 栏目)。\n * 运行时仅依赖 react(dsh 客户端平台种子表提供);宿主通信走同源 fetch,\n * Desktop 下由壳的 IPC 桥自动附加 Host 认证转发。\n */\nimport { createElement, useCallback, useEffect, useRef, useState, type FormEvent, type ReactNode } from 'react';\nimport {\n BIND_MANUAL_PATH,\n BIND_REFRESH_PATH,\n BIND_STATE_PATH,\n BIND_UNBIND_PATH,\n type BindStateWire,\n} from '../shared/bind-protocol.ts';\n\n/** 轮询间隔(毫秒):活跃轮询 / 失败退避 */\nconst ACTIVE_POLL_MS = 2000;\nconst RETRY_POLL_MS = 3000;\n\n/** 样式注入标记(幂等) */\nconst STYLE_ID = 'im-qqbot-bind-panel-styles';\n\n/**\n * 注入面板样式(一次)。交互态(hover/active/focus)无法用 inline style\n * 表达,统一收敛到类;DSW 主题变量缺失时用中性值兜底。\n */\nfunction usePanelStyles(): void {\n useEffect(() => {\n if (typeof document === 'undefined') return;\n if (document.getElementById(STYLE_ID) !== null) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = `\n.imqb-panel {\n display: flex; flex-direction: column; align-items: center; justify-content: center;\n gap: 14px; padding: 24px; min-height: 320px; text-align: center; font-size: 14px;\n}\n.imqb-title { margin: 0; font-size: 16px; font-weight: 600; }\n.imqb-hint { margin: 0; opacity: 0.75; line-height: 1.6; }\n.imqb-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; }\n.imqb-link { font-size: 13px; opacity: 0.85; color: var(--dsw-alias-brand, #4d6bfe); text-decoration: none; }\n.imqb-link:hover { text-decoration: underline; }\n.imqb-icon { font-size: 32px; line-height: 1; }\n.imqb-qr { border-radius: 10px; background: #fff; padding: 10px; max-width: 100%; }\n.imqb-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }\n.imqb-btn, .imqb-pill {\n font: inherit; cursor: pointer; color: inherit;\n transition: background-color .15s ease, border-color .15s ease, color .15s ease, opacity .15s ease;\n}\n.imqb-btn:disabled, .imqb-pill:disabled { opacity: 0.5; cursor: default; }\n.imqb-btn {\n padding: 7px 18px; border-radius: 8px; font-size: 13px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: transparent;\n}\n.imqb-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.12)); }\n.imqb-btn--primary {\n border-color: transparent;\n background: var(--dsw-alias-brand, #4d6bfe); color: #fff; font-weight: 500;\n}\n.imqb-btn--primary:hover:not(:disabled) { background: var(--dsw-alias-brand-strong, #3b5cf0); }\n.imqb-btn--danger { color: var(--dsw-alias-danger, #f26d6d); border-color: var(--dsw-alias-danger, rgba(242, 109, 109, 0.5)); }\n.imqb-btn--danger:hover:not(:disabled) { background: var(--dsw-alias-danger, rgba(242, 109, 109, 0.12)); }\n.imqb-ghost {\n border: none; background: transparent; opacity: 0.65; padding: 4px 8px;\n font: inherit; font-size: 13px; cursor: pointer; color: inherit;\n}\n.imqb-ghost:hover { opacity: 1; text-decoration: underline; }\n.imqb-pill {\n padding: 5px 16px; border-radius: 999px; font-size: 13px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4)); background: transparent;\n}\n.imqb-pill:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(128, 128, 128, 0.12)); }\n.imqb-btn:focus-visible, .imqb-pill:focus-visible, .imqb-input:focus-visible, .imqb-ghost:focus-visible {\n outline: 2px solid var(--dsw-alias-brand, #4d6bfe); outline-offset: 1px;\n}\n.imqb-form { display: flex; flex-direction: column; gap: 12px; width: min(320px, 100%); box-sizing: border-box; }\n.imqb-field { display: flex; flex-direction: column; gap: 5px; text-align: left; font-size: 13px; }\n.imqb-input {\n padding: 8px 10px; border-radius: 8px; box-sizing: border-box; width: 100%;\n border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.4));\n background: var(--dsw-alias-bg-input, rgba(128, 128, 128, 0.08));\n color: inherit; font: inherit; font-size: 13px; outline: none;\n}\n.imqb-input:focus { border-color: var(--dsw-alias-brand, #4d6bfe); }\n.imqb-error { margin: 0; font-size: 12px; color: var(--dsw-alias-danger, #f26d6d); }\n/* 紧凑状态视图(已绑定 / 过渡态):设置行风格,无居中大卡留白 */\n.imqb-status { display: flex; flex-direction: column; text-align: left; font-size: 14px; }\n.imqb-status-row { display: flex; align-items: center; gap: 12px; padding: 12px 0; }\n.imqb-status-icon { font-size: 24px; line-height: 1; flex: none; }\n.imqb-qqicon {\n flex: none; display: block; border-radius: 8px;\n background: #fff; /* 深色企鹅 logo 需浅底,暗色主题下保持可见 */\n}\n.imqb-status-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }\n.imqb-status-title { margin: 0; font-size: 14px; font-weight: 600; line-height: 1.5; }\n.imqb-status-sub { margin: 0; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-tertiary, inherit); opacity: .85; }\n.imqb-status-actions { display: flex; align-items: center; gap: 8px; padding: 4px 0 12px; }\n/* 扫码成功后的连接过渡:二维码模糊变暗 + 覆盖层 */\n.imqb-scanned { position: relative; display: inline-block; }\n.imqb-scanned-qr {\n display: block; border-radius: 10px; background: #fff; padding: 10px;\n filter: blur(6px) saturate(0.6); opacity: 0.55;\n}\n.imqb-scanned-veil {\n position: absolute; inset: 0; display: flex; flex-direction: column;\n align-items: center; justify-content: center; gap: 6px; text-align: center;\n}\n.imqb-scanned-title { margin: 0; font-size: 15px; font-weight: 600; }\n.imqb-scanned-sub { margin: 0; font-size: 12px; opacity: 0.75; line-height: 1.6; }\n `;\n document.head.appendChild(style);\n return () => { style.remove(); };\n }, []);\n}\n\n/**\n * 绑定状态轮询 hook。\n *\n * 404 语义分两种:从未拿到过路由 = 插件未启用(终态);\n * 曾经拿到过 = HMR 重载窗口期(manual/unbind 保存后路由短暂消失),\n * 低频等待恢复而不是判定终态——否则保存成功提示会被\n * 「插件未启用」覆盖且无法自愈。\n *\n * bound 为稳定态:轮询大幅降频(30s)。因此解绑等本地动作后必须\n * 调用 recheck() 立即重置节拍——否则视图最长停留 30s+ 才收敛到\n * 新相位(如解绑后的 waiting 二维码)。\n *\n * @returns 轮询快照与 recheck:state 为宿主相位(null = 加载中),\n * gone 表示插件未启用(从未注册过路由),recheck 废弃当前\n * 定时器并立即重新轮询(新轮询链取代旧链,不产生双计时器)。\n */\nfunction useBindState(): { state: BindStateWire | null; gone: boolean; recheck: () => void } {\n const [state, setState] = useState<BindStateWire | null>(null);\n const [gone, setGone] = useState(false);\n const recheckRef = useRef<() => void>(() => {});\n\n useEffect(() => {\n let stopped = false;\n let sawRoute = false;\n let generation = 0;\n let running = false;\n let restartAfterFlight = false;\n // recheck 后的快速收敛窗口:解绑等动作改变的是服务端状态(经\n // HMR 异步生效),动作后立即轮询拿到的可能仍是旧 fiber 的相位\n // (如 bound→30s 降频),窗口期内强制快轮询直到相位真正变化\n let urgentUntil = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const schedule = (ms: number): void => {\n timer = setTimeout(() => { void tick(); }, ms);\n };\n\n const intervalFor = (phase: BindStateWire['phase']): number => {\n const base = phase === 'saving' ? 500 : phase === 'bound' ? 30000 : ACTIVE_POLL_MS;\n return Date.now() < urgentUntil ? Math.min(base, ACTIVE_POLL_MS) : base;\n };\n\n const tick = async (): Promise<void> => {\n if (stopped || running) return;\n running = true;\n const myGeneration = generation;\n const alive = (): boolean => myGeneration === generation && !stopped;\n try {\n const res = await fetch(BIND_STATE_PATH, { cache: 'no-store' });\n if (res.status === 404) {\n if (!stopped && !sawRoute) setGone(true);\n // sawRoute:HMR 窗口期,保留当前视图等待路由恢复\n else if (alive()) schedule(Date.now() < urgentUntil ? 1000 : 3000);\n return;\n }\n if (!res.ok) {\n if (alive()) schedule(RETRY_POLL_MS);\n return;\n }\n sawRoute = true;\n const body = (await res.json()) as BindStateWire;\n if (!alive()) return;\n setState(body);\n schedule(intervalFor(body.phase));\n return;\n } catch {\n // 宿主启动窗口期不可达:退避重试\n } finally {\n running = false;\n // 在飞期间被 recheck 取代:接力启动新轮询链(否则链会断)\n if (restartAfterFlight && !stopped) {\n restartAfterFlight = false;\n void tick();\n }\n }\n if (alive()) schedule(RETRY_POLL_MS);\n };\n\n // 立即重置节拍:废弃当前计时器,旧轮询链(在飞请求)作废,\n // 新链立即发起,并进入快速收敛窗口\n recheckRef.current = (): void => {\n generation += 1;\n urgentUntil = Date.now() + 15000;\n if (timer !== undefined) clearTimeout(timer);\n if (running) {\n restartAfterFlight = true;\n return;\n }\n void tick();\n };\n\n void tick();\n return () => { stopped = true; if (timer !== undefined) clearTimeout(timer); };\n }, []);\n\n const recheck = useCallback((): void => { recheckRef.current(); }, []);\n\n return { state, gone, recheck };\n}\n\ninterface ManualResult { ok: boolean; message?: string; error?: string }\n\n/** 手动提交凭据(填写 / 更换共用) */\nasync function submitManualCredentials(appId: string, appSecret: string): Promise<ManualResult> {\n try {\n const res = await fetch(BIND_MANUAL_PATH, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ appId, appSecret }),\n });\n const body = (await res.json().catch(() => ({}))) as ManualResult;\n if (res.ok && body.ok) return { ok: true, message: body.message };\n return { ok: false, error: body.error ?? `保存失败(HTTP ${res.status})` };\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : String(err) };\n }\n}\n\n/**\n * 手动凭据表单(填写 / 更换共用)。\n *\n * appSecret 永不回显(占位符提示),提交成功后展示保存结果;\n * 宿主写入 profile patch 并经 HMR 重载,状态轮询随后收敛为 bound。\n */\nfunction ManualForm({ initialAppId }: { initialAppId?: string }): ReactNode {\n const [appId, setAppId] = useState(initialAppId ?? '');\n const [appSecret, setAppSecret] = useState('');\n const [busy, setBusy] = useState(false);\n const [result, setResult] = useState<ManualResult | null>(null);\n\n const submit = useCallback((event: FormEvent): void => {\n event.preventDefault();\n if (busy) return;\n const trimmedId = appId.trim();\n const trimmedSecret = appSecret.trim();\n if (trimmedId.length === 0 || trimmedSecret.length === 0) {\n setResult({ ok: false, error: 'appId 与 appSecret 均为必填' });\n return;\n }\n setBusy(true);\n void submitManualCredentials(trimmedId, trimmedSecret).then((outcome) => {\n setResult(outcome);\n setBusy(false);\n });\n }, [appId, appSecret, busy]);\n\n if (result !== null && result.ok) {\n return createElement('div', { className: 'imqb-status' },\n createElement('div', { className: 'imqb-status-row' },\n createElement('div', { className: 'imqb-status-icon' }, '✅'),\n createElement('div', { className: 'imqb-status-meta' },\n createElement('p', { className: 'imqb-status-title' }, result.message ?? '凭据已保存'),\n createElement('p', { className: 'imqb-status-sub' }, '插件将自动重载;稍后即可开始对话'),\n ),\n ),\n );\n }\n\n return createElement('form', { className: 'imqb-form', onSubmit: submit },\n createElement('label', { className: 'imqb-field' },\n 'AppID',\n createElement('input', {\n className: 'imqb-input', value: appId, placeholder: 'QQ 机器人 AppID',\n onChange: (event) => { setAppId(event.currentTarget.value); },\n autoComplete: 'off', spellCheck: false,\n }),\n ),\n createElement('label', { className: 'imqb-field' },\n 'AppSecret',\n createElement('input', {\n className: 'imqb-input', type: 'password', value: appSecret,\n placeholder: initialAppId === undefined ? 'QQ 机器人 AppSecret' : '输入新的 AppSecret(不回显)',\n onChange: (event) => { setAppSecret(event.currentTarget.value); },\n autoComplete: 'new-password',\n }),\n ),\n result !== null && !result.ok\n ? createElement('p', { className: 'imqb-error' }, result.error)\n : null,\n createElement('button', { className: 'imqb-btn imqb-btn--primary', type: 'submit', disabled: busy },\n busy ? '正在保存…' : '保存'),\n );\n}\n\n/** QQ 企鹅 logo(256×256 PNG data URI;深色图案配白底圆角,明暗主题下均可见) */\nconst QQ_BOT_ICON_PNG =\n 'data:image/png;base64,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';\n\n/** QQ 机器人图标(logo 图片,白底圆角容器内呈现) */\nfunction QqBotIcon({ size = 28 }: { size?: number }): ReactNode {\n return createElement('img', {\n src: QQ_BOT_ICON_PNG, alt: 'QQ', width: size, height: size, className: 'imqb-qqicon',\n });\n}\n\n/** 已绑定视图:回显 appId + 更换凭据 / 解绑入口 */\nfunction BoundView({ appId, recheck }: { appId: string; recheck: () => void }): ReactNode {\n const [editing, setEditing] = useState(false);\n const [unbindResult, setUnbindResult] = useState<{ ok: boolean; message: string } | null>(null);\n\n const unbind = useCallback((): void => {\n if (!window.confirm('确定解绑吗?\\n解绑后机器人立即下线,插件将重新进入扫码绑定流程。')) return;\n void fetch(BIND_UNBIND_PATH, { method: 'POST' })\n .then(async (res) => {\n const body = (await res.json().catch(() => ({}))) as { ok?: boolean; message?: string; error?: string };\n if (res.ok && body.ok === true) {\n setUnbindResult({ ok: true, message: body.message ?? '已解绑,插件将重载并重新进入绑定流程' });\n // bound 态轮询已降频到 30s:立即重置节拍,让 waiting(二维码)\n // 尽快出现而不是等下一个 30s 周期\n recheck();\n return;\n }\n setUnbindResult({ ok: false, message: body.error ?? `解绑失败(HTTP ${res.status})` });\n })\n .catch((err: unknown) => {\n setUnbindResult({ ok: false, message: err instanceof Error ? err.message : String(err) });\n });\n }, [recheck]);\n\n if (unbindResult !== null) {\n // 非终态视图:解绑成功后正常由轮询收敛到 waiting;若 HMR 未\n // 触发(状态停留 bound),用户仍有「刷新状态」出口而不是死胡同\n return createElement('div', { className: 'imqb-status' },\n createElement('div', { className: 'imqb-status-row' },\n createElement('div', { className: 'imqb-status-icon' }, unbindResult.ok ? '👋' : '⚠️'),\n createElement('div', { className: 'imqb-status-meta' },\n createElement('p', { className: 'imqb-status-title' }, unbindResult.message),\n ),\n unbindResult.ok\n ? createElement('button', { className: 'imqb-ghost', onClick: recheck }, '刷新状态')\n : createElement('button', { className: 'imqb-ghost', onClick: () => { setUnbindResult(null); } }, '返回'),\n ),\n );\n }\n\n if (editing) {\n return createElement('div', { className: 'imqb-status' },\n createElement('div', { className: 'imqb-status-row' },\n createElement('div', { className: 'imqb-status-meta' },\n createElement('p', { className: 'imqb-status-title' }, '更换凭据'),\n ),\n createElement('button', { className: 'imqb-ghost', onClick: () => { setEditing(false); } }, '返回'),\n ),\n createElement(ManualForm, { initialAppId: appId }),\n );\n }\n return createElement('div', { className: 'imqb-status' },\n createElement('div', { className: 'imqb-status-row' },\n createElement(QqBotIcon, { size: 28 }),\n createElement('div', { className: 'imqb-status-meta' },\n createElement('p', { className: 'imqb-status-title' }, '已绑定'),\n createElement('p', { className: 'imqb-status-sub imqb-mono' }, `AppID ${appId} · 私聊与群聊已接入`),\n ),\n ),\n createElement('div', { className: 'imqb-status-actions' },\n createElement('button', { className: 'imqb-btn imqb-btn--primary', onClick: () => { setEditing(true); } }, '更换凭据'),\n createElement('button', { className: 'imqb-btn imqb-btn--danger', onClick: unbind }, '解绑'),\n ),\n );\n}\n\n/** 未绑定视图:扫码 / 手动填写切换 */\nfunction UnboundView({ state }: { state: Extract<BindStateWire, { phase: 'waiting' }> }): ReactNode {\n const [mode, setMode] = useState<'qr' | 'manual'>('qr');\n const refresh = useCallback(() => { void fetch(BIND_REFRESH_PATH, { method: 'POST' }).catch(() => {}); }, []);\n\n if (mode === 'manual') {\n return createElement('div', { className: 'imqb-panel' },\n createElement('p', { className: 'imqb-title' }, '手动填写凭据'),\n createElement(ManualForm, {}),\n createElement('button', { className: 'imqb-ghost', onClick: () => { setMode('qr'); } }, '改用扫码绑定'),\n );\n }\n\n return createElement('div', { className: 'imqb-panel' },\n state.qrSvg === undefined\n ? null\n : createElement('img', { src: state.qrSvg, alt: 'QQ 扫码二维码', width: 220, height: 220, className: 'imqb-qr' }),\n createElement('p', { className: 'imqb-title' }, '扫码绑定 QQ 机器人'),\n createElement('p', { className: 'imqb-hint' }, '使用手机 QQ 扫描二维码完成绑定'),\n createElement('a', { href: state.url, target: '_blank', rel: 'noreferrer noopener', className: 'imqb-link' }, '二维码显示异常?在浏览器中打开'),\n createElement('div', { className: 'imqb-row' },\n createElement('button', { className: 'imqb-pill', onClick: refresh }, '刷新二维码'),\n createElement('button', { className: 'imqb-pill', onClick: () => { setMode('manual'); } }, '手动填写'),\n ),\n );\n}\n\n/** 依据宿主相位渲染面板主体(各展示位共用) */\nexport function BindPanelBody(): ReactNode {\n usePanelStyles();\n const { state, gone, recheck } = useBindState();\n // 扫码成功的连接过渡场景需要保留最后一帧二维码:waiting 相位持续记录,\n // bound 后清空(避免下次解绑重绑时旧码闪现)\n const [lastQr, setLastQr] = useState<string | null>(null);\n useEffect(() => {\n if (state === null) return;\n if (state.phase === 'waiting') {\n if (state.qrSvg !== undefined) setLastQr(state.qrSvg);\n } else if (state.phase === 'bound') {\n setLastQr(null);\n }\n }, [state]);\n\n if (gone) {\n return createElement('div', { className: 'imqb-panel' },\n createElement('div', { className: 'imqb-icon' }, '❔'),\n createElement('p', { className: 'imqb-hint' }, '插件未启用,或当前版本不支持端内配置'),\n );\n }\n\n if (state === null || state.phase === 'starting') {\n return createElement('div', { className: 'imqb-panel' },\n createElement('p', { className: 'imqb-hint' }, '正在获取绑定状态…'),\n );\n }\n\n if (state.phase === 'saving') {\n // 扫码路径:保留二维码做模糊过渡(手动填写路径无二维码,用紧凑态)\n if (lastQr !== null) {\n return createElement('div', { className: 'imqb-panel' },\n createElement('div', { className: 'imqb-scanned' },\n createElement('img', { src: lastQr, alt: 'QQ 扫码二维码', width: 220, height: 220, className: 'imqb-scanned-qr' }),\n createElement('div', { className: 'imqb-scanned-veil' },\n createElement('p', { className: 'imqb-scanned-title' }, '✅ 扫码成功'),\n createElement('p', { className: 'imqb-scanned-sub' }, '正在连接机器人,稍候…'),\n ),\n ),\n );\n }\n return createElement('div', { className: 'imqb-status' },\n createElement('div', { className: 'imqb-status-row' },\n createElement('div', { className: 'imqb-status-icon' }, '🎉'),\n createElement('div', { className: 'imqb-status-meta' },\n createElement('p', { className: 'imqb-status-title' }, '扫码成功'),\n createElement('p', { className: 'imqb-status-sub' }, '凭据已保存,插件正在重载,稍后即可开始对话'),\n ),\n ),\n );\n }\n\n if (state.phase === 'failed') {\n return createElement('div', { className: 'imqb-panel' },\n createElement('div', { className: 'imqb-icon' }, '⚠️'),\n createElement('p', { className: 'imqb-title' }, '绑定失败'),\n createElement('p', { className: 'imqb-hint' }, state.reason),\n createElement('div', { className: 'imqb-row' },\n createElement('button', { className: 'imqb-btn imqb-btn--primary', onClick: () => { window.location.reload(); } }, '重试'),\n ),\n );\n }\n\n if (state.phase === 'bound') {\n return createElement(BoundView, { appId: state.appId, recheck });\n }\n\n return createElement(UnboundView, { state });\n}\n","/**\n * QQ 机器人配置面板 — 必要配置的 UI 编辑\n *\n * UI 复刻 DSW 通用设置(settings-form 原语)的视觉风格:\n * 左对齐设置列、0.5px 行分隔、label 13px/500、控件右置;\n * Switch 为 36×20 aria-checked 结构(thumb translateX 动画);\n * 三值选择用分段控件(滑动指示器按段数/索引的 CSS 变量定位);\n * 保存按钮为 DSW 反色主按钮(label-primary 底 + bg-layer-3 字)。\n *\n * 数据流:GET /qqbot/im-qqbot/config 回显当前生效配置(appSecret\n * 已由宿主剔除)→ 表单编辑 → dirty diff → POST 仅提交变更键 →\n * 宿主白名单 + Schema 校验 → 最小写入 profile patch → HMR 重载生效。\n *\n * 字段策略:只开放用户必要的配置;cwd 只读展示;超时与性能调优\n * 类字段走 profile patch / 环境变量。\n */\nimport { createElement, useCallback, useEffect, useState, type CSSProperties, type ChangeEvent, type FormEvent, type ReactNode } from 'react';\nimport { BIND_CONFIG_PATH } from '../shared/bind-protocol.ts';\n\n/** 访问控制配置(与 src/config.ts 的 AccessControlConfig 对应的局部投影) */\ninterface AccessControlWire {\n c2cMode: 'open' | 'allowlist' | 'disabled';\n c2cAllow: string[];\n groupMode: 'open' | 'allowlist' | 'disabled';\n groupAllow: string[];\n}\n\n/** GET 回显的生效配置(appSecret 已剔除) */\ntype ConfigWire = {\n requireMention?: boolean;\n streaming?: boolean;\n showToolResults?: boolean;\n debug?: boolean;\n provider?: string;\n model?: string;\n preset?: string;\n groupPrompt?: string;\n directPrompt?: string;\n access?: AccessControlWire;\n cwd?: string;\n};\n\n/** 样式注入标记(幂等) */\nconst STYLE_ID = 'im-qqbot-config-panel-styles';\n\n/** DSW 通用设置风格(settings-form / Switch / SegmentedControl 原语复刻) */\nfunction useConfigPanelStyles(): void {\n useEffect(() => {\n if (typeof document === 'undefined') return;\n if (document.getElementById(STYLE_ID) !== null) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = `\n.imqb2-form { display: flex; flex-direction: column; width: 100%; }\n.imqb2-group-title {\n padding: 14px 0 2px; font-size: 12px; font-weight: 600; line-height: 18px;\n color: var(--dsw-alias-label-tertiary); text-transform: uppercase; letter-spacing: .04em;\n}\n.imqb2-field { display: flex; flex-direction: column; gap: 6px; padding: 12px 0; }\n.imqb2-field + .imqb2-field { border-top: 0.5px solid var(--dsw-alias-border-l2); }\n.imqb2-head { display: flex; align-items: center; gap: 8px; min-height: 28px; }\n.imqb2-label {\n flex: 1; min-width: 0; font-size: 13px; font-weight: 500; line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.imqb2-help { margin: 0; font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }\n.imqb2-hint { margin: 0; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); }\n/* Switch(aria-checked 驱动,视觉态与无障碍态同源) */\n.imqb2-switch {\n box-sizing: border-box; position: relative; flex: 0 0 auto;\n width: 36px; height: 20px; padding: 2px; border: 0; border-radius: 999px;\n background: var(--dsw-alias-border-l3); cursor: pointer; font: inherit;\n}\n.imqb2-switch[aria-checked='true'] { background: var(--dsw-alias-brand-primary); }\n.imqb2-switch:disabled { cursor: default; opacity: 0.5; }\n.imqb2-switch:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: 2px;\n}\n.imqb2-switch-thumb {\n display: block; width: 16px; height: 16px; border-radius: 50%;\n background: var(--dsw-alias-switch-thumb, #ffffff);\n transition: transform 120ms ease;\n}\n.imqb2-switch[aria-checked='true'] .imqb2-switch-thumb {\n background: var(--dsw-alias-label-primary-foreground, #ffffff);\n transform: translateX(16px);\n}\n/* 输入(官方 fields.input 复刻) */\n.imqb2-input {\n box-sizing: border-box; width: 100%; height: 34px; padding: 0 12px;\n border: 0.5px solid var(--dsw-alias-border-l4); border-radius: var(--dsw-radius-md, 8px);\n background: var(--dsw-alias-bg-layer-3); font: inherit; font-size: 13px; line-height: 1.5;\n color: var(--dsw-alias-label-primary); outline: none;\n}\n.imqb2-input:focus { outline: none; border-color: var(--dsw-alias-state-business-primary); }\n.imqb2-input:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }\n.imqb2-textarea { height: auto; min-height: 68px; padding: 8px 12px; resize: vertical; line-height: 1.5; }\n/* 分段控件(官方 SegmentedControl 复刻:指示器按段数/索引定位) */\n.imqb2-seg {\n position: relative; display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr;\n gap: 2px; padding: 4px; border-radius: var(--dsw-radius-md, 8px);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.imqb2-seg-indicator {\n position: absolute; top: 4px; left: 4px;\n width: calc((100% - 8px - 2px * (var(--imqb2-seg-count) - 1)) / var(--imqb2-seg-count));\n height: calc(100% - 8px);\n border: 0; border-radius: var(--dsw-radius-sm, 6px);\n background: var(--dsw-alias-bg-layer-1);\n box-shadow: var(--dsw-elevation-soft, 0 1px 3px rgba(0,0,0,.08));\n transform: translateX(calc(var(--imqb2-seg-index) * (100% + 2px)));\n transition: transform 160ms ease;\n pointer-events: none;\n}\n.imqb2-seg-tab {\n box-sizing: border-box; position: relative; z-index: 1; height: 28px; padding: 0 16px;\n border: 0; border-radius: var(--dsw-radius-sm, 6px); background: transparent;\n color: var(--dsw-alias-label-secondary); font: inherit; font-size: 13px; line-height: 20px;\n font-weight: 500; white-space: nowrap; cursor: pointer; transition: color 120ms ease;\n}\n.imqb2-seg-tab:hover, .imqb2-seg-tab[aria-selected='true'] { color: var(--dsw-alias-label-primary); }\n.imqb2-seg-tab:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: -2px;\n}\n/* 底栏(官方 SettingsForm.footer 复刻) */\n.imqb2-footer { display: flex; align-items: center; gap: 8px; padding-top: 16px; }\n.imqb2-note { flex: 1; min-width: 0; margin: 0; font-size: 12px; line-height: 1.5; color: var(--dsw-alias-label-tertiary); }\n.imqb2-note--error { color: var(--dsw-alias-state-error-primary, var(--dsw-alias-label-error)); }\n.imqb2-note--ok { color: var(--dsw-alias-label-secondary); }\n.imqb2-save {\n appearance: none; flex: none; border: 1px solid transparent;\n border-radius: var(--dsw-radius-md, 8px); padding: 5px 14px;\n font: inherit; font-size: 13px; line-height: 1.5; cursor: pointer;\n background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-3);\n}\n.imqb2-save:disabled { opacity: 0.4; cursor: default; }\n.imqb2-save:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: 1px;\n}\n/* cwd 只读行 */\n.imqb2-readonly { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--dsw-alias-label-secondary); word-break: break-all; margin: 0; }\n `;\n document.head.appendChild(style);\n return () => { style.remove(); };\n }, []);\n}\n\n/** 开关(官方 Switch 原语结构:aria-checked 驱动视觉态) */\nfunction Switch({ checked, label, onChange }: {\n checked: boolean; label: string; onChange: (next: boolean) => void;\n}): ReactNode {\n return createElement('button', {\n type: 'button', role: 'switch', 'aria-checked': checked, 'aria-label': label,\n className: 'imqb2-switch',\n onClick: () => { onChange(!checked); },\n }, createElement('span', { className: 'imqb2-switch-thumb' }));\n}\n\n/** 访问模式分段选择(官方 SegmentedControl 结构:CSS 变量定位滑动指示器) */\nfunction Segmented({ value, options, onChange, label }: {\n value: AccessMode; options: ReadonlyArray<{ value: AccessMode; label: string }>;\n onChange: (next: AccessMode) => void; label: string;\n}): ReactNode {\n const index = Math.max(0, options.findIndex((o) => o.value === value));\n const trackStyle = {\n '--imqb2-seg-count': options.length,\n '--imqb2-seg-index': index,\n } as CSSProperties;\n return createElement('div', { className: 'imqb2-seg', role: 'tablist', 'aria-label': label, style: trackStyle },\n createElement('span', { className: 'imqb2-seg-indicator' }),\n options.map((o) => createElement('button', {\n key: o.value, type: 'button', role: 'tab', 'aria-selected': o.value === value,\n className: 'imqb2-seg-tab', onClick: () => { onChange(o.value); },\n }, o.label)),\n );\n}\n\n/** 开关行(label 左 + Switch 右 + 可选 help) */\nfunction SwitchRow({ label, help, checked, onChange }: {\n label: string; help?: string; checked: boolean; onChange: (next: boolean) => void;\n}): ReactNode {\n return createElement('div', { className: 'imqb2-field' },\n createElement('div', { className: 'imqb2-head' },\n createElement('span', { className: 'imqb2-label' }, label),\n createElement(Switch, { checked, label, onChange }),\n ),\n help === undefined ? null : createElement('p', { className: 'imqb2-help' }, help),\n );\n}\n\n/** 文本行(label 上 + 输入下,与官方 fields 布局一致) */\nfunction FieldRow({ label, help, value, placeholder, onChange }: {\n label: string; help?: string; value: string; placeholder?: string; onChange: (next: string) => void;\n}): ReactNode {\n return createElement('div', { className: 'imqb2-field' },\n createElement('span', { className: 'imqb2-label' }, label),\n createElement('input', {\n className: 'imqb2-input', value, placeholder, spellCheck: false,\n onChange: (event: ChangeEvent<HTMLInputElement>) => { onChange(event.currentTarget.value); },\n }),\n help === undefined ? null : createElement('p', { className: 'imqb2-hint' }, help),\n );\n}\n\n/** 多行文本行 */\nfunction AreaRow({ label, help, value, rows, onChange }: {\n label: string; help?: string; value: string; rows?: number; onChange: (next: string) => void;\n}): ReactNode {\n return createElement('div', { className: 'imqb2-field' },\n createElement('span', { className: 'imqb2-label' }, label),\n createElement('textarea', {\n className: 'imqb2-input imqb2-textarea', value, rows: rows ?? 3,\n onChange: (event: ChangeEvent<HTMLTextAreaElement>) => { onChange(event.currentTarget.value); },\n }),\n help === undefined ? null : createElement('p', { className: 'imqb2-hint' }, help),\n );\n}\n\n/** 访问模式分段选择行 */\nfunction SegmentedRow({ label, help, value, options, onChange }: {\n label: string; help?: string; value: AccessMode;\n options: ReadonlyArray<{ value: AccessMode; label: string }>; onChange: (next: AccessMode) => void;\n}): ReactNode {\n return createElement('div', { className: 'imqb2-field' },\n createElement('div', { className: 'imqb2-head' },\n createElement('span', { className: 'imqb2-label' }, label),\n createElement(Segmented, { value, options, onChange, label }),\n ),\n help === undefined ? null : createElement('p', { className: 'imqb2-hint' }, help),\n );\n}\n\ntype AccessMode = AccessControlWire['c2cMode'];\n\nconst MODE_OPTIONS: ReadonlyArray<{ value: AccessMode; label: string }> = [\n { value: 'open', label: '开放' },\n { value: 'allowlist', label: '白名单' },\n { value: 'disabled', label: '停用' },\n];\n\n/** 深比较(表单值与初始回显值的 dirty 判断) */\nfunction shallowEqual(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n/**\n * 配置面板主体。\n *\n * 保存仅提交变更键(服务端按白名单合并),保存成功后提示\n * HMR 重载语义(进行中的机器人会话将重置)。\n */\nexport function ConfigPanel(): ReactNode {\n useConfigPanelStyles();\n const [initial, setInitial] = useState<ConfigWire | null>(null);\n const [form, setForm] = useState<ConfigWire | null>(null);\n const [busy, setBusy] = useState(false);\n const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null);\n const [loadError, setLoadError] = useState<string | null>(null);\n\n useEffect(() => {\n let stopped = false;\n void fetch(BIND_CONFIG_PATH, { cache: 'no-store' })\n .then(async (res) => {\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const body = (await res.json()) as { config: ConfigWire };\n if (stopped) return;\n setInitial(body.config);\n setForm(body.config);\n })\n .catch((err: unknown) => {\n if (!stopped) setLoadError(err instanceof Error ? err.message : String(err));\n });\n return () => { stopped = true; };\n }, []);\n\n const patch = useCallback(<K extends keyof ConfigWire>(key: K, value: ConfigWire[K]): void => {\n setForm((current) => current === null ? current : { ...current, [key]: value });\n setResult(null);\n }, []);\n\n const submit = useCallback((event: FormEvent): void => {\n event.preventDefault();\n if (busy || initial === null || form === null) return;\n const changed: Record<string, unknown> = {};\n for (const key of Object.keys(form) as Array<keyof ConfigWire>) {\n if (!shallowEqual(form[key], initial[key])) {\n changed[key] = form[key];\n }\n }\n if (Object.keys(changed).length === 0) return;\n\n setBusy(true);\n void fetch(BIND_CONFIG_PATH, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(changed),\n })\n .then(async (res) => {\n const body = (await res.json().catch(() => ({}))) as { ok?: boolean; message?: string; error?: string };\n if (res.ok && body.ok === true) {\n setResult({ ok: true, message: body.message ?? '配置已保存,插件将自动重载' });\n setInitial(form);\n return;\n }\n setResult({ ok: false, message: body.error ?? `保存失败(HTTP ${res.status})` });\n })\n .catch((err: unknown) => {\n setResult({ ok: false, message: err instanceof Error ? err.message : String(err) });\n })\n .finally(() => { setBusy(false); });\n }, [busy, form, initial]);\n\n if (loadError !== null) {\n return createElement('p', { className: 'imqb2-hint', style: { padding: '12px 0' } },\n `配置加载失败:${loadError}`);\n }\n if (initial === null || form === null) {\n return createElement('p', { className: 'imqb2-hint', style: { padding: '12px 0' } }, '正在加载配置…');\n }\n\n const dirty = (Object.keys(form) as Array<keyof ConfigWire>).some((key) => !shallowEqual(form[key], initial[key]));\n const access: AccessControlWire = form.access ?? { c2cMode: 'open', c2cAllow: [], groupMode: 'open', groupAllow: [] };\n\n return createElement('form', { className: 'imqb2-form', onSubmit: submit },\n createElement('div', { className: 'imqb2-group-title' }, '访问控制'),\n createElement(SegmentedRow, {\n label: '私聊', value: access.c2cMode, options: MODE_OPTIONS,\n onChange: (v) => { patch('access', { ...access, c2cMode: v }); },\n }),\n access.c2cMode === 'allowlist'\n ? createElement(AreaRow, {\n label: '私聊白名单', help: '每行一个 openid', rows: 3,\n value: access.c2cAllow.join('\\n'),\n onChange: (v) => { patch('access', { ...access, c2cAllow: v.split('\\n').map((s) => s.trim()).filter(Boolean) }); },\n })\n : null,\n createElement(SegmentedRow, {\n label: '群聊', value: access.groupMode, options: MODE_OPTIONS,\n onChange: (v) => { patch('access', { ...access, groupMode: v }); },\n }),\n access.groupMode === 'allowlist'\n ? createElement(AreaRow, {\n label: '群聊白名单', help: '每行一个群 openid', rows: 3,\n value: access.groupAllow.join('\\n'),\n onChange: (v) => { patch('access', { ...access, groupAllow: v.split('\\n').map((s) => s.trim()).filter(Boolean) }); },\n })\n : null,\n\n createElement('div', { className: 'imqb2-group-title' }, '基本'),\n createElement(SwitchRow, {\n label: '群聊需要 @机器人', checked: form.requireMention ?? true,\n help: '开启后群聊中只有 @机器人 的消息会触发回复',\n onChange: (v) => { patch('requireMention', v); },\n }),\n createElement(SwitchRow, {\n label: '流式输出', checked: form.streaming ?? true,\n help: '回复内容分段实时推送到聊天',\n onChange: (v) => { patch('streaming', v); },\n }),\n createElement(SwitchRow, {\n label: '显示工具调用结果', checked: form.showToolResults ?? false,\n help: '在聊天中展示 agent 工具调用的结构化结果',\n onChange: (v) => { patch('showToolResults', v); },\n }),\n\n createElement('div', { className: 'imqb2-group-title' }, '模型'),\n createElement(FieldRow, {\n label: 'Provider', value: form.provider ?? '', placeholder: '留空继承宿主默认',\n onChange: (v) => { patch('provider', v); },\n }),\n createElement(FieldRow, {\n label: 'Model', value: form.model ?? '', placeholder: '留空继承宿主默认',\n onChange: (v) => { patch('model', v); },\n }),\n createElement(FieldRow, {\n label: 'Preset', value: form.preset ?? '', placeholder: 'Agent preset id',\n onChange: (v) => { patch('preset', v); },\n }),\n\n createElement('div', { className: 'imqb2-group-title' }, '补充指令'),\n createElement(AreaRow, {\n label: '群聊额外 system prompt', value: form.groupPrompt ?? '',\n onChange: (v) => { patch('groupPrompt', v); },\n }),\n createElement(AreaRow, {\n label: '私聊额外 system prompt', value: form.directPrompt ?? '',\n onChange: (v) => { patch('directPrompt', v); },\n }),\n\n form.cwd !== undefined\n ? createElement('div', { className: 'imqb2-field' },\n createElement('div', { className: 'imqb2-head' },\n createElement('span', { className: 'imqb2-label' }, 'Agent 工作目录'),\n createElement('span', { className: 'imqb2-hint' }, '只读'),\n ),\n createElement('p', { className: 'imqb2-readonly' }, form.cwd),\n )\n : null,\n\n createElement('div', { className: 'imqb2-footer' },\n result !== null\n ? createElement('p', { className: `imqb2-note${result.ok ? ' imqb2-note--ok' : ' imqb2-note--error'}` },\n (result.ok ? '' : '⚠️ ') + result.message)\n : dirty\n ? createElement('p', { className: 'imqb2-note' }, '保存后插件将重载,进行中的机器人会话将重置')\n : createElement('p', { className: 'imqb2-note' }),\n createElement('button', { className: 'imqb2-save', type: 'submit', disabled: busy || !dirty },\n busy ? '正在保存…' : '保存'),\n ),\n );\n}\n","/**\n * im-qqbot 浏览器半边 —— QQ Bot 端内扫码绑定入口\n *\n * 宿主插件在凭据缺失时注册 /qqbot/im-qqbot/bind 状态路由;本插件在\n * Web / Desktop 提供两个展示位:\n *\n * 1. 设置页「QQ Bot」栏目(settings.section 席位)——注册即展示,\n * 无 Session surface 时序依赖,待绑定/已绑定状态常驻可见;\n * 2. 右侧栏「QQ Bot 绑定」页(sidebarRightTabs)——Start 页入口 +\n * 待绑定时自动打开(失败随轮询重试)。\n *\n * 运行时依赖仅 react(客户端平台种子表提供);宿主服务经 cordis inject\n * 声明访问(声明缺失时插件保持 inactive,不阻塞浏览器启动)。\n */\nimport type { Context } from '@deepseek-ai/cordis';\nimport { createElement, type ReactNode } from 'react';\nimport { BIND_STATE_PATH } from '../shared/bind-protocol.ts';\nimport { BindPanelBody } from './bind-page.ts';\nimport { ConfigPanel } from './config-panel.ts';\n\nexport const name = 'im-qqbot-client';\nexport const inject = ['slots', 'sidebarRight', 'sidebarRightTabs'];\n\n/** 侧栏 Tab 类型(ui-sidebar-right 契约的局部投影) */\nconst TAB_KIND = 'qqbot-bind';\n/** 席位注册 key(Tab 类型 id,席位按其分发;只需全局唯一,与包名解耦) */\nconst TAB_ID = 'im-qqbot/bind';\n\n/** ui-sidebar-right 的 Tab 注册表服务(局部投影) */\ninterface TabRegistryLike {\n register(definition: {\n id: string;\n kind: string;\n title: (address: string) => string;\n guide?: ReadonlyArray<{\n id: string;\n order: number;\n title: () => string;\n description: () => string;\n icon: unknown;\n }>;\n }): () => void;\n}\n\n/** ui-sidebar-right 的导航控制器服务(局部投影) */\ninterface SidebarRightLike {\n openTab(kind: string, options?: Record<string, unknown>): unknown;\n}\n\n/** ui-slots 的席位系统服务(局部投影) */\ninterface SlotsLike {\n inject(slotName: string, register: () => (() => void)): () => void;\n register(spec: Record<string, unknown>, component: unknown): () => void;\n}\n\n/** 绑定入口图标(QQ 机器人,跟随主题图标色变量) */\nfunction BindGuideIcon(): ReactNode {\n return createElement('svg', {\n width: 24, height: 24, viewBox: '0 0 24 24', fill: 'none', 'aria-hidden': true,\n },\n createElement('path', {\n d: 'M9.50039 11.1504C9.96983 11.1504 10.3504 11.5309 10.3504 12.0004V14.0004C10.3504 14.4698 9.96983 14.8504 9.50039 14.8504C9.03095 14.8504 8.65039 14.4698 8.65039 14.0004V12.0004C8.65039 11.5309 9.03095 11.1504 9.50039 11.1504Z',\n fill: 'var(------icon_primary, #1A1A1A)',\n }),\n createElement('path', {\n d: 'M14.5004 11.1504C14.9698 11.1504 15.3504 11.5309 15.3504 12.0004V14.0004C15.3504 14.4698 14.9698 14.8504 14.5004 14.8504C14.0309 14.8504 13.6504 14.4698 13.6504 14.0004V12.0004C13.6504 11.5309 14.0309 11.1504 14.5004 11.1504Z',\n fill: 'var(------icon_primary, #1A1A1A)',\n }),\n createElement('path', {\n fillRule: 'evenodd',\n clipRule: 'evenodd',\n d: 'M12.8504 5.3109C13.5299 4.99138 14.0004 4.30063 14.0004 3.5C14.0004 2.39543 13.1049 1.5 12.0004 1.5C10.8958 1.5 10.0004 2.39543 10.0004 3.5C10.0004 4.30065 10.4708 4.99141 11.1504 5.31092V6.18851C6.38487 6.61795 2.65039 10.6231 2.65039 15.5004V17.0004C2.65039 19.679 4.82181 21.8504 7.50039 21.8504H16.5004C19.179 21.8504 21.3504 19.679 21.3504 17.0004V15.5004C21.3504 10.6231 17.6159 6.61795 12.8504 6.18851V5.3109ZM12.0004 7.85039C7.77541 7.85039 4.35039 11.2754 4.35039 15.5004V17.0004C4.35039 18.7401 5.76069 20.1504 7.50039 20.1504H16.5004C18.2401 20.1504 19.6504 18.7401 19.6504 17.0004V15.5004C19.6504 11.2754 16.2254 7.85039 12.0004 7.85039Z',\n fill: 'var(------icon_primary, #1A1A1A)',\n }),\n );\n}\n\n/** 设置页「QQ Bot」栏目:面板常驻展示绑定状态 */\nfunction QQBotSettingsSection(): ReactNode {\n return createElement('div', null,\n createElement(BindPanelBody),\n createElement(ConfigPanel),\n );\n}\n\nexport function apply(ctx: Context): void {\n const tabs = (ctx as unknown as { sidebarRightTabs?: TabRegistryLike }).sidebarRightTabs;\n const right = (ctx as unknown as { sidebarRight?: SidebarRightLike }).sidebarRight;\n const slots = (ctx as unknown as { slots?: SlotsLike }).slots;\n\n const disposers: Array<() => void> = [];\n\n // 1. 设置页「QQ Bot」栏目(主展示位:注册即展示,无时序依赖)\n if (slots !== undefined) {\n disposers.push( slots.inject('settings.section', () => slots.register(\n { name: 'settings.section', id: 'qqbot', order: 7, label: 'QQ 机器人' },\n QQBotSettingsSection,\n )));\n }\n\n // 2. 侧栏绑定页(快捷展示位:Start 页入口 + 待绑定自动打开)\n if (tabs !== undefined && slots !== undefined) {\n disposers.push(tabs.register({\n id: TAB_ID,\n kind: TAB_KIND,\n title: () => 'QQ 机器人绑定',\n guide: [{\n id: 'bind',\n order: 90,\n title: () => 'QQ 机器人绑定',\n description: () => '扫码或手动填写,绑定 QQ 机器人',\n icon: BindGuideIcon,\n }],\n }));\n disposers.push(slots.inject('sidebar.right.pane.tab', () => slots.register(\n { name: 'sidebar.right.pane.tab', key: TAB_ID },\n BindPanelBody,\n )));\n // 待绑定检测与自动引导\n disposers.push(watchAndPrompt(right));\n }\n\n (ctx as unknown as { effect(fn: () => () => void, name?: string): void }).effect(() => () => {\n for (const dispose of disposers.reverse()) dispose();\n }, 'im-qqbot-client.binding');\n}\n\n/**\n * 轮询绑定状态:待绑定时自动打开绑定页。\n *\n * - 页面隐藏时降频轮询(可见时立即恢复节拍)\n * - 404(已绑定/未安装)时退避到 30s,保持低成本常驻探测\n * - 每次页面加载至多打开一次(用户手动关闭后不打扰);\n * 打开失败(如启动早期 Session surface 尚未挂载)不消费机会,\n * 由下一轮轮询(3s)自动重试\n */\nfunction watchAndPrompt(right: SidebarRightLike | undefined): () => void {\n let stopped = false;\n let opened = right === undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const schedule = (ms: number): void => {\n timer = setTimeout(() => { void tick(); }, ms);\n };\n\n const tick = async (): Promise<void> => {\n if (stopped) return;\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {\n schedule(5000);\n return;\n }\n try {\n const res = await fetch(BIND_STATE_PATH, { cache: 'no-store' });\n if (res.ok) {\n const body = (await res.json()) as { phase?: string };\n if (body.phase === 'waiting' && !opened && right !== undefined) {\n try {\n right.openTab(TAB_KIND);\n opened = true;\n } catch (err) {\n // 启动早期 Session surface 可能尚未挂载:保留机会,3s 后随轮询重试\n console.error('[im-qqbot] 打开绑定页失败,稍后重试', err);\n }\n }\n schedule(3000);\n return;\n }\n // 404:已绑定或插件未启用\n schedule(30000);\n return;\n } catch {\n // 宿主暂不可达(启动窗口期):退避重试\n }\n schedule(10000);\n };\n\n void tick();\n return () => { stopped = true; if (timer !== undefined) clearTimeout(timer); };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-connect/dsh-qqbot",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "QQ Bot IM channel plugin for deepseek-harness (dsh)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -9,15 +9,20 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "import": "./dist/index.js"
12
+ },
13
+ "./client": {
14
+ "default": "./lib/client.js"
12
15
  }
13
16
  },
14
17
  "scripts": {
15
- "build": "tsc",
18
+ "build": "tsc && tsdown --config tsdown.client.config.ts",
19
+ "build:client": "tsdown --config tsdown.client.config.ts",
16
20
  "dev": "tsc --watch",
17
21
  "prepublishOnly": "npm run build",
18
22
  "lint": "eslint src/",
19
23
  "test": "vitest run",
20
- "clean": "rm -rf dist",
24
+ "typecheck:client": "tsc -p tsconfig.client.json",
25
+ "clean": "rm -rf dist lib",
21
26
  "ci:prepare-test-pkg": "node scripts/prepare-test-pkg.js"
22
27
  },
23
28
  "keywords": [
@@ -29,7 +34,7 @@
29
34
  ],
30
35
  "license": "MIT",
31
36
  "engines": {
32
- "node": ">=18"
37
+ "node": ">=20.3.0"
33
38
  },
34
39
  "repository": {
35
40
  "type": "git",
@@ -42,12 +47,23 @@
42
47
  "dsh": {
43
48
  "bundle": {
44
49
  "patch": "./cordis.patch.yml"
50
+ },
51
+ "client": {
52
+ "platform": "web"
53
+ },
54
+ "compatibility": {
55
+ "dshReleases": {
56
+ "0.1.0-rc.8": "compatible",
57
+ "0.2.0-rc.2": "compatible"
58
+ },
59
+ "note": "0.1.0-rc.8 为 CLI(dsh web)已验证:扫码/手动/解绑全流程、终端二维码与端内绑定页;0.2.0-rc.2 为 Desktop 内嵌运行时已验证:端内绑定页(settings.section 与侧栏)、HMR 重载收敛。客户端依赖 settings.section / sidebarRightTabs / sidebar.right.pane.tab 席位契约与 dsh.client 模块系统,宿主大版本升级后请重新验收席位行为。"
45
60
  }
46
61
  },
47
62
  "dependencies": {
48
63
  "@tencent-connect/qqbot-connector": "1.2.0",
49
- "@tencent-connect/qqbot-nodejs": "^1.0.4",
50
- "js-yaml": "^4.1.0"
64
+ "@tencent-connect/qqbot-nodejs": "^1.0.5",
65
+ "js-yaml": "^4.1.0",
66
+ "qrcode": "^1.5.4"
51
67
  },
52
68
  "peerDependencies": {
53
69
  "@deepseek-ai/cordis": ">=4.0.1",
@@ -88,6 +104,10 @@
88
104
  "@deepseek-ai/schemastery": "3.18.1",
89
105
  "@types/js-yaml": "^4.0.9",
90
106
  "@types/node": "^26.2.0",
107
+ "@types/qrcode": "^1.5.5",
108
+ "@types/react": "~18.3.1",
109
+ "react": "^18.2.0",
110
+ "tsdown": "^0.22.2",
91
111
  "typescript": "^5.5.0",
92
112
  "vitest": "^4.1.8"
93
113
  }
@@ -14,3 +14,4 @@ minimumReleaseAgeExclude:
14
14
  - '@deepseek-ai/dsh-timeout@0.1.0-rc.6'
15
15
  - '@deepseek-ai/dsh-typert-protocol@0.1.0-rc.6'
16
16
  - '@deepseek-ai/schemastery@3.18.1'
17
+ - '@tencent-connect/qqbot-nodejs@1.0.5'
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "allowImportingTsExtensions": true,
7
+ "rewriteRelativeImportExtensions": true,
8
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
9
+ "types": [],
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "noEmit": true,
13
+ "isolatedModules": true,
14
+ "noUnusedLocals": true,
15
+ "noUnusedParameters": true,
16
+ "forceConsistentCasingInFileNames": true
17
+ },
18
+ "include": ["src/client/**/*.ts", "src/shared/bind-protocol.ts"]
19
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 浏览器客户端 bundle 构建配置
3
+ *
4
+ * 产出 lib/client.js,遵循 dsh 客户端模块系统的加载契约:
5
+ * - CJS 闭包工厂:`window.__ModuleLoader__.load({ id, factory: (require) => {...} })`
6
+ * - externals 经注入 require 解析(客户端平台种子表)
7
+ * - 单文件入口(无动态 chunk),sourcemap 供浏览器诊断
8
+ *
9
+ * 与 deepseek-harness 仓库 packages/client/tsdown.client.ts 的契约保持一致,
10
+ * 仅保留外部插件所需的最小集。
11
+ */
12
+ import { readFileSync } from 'node:fs';
13
+ import { defineConfig } from 'tsdown';
14
+
15
+ /** 浏览器模块 id:从 package.json 的 name 动态读取。
16
+ *
17
+ * 宿主 client-modules 按包名(最近归属的 package manifest)注册 boot 图行,
18
+ * bundle 的 __ModuleLoader__.load({ id }) 必须与其一致——改名发布
19
+ * (fork/scope 迁移)时硬编码会导致「loaded without registering」。
20
+ */
21
+ const ID = (JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')) as { name: string }).name;
22
+
23
+ /** 客户端平台种子表(baseline externals;react/cordis 之外的依赖须整体内联) */
24
+ const BASELINE_EXTERNALS = [
25
+ 'react',
26
+ 'react/jsx-runtime',
27
+ 'react-dom',
28
+ 'react-dom/client',
29
+ '@deepseek-ai/cordis',
30
+ ];
31
+
32
+ const isBaseline = (specifier: string): boolean =>
33
+ BASELINE_EXTERNALS.some((name) => specifier === name || specifier.startsWith(`${name}/`));
34
+
35
+ export default defineConfig({
36
+ name: `${ID}/client`,
37
+ entry: { client: 'src/client/index.ts' },
38
+ outDir: 'lib',
39
+ format: 'cjs',
40
+ platform: 'browser',
41
+ target: 'es2022',
42
+ dts: false,
43
+ clean: false,
44
+ sourcemap: true,
45
+ deps: {
46
+ // 平台种子表内的说明符保持外部引用(经注入 require 解析)
47
+ neverBundle: isBaseline,
48
+ // 其余依赖整体内联(本客户端无第三方依赖,此为兜底保护)
49
+ alwaysBundle: (specifier: string) => !isBaseline(specifier),
50
+ },
51
+ outputOptions: {
52
+ entryFileNames: 'client.js',
53
+ banner: (chunk) =>
54
+ `window.__ModuleLoader__.load({ id: ${JSON.stringify(ID)},${chunk.isEntry ? '' : ` chunk: ${JSON.stringify(chunk.fileName)},`} factory: (require) => {`,
55
+ intro: 'var module = { exports: {} }; var exports = module.exports;',
56
+ footer: 'return module.exports; } });',
57
+ },
58
+ });
package/dist/setup.d.ts DELETED
@@ -1,24 +0,0 @@
1
- /** 凭据结果 */
2
- export interface SetupCredentials {
3
- appId: string;
4
- appSecret: string;
5
- }
6
- /**
7
- * 执行 QR 扫码绑定,获取 QQ Bot 凭据
8
- *
9
- * 在终端打印二维码等待用户扫码;同时输出扫码 URL,
10
- * 供二维码因系统字符问题渲染错位时点击/复制到浏览器打开扫码。
11
- */
12
- export declare function runQrSetup(source?: string): Promise<SetupCredentials | null>;
13
- /**
14
- * 将凭据写入 dsh profile 的 cordis.patch.yml
15
- *
16
- * 用 js-yaml 解析现有文件后更新/追加 im-qqbot 条目,再 dump 写回,
17
- * 保证输出始终是合法 YAML。文件不存在或为空时安全重建;
18
- * 解析失败或结构异常时拒绝写入、保留原文件(避免覆盖用户其他配置)。
19
- */
20
- export declare function persistCredentialsToProfile(credentials: SetupCredentials, profileDir?: string, logger?: {
21
- info(msg: string, ...args: unknown[]): void;
22
- warn(msg: string, ...args: unknown[]): void;
23
- }): boolean;
24
- //# sourceMappingURL=setup.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAYA,WAAW;AACX,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAcD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,MAAM,SAAc,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAyDvF;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,gBAAgB,EAC7B,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE;IAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAAE,GACpG,OAAO,CAoDT"}
package/dist/setup.js DELETED
@@ -1,170 +0,0 @@
1
- /**
2
- * QQ Bot 凭据初始化 — 扫码绑定
3
- *
4
- * 当 appId/appSecret 未配置时,通过 @tencent-connect/qqbot-connector
5
- * 唤起终端扫码流程获取凭据,并写入 dsh profile 配置。
6
- */
7
- import { resolve } from 'node:path';
8
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
9
- import yaml from 'js-yaml';
10
- import { startQrConnect } from '@tencent-connect/qqbot-connector';
11
- /** 终端可点击的 OSC 8 超链接(不支持 OSC 8 的终端会忽略控制序列,退化为纯 URL 文本) */
12
- function clickableLink(url) {
13
- return `\u001b]8;;${url}\u001b\\${url}\u001b]8;;\u001b\\`;
14
- }
15
- /**
16
- * 执行 QR 扫码绑定,获取 QQ Bot 凭据
17
- *
18
- * 在终端打印二维码等待用户扫码;同时输出扫码 URL,
19
- * 供二维码因系统字符问题渲染错位时点击/复制到浏览器打开扫码。
20
- */
21
- export async function runQrSetup(source = 'dsh-qqbot') {
22
- console.log('\n══════════════════════════════════════════════════════');
23
- console.log(' QQ Bot 凭据未配置,启动扫码绑定');
24
- console.log('══════════════════════════════════════════════════════\n');
25
- try {
26
- console.log('请使用手机 QQ 扫描下方二维码完成绑定...\n');
27
- // 回调风格:onQrDisplayed 会在二维码打印后、轮询开始前回调扫码 URL,
28
- // 让用户在二维码错位时也能通过链接打开扫码页(二维码过期刷新会再次触发)。
29
- const credentials = await new Promise((resolve, reject) => {
30
- startQrConnect({
31
- onSuccess: resolve,
32
- onFailure: reject,
33
- onQrDisplayed: (url) => {
34
- console.log('二维码显示异常?点击下方链接,用浏览器打开即可完成扫码:');
35
- console.log(` ${clickableLink(url)}\n`);
36
- },
37
- onQrExpired: () => {
38
- console.log('二维码已过期,正在刷新…\n');
39
- },
40
- }, { source });
41
- });
42
- if (!credentials || credentials.length === 0) {
43
- console.error('[im-qqbot] 扫码未返回凭据');
44
- return null;
45
- }
46
- const cred = credentials[0];
47
- if (!cred) {
48
- console.error('[im-qqbot] 扫码未返回有效凭据');
49
- return null;
50
- }
51
- console.log(`\n✔ 绑定成功!AppID: ${cred.appId}\n`);
52
- return {
53
- appId: cred.appId,
54
- appSecret: cred.appSecret,
55
- };
56
- }
57
- catch (err) {
58
- const msg = err instanceof Error ? err.message : String(err);
59
- if (msg.includes('Cannot find') || msg.includes('ERR_MODULE_NOT_FOUND')) {
60
- console.error('[im-qqbot] @tencent-connect/qqbot-connector 未安装,无法扫码绑定');
61
- console.error('[im-qqbot] 请通过环境变量配置:');
62
- console.error(' export QQBOT_APPID="你的AppID"');
63
- console.error(' export QQBOT_SECRET="你的AppSecret"');
64
- }
65
- else {
66
- console.error(`[im-qqbot] 扫码绑定失败: ${msg}`);
67
- }
68
- return null;
69
- }
70
- }
71
- /**
72
- * 将凭据写入 dsh profile 的 cordis.patch.yml
73
- *
74
- * 用 js-yaml 解析现有文件后更新/追加 im-qqbot 条目,再 dump 写回,
75
- * 保证输出始终是合法 YAML。文件不存在或为空时安全重建;
76
- * 解析失败或结构异常时拒绝写入、保留原文件(避免覆盖用户其他配置)。
77
- */
78
- export function persistCredentialsToProfile(credentials, profileDir, logger) {
79
- const log = logger ?? console;
80
- const dir = profileDir;
81
- if (!dir) {
82
- // 开发模式:插件从源码加载、不在 node_modules 下,无法定位 profile 目录
83
- printEnvInstructions(credentials);
84
- return false;
85
- }
86
- const patchPath = resolve(dir, 'cordis.patch.yml');
87
- try {
88
- // 1. 解析现有条目(文件不存在/为空才重建;解析失败会抛错,保留原文件)
89
- let entries = [];
90
- if (existsSync(patchPath)) {
91
- entries = parsePatchEntries(readFileSync(patchPath, 'utf8'));
92
- }
93
- // 2. 查找已有 im-qqbot 条目
94
- const existing = entries.find((e) => e.id === 'im-qqbot');
95
- if (existing) {
96
- // 更新已有条目的 config
97
- existing.config = {
98
- ...(existing.config ?? {}),
99
- appId: credentials.appId,
100
- appSecret: credentials.appSecret,
101
- };
102
- }
103
- else {
104
- // 追加新条目
105
- entries.push({
106
- id: 'im-qqbot',
107
- config: {
108
- appId: credentials.appId,
109
- appSecret: credentials.appSecret,
110
- },
111
- });
112
- }
113
- // 3. dump 写回(保证合法 YAML)
114
- // 先确保 profile 目录存在(文件不存在时也能正常创建,而非误入 ENOENT)
115
- const output = `# QQ Bot 凭据(扫码绑定自动生成)\n${yaml.dump(entries)}`;
116
- mkdirSync(dir, { recursive: true });
117
- writeFileSync(patchPath, output, 'utf8');
118
- log.info(`✔ 凭据已写入: ${patchPath}`);
119
- log.info(` 下次启动将自动使用保存的凭据`);
120
- return true;
121
- }
122
- catch (err) {
123
- log.warn(`写入配置失败: ${err instanceof Error ? err.message : String(err)}`);
124
- printYamlInstructions(credentials, patchPath);
125
- return false;
126
- }
127
- }
128
- /**
129
- * 解析 cordis.patch.yml 为条目数组
130
- *
131
- * - 空内容 / 仅注释(yaml.load 返回 null/undefined)→ 空数组(允许安全重建)
132
- * - 空数组 `[]` / 条目列表 → 过滤出含 id 的对象
133
- * - 非数组结构 / YAML 语法错误 → 抛错(由调用方保留原文件,避免覆盖用户其他配置)
134
- */
135
- function parsePatchEntries(content) {
136
- if (!content.trim())
137
- return [];
138
- const parsed = yaml.load(content);
139
- // 仅注释/空白 → null/undefined,等价于空文件,允许重建
140
- if (parsed === null || parsed === undefined)
141
- return [];
142
- if (!Array.isArray(parsed)) {
143
- throw new Error('cordis.patch.yml 顶层必须是 YAML 数组');
144
- }
145
- return parsed.filter((e) => typeof e === 'object' && e !== null && typeof e.id === 'string');
146
- }
147
- /** 开发模式引导:未定位到 profile 目录,引导用环境变量配置(console.log 确保可见) */
148
- function printEnvInstructions(credentials) {
149
- console.log('未检测到 profile 目录(开发模式),请通过环境变量配置凭据:');
150
- if (process.platform === 'win32') {
151
- // Windows CMD 语法(PowerShell 请用 $env:QQBOT_APPID="xxx")
152
- console.log(` set QQBOT_APPID=${credentials.appId}`);
153
- console.log(` set QQBOT_SECRET=${credentials.appSecret}`);
154
- }
155
- else {
156
- console.log(` export QQBOT_APPID="${credentials.appId}"`);
157
- console.log(` export QQBOT_SECRET="${credentials.appSecret}"`);
158
- }
159
- }
160
- /** 正式安装引导:自动写入失败,引导手动在 cordis.patch.yml 配置(console.log 确保可见) */
161
- function printYamlInstructions(credentials, patchPath) {
162
- console.log('无法自动保存凭据,请手动打开以下文件添加配置:');
163
- console.log(` ${patchPath}`);
164
- console.log('');
165
- console.log(' - id: im-qqbot');
166
- console.log(' config:');
167
- console.log(` appId: "${credentials.appId}"`);
168
- console.log(` appSecret: "${credentials.appSecret}"`);
169
- }
170
- //# sourceMappingURL=setup.js.map
package/dist/setup.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}