feat(route): bluesky user posts (#14130)
This commit is contained in:
parent
526d1ea94d
commit
2eefc7fecc
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = {
|
||||
'/keyword/:keyword': ['untitaker'],
|
||||
'/profile/:handle': ['TonyRL'],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
const { parseDate } = require('@/utils/parse-date');
|
||||
const { resolveHandle, getProfile, getAuthorFeed } = require('./utils');
|
||||
const { art } = require('@/utils/render');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const { handle } = ctx.params;
|
||||
const DID = await resolveHandle(handle, ctx.cache.tryGet);
|
||||
const profile = await getProfile(DID, ctx.cache.tryGet);
|
||||
const authorFeed = await getAuthorFeed(DID, ctx.cache.tryGet);
|
||||
|
||||
const items = authorFeed.feed.map(({ post }) => ({
|
||||
title: post.record.text.split('\n')[0],
|
||||
description: art(join(__dirname, 'templates/post.art'), {
|
||||
text: post.record.text.replace(/\n/g, '<br>'),
|
||||
embed: post.embed,
|
||||
// embed.$type "app.bsky.embed.record#view" and "app.bsky.embed.recordWithMedia#view"
|
||||
// are not handled
|
||||
}),
|
||||
author: post.author.displayName,
|
||||
pubDate: parseDate(post.record.createdAt),
|
||||
link: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('app.bsky.feed.post/')[1]}`,
|
||||
upvotes: post.likeCount,
|
||||
comments: post.replyCount,
|
||||
}));
|
||||
|
||||
ctx.state.data = {
|
||||
title: `${profile.displayName} (@${profile.handle}) — Bluesky`,
|
||||
description: profile.description.replace(/\n/g, ' '),
|
||||
link: `https://bsky.app/profile/${profile.handle}`,
|
||||
image: profile.banner,
|
||||
icon: profile.avatar,
|
||||
logo: profile.avatar,
|
||||
item: items,
|
||||
};
|
||||
|
||||
ctx.state.json = {
|
||||
DID,
|
||||
profile,
|
||||
authorFeed,
|
||||
};
|
||||
};
|
||||
|
|
@ -8,6 +8,12 @@ module.exports = {
|
|||
source: '/search',
|
||||
target: (params, url) => `/bsky/keyword/${new URL(url).searchParams.get('q')}`,
|
||||
},
|
||||
{
|
||||
title: 'Post',
|
||||
docs: 'https://docs.rsshub.app/routes/social-media#bluesky-bsky',
|
||||
source: '/profile/:handle',
|
||||
target: '/bsky/profile/:handle',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
module.exports = (router) => {
|
||||
router.get('/keyword/:keyword', require('./keyword'));
|
||||
router.get('/profile/:handle', require('./posts'));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
{{ if text }}
|
||||
{{@ text }}<br>
|
||||
{{ /if }}
|
||||
|
||||
{{ if embed }}
|
||||
{{ if embed.$type == 'app.bsky.embed.images#view'}}
|
||||
{{ each embed.images i }}
|
||||
<img src="{{ i.fullsize }}" alt="{{ i.alt }}"><br>
|
||||
{{ /each }}
|
||||
{{ else if embed.$type == 'app.bsky.embed.external#view' }}
|
||||
<a href="{{ embed.external.uri }}"><b>{{ embed.external.title }}</b><br>
|
||||
{{ embed.external.description }}
|
||||
</a>
|
||||
{{ /if }}
|
||||
{{ /if }}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
const got = require('@/utils/got');
|
||||
const config = require('@/config').value;
|
||||
|
||||
/**
|
||||
* docs: https://atproto.com/lexicons/app-bsky
|
||||
*/
|
||||
|
||||
// https://github.com/bluesky-social/atproto/blob/main/lexicons/com/atproto/identity/resolveHandle.json
|
||||
const resolveHandle = (handle, tryGet) =>
|
||||
tryGet(`bsky:${handle}`, async () => {
|
||||
const { data } = await got('https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle', {
|
||||
searchParams: {
|
||||
handle,
|
||||
},
|
||||
});
|
||||
return data.did;
|
||||
});
|
||||
|
||||
// https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/actor/getProfile.json
|
||||
const getProfile = (did, tryGet) =>
|
||||
tryGet(`bsky:profile:${did}`, async () => {
|
||||
const { data } = await got('https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile', {
|
||||
searchParams: {
|
||||
actor: did,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
});
|
||||
|
||||
// https://github.com/bluesky-social/atproto/blob/main/lexicons/app/bsky/feed/getAuthorFeed.json
|
||||
const getAuthorFeed = (did, tryGet) =>
|
||||
tryGet(
|
||||
`bsky:authorFeed:${did}`,
|
||||
async () => {
|
||||
const { data } = await got('https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed', {
|
||||
searchParams: {
|
||||
actor: did,
|
||||
filter: 'posts_and_author_threads',
|
||||
limit: 30,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
},
|
||||
config.cache.routeExpire,
|
||||
false
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
resolveHandle,
|
||||
getProfile,
|
||||
getAuthorFeed,
|
||||
};
|
||||
|
|
@ -339,6 +339,10 @@
|
|||
|
||||
## Bluesky (bsky) {#bluesky-bsky}
|
||||
|
||||
### Post {#bluesky-bsky-post}
|
||||
|
||||
<Route author="TonyRL" example="/bsky/profile/bsky.app" path="/bsky/profile/:handle" paramsDesc={['User handle, can be found in URL']} radar="1" />
|
||||
|
||||
### Keywords {#bluesky-bsky-keywords}
|
||||
|
||||
<Route author="untitaker" example="/bsky/keyword/hello" path="/bsky/keyword/:keyword" radar="1" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue