66 lines
1.6 KiB
TypeScript
66 lines
1.6 KiB
TypeScript
const GRAPH_BASE = "https://graph.threads.net/v1.0";
|
|
|
|
export class ThreadsApiError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public readonly code?: number,
|
|
public readonly subcode?: number
|
|
) {
|
|
super(message);
|
|
this.name = "ThreadsApiError";
|
|
}
|
|
}
|
|
|
|
async function parseJson(res: Response): Promise<Record<string, unknown>> {
|
|
const text = await res.text();
|
|
try {
|
|
return JSON.parse(text) as Record<string, unknown>;
|
|
} catch {
|
|
throw new ThreadsApiError(text || `HTTP ${res.status}`, res.status);
|
|
}
|
|
}
|
|
|
|
export async function threadsGraphGet<T>(
|
|
path: string,
|
|
params: Record<string, string>
|
|
): Promise<T> {
|
|
const url = new URL(`${GRAPH_BASE}${path}`);
|
|
for (const [key, value] of Object.entries(params)) {
|
|
url.searchParams.set(key, value);
|
|
}
|
|
|
|
const res = await fetch(url.toString(), { method: "GET" });
|
|
const json = await parseJson(res);
|
|
|
|
if (!res.ok) {
|
|
throw new ThreadsApiError(
|
|
String(json.error_message ?? json.error ?? "Threads API 請求失敗"),
|
|
typeof json.code === "number" ? json.code : res.status
|
|
);
|
|
}
|
|
|
|
return json as T;
|
|
}
|
|
|
|
export async function threadsGraphPost<T>(
|
|
path: string,
|
|
params: Record<string, string>
|
|
): Promise<T> {
|
|
const body = new URLSearchParams(params);
|
|
const res = await fetch(`${GRAPH_BASE}${path}`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
body,
|
|
});
|
|
|
|
const json = await parseJson(res);
|
|
if (!res.ok) {
|
|
throw new ThreadsApiError(
|
|
String(json.error_message ?? json.error ?? "Threads API 請求失敗"),
|
|
typeof json.code === "number" ? json.code : res.status
|
|
);
|
|
}
|
|
|
|
return json as T;
|
|
}
|