feat(route): daily.dev user (#15403)

This commit is contained in:
Tony 2024-04-30 01:44:56 +08:00 committed by GitHub
parent 3cb4b741ae
commit 715bad0663
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 275 additions and 45 deletions

View File

@ -59,21 +59,10 @@ const graphqlQuery = {
export const route: Route = {
path: '/discussed',
categories: ['social-media'],
example: '/daily/discussed',
parameters: {},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['daily.dev/popular'],
target: '',
},
],
name: 'Most Discussed',

View File

@ -69,13 +69,13 @@ const graphqlQuery = {
export const route: Route = {
path: '/',
example: '/daily',
radar: [
{
source: ['daily.dev/popular'],
target: '',
},
],
name: 'Unknown',
name: 'Popular',
maintainers: ['Rjnishant530'],
handler,
url: 'daily.dev/popular',

View File

@ -3,4 +3,5 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Daily.dev',
url: 'daily.dev',
categories: ['social-media'],
};

View File

@ -0,0 +1,7 @@
{{ if image }}
<img src="{{ image }}"><br>
{{ /if }}
{{ if content }}
{{@ content }}
{{ /if }}

View File

@ -16,7 +16,7 @@ const query = `
...FeedPostConnection
}
}
fragment FeedPostConnection on PostConnection {
edges {
node {
@ -24,11 +24,11 @@ const query = `
}
}
}
fragment FeedPost on Post {
...SharedPostInfo
}
fragment SharedPostInfo on Post {
id
title
@ -44,7 +44,7 @@ const query = `
}
tags
}
fragment UserShortInfo on User {
id
name
@ -53,7 +53,7 @@ const query = `
username
bio
}
`;
const graphqlQuery = {
@ -63,21 +63,10 @@ const graphqlQuery = {
export const route: Route = {
path: '/upvoted',
categories: ['social-media'],
example: '/daily/upvoted',
parameters: {},
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['daily.dev/popular'],
target: '',
},
],
name: 'Most upvoted',

227
lib/routes/daily/user.ts Normal file
View File

@ -0,0 +1,227 @@
import { Route } from '@/types';
import { baseUrl, getBuildId, getData } from './utils';
import ofetch from '@/utils/ofetch';
import cache from '@/utils/cache';
import { config } from '@/config';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import path from 'path';
import { getCurrentPath } from '@/utils/helpers';
const __dirname = getCurrentPath(import.meta.url);
const userPostQuery = `
query AuthorFeed(
$loggedIn: Boolean! = false
$userId: ID!
$after: String
$first: Int
$supportedTypes: [String!] = [
"article"
"share"
"freeform"
"video:youtube"
"collection"
]
) {
page: authorFeed(
author: $userId
after: $after
first: $first
ranking: TIME
supportedTypes: $supportedTypes
) {
...FeedPostConnection
}
}
fragment FeedPostConnection on PostConnection {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
...FeedPost
contentHtml
...UserPost @include(if: $loggedIn)
}
}
}
fragment FeedPost on Post {
...SharedPostInfo
sharedPost {
...SharedPostInfo
}
trending
feedMeta
collectionSources {
handle
image
}
numCollectionSources
updatedAt
slug
}
fragment SharedPostInfo on Post {
id
title
titleHtml
image
readTime
permalink
commentsPermalink
summary
createdAt
private
upvoted
commented
bookmarked
views
numUpvotes
numComments
videoId
scout {
...UserShortInfo
}
author {
...UserShortInfo
}
type
tags
source {
...SourceBaseInfo
}
downvoted
flags {
promoteToPublic
}
userState {
vote
flags {
feedbackDismiss
}
}
slug
}
fragment SourceBaseInfo on Source {
id
active
handle
name
permalink
public
type
description
image
membersCount
privilegedMembers {
user {
id
}
role
}
currentMember {
...CurrentMember
}
memberPostingRole
memberInviteRole
}
fragment CurrentMember on SourceMember {
user {
id
}
permissions
role
referralToken
flags {
hideFeedPosts
collapsePinnedPosts
}
}
fragment UserShortInfo on User {
id
name
image
permalink
username
bio
createdAt
reputation
}
fragment UserPost on Post {
read
upvoted
commented
bookmarked
downvoted
}`;
const render = (data) => art(path.join(__dirname, 'templates/posts.art'), data);
export const route: Route = {
path: '/user/:userId',
example: '/daily/user/kramer',
radar: [
{
source: ['daily.dev/:userId/posts', 'daily.dev/:userId'],
},
],
name: 'User Posts',
maintainers: ['TonyRL'],
handler,
url: 'daily.dev',
};
async function handler(ctx) {
const userId = ctx.req.param('userId');
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 7;
const buildId = await getBuildId();
const userData = await cache.tryGet(`daily:user:${userId}`, async () => {
const resposne = await ofetch(`${baseUrl}/_next/data/${buildId}/en/${userId}.json`, {
query: {
userId,
},
});
return resposne.pageProps;
});
const user = (userData as any).user;
const items = await cache.tryGet(
`daily:user:${userId}:posts`,
async () => {
const edges = await getData({
query: userPostQuery,
variables: {
userId: user.id,
first: limit,
loggedIn: false,
},
});
return edges.map(({ node }) => ({
title: node.title,
description: render({
image: node.image,
content: node.contentHtml?.replaceAll('\n', '<br>') ?? node.summary,
}),
link: node.permalink,
author: node.author?.name,
category: node.tags,
pubDate: parseDate(node.createdAt),
}));
},
config.cache.routeExpire,
false
);
return {
title: `${user.name} | daily.dev`,
description: user.bio,
link: `${baseUrl}/${userId}/posts`,
item: items,
image: user.image,
logo: user.image,
icon: user.image,
language: 'en-us',
};
}

View File

@ -1,19 +1,34 @@
import dayjs from 'dayjs';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import ofetch from '@/utils/ofetch';
import cache from '@/utils/cache';
import { config } from '@/config';
const getData = async (graphqlQuery) =>
(
await got
.post('https://app.daily.dev/api/graphql', {
json: graphqlQuery,
})
.json()
).data.page.edges;
const baseUrl = 'https://app.daily.dev';
const getBuildId = () =>
cache.tryGet(
'daily:buildId',
async () => {
const response = await ofetch(`${baseUrl}/onboarding`);
const buildId = response.match(/"buildId":"(.*?)"/)[1];
return buildId;
},
config.cache.routeExpire,
false
);
const getData = async (graphqlQuery) => {
const response = await ofetch(`${baseUrl}/api/graphql`, {
method: 'POST',
body: graphqlQuery,
});
return response.data.page.edges;
};
const getList = (data) =>
data.map((value) => {
const { id, title, image, permalink, summary, createdAt, numUpvotes, author, tags, numComments } = value.node;
const pubDate = dayjs(createdAt);
const pubDate = parseDate(createdAt);
return {
id,
title,
@ -30,10 +45,12 @@ const getList = (data) =>
const getRedirectedLink = (data) =>
Promise.all(
data.map(async (v) => {
const resp = await got(v.link);
return { ...v, link: resp.headers.link.split(/[<>]/g)[1] };
})
data.map((v) =>
cache.tryGet(v.link, async () => {
const resp = await ofetch.raw(v.link);
return { ...v, link: resp.headers.get('location') };
})
)
);
export { getData, getList, getRedirectedLink };
export { baseUrl, getBuildId, getData, getList, getRedirectedLink };