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 { CompiledConfig } from "../../cfg/config.ts";
import { checkUser, checkUserOnNewChatMember, onMemberLeftChat, checkUserOnStart } from "./user_managment.ts"; import { checkUser, checkUserOnNewChatMember, onMemberLeftChat, checkUserOnStart } from "./user_managment.ts";
import { checkCaptchaSolution, initUserCaptcha } from "./captcha.ts"; import { checkCaptchaSolution, initUserCaptcha } from "./captcha.ts";
import { getSafebooruPosts } from "../../external/safebooru.ts";
import { handleSafebooruQuery } from "./safebooru.ts"; import { handleSafebooruQuery } from "./safebooru.ts";
interface ChatMemberUpdateStatus { interface ChatMemberUpdateStatus {
@ -79,7 +78,5 @@ export const init = (bot: Bot<Ctx>, db: Kysely<Database>, cfg: CompiledConfig) =
await next() await next()
}) })
bot.inlineQuery(/sfbr */, async ctx => { bot.inlineQuery(/sfbr */, async ctx => { await handleSafebooruQuery(ctx) })
await handleSafebooruQuery(ctx, getSafebooruPosts)
})
} }

View File

@ -1,21 +1,35 @@
import { InlineQueryResultBuilder, type InlineQueryContext } from "https://deno.land/x/grammy/mod.ts"; 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 { InlineQueryResult } from "https://deno.land/x/grammy_types/inline.ts";
import type { Ctx } from "../ctx.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"; import { SAFEBOORU_HOST } from "../../external/safebooru.ts";
export const DEFAULT_POSTS_LIMIT = 30 export const DEFAULT_POSTS_LIMIT = 30
export const DEFAULT_START_PAGE_ID = 0
export const handleSafebooruQuery = async ( export const handleSafebooruQuery = async (
ctx: InlineQueryContext<Ctx>, ctx: InlineQueryContext<Ctx>,
getPosts: GetSafebooruPostsFunc
): Promise<void> => { ): Promise<void> => {
// Remove the safebooru refix
const query = ctx.inlineQuery.query.slice("sfbr".length) const query = ctx.inlineQuery.query.slice("sfbr".length)
const tags = query.split(" ") 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[] = [] const results: InlineQueryResult[] = []
posts.map((post) => { posts.map((post) => {
@ -25,6 +39,5 @@ export const handleSafebooruQuery = async (
parse_mode: "MarkdownV2", parse_mode: "MarkdownV2",
})) }))
}) })
// await ctx.answerInlineQuery(results, { cache_time: 0 })
await ctx.api.answerInlineQuery(ctx.inlineQuery.id, results, { cache_time: 1 }) await ctx.api.answerInlineQuery(ctx.inlineQuery.id, results, { cache_time: 1 })
} }

52
external/safebooru.ts vendored
View File

@ -2,8 +2,7 @@ import { ReqUrlBuilder } from "../utils/url_builder.ts";
export const SAFEBOORU_HOST = "safebooru.org" export const SAFEBOORU_HOST = "safebooru.org"
export type GetSafebooruPostsFunc = (limit: number, pageId: number, tags: string[]) => Promise<SafebooruPostData[]> export interface SafebooruPostData {
interface SafebooruPostData {
preview_url: string; preview_url: string;
sample_url: string; sample_url: string;
file_url: string; file_url: string;
@ -28,8 +27,26 @@ interface SafebooruPostData {
comment_count: number; comment_count: number;
} }
// 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",
}
const tagsArrToUrlParam = (tags: string[]): string => { 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) { if (tags.length > 0) {
let hasNonEmpty = false let hasNonEmpty = false
for (let i = 0; i < tags.length; i++) { for (let i = 0; i < tags.length; i++) {
@ -38,31 +55,10 @@ const tagsArrToUrlParam = (tags: string[]): string => {
break break
} }
} }
if (!hasNonEmpty) return "" if (!hasNonEmpty) return this
this.setParams({ "tags:": tags.join(" ") })
let param: string = "tags="
tags.forEach((tag, i) => {
if (i != 0) { param += " " }
param += tag
})
return param
} }
return "" return this
} }
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

@ -4,8 +4,8 @@ export class ReqUrlBuilder {
protected path: string = ""; protected path: string = "";
protected params: { [key: string]: string } = {}; protected params: { [key: string]: string } = {};
constructor(url: string) { constructor(url?: string) {
const [protocol, host] = url.split("://"); const [protocol, host] = url ? url.split("://") : [this.host];
if (host) { if (host) {
this.protocol = protocol this.protocol = protocol
this.host = host this.host = host
@ -27,36 +27,32 @@ export class ReqUrlBuilder {
return this return this
} }
private addParam(key: string, value: string): ReqUrlBuilder { setParamsStr(...params: string[]): ReqUrlBuilder {
if (key && value) {
this.params[key] = value;
}
return this
}
setParams(...params: string[]): ReqUrlBuilder {
params.forEach((param) => { params.forEach((param) => {
const [key, value] = param.split("=") const [key, value] = param.split("=")
if (key && value) { if (key && value) {
this.addParam(key, value) this.params[key] = value
} }
}) })
return this return this
} }
setQueryParams(queryParams: { [key: string]: string }): ReqUrlBuilder { setParams(params: { [key: string]: string }): ReqUrlBuilder {
Object.entries(queryParams).forEach(([key, value]) => { Object.entries(params).forEach(([key, value]) => {
this.addParam(key, value) this.params[key] = value
}) })
return this return this
} }
deleteParam(name: string) {
delete this.params[name]
}
getReqURL(): string { getReqURL(): string {
const queryString = Object.keys(this.params) let queryString = Object.keys(this.params)
.map((key) => `${key}=${encodeURIComponent(this.params[key])}`) .map((key) => `${key}=${encodeURIComponent(this.params[key])}`)
.join("&") .join("&")
return `${this.protocol}://${this.host}${this.path}${ queryString = queryString ? "?" + queryString : ""
queryString ? "?" + queryString : "" return this.protocol+`://`+this.host+this.path+queryString
}`
} }
} }