Compare commits

...

10 Commits

Author SHA1 Message Date
fzzinchemical
ddba2854c4 Update environment configuration and fix test case naming
Some checks failed
Deno-Workflow / Deploy (push) Has been cancelled
2025-11-12 08:57:43 +01:00
fzzinchemical
bfd7680cf2 Add RULE34_API_KEY to environment configuration and update request URL generation 2025-11-12 08:24:27 +01:00
fzzinchemical
12f1fa81a2 Merge branch 'main' of https://gitea.fzzin.31718216.xyz/fzzinchemical/HotDog 2025-04-15 18:24:56 +02:00
fzzinchemical
e48502c591 small fixes 2025-04-15 18:24:54 +02:00
c579c0d3d9 Update Dockerfile 2025-04-15 16:09:12 +00:00
fzzinchemical
001ee371d6 prepare for deploy 2025-04-15 18:03:59 +02:00
fzzinchemical
769693f87b remove unnecessary codeblocks and refresh tests. 2025-04-15 17:58:19 +02:00
fzzinchemical
f9a8650243 rule34 implementation and code upgrades 2025-04-15 17:55:29 +02:00
fzzinchemical
c32b80e87b update tests and remove old code block 2025-04-15 17:18:22 +02:00
fzzinchemical
8df50c8c8f Yande.re API fully functional 2025-04-15 17:11:37 +02:00
15 changed files with 199 additions and 268 deletions

View File

@@ -10,4 +10,4 @@ COPY . /app
EXPOSE 6969 EXPOSE 6969
# Set the default command to run the Deno task # Set the default command to run the Deno task
CMD ["deno", "task", "admin"] CMD ["deno", "task", "all"]

25
deno.lock generated
View File

