//TODO Add optional extensions like limit at Posts with tags etc. import * as xml_parser from "jsr:@melvdouc/xml-parser"; export type ImageResponse = { preview_url: string; sample_url: string; file_url: string; directory: number; hash: string; width: number; height: number; id: number; image: string; change: number; owner: string; parent_id: number; rating: string; sample: boolean; sample_height: number; sample_width: number; score: number; tags: string; source: string; status: string; has_notes: boolean; comment_count: number; }; type CommentResponse = { created_at: string; post_id: number; body: string; creator: string; id: number; creator_id: number; }; type TagResponse = { type: number; count: number; name: string; ambiguous: boolean; id: number; }; // Define the API URL const baseUrl = "https://api.rule34.xxx"; export const postUrl = new URL(`${baseUrl}/index.php?page=dapi&s=post&q=index&json=1`); const tagUrl = `${baseUrl}/index.php?page=dapi&s=tag&q=index`; const commentsUrl = `${baseUrl}/index.php?page=dapi&s=comment&q=index`; export async function requestJSON(url: string) { const response = await requestRaw(url); return await response.json(); } async function requestRaw(url: string) { const response = await fetch(url, { headers: { "Accept": "application/json" }, }); if (!response.ok) { throw new Error("Network response was not ok"); } return response; } //List // Comments export async function getPostComments(postID: number): Promise { const response = await requestRaw(`${commentsUrl}&post_id=${postID}`); return XMLtoGenericDatatypeParser(response, parseComment); } export async function getTagByID(id: number): Promise { const response = await requestRaw(`${tagUrl}&id=${id}`); return XMLtoGenericDatatypeParser(response, parseTag); } export async function getTagList(n: number): Promise { const response = await requestRaw(`${tagUrl}&limit=${n}`); return XMLtoGenericDatatypeParser(response, parseTag); } function parseTag(attributes: Record) { return Object.fromEntries( Object.entries(attributes).map(([k, v]) => { if (k === "ambiguous") { return [k, v === "true"]; } else if (k === "name") { return [k, v]; } else { return [k, convertStringtoNumber(v)]; } }), ); } function parseComment(attributes: Record) { return Object.fromEntries( Object.entries(attributes).map(([k, v]) => { if (k === "id" || k === "creator_id" || k === "post_id") { return [k, convertStringtoNumber(v)]; } else { return [k, v]; } }), ); } function convertStringtoNumber(str: string) { return parseFloat(str); } async function XMLtoGenericDatatypeParser( response: Response, callback: (attributes: Record) => T, ) { const parsedResponse = xml_parser.parse(await response.text()); const stack: T[] = []; for (const v of parsedResponse) { if (v.kind === "REGULAR_TAG_NODE") { for (const entry of v.children) { if (entry.kind === "ORPHAN_TAG_NODE") { if (entry.attributes !== undefined) { stack.push(callback(entry.attributes)); } } } } } return stack; }