const BLOCKED_OBJECT_KEYS = new Set(['__proto__', 'prototype', 'constructor']); const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i; const INVALID_CHARS_RE = /[^a-z0-9_-]+/g; const LEADING_DASH_RE = /^-+/; const TRAILING_DASH_RE = /-+$/; export const UI_WECHAT_CHANNEL_TYPE = 'wechat'; export const OPENCLAW_WECHAT_CHANNEL_TYPE = 'openclaw-weixin'; export type QrChannelEvent = 'qr' | 'success' | 'error'; export function toOpenClawChannelType(channelType: string): string { return channelType === UI_WECHAT_CHANNEL_TYPE ? OPENCLAW_WECHAT_CHANNEL_TYPE : channelType; } export function toUiChannelType(channelType: string): string { return channelType === OPENCLAW_WECHAT_CHANNEL_TYPE ? UI_WECHAT_CHANNEL_TYPE : channelType; } export function isWechatChannelType(channelType: string | null | undefined): boolean { return channelType === UI_WECHAT_CHANNEL_TYPE || channelType === OPENCLAW_WECHAT_CHANNEL_TYPE; } export function buildQrChannelEventName(channelType: string, event: QrChannelEvent): string { return `channel:${toUiChannelType(channelType)}-${event}`; } function canonicalizeAccountId(value: string): string { if (VALID_ID_RE.test(value)) return value.toLowerCase(); return value .toLowerCase() .replace(INVALID_CHARS_RE, '-') .replace(LEADING_DASH_RE, '') .replace(TRAILING_DASH_RE, '') .slice(0, 64); } export function normalizeOpenClawAccountId(value: string | null | undefined, fallback = 'default'): string { const trimmed = (value ?? '').trim(); if (!trimmed) return fallback; const normalized = canonicalizeAccountId(trimmed); if (!normalized || BLOCKED_OBJECT_KEYS.has(normalized)) { return fallback; } return normalized; } export function isCanonicalOpenClawAccountId(value: string | null | undefined): boolean { const trimmed = (value ?? '').trim(); if (!trimmed) return false; return normalizeOpenClawAccountId(trimmed, '') === trimmed; }