feat(route/tumblr): Add Tumblr tagged route (#20576)

* tumblr.com: Code to access tagged/ endpoint

* tumblr.com: Change processPost for tagged endpoint

* Tumblr.com: "Tagged" response is a list of posts

* tumblr.com: removed unnecessary link

* tumblr.com: fixing api key auth as per https://www.tumblr.com/docs/en/api/v2#authentication

* tumblr.com: user query params properly

* tumblr.com: compliance with Script Standard

* tumblr.com: ensuring `api_key` works for posts.ts

* tumblr: don't mess up the types this time

* tumblr/tagged/:tag Feed Description + Link

* /tumblr Can now parse blog answers

* /tumblr "answer" is the correct type

* /tumblr: Better format for blog answers

* /tumblr: fixed misplaced </p> tag

* lib/routes/tumblr/tagged.ts: Removed unnecessary template literals

Co-authored-by: Tony <TonyRL@users.noreply.github.com>

* /tumblr/tagged: Only one category

* /tumblr: Fixed how the asker's url is handled

* Ran ESLint

---------

Co-authored-by: Tony <TonyRL@users.noreply.github.com>
This commit is contained in:
PolarisStarnor 2025-12-02 22:13:09 -07:00 committed by GitHub
parent 27cd651d58
commit 8fcbaac983
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 82 additions and 5 deletions

View File

@ -38,7 +38,7 @@ export const route: Route = {
supportScihub: false,
},
name: 'Posts',
maintainers: ['Rakambda'],
maintainers: ['Rakambda', 'PolarisStarnor'],
description: `::: tip
Tumblr provides official RSS feeds for non "dashboard only" blogs, for instance [https://biketouring-nearby.tumblr.com](https://biketouring-nearby.tumblr.com/rss).
:::`,
@ -55,7 +55,7 @@ async function handler(ctx: Context): Promise<Data> {
const response = await got.get(`https://api.tumblr.com/v2/blog/${blogIdentifier}/posts`, {
searchParams: {
...utils.generateAuthParams(),
api_key: utils.generateAuthParams(),
limit,
},
headers: await utils.generateAuthHeaders(),

View File

@ -0,0 +1,71 @@
import type { Context } from 'hono';
import { config } from '@/config';
import ConfigNotFoundError from '@/errors/types/config-not-found';
import type { Data, Route } from '@/types';
import got from '@/utils/got';
import { fallback, queryToInteger } from '@/utils/readable-social';
import utils from './utils';
export const route: Route = {
path: '/tagged/:tag',
categories: ['social-media'],
example: '/tumblr/tagged/nature',
parameters: {
tag: 'Tag name (see `https://www.tumblr.com/docs/en/api/v2#tagged--get-posts-with-tag`)',
},
radar: [],
features: {
requireConfig: [
{
name: 'TUMBLR_CLIENT_ID',
description: 'Please see above for details.',
},
{
name: 'TUMBLR_CLIENT_SECRET',
description: 'Please see above for details.',
},
{
name: 'TUMBLR_REFRESH_TOKEN',
description: 'Please see above for details.',
},
],
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
name: 'Tagged Posts',
maintainers: ['PolarisStarnor'],
handler,
};
async function handler(ctx: Context): Promise<Data> {
if (!config.tumblr || !config.tumblr.clientId) {
throw new ConfigNotFoundError('Tumblr RSS is disabled due to the lack of <a href="https://docs.rsshub.app/deploy/config#route-specific-configurations">relevant config</a>');
}
const tag = ctx.req.param('tag');
const limit = fallback(undefined, queryToInteger(ctx.req.query('limit')), 20);
const response = await got.get('https://api.tumblr.com/v2/tagged', {
searchParams: {
tag,
api_key: utils.generateAuthParams(),
limit,
},
headers: await utils.generateAuthHeaders(),
});
const posts = response.data.response.map((post: any) => utils.processPost(post));
return {
title: `Tumblr - ${tag}`,
description: `Tumblr posts tagged #${tag}`,
link: `https://tumblr.com/tagged/${tag}`,
item: posts,
allowEmpty: true,
};
}

View File

@ -31,9 +31,7 @@ const generateAuthHeaders: () => Promise<{ Authorization?: string }> = async ()
};
};
const generateAuthParams: () => { apiKey?: string } = () => ({
apiKey: config.tumblr.clientId,
});
const generateAuthParams: () => string = () => config.tumblr.clientId!;
const processPost: (post: any) => DataItem = (post) => {
let description = '';
@ -42,6 +40,13 @@ const processPost: (post: any) => DataItem = (post) => {
case 'text':
description = post.body;
break;
case 'answer':
description += post.asking_url === null ? `<p>${post.asking_name} asks:</p>` : `<p><a href="${post.asking_url}">${post.asking_name}</a> asks:</p>`;
description += post.question;
description += '<hr>';
description += `<p><a href="${post.blog.url}">${post.blog_name}</a> answers:</p>`;
description += post.answer;
break;
case 'photo':
for (const photo of post.photos ?? []) {
description += `<img src="${photo.original_size.url}"/><br/>`;
@ -58,6 +63,7 @@ const processPost: (post: any) => DataItem = (post) => {
}
return {
author: post.blog_name,
id: post.id_string,
title: post.summary ?? `New post from ${post.blog_name}`,
link: post.post_url,