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

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 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,41 +27,38 @@ interface SafebooruPostData {
comment_count: number; comment_count: number;
} }
// https://safebooru.org/index.php?page=help&topic=dapi
const tagsArrToUrlParam = (tags: string[]): string => { export class SfbrPostsReqQueryBuilder extends ReqUrlBuilder {
if (tags.length > 0) { override host = SAFEBOORU_HOST
let hasNonEmpty = false override path = "/index.php"
for (let i = 0; i < tags.length; i++) { override params = {
if (tags[i].length > 0) { "page": "dapi",
hasNonEmpty = true "s": "post",
break "q": "index",
} "json": "1",
} }
if (!hasNonEmpty) return ""
setPostsLimit(limit: number) {
let param: string = "tags=" this.setParams({ "limit": limit.toString() })
tags.forEach((tag, i) => { return this
if (i != 0) { param += " " } }
param += tag setPageID(pid: number) {
}) this.setParams({ "pid": pid.toString() })
return param 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 { export class ReqUrlBuilder {
protected protocol: string = "https"; protected protocol: string = "https";
protected host: string = ""; protected host: string = "";
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
} 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 {
this.host = host 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 setProtocol(protocol: string): ReqUrlBuilder {
} this.protocol = protocol.replace("://", "")
return this
private addParam(key: string, value: string): ReqUrlBuilder { }
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) { if (key && value) {
this.params[key] = value; this.params[key] = value
} }
return this })
} return this
}
setParams(...params: string[]): ReqUrlBuilder {
params.forEach((param) => { setParams(params: { [key: string]: string }): ReqUrlBuilder {
const [key, value] = param.split("=") Object.entries(params).forEach(([key, value]) => {
if (key && value) { this.params[key] = value
this.addParam(key, value) })
} return this
}) }
return this
} deleteParam(name: string) {
delete this.params[name]
setQueryParams(queryParams: { [key: string]: string }): ReqUrlBuilder { }
Object.entries(queryParams).forEach(([key, value]) => {
this.addParam(key, value) getReqURL(): string {
}) let queryString = Object.keys(this.params)
return this .map((key) => `${key}=${encodeURIComponent(this.params[key])}`)
} .join("&")
queryString = queryString ? "?" + queryString : ""
getReqURL(): string { return this.protocol+`://`+this.host+this.path+queryString
const queryString = Object.keys(this.params) }
.map((key) => `${key}=${encodeURIComponent(this.params[key])}`)
.join("&")
return `${this.protocol}://${this.host}${this.path}${
queryString ? "?" + queryString : ""
}`
}
} }