mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-08-29 01:53:47 +08:00
feat(route): add route Gelbooru (#18320)
* feat(route): add route Gelbooru * feat(route): add image quality option for Gelbooru * feat(route): remove URL.canParse * feat(route): enhance display * Update lib/routes/gelbooru/namespace.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * Update lib/routes/gelbooru/namespace.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * fix: Apply code review * fix: Apply code review * fix: add __dirname var * Update lib/routes/gelbooru/templates/description.art Co-authored-by: Tony <TonyRL@users.noreply.github.com> * Update lib/routes/gelbooru/post.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * fix: Add config key by alphabetical order * fix: `e` comes before `u` in alphabet ---------
This commit is contained in:
+8
-1
@@ -164,6 +164,10 @@ export type Config = {
|
||||
game4399: {
|
||||
cookie?: string;
|
||||
};
|
||||
gelbooru: {
|
||||
apiKey?: string;
|
||||
userId?: string;
|
||||
};
|
||||
github: {
|
||||
access_token?: string;
|
||||
};
|
||||
@@ -597,6 +601,10 @@ const calculateValue = () => {
|
||||
game4399: {
|
||||
cookie: envs.GAME_4399,
|
||||
},
|
||||
gelbooru: {
|
||||
apiKey: envs.GELBOORU_API_KEY,
|
||||
userId: envs.GELBOORU_USER_ID,
|
||||
},
|
||||
github: {
|
||||
access_token: envs.GITHUB_ACCESS_TOKEN,
|
||||
},
|
||||
@@ -610,7 +618,6 @@ const calculateValue = () => {
|
||||
cookies: envs.GUOZAOKE_COOKIES,
|
||||
},
|
||||
hefeng: {
|
||||
// weather
|
||||
key: envs.HEFENG_KEY,
|
||||
},
|
||||
infzm: {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: 'Gelbooru',
|
||||
url: 'gelbooru.com',
|
||||
description: 'gelbooru posts',
|
||||
};
|
||||
@@ -0,0 +1,113 @@
|
||||
import { Route, ViewType } from '@/types';
|
||||
import { Context } from 'hono';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import { renderDesc, getAPIKeys } from './utils';
|
||||
|
||||
import got from '@/utils/got';
|
||||
import queryString from 'query-string';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/post/:tags?/:quality?',
|
||||
categories: ['picture'],
|
||||
view: ViewType.Pictures,
|
||||
example: '/gelbooru/post/1girl rating:general',
|
||||
parameters: {
|
||||
tags: '要搜索的标签,多个标签用 ` `(空格)隔开',
|
||||
quality: {
|
||||
description: '图片质量,可选值为 `sample`(压缩后的图片,推荐值) 或 `orig`(原图),默认为 `sample`',
|
||||
default: 'sample',
|
||||
},
|
||||
},
|
||||
features: {
|
||||
requireConfig: [
|
||||
{
|
||||
name: 'GELBOORU_API_KEY',
|
||||
description: 'Gelbooru 偶尔会开启 API 认证,需配合 `GELBOORU_USER_ID`,从 `https://gelbooru.com/index.php?page=account&s=options` 获取',
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
name: 'GELBOORU_USER_ID',
|
||||
description: '参见 `GELBOORU_API_KEY`',
|
||||
optional: true,
|
||||
},
|
||||
],
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
supportRadar: true,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['gelbooru.com/index.php'],
|
||||
},
|
||||
],
|
||||
name: '标签查询',
|
||||
maintainers: ['magicFeirl'],
|
||||
description: `
|
||||
- 默认查询: \`/gelbooru/post\` 功能等同查询 Gelbooru 网站最新的投稿
|
||||
- 单标签查询: \`/gelbooru/post/1girl\` 查询 \`1girl\` 的最新投稿
|
||||
- 多标签查询: \`/gelbooru/post/1girl school_uniform rating:general\`
|
||||
- 指定为原图: \`/gelbooru/post/1girl school_uniform rating:general/orig\`
|
||||
- 更多例子: 请参考 Gelbooru 官方 wiki https://gelbooru.com/index.php?page=wiki&s=&s=view&id=25921
|
||||
|
||||
**可选的 URL 参数**
|
||||
- limit 页面返回数据量,默认 40,可选 1 ~ 100
|
||||
|
||||
e.g.: \`/gelbooru/post?limit=20&\`
|
||||
`,
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
const { tags: _tags = '', quality = 'sample' }: { tags?: string; quality?: 'sample' | 'orig' } = ctx.req.param();
|
||||
|
||||
const tags = decodeURIComponent(_tags).trim();
|
||||
|
||||
const { limit = 40 }: { limit?: number } = ctx.req.query();
|
||||
const { apiKey, useId } = getAPIKeys();
|
||||
|
||||
const response = await got({
|
||||
url: 'https://gelbooru.com/index.php',
|
||||
searchParams: queryString.stringify({
|
||||
page: 'dapi',
|
||||
s: 'post',
|
||||
q: 'index',
|
||||
tags,
|
||||
api_key: apiKey,
|
||||
user_id: useId,
|
||||
limit: limit <= 0 || limit > 100 ? 40 : limit,
|
||||
json: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
const posts = response.data.post;
|
||||
|
||||
return {
|
||||
title: tags ? `${tags} - gelbooru.com` : 'gelbooru.com post list',
|
||||
link: `https://gelbooru.com/index.php?page=post&s=list&tags=${tags}`,
|
||||
icon: 'https://gelbooru.com/favicon.png',
|
||||
logo: 'https://gelbooru.com/favicon.png',
|
||||
description: 'Gelbooru post list',
|
||||
item: posts.map((post) => ({
|
||||
title: post.id,
|
||||
id: post.id,
|
||||
link: `https://gelbooru.com/index.php?page=post&s=view&id=${post.id}`,
|
||||
author: post.owner,
|
||||
pubDate: parseDate(post.created_at),
|
||||
description: renderDesc(post, `https://gelbooru.com/index.php?page=post&s=view&id=${post.id}`, quality),
|
||||
upvotes: post.score,
|
||||
updated: parseDate(post.change),
|
||||
media: {
|
||||
content: {
|
||||
url: post.file_url,
|
||||
},
|
||||
thumbnail: {
|
||||
url: post.preview_url,
|
||||
},
|
||||
},
|
||||
category: post.tags.split(/\s+/g),
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<div>
|
||||
{{if isVideo}}
|
||||
<video controls preload="metadata" src="{{contentURL}}"></video>
|
||||
{{else}}
|
||||
<img src="{{contentURL}}" />
|
||||
{{/if}}
|
||||
</div>
|
||||
<h2>Info of <a href="{{link}}">#{{id}}</a>: </h2>
|
||||
<p>
|
||||
{{if isHttp}}
|
||||
<a href="{{source}}">Source</a>
|
||||
{{else}}
|
||||
<span>Source: {{source}}</span>
|
||||
{{/if}}
|
||||
({{sourceHost}})
|
||||
</p>
|
||||
<p>Upload by: <a href="https://gelbooru.com/index.php?page=post&s=list&tags=user:{{owner}}">{{owner}}</a></p>
|
||||
<p>Score: {{score}}</p>
|
||||
<p>Tags: <p>{{tags}}</p></p>
|
||||
@@ -0,0 +1,43 @@
|
||||
import { getCurrentPath } from '@/utils/helpers';
|
||||
const __dirname = getCurrentPath(import.meta.url);
|
||||
|
||||
import path from 'node:path';
|
||||
import { art } from '@/utils/render';
|
||||
import { config } from '@/config';
|
||||
|
||||
export function renderDesc(post, link, quality: 'sample' | 'orig') {
|
||||
const { id, source, owner, file_url: fileUrl, tags, score } = post;
|
||||
const isHttp = /^https?:\/\//.test(source);
|
||||
const sourceHost = isHttp ? new URL(source).host : source || 'unknown';
|
||||
const imgQualityMap = { sample: 'sample_url', orig: 'file_url' };
|
||||
|
||||
// 判断是否是视频链接
|
||||
const videoExtList = ['mp4', 'webm'];
|
||||
const fileExt = fileUrl.slice(fileUrl.lastIndexOf('.') + 1);
|
||||
const isVideo = videoExtList.includes(fileExt);
|
||||
// 如果是视频则始终使用 fileUrl(原文件)
|
||||
let contentURL = post[imgQualityMap[quality]] || fileUrl;
|
||||
if (isVideo) {
|
||||
contentURL = fileUrl;
|
||||
}
|
||||
|
||||
return art(path.join(__dirname, 'templates/description.art'), {
|
||||
id,
|
||||
source,
|
||||
owner,
|
||||
tags,
|
||||
link,
|
||||
isHttp,
|
||||
sourceHost,
|
||||
contentURL,
|
||||
isVideo,
|
||||
score: score || 0,
|
||||
});
|
||||
}
|
||||
|
||||
export function getAPIKeys() {
|
||||
return {
|
||||
apiKey: config.gelbooru.apiKey || '',
|
||||
userId: config.gelbooru.userId || '',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user