import { decodeEntities, htmlFragmentToMarkdown } from "../html.js";
import { FetchError, defineHandler, getJson } from "./handler.js";
const SE_SITES: Record = {
"stackoverflow.com": "stackoverflow",
"superuser.com": "superuser",
"serverfault.com": "serverfault",
"askubuntu.com": "askubuntu",
};
export const stackExchangeHandler = defineHandler({
name: "stackexchange",
description: "Stack Overflow / Stack Exchange: question + top answers via the SE API",
match: (url) => {
const host = url.hostname.replace(/^www\./, "");
return (host in SE_SITES || host.endsWith(".stackexchange.com")) && /^\/(questions|q)\/\d+/.test(url.pathname);
},
async fetch(url, ctx) {
const host = url.hostname.replace(/^www\./, "");
const site = SE_SITES[host] ?? host.replace(".stackexchange.com", "");
const id = url.pathname.match(/\/(?:questions|q)\/(\d+)/)![1];
const api = `https://api.stackexchange.com/2.3/questions/${id}`;
const q = (await getJson(`${api}?site=${site}&filter=withbody`, ctx.signal)).items?.[0];
if (!q) throw new FetchError(`Question ${id} not found on ${site}`);
const answers: any[] =
(await getJson(`${api}/answers?site=${site}&filter=withbody&sort=votes&pagesize=5`, ctx.signal)).items ?? [];
// accepted answer first, then by votes
answers.sort((a, b) => Number(b.is_accepted) - Number(a.is_accepted) || b.score - a.score);
const parts = [
`# ${decodeEntities(q.title)}`,
`${q.score} votes | ${q.answer_count} answers | asked by ${q.owner?.display_name ?? "?"} | ${site}`,
"",
htmlFragmentToMarkdown(q.body ?? ""),
...answers.map(
(a) =>
`\n---\n## Answer (${a.score} votes${a.is_accepted ? ", accepted" : ""}) by ${a.owner?.display_name ?? "?"}\n\n${htmlFragmentToMarkdown(a.body ?? "")}`,
),
];
return { kind: "qa", title: decodeEntities(q.title), content: parts.join("\n") };
},
});