const axios = require('axios'); const { DEFAULT_VOICE_ASSISTANT_PROFILE, resolveAssistantProfile, } = require('./assistantProfileConfig'); const assistantProfileCache = new Map(); function getAssistantProfileApiUrl() { const value = String(process.env.ASSISTANT_PROFILE_API_URL || '').trim(); return value && !value.startsWith('your_') ? value : ''; } function getAssistantProfileApiMethod() { const method = String(process.env.ASSISTANT_PROFILE_API_METHOD || 'GET').trim().toUpperCase(); return method === 'POST' ? 'POST' : 'GET'; } function getAssistantProfileTimeoutMs() { const value = Number(process.env.ASSISTANT_PROFILE_API_TIMEOUT_MS || 5000); return Number.isFinite(value) && value > 0 ? value : 5000; } function getAssistantProfileCacheTtlMs() { const value = Number(process.env.ASSISTANT_PROFILE_CACHE_TTL_MS || 60000); return Number.isFinite(value) && value >= 0 ? value : 60000; } function getAssistantProfileCacheKey(userId = null) { return String(userId || 'global').trim() || 'global'; } function parseAssistantProfileHeaders() { const raw = String(process.env.ASSISTANT_PROFILE_API_HEADERS || '').trim(); if (!raw) return {}; try { const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { return {}; } return Object.fromEntries( Object.entries(parsed) .map(([key, value]) => [String(key || '').trim(), String(value || '').trim()]) .filter(([key, value]) => key && value) ); } catch (error) { console.warn('[AssistantProfile] parse headers failed:', error.message); return {}; } } function pickAssistantProfilePayload(payload) { if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { return {}; } const candidates = [ payload.assistantProfile, payload.profile, payload.data?.assistantProfile, payload.data?.profile, payload.data, payload, ]; for (const candidate of candidates) { if (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) { return candidate; } } return {}; } function sanitizeAssistantProfilePayload(payload) { const source = pickAssistantProfilePayload(payload); return { documents: source.documents, email: source.email, nickname: source.nickname, wxl: source.wxl, mobile: source.mobile, wx_code: source.wx_code, intro: source.intro, sign: source.sign, story: source.story, }; } async function fetchRemoteAssistantProfile(userId = null) { const url = getAssistantProfileApiUrl(); if (!url) { return { profile: resolveAssistantProfile(), source: 'default', cached: false, fetchedAt: Date.now(), configured: false, error: null, }; } const headers = { Accept: 'application/json', ...parseAssistantProfileHeaders(), }; const token = String(process.env.ASSISTANT_PROFILE_API_TOKEN || '').trim(); if (token) { headers.Authorization = `Bearer ${token}`; } const method = getAssistantProfileApiMethod(); const timeout = getAssistantProfileTimeoutMs(); const params = userId ? { userId } : undefined; const response = method === 'POST' ? await axios.post(url, userId ? { userId } : {}, { headers, timeout }) : await axios.get(url, { headers, timeout, params }); const profile = resolveAssistantProfile(sanitizeAssistantProfilePayload(response.data)); return { profile, source: 'remote_api', cached: false, fetchedAt: Date.now(), configured: true, error: null, }; } async function getAssistantProfile(options = {}) { const userId = String(options.userId || '').trim() || null; const forceRefresh = !!options.forceRefresh; const overrides = options.overrides && typeof options.overrides === 'object' ? options.overrides : null; const cacheKey = getAssistantProfileCacheKey(userId); const ttlMs = getAssistantProfileCacheTtlMs(); const cached = assistantProfileCache.get(cacheKey); if (!forceRefresh && cached && (Date.now() - cached.fetchedAt) <= ttlMs) { return { profile: resolveAssistantProfile({ ...cached.profile, ...(overrides || {}) }), source: cached.source, cached: true, fetchedAt: cached.fetchedAt, configured: cached.configured, error: null, }; } try { const fetched = await fetchRemoteAssistantProfile(userId); assistantProfileCache.set(cacheKey, fetched); return { profile: resolveAssistantProfile({ ...fetched.profile, ...(overrides || {}) }), source: fetched.source, cached: false, fetchedAt: fetched.fetchedAt, configured: fetched.configured, error: null, }; } catch (error) { const fallback = cached?.profile || DEFAULT_VOICE_ASSISTANT_PROFILE; return { profile: resolveAssistantProfile({ ...fallback, ...(overrides || {}) }), source: cached ? 'cache_fallback' : 'default_fallback', cached: !!cached, fetchedAt: cached?.fetchedAt || null, configured: !!getAssistantProfileApiUrl(), error: error.message, }; } } function clearAssistantProfileCache(userId = null) { if (userId == null || String(userId).trim() === '') { assistantProfileCache.clear(); return; } assistantProfileCache.delete(getAssistantProfileCacheKey(userId)); } module.exports = { getAssistantProfile, clearAssistantProfileCache, };