@@ -1,15 +1,29 @@
{ {
"version": "4", "version": "5",
"specifiers": { "specifiers": {
"jsr:@melvdouc/xml-parser@*": "0.1.1",
"jsr:@melvdouc/xml-parser@~0.1.1": "0.1.1",
"jsr:@std/assert@1": "1.0.15",
"jsr:@std/dotenv@*": "0.225.3",
"jsr:@std/dotenv@0.225.3": "0.225.3", "jsr:@std/dotenv@0.225.3": "0.225.3",
"jsr:@std/fmt@^1.0.5": "1.0.6", "jsr:@std/fmt@^1.0.5": "1.0.6",
"jsr:@std/fs@^1.0.11": "1.0.15", "jsr:@std/fs@^1.0.11": "1.0.15",
"jsr:@std/internal@^1.0.12": "1.0.12",
"jsr:@std/io@~0.225.2": "0.225.2", "jsr:@std/io@~0.225.2": "0.225.2",
"jsr:@std/log@*": "0.224.14", "jsr:@std/log@*": "0.224.14",
"npm:@types/node@^22.14.1": "22.14.1", "npm:@types/node@^22.14.1": "22.14.1",
"npm:discord.js@14.18.0": "14.18.0" "npm:discord.js@14.18.0": "14.18.0"
}, },
"jsr": { "jsr": {
"@melvdouc/xml-parser@0.1.1": {
"integrity": "5c79d37c6471cb74efb344988317270b57b4f181decb873e441453db42eb6e5f"
},
"@std/assert@1.0.15": {
"integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b",
"dependencies": [
"jsr:@std/internal"
]
},
"@std/dotenv@0.225.3": { "@std/dotenv@0.225.3": {
"integrity": "a95e5b812c27b0854c52acbae215856d9cce9d4bbf774d938c51d212711e8d4a" "integrity": "a95e5b812c27b0854c52acbae215856d9cce9d4bbf774d938c51d212711e8d4a"
}, },
@@ -19,6 +33,9 @@
"@std/fs@1.0.15": { "@std/fs@1.0.15": {
"integrity": "c083fb479889d6440d768e498195c3fc499d426fbf9a6592f98f53884d1d3f41" "integrity": "c083fb479889d6440d768e498195c3fc499d426fbf9a6592f98f53884d1d3f41"
}, },
"@std/internal@1.0.12": {
"integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027"
},
"@std/io@0.225.2": { "@std/io@0.225.2": {
"integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7" "integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7"
}, },
@@ -169,7 +186,11 @@
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==" "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ=="
}, },
"ws@8.18.1": { "ws@8.18.1": {
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==" "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
"optionalPeers": [
"bufferutil@^4.0.1",
"utf-8-validate@>=5.0.2"
]
} }
}, },
"workspace": { "workspace": {

View File

@@ -5,19 +5,12 @@ import {
REST, REST,
Routes, Routes,
} from "@discordjs"; } from "@discordjs";
import * as stdDotenv from "jsr:@std/dotenv@0.225.3";
import { commandRouter } from "@root/commands.ts"; import { commandRouter } from "@root/commands.ts";
import { EnvConst, loadConfig } from "@root/core/configLoader.ts";
const env = await stdDotenv.load();
const token = env.TOKEN;
if (token === undefined) throw Error("no bot token");
// import { EnvConst, loadConfig } from "@root/core/configLoader.ts";
// import { messagehandler } from "@root/messages.ts";
// const env: EnvConst = loadConfig();
const { promise, resolve, reject } = Promise.withResolvers<void>(); const { promise, resolve, reject } = Promise.withResolvers<void>();
const env: EnvConst = loadConfig();
const client = new Client({ const client = new Client({
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
@@ -33,7 +26,6 @@ client.on(Events.ClientReady, (readyClient) => {
const commands = commandRouter(); const commands = commandRouter();
client.on(Events.InteractionCreate, async (interaction) => { client.on(Events.InteractionCreate, async (interaction) => {
// messagehandler(interaction.command)
if (!interaction.isChatInputCommand()) return; if (!interaction.isChatInputCommand()) return;
const command = commands.find((c) => c.data.name === interaction.commandName); const command = commands.find((c) => c.data.name === interaction.commandName);
@@ -46,9 +38,9 @@ client.on(Events.Error, (e) => {
reject(e); reject(e);
}); });
const rest = new REST({ version: "10" }).setToken(token); const rest = new REST({ version: "10" }).setToken(env.BOT_TOKEN);
client.login(token); client.login(env.BOT_TOKEN);
await promise; await promise;

View File

@@ -1,6 +1,17 @@
import { load } from "jsr:@std/dotenv";
const _env = await load({
// optional: choose a specific path (defaults to ".env")
envPath: ".env",
// optional: also export to the process environment (so Deno.env can read it)
export: true,
});
export type EnvConst = { export type EnvConst = {
ADMIN: string, ADMIN: string,
BOT_TOKEN: string, BOT_TOKEN: string,
RULE34_API_KEY: string,
RULE34_API_USER: string,
// DB_LOCATION: string // DB_LOCATION: string
} }
@@ -8,6 +19,8 @@ export function loadConfig() {
return { return {
ADMIN: loadEnv("ADMIN"), ADMIN: loadEnv("ADMIN"),
BOT_TOKEN: loadEnv("BOT_TOKEN"), BOT_TOKEN: loadEnv("BOT_TOKEN"),
RULE34_API_KEY: loadEnv("RULE34_API_KEY"),
RULE34_API_USER: loadEnv("RULE34_API_USER"),
// DB_LOCATION: loadEnv("DB_LOCATION") // DB_LOCATION: loadEnv("DB_LOCATION")
} }
} }

View File

@@ -1,9 +1,9 @@
import * as stdlogger from "jsr:@std/log"; // import * as stdlogger from "jsr:@std/log";
import { Message } from "@discordjs"; // import { Message } from "@discordjs";
export function logMessage(message: Message) { // export function logMessage(message: Message) {
return stdlogger.info( // return stdlogger.info(
`Message from ${message.author} in ${message.guildId?? "Private"} ${message.channelId} Command:[${message.content}]` // `Message from ${message.author} in ${message.guildId?? "Private"} ${message.channelId} Command:[${message.content}]`
); // );
} // }

View File

@@ -1,6 +1,6 @@
//TODO Add optional extensions like limit at Posts with tags etc. //TODO Add optional extensions like limit at Posts with tags etc.
import * as xml_parser from "jsr:@melvdouc/xml-parser"; import * as xml_parser from "jsr:@melvdouc/xml-parser";
import { requestRaw, requestJSON } from "@root/structures/apiRequest.ts"; import { requestRaw } from "@root/structures/apiRequest.ts";
export type ImageResponse = { export type ImageResponse = {
preview_url: string; preview_url: string;

View File

@@ -1,32 +1,5 @@
import { ChatInputCommandInteraction, SlashCommandBuilder } from "@discordjs"; import { ChatInputCommandInteraction, SlashCommandBuilder } from "@discordjs";
// import { drop, help, requestWorker } from "./plugin.ts"; import { requestWorker } from "@root/plugins/rule34/plugin.ts";
import { logMessage } from "@root/logging.ts";
import { defaultString } from "@root/defaultString.ts";
// export async function rule34MessageHandler(message: Message) {
// const command = message.content.trim().split(" ").slice(1).join(" ");
// if (command === "drop") {
// bot.helpers.sendMessage(message.channelId, {
// embeds: [await drop()]
// })
// } else if (command.startsWith("drop [") && command.endsWith("]")) {
// logMessage(message);
// const generatedEmbeds: Embed[] = await requestWorker(command);
// for (const embed of generatedEmbeds) {
// bot.helpers.sendMessage(message.channelId, {
// embeds: [embed],
// });
// }
// } else if (command === "help") {
// logMessage(message);
// bot.helpers.sendMessage(message.channelId, {
// content: defaultString(help()),
// });
// } else {
// drop();
// }
// }
export const commands = [ export const commands = [
{ {
@@ -38,9 +11,41 @@ export const commands = [
subcommand subcommand
.setName("drop") .setName("drop")
.setDescription("Drops one or multiple images using the rule34 API.") .setDescription("Drops one or multiple images using the rule34 API.")
.addStringOption((option) =>
option
.setName("limit")
.setDescription("Post limitation")
)
.addStringOption((option) =>
option
.setName("tags")
.setDescription("Tags to add to query")
)
.addStringOption((option) =>
option
.setName("page")
.setDescription("Which page to query")
)
.addStringOption((option) =>
option
.setName("pid")
.setDescription("Which page to query")
)
), ),
async execute(interaction: ChatInputCommandInteraction) { async execute(interaction: ChatInputCommandInteraction) {
await interaction.reply(`VIOLENCE!`); const limit = interaction.options.getString("limit");
const tags = interaction.options.getString("tags");
const page = interaction.options.getString("page");
const pid = interaction.options.getString("pid");
await interaction.reply({
embeds: await requestWorker({
limit: limit ?? "1",
tags: tags,
page: page,
pid: pid,
}),
});
}, },
}, },
]; ];

View File

@@ -1,102 +1,52 @@
import { assert } from "@std/assert/assert";
import { requestJSON } from "@root/structures/apiRequest.ts"; import { requestJSON } from "@root/structures/apiRequest.ts";
import { ImageResponse, postUrl } from "./api.ts"; import { ImageResponse, postUrl } from "./api.ts";
import { EmbedBuilder } from "@discordjs";
const keys = ["limit", "id", "pid", "tags"] as const;
type PostKeys = typeof keys[number];
function isKey(key: string): key is PostKeys { export type PostRequestParameters = {
return keys.includes(key as any); limit?: string| null| undefined,
id?: string| null| undefined,
pid?: string| null| undefined,
tags?: string| null| undefined,
page?: string| null| undefined,
} }
// export async function drop() { export async function requestWorker(postRequestParams: PostRequestParameters) {
// try { const embeds = [];
// const response = await requestJSON<ImageResponse[]>(`${postUrl}&limit=1`); try {
// const img = response[0]; const response = await requestJSON<ImageResponse[]>(
// if (img === undefined) throw Error("An undefined Array was given!"); generateRequestURL(postRequestParams),
// return new Embed( );
// `ID: ${img.id}`, for (const post of response) {
// img.sample_url, embeds.push(
// new EmbedAuthor(img.owner), new EmbedBuilder()
// // [new EmbedField("Tags", img.tags, true)], .setTitle(post.id.toString())
// new EmbedImage(img.file_url, img.height, img.width), .setURL(post.file_url)
// ); .setAuthor(
// } catch (e) { {
// console.error((e as Error).message); name: post.owner,
// return new Embed( },
// "Error Message", )
// "https://gifdb.com/images/thumbnail/nuh-uh-demon-slayer-girl-hm7q3hqa4lnqyl6d.gif", .setImage(post.preview_url),
// new EmbedAuthor("fzzinChemical"), );
// new EmbedImage(
// "https://gifdb.com/images/thumbnail/nuh-uh-demon-slayer-girl-hm7q3hqa4lnqyl6d.gif",
// 250,
// 250,
// ),
// );
// }
// }
// export async function requestWorker(requestString: string) {
// try {
// const response = await requestJSON<ImageResponse[]>(
// generateRequestURL(requestString),
// );
// const stack: Embed[] = [];
// for (const img of response) {
// stack.push(
// new Embed(
// `ID: ${img.id}`,
// img.sample_url,
// new EmbedAuthor(img.owner),
// // [new EmbedField("Tags", img.tags, true)],
// new EmbedImage(img.file_url, img.height, img.width),
// ),
// );
// }
// return stack;
// } catch (e) {
// console.error((e as Error).message);
// return [];
// }
// }
//TODO: get help in form of a nice beautiful format
export function help() {
return "WIP";
}
export function requestParser(requestString: string) {
const res = requestString.match(/\[([\s*w+:\s*[\d+|\w+,]+)\]/g);
const map = new Map<PostKeys, string>();
if (res !== null) {
res[0]
.split(",")
.forEach((param) => {
const match = param.match(/\s*(\w+)\s*:\s*([\w+,+]+)/);
console.debug({ match });
if (match !== null) {
if (match[1] === undefined || match[2] === undefined) {
throw Error("Unreachable");
} }
if (!isKey(match[1])) throw Error(`Key: ${match[1]} is not a Key!`); } catch (e) {
map.set(match[1], match[2]); console.error((e as Error).message);
} else { return [];
throw Error(`match returned null in param = ${Deno.inspect(param)}`); }
return embeds;
}
export function generateRequestURL(postRequestParams: PostRequestParameters) {
const postCpy = new URL(postUrl);
const parameterStrings: string[] = []
Object.entries(postRequestParams).forEach(([k, v]) => {
if (v !== null) {
parameterStrings.push(`&${k}=${v}`)
} }
}); });
} else {
throw Error("Request String had some major issues chief");
}
return map;
}
// + is replaced with a space... this is pain parameterStrings.push('&api_key=' + (Deno.env.get("RULE34_API_KEY")) + '&user_id=' + (Deno.env.get("RULE34_API_USER")));
// see percent code + === 2B return postCpy.href + parameterStrings.join('');
export function generateRequestURL(requestString: string) {
const postCpy = new URL(postUrl.toString());
for (const [k, v] of requestParser(requestString)) {
postCpy.searchParams.append(k, v);
}
return postCpy.href.toString().replaceAll("%2B", "+");
} }

View File

@@ -1,25 +1,20 @@
import { assert } from "jsr:@std/assert"; import {generateRequestURL, requestWorker } from "@root/plugins/rule34/plugin.ts";
import { drop, generateRequestURL, requestParser, requestWorker } from "@root/plugins/rule34/plugin.ts";
Deno.test("Test Request Parser", () => {
assert(
requestParser("[limit: 12,tags:bro+likes+bread]"),
'{ "limit" => "12", "tags" => "bro+likes+bread" }',
);
});
Deno.test("Test URL Search Parameters", () => { Deno.test("Test URL Search Parameters", () => {
console.debug(generateRequestURL("[limit:3]")); console.debug(generateRequestURL(
{limit: "3"}
));
}); });
Deno.test("Test Drop", async () => { Deno.test("Test Request Worker", async () => {
console.debug(await drop()); console.debug(await requestWorker({
limit: "12",
tags: "sfw",
}));
}); });
Deno.test("Test Request Workder", async () => {
console.debug(await requestWorker("[limit: 12,tags:sfw]"));
});
Deno.test("Test Request Workder 2", async () => {
console.debug(await requestWorker("[limit: 5,tags: AI+catgirl]")); Deno.test("Test Request Worker 2", async () => {
console.debug(await requestWorker({limit: "5",tags: "AI+catgirl"}));
}); });

View File

@@ -1,4 +1,4 @@
import { handlePostRequest } from "@root/plugins/yandere/api/post.ts"; import { handlePostRequest, postKeys } from "@root/plugins/yandere/api/post.ts";
const baseURL = "https://yande.re"; const baseURL = "https://yande.re";
const apikeys = [ const apikeys = [
@@ -17,40 +17,20 @@ function prepareURLObjectForRequest(type: APIKeys) {
const tmp = new URL(baseURL); const tmp = new URL(baseURL);
tmp.searchParams.append("api_version", "3"); tmp.searchParams.append("api_version", "3");
switch (type) { switch (type) {
case "post": case "post": tmp.pathname = "post.json"; break;
tmp.pathname = "post.json"; case "tags": tmp.pathname = "tag.json"; break;
break; case "artist": tmp.pathname = "artist.json"; break;
case "tags": case "comments": tmp.pathname = "comment"; break;
tmp.pathname = "tag.json"; case "wiki": tmp.pathname = "wiki.json"; break;
break; case "notes": tmp.pathname = "note.json"; break;
case "artist": case "users": tmp.pathname = "user.json"; break;
tmp.pathname = "artist.json"; case "forum": tmp.pathname = "forum.json"; break;
break; case "pools": tmp.pathname = "pool.json"; break;
case "comments": default: throw Error("unknown ");
tmp.pathname = "comment";
break;
case "wiki":
tmp.pathname = "wiki.json";
break;
case "notes":
tmp.pathname = "note.json";
break;
case "users":
tmp.pathname = "user.json";
break;
case "forum":
tmp.pathname = "forum.json";
break;
case "pools":
tmp.pathname = "pool.json";
break;
default:
throw Error("unknown ");
} }
return tmp; return tmp;
} }
export function getPosts(search? : string){ export async function getPosts(args? : postKeys){
const url = prepareURLObjectForRequest("post") return await handlePostRequest(prepareURLObjectForRequest("post"), args)
return handlePostRequest(url, search)
} }

View File

@@ -46,29 +46,20 @@ type PostResponse = {
last_commented_at: string; last_commented_at: string;
}[]; }[];
const postSearchkeys = ["limit", "page", "tags"]; export type postKeys = {
limit?: string | null | undefined;
page?: string | null | undefined;
tags?: string | null | undefined;
};
function parsePostListArgs(url: URL, args: string) { function parsePostListArgs(url: URL, args: postKeys | undefined) {
// const urlCopy: URL = copyObject<URL>(url); if (args === undefined) args = { limit: "1" }; //TODO that could be better
const argarr = args.replaceAll(/(\[|\ |\])/g, "").split(","); Object.entries(args).forEach(([key, value]) => {
for (const arg of argarr) { if (value !== null) {
const [k, v] = arg.split(":"); console.debug(`appending k:${key}, v:${value}`);
if (url === undefined) throw Error("undefined Object: url!") url.searchParams.append(key, value);
if (k === undefined || v === undefined) {
throw Error(
`undefined key or value in ${parsePostListArgs.name}, got k:${k}, v:${v}`,
);
}
if (postSearchkeys.includes(k)) {
console.debug(k, v)
console.debug(JSON.stringify(url))
url.searchParams.append(k, v);
} else {
throw Error(
`unknown parameter was given in ${parsePostListArgs.name}, got k:${k}, v:${v}`,
);
}
} }
});
return url; return url;
} }
@@ -87,29 +78,20 @@ async function returnDiscordEmbeds(
.setAuthor( .setAuthor(
{ {
name: post.author, name: post.author,
} },
)
.setImage(post.file_url)
) )
.setImage(post.preview_url),
);
} }
return embeds; return embeds;
} }
// function copyObject<T>(obj: T) {
// const newObject: T = Object.
/** /**
* Post-Request Handler Function, that returns DiscordEmbeds. * Post-Request Handler Function, that returns DiscordEmbeds.
* @param url URL Object * @param url URL Object
* @param search Search string formatted like the following: [tags: something, page: 1, limit: 666], some parameters can miss. * @param args Search string formatted like the following: [tags: something, page: 1, limit: 666], some parameters can miss.
*/ */
export async function handlePostRequest(url: URL, search?: string) { export async function handlePostRequest(url: URL, args?: postKeys) {
// const urlCopy = copyObject<URL>(url);
//parse //parse
if (search) { return await returnDiscordEmbeds(parsePostListArgs(url, args));
return await returnDiscordEmbeds(parsePostListArgs(url, search));
} else {
return await returnDiscordEmbeds(url);
}
} }

View File

@@ -1,30 +1,6 @@
import { import { ChatInputCommandInteraction, SlashCommandBuilder } from "@discordjs";
ChatInputCommandInteraction,
SlashCommandBuilder,
} from "@discordjs";
import { getPosts } from "@root/plugins/yandere/api/api.ts"; import { getPosts } from "@root/plugins/yandere/api/api.ts";
// export async function yandereMessageHandler(message: Message) {
// const command = message.content.split(" ").slice(1).join(" ");
// const args = command.match(/(\[.+\])/g)
// if (command === "drop") {
// const embed: DiscordEmbed | undefined = (await getPosts("[limit: 1]"))[0]
// if (embed === undefined) throw Error("undefined embed")
// bot.helpers.sendMessage(message.channelId, {
// embeds: [embed]
// })
// } else if (command.startsWith("drop [") && command.endsWith("]")) {
// logMessage(message);
// if (args === null) throw Error("args is null")
// const generatedEmbeds = await getPosts(args[0]);
// for (const embed of generatedEmbeds) {
// bot.helpers.sendMessage(message.channelId, {
// embeds: [embed],
// });
// }
// }
// }
export const commands = [ export const commands = [
{ {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
@@ -34,25 +10,36 @@ export const commands = [
.addSubcommand((subcommand) => .addSubcommand((subcommand) =>
subcommand subcommand
.setName("drop") .setName("drop")
.setDescription("Drops one or multiple images using the yande.re API.") .setDescription(
.addStringOption(option => "Drops one or multiple images using the yande.re API.",
)
.addStringOption((option) =>
option option
.setName("limit") .setName("limit")
.setDescription("Post limitation") .setDescription("Post limitation")
) )
.addStringOption(option => .addStringOption((option) =>
option option
.setName("tags") .setName("tags")
.setDescription("Tags to add to query") .setDescription("Tags to add to query")
) )
.addStringOption(option => .addStringOption((option) =>
option option
.setName("page") .setName("page")
.setDescription("Which page to query") .setDescription("Which page to query")
) )
), ),
async execute(interaction: ChatInputCommandInteraction) { async execute(interaction: ChatInputCommandInteraction) {
await interaction.reply({embeds: await getPosts("[limit: 5]")}); const limit = interaction.options.getString("limit");
const tags = interaction.options.getString("tags");
const page = interaction.options.getString("page");
await interaction.reply({
embeds: await getPosts({
limit: limit ?? "1",
tags: tags,
page: page,
}),
});
}, },
}, },
]; ];

View File

@@ -5,13 +5,19 @@ Deno.test("Empty Post Request", async() => {
}) })
Deno.test("Post Request with tag", async() => { Deno.test("Post Request with tag", async() => {
console.debug(await getPosts("[tags:love_live!_(series)]")) console.debug(await getPosts(
{tags: "love_live!_(series)"}
))
}) })
Deno.test("Post Request with limit", async() => { Deno.test("Post Request with limit", async() => {
console.debug(await getPosts("[limit: 1)]")) console.debug(await getPosts({
limit: "1"
}))
}) })
Deno.test("Post Request with page", async() => { Deno.test("Post Request with page", async() => {
console.debug(await getPosts("[page: 30)]")) console.debug(await getPosts(
{page: "30"}
))
}) })