sfbr inline query fix and refactoring

This commit is contained in:
Dmitry Anderson 2024-10-27 10:12:11 +01:00
parent e847cf5af8
commit 84efb90a22
4 changed files with 106 additions and 104 deletions

View File

@ -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<Ctx>, db: Kysely<Database>, cfg: CompiledConfig) =
await next()
})
bot.inlineQuery(/sfbr */, async ctx => {
await handleSafebooruQuery(ctx, getSafebooruPosts)
})
bot.inlineQuery(/sfbr */, async ctx => { await handleSafebooruQuery(ctx) })
}

View File

@ -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<Ctx>,
getPosts: GetSafebooruPostsFunc
): Promise<void> => {
// 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 })
}

70
external/safebooru.ts vendored
View File

@ -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<SafebooruPostData[]>
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<SafebooruPostData[]> => {
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
}

View File

@ -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
}
}