From 84efb90a2212486171aa576a3e9d49cbbd182aa7 Mon Sep 17 00:00:00 2001 From: Dmitry Anderson <4nd3r5z0n@gmail.com> Date: Sun, 27 Oct 2024 10:12:11 +0100 Subject: [PATCH] sfbr inline query fix and refactoring --- bot/normal_mode/init.ts | 5 +- bot/normal_mode/safebooru.ts | 27 ++++++--- external/safebooru.ts | 70 +++++++++++------------ utils/url_builder.ts | 108 +++++++++++++++++------------------ 4 files changed, 106 insertions(+), 104 deletions(-) diff --git a/bot/normal_mode/init.ts b/bot/normal_mode/init.ts index 878821b..4e26e98 100644 --- a/bot/normal_mode/init.ts +++ b/bot/normal_mode/init.ts @@ -6,7 +6,6 @@ import { Database } from "../../repo/exports.ts"; import { CompiledConfig } from "../../cfg/config.ts"; import { checkUser, checkUserOnNewChatMember, onMemberLeftChat, checkUserOnStart } from "./user_managment.ts"; import { checkCaptchaSolution, initUserCaptcha } from "./captcha.ts"; -import { getSafebooruPosts } from "../../external/safebooru.ts"; import { handleSafebooruQuery } from "./safebooru.ts"; interface ChatMemberUpdateStatus { @@ -79,7 +78,5 @@ export const init = (bot: Bot, db: Kysely, cfg: CompiledConfig) = await next() }) - bot.inlineQuery(/sfbr */, async ctx => { - await handleSafebooruQuery(ctx, getSafebooruPosts) - }) + bot.inlineQuery(/sfbr */, async ctx => { await handleSafebooruQuery(ctx) }) } diff --git a/bot/normal_mode/safebooru.ts b/bot/normal_mode/safebooru.ts index e2aef6a..c2fbb36 100644 --- a/bot/normal_mode/safebooru.ts +++ b/bot/normal_mode/safebooru.ts @@ -1,21 +1,35 @@ import { InlineQueryResultBuilder, type InlineQueryContext } from "https://deno.land/x/grammy/mod.ts"; import type { InlineQueryResult } from "https://deno.land/x/grammy_types/inline.ts"; import type { Ctx } from "../ctx.ts"; -import type { GetSafebooruPostsFunc } from "../../external/safebooru.ts"; +import { SfbrPostsReqQueryBuilder } from "../../external/safebooru.ts"; +import type { SafebooruPostData } from "../../external/safebooru.ts"; import { SAFEBOORU_HOST } from "../../external/safebooru.ts"; export const DEFAULT_POSTS_LIMIT = 30 -export const DEFAULT_START_PAGE_ID = 0 export const handleSafebooruQuery = async ( ctx: InlineQueryContext, - getPosts: GetSafebooruPostsFunc ): Promise => { - // Remove the safebooru refix const query = ctx.inlineQuery.query.slice("sfbr".length) const tags = query.split(" ") - // So we make a request, get some posts in a structure - const posts = await getPosts(DEFAULT_POSTS_LIMIT, DEFAULT_START_PAGE_ID, tags) + + const reqURL = new SfbrPostsReqQueryBuilder() + .setPostsLimit(DEFAULT_POSTS_LIMIT) + .setPageID(1).setTags(tags).getReqURL() + + const resp = await fetch(reqURL) + if (!resp.ok) { + console.log(`Error response from safebooru. URL: ${reqURL} Status: ${resp.status} ${resp.text}`) + const results: InlineQueryResult[] = [] + await ctx.api.answerInlineQuery(ctx.inlineQuery.id, results, { cache_time: 1 }) + return + } + const posts: SafebooruPostData[] = await resp.json() + if (posts.length === 0) { + console.log(`Empty response body from safebooru. URL: ${reqURL}`) + await ctx.api.answerInlineQuery(ctx.inlineQuery.id, [], { cache_time: 1 }) + return + } const results: InlineQueryResult[] = [] posts.map((post) => { @@ -25,6 +39,5 @@ export const handleSafebooruQuery = async ( parse_mode: "MarkdownV2", })) }) - // await ctx.answerInlineQuery(results, { cache_time: 0 }) await ctx.api.answerInlineQuery(ctx.inlineQuery.id, results, { cache_time: 1 }) } diff --git a/external/safebooru.ts b/external/safebooru.ts index 9ac410d..4f3245b 100644 --- a/external/safebooru.ts +++ b/external/safebooru.ts @@ -2,8 +2,7 @@ import { ReqUrlBuilder } from "../utils/url_builder.ts"; export const SAFEBOORU_HOST = "safebooru.org" -export type GetSafebooruPostsFunc = (limit: number, pageId: number, tags: string[]) => Promise -interface SafebooruPostData { +export interface SafebooruPostData { preview_url: string; sample_url: string; file_url: string; @@ -28,41 +27,38 @@ interface SafebooruPostData { comment_count: number; } - -const tagsArrToUrlParam = (tags: string[]): string => { - if (tags.length > 0) { - let hasNonEmpty = false - for (let i = 0; i < tags.length; i++) { - if (tags[i].length > 0) { - hasNonEmpty = true - break - } - } - if (!hasNonEmpty) return "" - - let param: string = "tags=" - tags.forEach((tag, i) => { - if (i != 0) { param += " " } - param += tag - }) - return param +// https://safebooru.org/index.php?page=help&topic=dapi +export class SfbrPostsReqQueryBuilder extends ReqUrlBuilder { + override host = SAFEBOORU_HOST + override path = "/index.php" + override params = { + "page": "dapi", + "s": "post", + "q": "index", + "json": "1", + } + + setPostsLimit(limit: number) { + this.setParams({ "limit": limit.toString() }) + return this + } + setPageID(pid: number) { + this.setParams({ "pid": pid.toString() }) + return this + } + setTags(tags: string[]) { + if (tags.length > 0) { + let hasNonEmpty = false + for (let i = 0; i < tags.length; i++) { + if (tags[i].length > 0) { + hasNonEmpty = true + break + } + } + if (!hasNonEmpty) return this + this.setParams({ "tags:": tags.join(" ") }) + } + return this } - return "" } -const mkPostsLimitParam = (limit: number): string => `limit=${limit}` -const mkPageIdParam = (pid: number): string => `pid=${pid}` - -export const getSafebooruPosts = async (limit: number, pageId: number, tags: string[]): Promise => { - const reqURL = new ReqUrlBuilder(`${SAFEBOORU_HOST}`).setProtocol("https").setPath("/index.php") - .setParams("page=dapi", "s=post", "q=index", "json=1") // Default for safebooru API - .setParams(mkPostsLimitParam(limit), mkPageIdParam(pageId)) - .setParams(tagsArrToUrlParam(tags)).getReqURL() - - const resp = await fetch(reqURL) - if (!resp.ok) { return [] } - const respData = await resp.text() - if (respData.length < 3) return [] - const posts: SafebooruPostData[] = JSON.parse(respData) - return posts -} diff --git a/utils/url_builder.ts b/utils/url_builder.ts index 0e3f1ea..7ad2d7f 100644 --- a/utils/url_builder.ts +++ b/utils/url_builder.ts @@ -1,62 +1,58 @@ export class ReqUrlBuilder { - protected protocol: string = "https"; - protected host: string = ""; - protected path: string = ""; - protected params: { [key: string]: string } = {}; - - constructor(url: string) { - const [protocol, host] = url.split("://"); - if (host) { - this.protocol = protocol - this.host = host - } else { - this.host = protocol // Default to https if no protocol provided - } - } - - setProtocol(protocol: string): ReqUrlBuilder { - this.protocol = protocol.replace("://", "") - return this - } - setHost(host: string): ReqUrlBuilder { + protected protocol: string = "https"; + protected host: string = ""; + protected path: string = ""; + protected params: { [key: string]: string } = {}; + + constructor(url?: string) { + const [protocol, host] = url ? url.split("://") : [this.host]; + if (host) { + this.protocol = protocol this.host = host - return this + } else { + this.host = protocol // Default to https if no protocol provided } - setPath(path: string): ReqUrlBuilder { - this.path = path.startsWith("/") ? path : `/${path}` - return this - } - - private addParam(key: string, value: string): ReqUrlBuilder { + } + + setProtocol(protocol: string): ReqUrlBuilder { + this.protocol = protocol.replace("://", "") + return this + } + setHost(host: string): ReqUrlBuilder { + this.host = host + return this + } + setPath(path: string): ReqUrlBuilder { + this.path = path.startsWith("/") ? path : `/${path}` + return this + } + + setParamsStr(...params: string[]): ReqUrlBuilder { + params.forEach((param) => { + const [key, value] = param.split("=") if (key && value) { - this.params[key] = value; + this.params[key] = value } - return this - } - - setParams(...params: string[]): ReqUrlBuilder { - params.forEach((param) => { - const [key, value] = param.split("=") - if (key && value) { - this.addParam(key, value) - } - }) - return this - } - - setQueryParams(queryParams: { [key: string]: string }): ReqUrlBuilder { - Object.entries(queryParams).forEach(([key, value]) => { - this.addParam(key, value) - }) - return this - } - - getReqURL(): string { - const queryString = Object.keys(this.params) - .map((key) => `${key}=${encodeURIComponent(this.params[key])}`) - .join("&") - return `${this.protocol}://${this.host}${this.path}${ - queryString ? "?" + queryString : "" - }` - } + }) + return this + } + + setParams(params: { [key: string]: string }): ReqUrlBuilder { + Object.entries(params).forEach(([key, value]) => { + this.params[key] = value + }) + return this + } + + deleteParam(name: string) { + delete this.params[name] + } + + getReqURL(): string { + let queryString = Object.keys(this.params) + .map((key) => `${key}=${encodeURIComponent(this.params[key])}`) + .join("&") + queryString = queryString ? "?" + queryString : "" + return this.protocol+`://`+this.host+this.path+queryString + } }