92 lines
3.7 KiB
TypeScript
92 lines
3.7 KiB
TypeScript
// Thin client for the HTTP API in EVENTS.md.
|
|
|
|
export type PostListResult =
|
|
| { ok: true; listId: string }
|
|
| { ok: false; pranked: true; message: string; cooldownS: number }
|
|
| { ok: false; pranked: false; message: string };
|
|
|
|
export async function postList(text: string, requester: string, nestId: string): Promise<PostListResult> {
|
|
const res = await fetch("/api/lists", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ text, requester, nest_id: nestId }),
|
|
});
|
|
const body = await res.json().catch(() => ({}));
|
|
if (res.ok) return { ok: true, listId: String(body.list_id) };
|
|
if (res.status === 429) return { ok: false, pranked: true, message: String(body.message ?? "Caw."), cooldownS: Number(body.cooldown_s ?? 30) };
|
|
return { ok: false, pranked: false, message: String(body.message ?? `The flock said no (${res.status}).`) };
|
|
}
|
|
|
|
export async function postVerdict(itemId: string, target: "triage" | "output", approve: boolean): Promise<void> {
|
|
const res = await fetch("/api/verdicts", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ item_id: itemId, target, approve }),
|
|
});
|
|
if (!res.ok) throw new Error(res.status === 404 ? "That question already has an answer." : `Verdict failed (${res.status}).`);
|
|
}
|
|
|
|
export type TrustLevels = Map<string, number>; // "Name|task_type" -> level
|
|
|
|
export async function getTrust(): Promise<TrustLevels> {
|
|
const out: TrustLevels = new Map();
|
|
try {
|
|
const res = await fetch("/api/trust");
|
|
const body = await res.json();
|
|
for (const [key, rec] of Object.entries<{ level?: number }>(body.crows ?? {})) out.set(key, rec.level ?? 0);
|
|
} catch {
|
|
// Badges start at zero if the ledger can't be read.
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function listEvents(listId: string): EventSource {
|
|
return new EventSource(`/api/events?list_id=${encodeURIComponent(listId)}`);
|
|
}
|
|
|
|
// ---------- nests ----------
|
|
export type Nest = { nestId: string; name: string; crows: string[] };
|
|
export type Nests = { roster: string[]; nests: Nest[] };
|
|
|
|
const DEFAULT_ROSTER = ["Hugin", "Munin", "Corvina", "Soot", "Pepper", "Inky", "Edgar", "Jet"];
|
|
|
|
export async function getNests(): Promise<Nests> {
|
|
try {
|
|
const res = await fetch("/api/nests");
|
|
if (!res.ok) throw new Error(String(res.status));
|
|
const body = await res.json();
|
|
return {
|
|
roster: (body.roster ?? DEFAULT_ROSTER).map(String),
|
|
nests: (body.nests ?? []).map((n: any) => ({ nestId: String(n.nest_id), name: String(n.name), crows: (n.crows ?? []).map(String) })),
|
|
};
|
|
} catch {
|
|
// A server without nests still has the default room.
|
|
return { roster: DEFAULT_ROSTER, nests: [{ nestId: "todo", name: "todo", crows: [] }] };
|
|
}
|
|
}
|
|
|
|
export async function createNest(name: string, crows: string[]): Promise<Nest> {
|
|
const res = await fetch("/api/nests", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ name, crows }),
|
|
});
|
|
const body = await res.json().catch(() => ({}));
|
|
if (!res.ok) throw new Error(String(body.message ?? `Couldn't build that nest (${res.status}).`));
|
|
return { nestId: String(body.nest_id), name: String(body.name), crows: (body.crows ?? []).map(String) };
|
|
}
|
|
|
|
// ---------- personas ----------
|
|
export type Persona = { name: string; epithet: string; strength: string; backstory: string };
|
|
|
|
export async function getCrows(): Promise<Map<string, Persona>> {
|
|
const out = new Map<string, Persona>();
|
|
try {
|
|
const res = await fetch("/api/personas");
|
|
if (res.ok) for (const p of await res.json()) out.set(String(p.name), { name: String(p.name), epithet: String(p.epithet), strength: String(p.strength), backstory: String(p.backstory) });
|
|
} catch {
|
|
// No personas: tiles show names only.
|
|
}
|
|
return out;
|
|
}
|