import axios from 'axios'; import crypto from 'crypto'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const CONFIG_PATH = path.join(__dirname, '../../data/meta.json'); const DOC_ID = '3337abb17af05c667363c229aaf0d1b3'; const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0'; const ENDPOINT = 'https://www.meta.ai/api/graphql'; export const META_MODELS = { 'meta-ai': { id: 'meta-ai', name: 'Meta AI (Llama)' }, }; const MODE_MAP = { normal: 'chat', fast: 'think_fast', thinking: 'think_hard', }; function resolveGraphMode(mode) { return MODE_MAP[mode] || 'chat'; } export class MetaError extends Error { constructor(message, code, details) { super(message); this.code = code || 'meta_error'; this.details = details || null; } } function readMetaConfig() { try { if (!fs.existsSync(CONFIG_PATH)) return { cookies: {}, accessToken: null }; return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); } catch { return { cookies: {}, accessToken: null }; } } function saveMetaConfig(cfg) { const dir = path.dirname(CONFIG_PATH); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf8'); } function getCookieHeader(cookies) { return Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; '); } function extractValue(text, startStr, endStr) { const start = text.indexOf(startStr); if (start === -1) return ''; const valueStart = start + startStr.length; const end = text.indexOf(endStr, valueStart); if (end === -1) return ''; return text.slice(valueStart, end); } function detectChallengePage(htmlText) { if (htmlText.includes('executeChallenge') || htmlText.includes('__rd_verify')) { const match = htmlText.match(/fetch\('(\/__rd_verify[^']+)'/); if (match) return match[1]; } return null; } async function handleMetaAIChallenge(session, cookies, challengeUrl) { const headers = { 'User-Agent': USER_AGENT, 'Accept': '*/*', 'Referer': 'https://meta.ai', 'Origin': 'https://meta.ai', 'Cookie': getCookieHeader(cookies), }; for (let attempt = 0; attempt < 3; attempt++) { try { const response = await session.post(`https://meta.ai${challengeUrl}`, null, { headers, timeout: 10000 }); if (response.status === 200) { const setCookie = response.headers['set-cookie']; if (setCookie) { const all = Array.isArray(setCookie) ? setCookie.join('; ') : setCookie; const rdMatch = all.match(/rd_challenge=([^;]+)/); if (rdMatch) cookies.rd_challenge = rdMatch[1]; } return true; } } catch {} await new Promise(r => setTimeout(r, 1500)); } return false; } let _cachedToken = null; async function extractAccessToken(cookies) { if (_cachedToken) return _cachedToken; const session = axios.create({ timeout: 30000 }); const headers = { 'User-Agent': USER_AGENT, 'Cookie': getCookieHeader(cookies) }; let response; try { response = await session.get('https://meta.ai', { headers, validateStatus: () => true }); } catch (e) { if (e.response) response = e.response; else throw new MetaError(`Failed to fetch meta.ai: ${e.message}`, 'network_error'); } if (response.status === 403 || detectChallengePage(response.data)) { const challengeUrl = detectChallengePage(response.data); if (challengeUrl) { const ok = await handleMetaAIChallenge(session, cookies, challengeUrl); if (ok) { headers['Cookie'] = getCookieHeader(cookies); response = await session.get('https://meta.ai', { headers, validateStatus: () => true }); if (response.status !== 200) return null; const match = response.data?.match(/accessToken":"(ecto1:[^"\\]+)/); if (match) { _cachedToken = match[1]; return _cachedToken; } } } return null; } if (response.status !== 200) return null; if (detectChallengePage(response.data)) { const challengeUrl = detectChallengePage(response.data); if (challengeUrl) { const ok = await handleMetaAIChallenge(session, cookies, challengeUrl); if (ok) { headers['Cookie'] = getCookieHeader(cookies); response = await session.get('https://meta.ai', { headers, validateStatus: () => true }); if (response.status !== 200) return null; const match = response.data?.match(/accessToken":"(ecto1:[^"\\]+)/); if (match) { _cachedToken = match[1]; return _cachedToken; } } } return null; } const match = response.data?.match(/accessToken":"(ecto1:[^"\\]+)/); if (match) { _cachedToken = match[1]; return _cachedToken; } return null; } async function ensureToken(forceRefresh = false) { const cfg = readMetaConfig(); if (forceRefresh) _cachedToken = null; if (cfg.accessToken && !forceRefresh) { _cachedToken = cfg.accessToken; return cfg; } const token = await extractAccessToken(cfg.cookies); if (token) { cfg.accessToken = token; saveMetaConfig(cfg); } else if (!cfg.accessToken) { throw new MetaError('Meta AI not configured — need cookies and accessToken in data/meta.json', 'auth_error'); } _cachedToken = cfg.accessToken; return cfg; } function extractChatSnapshot(event) { const snapshots = []; const add = (v) => { if (v && typeof v === 'string' && v.trim()) snapshots.push(v.trim()); }; const stream = event?.data?.sendMessageStream; if (stream) { add(stream?.content); if (stream?.message) { add(stream.message?.content); add(stream.message?.text); } } const msg = event?.data?.message; if (msg) { add(msg?.content); add(msg?.text); add(msg?.streaming_text); if (msg?.composed_text?.content) { for (const item of msg.composed_text.content) { if (item?.text) add(item.text); } } } const node = event?.data?.node; if (node?.bot_response_message) { const brm = node.bot_response_message; add(brm?.content); add(brm?.text); add(brm?.streaming_text); if (brm?.composed_text?.content) { for (const item of brm.composed_text.content) { if (item?.text) add(item.text); } } if (brm?.content?.agent_steps) { for (const step of brm.content.agent_steps) { if (step?.composed_text?.content) { for (const item of step.composed_text.content) { if (item?.text) add(item.text); } } } } } return snapshots.length ? snapshots.reduce((a, b) => a.length >= b.length ? a : b) : ''; } function extractGraphQLErrors(event) { const errors = []; if (Array.isArray(event?.errors)) errors.push(...event.errors); const dataObj = event?.data; if (dataObj && typeof dataObj === 'object') { if (Array.isArray(dataObj.errors)) errors.push(...dataObj.errors); if (dataObj?.sendMessageStream && Array.isArray(dataObj.sendMessageStream.errors)) { errors.push(...dataObj.sendMessageStream.errors); } } return errors; } function buildVariables(content, conversationId, graphMode = 'chat', extra = {}) { return { conversationId, content, userMessageId: crypto.randomUUID(), assistantMessageId: crypto.randomUUID(), userUniqueMessageId: String(BigInt(`0x${crypto.randomBytes(8).toString('hex')}`) % BigInt(10 ** 13)), turnId: crypto.randomUUID(), mode: graphMode, isNewConversation: true, clientTimezone: 'Asia/Kolkata', entryPoint: 'KADABRA__UNKNOWN', promptSessionId: crypto.randomUUID(), userAgent: USER_AGENT, currentBranchPath: '0', promptEditType: 'new_message', userLocale: 'en-US', attachmentsV2: null, imagineOperationRequest: null, ...extra, }; } function buildHeaders(cookieStr, accessToken) { return { 'Cookie': cookieStr, 'Authorization': `OAuth ${accessToken}`, 'User-Agent': USER_AGENT, 'Content-Type': 'application/json', 'Origin': 'https://www.meta.ai', 'Referer': 'https://www.meta.ai/', 'Accept': 'text/event-stream, application/json', }; } async function postChat(session, cookieStr, accessToken, content, conversationId, graphMode = 'chat') { const response = await session.post(ENDPOINT, { doc_id: DOC_ID, variables: buildVariables(content, conversationId, graphMode), }, { headers: buildHeaders(cookieStr, accessToken), responseType: 'stream', timeout: 120000, }); if (response.status === 403) throw new MetaError('Meta AI session expired — refresh cookies', 'auth_error'); return response; } function normalizeText(text) { return (text || '').replace(/\{.*?\}<\/inline>/g, '').replace(/(.*?)<\/inline>/g, '').trim(); } export async function metaChat(messages, options = {}) { const { onDelta, mode } = options; const graphMode = resolveGraphMode(mode); const cfg = await ensureToken(); if (!cfg.accessToken) throw new MetaError('Meta AI not configured — need cookies and accessToken in data/meta.json', 'auth_error'); if (!cfg.cookies?.ecto_1_sess) throw new MetaError('Meta AI cookies missing ecto_1_sess', 'auth_error'); const content = messages.map(m => m.content).filter(Boolean).join('\n') || 'Hello'; const conversationId = crypto.randomUUID(); const cookieStr = getCookieHeader(cfg.cookies); const session = axios.create({ timeout: 120000 }); try { const response = await postChat(session, cookieStr, cfg.accessToken, content, conversationId, graphMode); let lastContent = ''; let buffer = ''; let graphqlErrors = []; for await (const chunk of response.data) { buffer += chunk.toString('utf-8'); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const rawLine of lines) { const line = rawLine.trim(); if (!line || line.startsWith('event:') || line === '[DONE]') continue; const payloadLine = line.startsWith('data:') ? line.slice(5).trim() : line; if (!payloadLine) continue; try { const parsed = JSON.parse(payloadLine); const gqlErrors = extractGraphQLErrors(parsed); if (gqlErrors.length) graphqlErrors.push(...gqlErrors); const snapshot = extractChatSnapshot(parsed); if (snapshot) { lastContent = snapshot; if (onDelta) onDelta(snapshot); } } catch {} } } if (!lastContent) { if (graphqlErrors.length) { throw new MetaError(`Meta AI GraphQL error: ${graphqlErrors.map(e => e.message).join('; ')}`, 'api_error'); } throw new MetaError('No response from Meta AI', 'api_error'); } const finalMsg = normalizeText(lastContent); return { text: finalMsg, model: 'meta-ai', usage: {}, metadata: { conversationId } }; } catch (e) { if (e instanceof MetaError) throw e; if (e.code === 'ERR_BAD_RESPONSE' || e.code === 'ERR_BAD_REQUEST') { throw new MetaError(`Meta AI API error: ${e.message}`, 'api_error'); } throw new MetaError(`Meta AI request failed: ${e.message}`, 'network_error'); } } export async function metaChatV1(messages, options = {}) { const { model, system, max_tokens } = options; const msgs = []; if (system) msgs.push({ role: 'system', content: system }); msgs.push(...messages); try { const result = await metaChat(msgs, { maxTokens: max_tokens }); return { id: `msg_${Date.now()}`, type: 'message', role: 'assistant', content: [{ type: 'text', text: result.text }], model: 'meta-ai', stop_reason: 'end_turn', stop_sequence: null, usage: { input_tokens: -1, output_tokens: -1 }, }; } catch (e) { throw e; } } export async function metaImageGen(prompt, options = {}) { const cfg = await ensureToken(); if (!cfg.accessToken) throw new MetaError('Meta AI not configured', 'auth_error'); if (!cfg.cookies?.ecto_1_sess) throw new MetaError('Meta AI cookies missing ecto_1_sess', 'auth_error'); const orientation = options.orientation || 'VERTICAL'; const cookieStr = getCookieHeader(cfg.cookies); const headers = buildHeaders(cookieStr, cfg.accessToken); try { const response = await axios.post(ENDPOINT, { doc_id: DOC_ID, variables: buildVariables(prompt, crypto.randomUUID(), { imagineOperationRequest: { orientation }, }), }, { headers, timeout: 60000 }); const data = response.data; let imageUrls = []; if (data?.data?.sendMessageStream?.imagineMedia) { const media = data.data.sendMessageStream.imagineMedia; if (Array.isArray(media)) { for (const m of media) { if (m?.url) imageUrls.push(m.url); } } } return { success: !!imageUrls.length, image_urls: imageUrls, status: imageUrls.length ? 'READY' : 'PROCESSING', prompt, orientation, }; } catch (e) { return { success: false, status: 'FAILED', error: e.message, prompt }; } } export async function metaVideoGen(prompt, options = {}) { const cfg = await ensureToken(); if (!cfg.accessToken) throw new MetaError('Meta AI not configured', 'auth_error'); if (!cfg.cookies?.ecto_1_sess) throw new MetaError('Meta AI cookies missing ecto_1_sess', 'auth_error'); const cookieStr = getCookieHeader(cfg.cookies); const headers = buildHeaders(cookieStr, cfg.accessToken); try { const response = await axios.post(ENDPOINT, { doc_id: DOC_ID, variables: buildVariables(prompt, crypto.randomUUID(), { imagineOperationRequest: { mediaType: 'VIDEO' }, }), }, { headers, timeout: 120000 }); return { success: true, status: 'PROCESSING', prompt, response: response.data, }; } catch (e) { return { success: false, status: 'FAILED', error: e.message, prompt }; } } export async function metaDeleteConversation(conversationId) { const cfg = readMetaConfig(); if (!cfg.cookies?.ecto_1_sess) throw new MetaError('Meta AI not configured', 'auth_error'); try { const response = await axios.post(ENDPOINT, { doc_id: 'ad35bda8475e29ba4264ef0d6cc0958a', variables: { input: { id: conversationId } }, }, { headers: { 'Cookie': getCookieHeader(cfg.cookies), 'Content-Type': 'application/json', 'Origin': 'https://www.meta.ai', 'Referer': 'https://www.meta.ai/', }, timeout: 30000, }); const ok = response?.data?.data?.deleteConversation?.success === true; return { success: ok, status_code: response.status }; } catch (e) { return { success: false, error: e.message }; } } export async function metaListModels() { return [{ id: 'meta-ai', name: 'Meta AI (Llama)' }]; } export function loadMetaConfig() { const cfg = readMetaConfig(); return { configured: !!(cfg.cookies?.ecto_1_sess), hasAccessToken: !!cfg.accessToken, cookieKeys: Object.keys(cfg.cookies), }; }