import * as QiaoqiaoSDK from "@larksuiteoapi/node-sdk"; import { HttpsProxyAgent } from "https-proxy-agent"; import type { QiaoqiaoDomain, ResolvedQiaoqiaoAccount } from "./types.js"; function getWsProxyAgent(): HttpsProxyAgent | undefined { const proxyUrl = process.env.https_proxy || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.HTTP_PROXY; if (!proxyUrl) return undefined; return new HttpsProxyAgent(proxyUrl); } // Multi-account client cache const clientCache = new Map< string, { client: QiaoqiaoSDK.Client; config: { appId: string; appSecret: string; domain?: QiaoqiaoDomain }; } >(); function resolveDomain(domain: QiaoqiaoDomain | undefined): QiaoqiaoSDK.Domain | string { if (domain === "qiaoqiao" || !domain) { // 检查是否有环境变量覆盖 const envDomain = process.env.QIAOQIAO_DOMAIN; if (envDomain) { return envDomain.replace(/\/+$/, ""); } // SDK 没有 Qiaoqiao 枚举,返回自定义域名 return "https://open.qiaoqiao.cn"; } // 对于自定义域名,确保是有效的 HTTPS URL const cleanDomain = domain.replace(/\/+$/, ""); if (cleanDomain.startsWith('http://')) { console.warn(`[Qiaoqiao] Converting HTTP to HTTPS for domain: ${cleanDomain}`); return cleanDomain.replace('http://', 'https://'); } if (!cleanDomain.startsWith('https://')) { console.warn(`[Qiaoqiao] Adding HTTPS protocol to domain: ${cleanDomain}`); return `https://${cleanDomain}`; } return cleanDomain; } /** * Credentials needed to create a Qiaoqiao client. * Both QiaoqiaoConfig and ResolvedQiaoqiaoAccount satisfy this interface. */ export type QiaoqiaoClientCredentials = { accountId?: string; appId?: string; appSecret?: string; domain?: QiaoqiaoDomain; }; /** * Create or get a cached Qiaoqiao client for an account. * Accepts any object with appId, appSecret, and optional domain/accountId. */ export function createQiaoqiaoClient(creds: QiaoqiaoClientCredentials): QiaoqiaoSDK.Client { const { accountId = "default", appId, appSecret, domain } = creds; if (!appId || !appSecret) { throw new Error(`Qiaoqiao credentials not configured for account "${accountId}"`); } // Check cache const cached = clientCache.get(accountId); if ( cached && cached.config.appId === appId && cached.config.appSecret === appSecret && cached.config.domain === domain ) { return cached.client; } // Create new client const client = new QiaoqiaoSDK.Client({ appId, appSecret, appType: QiaoqiaoSDK.AppType.SelfBuild, domain: resolveDomain(domain), }); // Cache it clientCache.set(accountId, { client, config: { appId, appSecret, domain }, }); return client; } /** * Create a Qiaoqiao WebSocket client for an account. * Note: WSClient is not cached since each call creates a new connection. */ export function createQiaoqiaoWSClient(account: ResolvedQiaoqiaoAccount): QiaoqiaoSDK.WSClient { const { accountId, appId, appSecret, domain } = account; if (!appId || !appSecret) { throw new Error(`Qiaoqiao credentials not configured for account "${accountId}"`); } const agent = getWsProxyAgent(); return new QiaoqiaoSDK.WSClient({ appId, appSecret, domain: resolveDomain(domain), loggerLevel: QiaoqiaoSDK.LoggerLevel.info, ...(agent ? { agent } : {}), }); } /** * Create an event dispatcher for an account. */ export function createEventDispatcher(account: ResolvedQiaoqiaoAccount): QiaoqiaoSDK.EventDispatcher { return new QiaoqiaoSDK.EventDispatcher({ encryptKey: account.encryptKey, verificationToken: account.verificationToken, }); } /** * Get a cached client for an account (if exists). */ export function getQiaoqiaoClient(accountId: string): QiaoqiaoSDK.Client | null { return clientCache.get(accountId)?.client ?? null; } /** * Clear client cache for a specific account or all accounts. */ export function clearClientCache(accountId?: string): void { if (accountId) { clientCache.delete(accountId); } else { clientCache.clear(); } }