Files
Hotodog-Discord-Bot/src/plugins/rule34/api.ts
2025-03-31 20:28:24 +02:00

133 lines
3.2 KiB
TypeScript
Executable File

//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<T>(url: string) {
const response = await requestRaw(url);
return <T> 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<CommentResponse[]> {
const response = await requestRaw(`${commentsUrl}&post_id=${postID}`);
return XMLtoGenericDatatypeParser(response, parseComment);
}
export async function getTagByID(id: number): Promise<TagResponse[]> {
const response = await requestRaw(`${tagUrl}&id=${id}`);
return XMLtoGenericDatatypeParser(response, parseTag);
}
export async function getTagList(n: number): Promise<TagResponse[]> {
const response = await requestRaw(`${tagUrl}&limit=${n}`);
return XMLtoGenericDatatypeParser(response, parseTag);
}
function parseTag(attributes: Record<string, string>) {
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<string, string>) {
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<T>(
response: Response,
callback: (attributes: Record<string, string>) => 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;
}