import { Readability } from "readability";
import { JSDOM } from "jsdom";
import html2md from "html-to-md";
import axios from "axios";
export interface SearchResult {
title: string;
url: string;
}
export async function searchDocs(q: string): Promise<SearchResult[]> {
const url = "https://docs.godotengine.org/en/stable/searchindex.js";
const res = await axios.get(url);
const match = res.data.match(/Search\.setIndex\((.*)\)/s);
if (!match) throw new Error("cannot load searchindex.js");
const index = JSON.parse(match[1]);
const { titles, filenames, terms } = index;
const words = q.toLowerCase().split(/\s+/).filter(Boolean);
const sets: Set<number>[] = [];
for (const word of words) {
const set = new Set<number>();
for (let i = 0; i < titles.length; i++) {
if (titles[i].toLowerCase().includes(word)) {
set.add(i);
}
}
sets.push(set);
}
if (terms) {
for (const word of words) {
const set = new Set<number>();
for (const [term, docIdxs] of Object.entries(terms)) {
if (term.toLowerCase().includes(word)) {
const docIndices = Array.isArray(docIdxs) ? docIdxs : [docIdxs];
for (const idx of docIndices) {
set.add(idx);
}
}
}
sets.push(set);
}
}
const hitDocIdx: Set<number> = sets.reduce((acc, set) => {
if (acc === null) return new Set(set);
return new Set([...acc].filter((idx) => set.has(idx)));
}, null as Set<number> | null) || new Set();
const results: SearchResult[] = [];
for (const idx of hitDocIdx) {
const url = `https://docs.godotengine.org/en/stable/${
filenames[idx].replace(/\.rst$/, ".html")
}`;
results.push({
title: titles[idx],
url,
});
}
return results;
}
export function getPage(url: string): Promise<string> {
return readContent(url);
}
export function getClass(classname: string): Promise<string> {
return readContent(
`https://docs.godotengine.org/en/stable/classes/class_${classname.toLowerCase()}.html`,
);
}
async function readContent(url: string): Promise<string> {
const response = await fetch(url);
const doc = new JSDOM(await response.text());
const article = new Readability(doc.window.document).parse();
if (!article) {
throw new Error("Article not found");
}
return (html2md as any)(article!.content);
}