// Qiaoqiao API utilities export function json(data: unknown) { return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }], details: data, }; } export function errorResult(err: unknown) { return json({ error: err instanceof Error ? err.message : String(err) }); } export type QiaoqiaoApiResponse = { success: boolean; data?: unknown; error?: string; code?: string; }; type QiaoqiaoBackendApiParams = { method?: "GET" | "POST" | "PUT" | "DELETE"; path: string; appId: string; appSecret: string; apiBase?: string; body?: unknown; }; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function extractApiMessage(payload: unknown): string | undefined { if (typeof payload === "string") { const trimmed = payload.trim(); return trimmed || undefined; } if (!isRecord(payload)) { return undefined; } const candidates = [ payload.error, payload.message, payload.code, ]; for (const candidate of candidates) { if (typeof candidate === "string" && candidate.trim()) { return candidate.trim(); } } return undefined; } export async function runQiaoqiaoApiCall( context: string, fn: () => Promise, ): Promise { try { return await fn(); } catch (err) { const message = err instanceof Error ? err.message : String(err); throw new Error(`${context} failed: ${message}`); } } function normalizeApiBasePath(rawBase: string): string { const trimmed = rawBase.trim(); if (!trimmed) return "https://qiaoqiao.social/api"; try { const url = new URL(trimmed); const normalizedPath = url.pathname.replace(/\/+$/, ""); if (!normalizedPath) { url.pathname = "/api"; } else { url.pathname = normalizedPath; } return url.toString().replace(/\/+$/, ""); } catch { return trimmed.replace(/\/+$/, ""); } } export function resolveQiaoqiaoApiBase(preferred?: string): string { return normalizeApiBasePath(preferred || "https://qiaoqiao.social/api"); } export function unwrapQiaoqiaoApiPayload(payload: unknown): T { if (!isRecord(payload)) { return payload as T; } if (typeof payload.success === "boolean") { if (!payload.success) { throw new Error(extractApiMessage(payload) ?? "Qiaoqiao API request failed"); } return (Object.prototype.hasOwnProperty.call(payload, "data") ? payload.data : payload) as T; } return payload as T; } export function buildQiaoqiaoAgentApiPath(path: string): string { const normalized = path.startsWith("/") ? path : `/${path}`; return normalized.startsWith("/agent/") ? normalized : `/agent${normalized}`; } export async function callQiaoqiaoBackendApi(params: QiaoqiaoBackendApiParams): Promise { const { method = "GET", path, appId, appSecret, apiBase, body } = params; const base = resolveQiaoqiaoApiBase(apiBase); const url = `${base}${path.startsWith("/") ? path : `/${path}`}`; console.log("fetching url: " + url); const res = await fetch(url, { method, headers: { "X-App-ID": appId, "X-App-Secret": appSecret, "Content-Type": "application/json", }, ...(body !== undefined ? { body: JSON.stringify(body) } : {}), }); const text = await res.text(); let parsed: unknown = null; if (text) { try { parsed = JSON.parse(text); } catch { parsed = text; } } if (!res.ok) { const message = extractApiMessage(parsed) ?? (typeof parsed === "string" ? parsed : parsed ? JSON.stringify(parsed) : res.statusText); throw new Error( `HTTP ${res.status}: ${message}`, ); } return parsed as T; } export async function callQiaoqiaoAgentApi( params: QiaoqiaoBackendApiParams, ): Promise { const raw = await callQiaoqiaoBackendApi({ ...params, path: buildQiaoqiaoAgentApiPath(params.path), }); return unwrapQiaoqiaoApiPayload(raw); }