| 1 | import { asArray } from "../lib/array"; |
| 2 | import type { useT } from "../lib/i18n"; |
| 3 | import type { BotAccessView, BotConnectionDiagnostic, BotConnectionView, BotSettingsView } from "../lib/types"; |
| 4 | |
| 5 | export type BotInstallTarget = "qq" | "feishu" | "lark" | "weixin" | "dingtalk"; |
| 6 | export type BotOfficialInstallTarget = Exclude<BotInstallTarget, "qq">; |
| 7 | |
| 8 | export function diagnosticMessage(diag?: BotConnectionDiagnostic | string): string { |
| 9 | if (typeof diag === "string") return diag; |
| 10 | return diag?.message || diag?.status || ""; |
| 11 | } |
| 12 | |
| 13 | export function diagnosticReportDetail(diag?: BotConnectionDiagnostic | string): string { |
| 14 | if (typeof diag === "string") return ""; |
| 15 | return diag?.reportDetail || ""; |
| 16 | } |
| 17 | |
| 18 | export function botTargetLabel(target: BotInstallTarget, t: ReturnType<typeof useT>): string { |
| 19 | switch (target) { |
| 20 | case "qq": return "QQ"; |
| 21 | case "lark": return "Lark"; |
| 22 | case "weixin": return t("settings.botWeixin"); |
| 23 | case "dingtalk": return t("settings.botDingtalk"); |
| 24 | default: return t("settings.botFeishu"); |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | export function botTargetHint(target: BotInstallTarget, t: ReturnType<typeof useT>): string { |
| 29 | switch (target) { |
| 30 | case "qq": return t("settings.botInstallQQHint"); |
| 31 | case "lark": return t("settings.botInstallLarkHint"); |
| 32 | case "weixin": return t("settings.botInstallWeixinHint"); |
| 33 | case "dingtalk": return t("settings.botInstallDingtalkHint"); |
| 34 | default: return t("settings.botInstallFeishuHint"); |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | export function qqBotAdded(qq: BotSettingsView["qq"]): boolean { |
| 39 | return Boolean(qq.enabled || qq.secretSet || qq.appId.trim()); |
| 40 | } |
| 41 | |
| 42 | export function botAccessEntryCount(access: BotAccessView): number { |
| 43 | return [ |
| 44 | ...asArray(access.users), |
| 45 | ...asArray(access.groups), |
| 46 | ...asArray(access.approvers), |
| 47 | ...asArray(access.admins), |
| 48 | ].filter((value) => value.trim()).length; |
| 49 | } |
| 50 | |
| 51 | export function botAccessReady(access: BotAccessView): boolean { |
| 52 | if (access.allowAll || access.pairingEnabled) return true; |
| 53 | if (!access.enabled) return false; |
| 54 | return botAccessEntryCount(access) > 0; |
| 55 | } |
| 56 | |
| 57 | export function botInstallTargetMatchesConnection(target: BotOfficialInstallTarget, connection: BotConnectionView): boolean { |
| 58 | if (target === "weixin") return connection.provider === "weixin"; |
| 59 | if (target === "dingtalk") return connection.provider === "dingtalk"; |
| 60 | if (target === "lark") return connection.provider === "feishu" && connection.domain === "lark"; |
| 61 | return connection.provider === "feishu" && connection.domain !== "lark"; |
| 62 | } |
| 63 | |
| 64 | export function botInstallTargetForConnection(connection: BotConnectionView): BotInstallTarget { |
| 65 | if (connection.provider === "weixin") return "weixin"; |
| 66 | if (connection.provider === "dingtalk") return "dingtalk"; |
| 67 | if (connection.provider === "feishu" && connection.domain === "lark") return "lark"; |
| 68 | if (connection.provider === "qq") return "qq"; |
| 69 | return "feishu"; |
| 70 | } |
| 71 | |
| 72 | export function formatInstallUserCode(code: string): string { |
| 73 | const compact = code.replace(/[^a-z0-9]/gi, "").toUpperCase().slice(0, 8); |
| 74 | if (compact.length <= 4) return compact; |
| 75 | return `${compact.slice(0, 4)}-${compact.slice(4)}`; |
| 76 | } |
| 77 | |
| 78 | export function formatInstallTimeLeft(seconds: number): string { |
| 79 | const value = Math.max(0, Math.floor(seconds)); |
| 80 | const minutes = Math.floor(value / 60); |
| 81 | const rest = value % 60; |
| 82 | return `${minutes}:${String(rest).padStart(2, "0")}`; |
| 83 | } |
| 84 | |
| 85 | export function botConnectionLabel(connection: BotConnectionView, t: ReturnType<typeof useT>): string { |
| 86 | if (connection.domain === "lark") return "Lark"; |
| 87 | if (connection.provider === "weixin") return t("settings.botWeixin"); |
| 88 | if (connection.provider === "dingtalk") return t("settings.botDingtalk"); |
| 89 | if (connection.provider === "qq") return "QQ"; |
| 90 | return t("settings.botFeishu"); |
| 91 | } |
| 92 | |
| 93 | export function firstConnectionRemote(connection: BotConnectionView): string { |
| 94 | return connection.sessionMappings.find((mapping) => mapping.remoteId.trim())?.remoteId ?? ""; |
| 95 | } |
| 96 | |
| 97 | export function botConnectionScopeLabel(connection: BotConnectionView, t: ReturnType<typeof useT>): string { |
| 98 | return connection.workspaceRoot.trim() ? t("settings.botScopeProject") : t("settings.botScopeGlobal"); |
| 99 | } |
| 100 | |
| 101 | export function botConnectionSecretEnv(connection: BotConnectionView): string { |
| 102 | return connection.provider === "weixin" ? connection.credential.tokenEnv : connection.credential.appSecretEnv; |
| 103 | } |
| 104 | |
| 105 | export function botConnectionSecretPatch(connection: BotConnectionView, value: string): Partial<BotConnectionView["credential"]> { |
| 106 | return connection.provider === "weixin" ? { tokenEnv: value } : { appSecretEnv: value }; |
| 107 | } |
| 108 | |
| 109 | export function botConnectionCredentialSummary(connection: BotConnectionView, t: ReturnType<typeof useT>): string { |
| 110 | if (connection.provider === "weixin") { |
| 111 | return connection.credential.accountId |
| 112 | ? t("settings.botCredentialAccount", { value: connection.credential.accountId }) |
| 113 | : t("settings.botCredentialLocalWeixin"); |
| 114 | } |
| 115 | if (connection.credential.appId) { |
| 116 | if (connection.provider === "dingtalk") { |
| 117 | return t("settings.botCredentialClientId", { value: connection.credential.appId }); |
| 118 | } |
| 119 | return t("settings.botCredentialApp", { value: connection.credential.appId }); |
| 120 | } |
| 121 | return t("settings.botCredentialConfigured"); |
| 122 | } |
| 123 |