feat(route): daily.dev sources (#17464)

This commit is contained in:
Tony 2024-11-05 10:25:45 -08:00 committed by GitHub
parent ee2f07d17b
commit d64c7620f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 281 additions and 113 deletions

View File

@ -5,7 +5,7 @@ import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
import cache from '@/utils/cache';
import { art } from '@/utils/render';
import path from 'path';
import path from 'node:path';
import { getCurrentPath } from '@/utils/helpers';
const __dirname = getCurrentPath(import.meta.url);

View File

@ -1,9 +1,5 @@
import { Route } from '@/types';
import { getData, getList, getRedirectedLink } from './utils.js';
const variables = {
first: 15,
};
import { baseUrl, getData, getList } from './utils.js';
const query = `
query MostDiscussedFeed(
@ -19,6 +15,7 @@ const query = `
edges {
node {
...FeedPost
contentHtml
}
}
}
@ -33,6 +30,7 @@ const query = `
image
readTime
permalink
commentsPermalink
summary
createdAt
numUpvotes
@ -52,37 +50,40 @@ const query = `
bio
}
`;
const graphqlQuery = {
query,
variables,
};
export const route: Route = {
path: '/discussed',
example: '/daily/discussed',
radar: [
{
source: ['daily.dev/popular'],
source: ['app.daily.dev/discussed'],
},
],
name: 'Most Discussed',
maintainers: ['Rjnishant530'],
handler,
url: 'daily.dev/popular',
url: 'app.daily.dev/discussed',
};
async function handler() {
const baseUrl = 'https://app.daily.dev/discussed';
const data = await getData(graphqlQuery);
const list = getList(data);
const items = await getRedirectedLink(list);
async function handler(ctx) {
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20;
const link = `${baseUrl}/discussed`;
const data = await getData({
query,
variables: {
first: limit,
},
});
const items = getList(data);
return {
title: 'Most Discussed',
link: baseUrl,
title: 'Real-time discussions in the developer community | daily.dev',
link,
item: items,
description: 'Most Discussed Posts on Daily.dev',
logo: 'https://app.daily.dev/favicon-32x32.png',
icon: 'https://app.daily.dev/favicon-32x32.png',
description: 'Stay on top of real-time developer discussions on daily.dev. Join conversations happening now and engage with the most active community members.',
logo: `${baseUrl}/favicon-32x32.png`,
icon: `${baseUrl}/favicon-32x32.png`,
language: 'en-us',
};
}

View File

@ -1,5 +1,5 @@
import { Route } from '@/types';
import { getData, getList, getRedirectedLink } from './utils.js';
import { baseUrl, getData, getList } from './utils.js';
const variables = {
version: 11,
@ -28,6 +28,7 @@ const query = `
edges {
node {
...FeedPost
contentHtml
}
}
}
@ -42,6 +43,7 @@ const query = `
image
readTime
permalink
commentsPermalink
summary
createdAt
numUpvotes
@ -72,27 +74,28 @@ export const route: Route = {
example: '/daily',
radar: [
{
source: ['daily.dev/popular'],
source: ['app.daily.dev/popular'],
},
],
name: 'Popular',
maintainers: ['Rjnishant530'],
handler,
url: 'daily.dev/popular',
url: 'app.daily.dev/popular',
};
async function handler() {
const baseUrl = 'https://app.daily.dev/popular';
const link = `${baseUrl}/popular`;
const data = await getData(graphqlQuery);
const list = getList(data);
const items = await getRedirectedLink(list);
const items = getList(data);
return {
title: 'Popular',
link: baseUrl,
title: 'Popular posts on daily.dev',
link,
item: items,
description: 'Popular Posts on Daily.dev',
logo: 'https://app.daily.dev/favicon-32x32.png',
icon: 'https://app.daily.dev/favicon-32x32.png',
description: 'daily.dev is the easiest way to stay updated on the latest programming news. Get the best content from the top tech publications on any topic you want.',
logo: `${baseUrl}/favicon-32x32.png`,
icon: `${baseUrl}/favicon-32x32.png`,
language: 'en-us',
};
}

View File

@ -2,7 +2,7 @@ import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Daily.dev',
url: 'daily.dev',
url: 'app.daily.dev',
categories: ['social-media'],
lang: 'en',
};

186
lib/routes/daily/source.ts Normal file
View File

@ -0,0 +1,186 @@
import { Route } from '@/types';
import { baseUrl, getBuildId, getData, getList } from './utils';
import ofetch from '@/utils/ofetch';
import cache from '@/utils/cache';
import { config } from '@/config';
interface Source {
id: string;
name: string;
handle: string;
image: string;
permalink: string;
description: string;
type: string;
}
const sourceFeedQuery = `
query SourceFeed($source: ID!, $loggedIn: Boolean! = false, $first: Int, $after: String, $ranking: Ranking, $supportedTypes: [String!]) {
page: sourceFeed(
source: $source
first: $first
after: $after
ranking: $ranking
supportedTypes: $supportedTypes
) {
...FeedPostConnection
}
}
fragment FeedPostConnection on PostConnection {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
...FeedPost
pinnedAt
contentHtml
...UserPost @include(if: $loggedIn)
}
}
}
fragment FeedPost on Post {
...FeedPostInfo
sharedPost {
id
title
image
readTime
permalink
commentsPermalink
createdAt
type
tags
source {
id
handle
permalink
image
}
slug
}
trending
feedMeta
collectionSources {
handle
image
}
numCollectionSources
updatedAt
slug
}
fragment FeedPostInfo on Post {
id
title
image
readTime
permalink
commentsPermalink
createdAt
commented
bookmarked
views
numUpvotes
numComments
summary
bookmark {
remindAt
}
author {
id
name
image
username
permalink
}
type
tags
source {
id
handle
name
permalink
image
type
}
userState {
vote
flags {
feedbackDismiss
}
}
slug
}
fragment UserPost on Post {
read
upvoted
commented
bookmarked
downvoted
}`;
export const route: Route = {
path: '/source/:sourceId',
example: '/daily/source/hn',
parameters: {
sourceId: 'The source id',
},
radar: [
{
source: ['app.daily.dev/sources/:sourceId'],
},
],
name: 'Source Posts',
maintainers: ['TonyRL'],
handler,
url: 'app.daily.dev',
};
async function handler(ctx) {
const sourceId = ctx.req.param('sourceId');
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10;
const link = `${baseUrl}/sources/${sourceId}`;
const buildId = await getBuildId();
const userData = (await cache.tryGet(`daily:source:${sourceId}`, async () => {
const response = await ofetch(`${baseUrl}/_next/data/${buildId}/en/sources/${sourceId}.json`);
return response.pageProps.source;
})) as Source;
const items = await cache.tryGet(
`daily:source:${sourceId}:posts`,
async () => {
const edges = await getData({
query: sourceFeedQuery,
variables: {
source: sourceId,
supportedTypes: ['article', 'video:youtube', 'collection'],
period: 30,
first: limit,
after: '',
loggedIn: false,
},
});
return getList(edges);
},
config.cache.routeExpire,
false
);
return {
title: `${userData.name} posts on daily.dev`,
description: userData.description,
link,
item: items,
image: userData.image,
logo: userData.image,
icon: userData.image,
language: 'en-us',
};
}

View File

@ -1,5 +1,5 @@
import { Route } from '@/types';
import { getData, getList, getRedirectedLink } from './utils.js';
import { baseUrl, getData, getList } from './utils.js';
const variables = {
period: 7,
@ -21,6 +21,7 @@ const query = `
edges {
node {
...FeedPost
contentHtml
}
}
}
@ -35,6 +36,7 @@ const query = `
image
readTime
permalink
commentsPermalink
summary
createdAt
numUpvotes
@ -56,37 +58,35 @@ const query = `
`;
const graphqlQuery = {
query,
variables,
};
export const route: Route = {
path: '/upvoted',
example: '/daily/upvoted',
radar: [
{
source: ['daily.dev/popular'],
source: ['app.daily.dev/upvoted'],
},
],
name: 'Most upvoted',
maintainers: ['Rjnishant530'],
handler,
url: 'daily.dev/popular',
url: 'app.daily.dev/upvoted',
};
async function handler() {
const baseUrl = 'https://app.daily.dev/upvoted';
const data = await getData(graphqlQuery);
const list = getList(data);
const items = await getRedirectedLink(list);
const link = `${baseUrl}/upvoted`;
const data = await getData({
query,
variables,
});
const items = getList(data);
return {
title: 'Most Upvoted',
link: baseUrl,
title: 'Most upvoted posts for developers | daily.dev',
link,
item: items,
description: 'Most Upvoted Posts on Daily.dev',
logo: 'https://app.daily.dev/favicon-32x32.png',
icon: 'https://app.daily.dev/favicon-32x32.png',
description: 'Find the most upvoted developer posts on daily.dev. Explore top-rated content in coding, tutorials, and tech news from the largest developer network in the world.',
logo: `${baseUrl}/favicon-32x32.png`,
icon: `${baseUrl}/favicon-32x32.png`,
language: 'en-us',
};
}

View File

@ -1,13 +1,8 @@
import { Route } from '@/types';
import { baseUrl, getBuildId, getData } from './utils';
import { baseUrl, getBuildId, getData, getList } 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(
@ -155,20 +150,18 @@ const userPostQuery = `
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'],
source: ['app.daily.dev/:userId/posts', 'app.daily.dev/:userId'],
},
],
name: 'User Posts',
maintainers: ['TonyRL'],
handler,
url: 'daily.dev',
url: 'app.daily.dev',
};
async function handler(ctx) {
@ -178,12 +171,12 @@ async function handler(ctx) {
const buildId = await getBuildId();
const userData = await cache.tryGet(`daily:user:${userId}`, async () => {
const resposne = await ofetch(`${baseUrl}/_next/data/${buildId}/en/${userId}.json`, {
const response = await ofetch(`${baseUrl}/_next/data/${buildId}/en/${userId}.json`, {
query: {
userId,
},
});
return resposne.pageProps;
return response.pageProps;
});
const user = (userData as any).user;
@ -198,17 +191,7 @@ async function handler(ctx) {
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),
}));
return getList(edges);
},
config.cache.routeExpire,
false

View File

@ -2,10 +2,15 @@ import { parseDate } from '@/utils/parse-date';
import ofetch from '@/utils/ofetch';
import cache from '@/utils/cache';
import { config } from '@/config';
import { art } from '@/utils/render';
import path from 'node:path';
import { getCurrentPath } from '@/utils/helpers';
const __dirname = getCurrentPath(import.meta.url);
const baseUrl = 'https://app.daily.dev';
export const baseUrl = 'https://app.daily.dev';
const gqlUrl = `https://api.daily.dev/graphql`;
const getBuildId = () =>
export const getBuildId = () =>
cache.tryGet(
'daily:buildId',
async () => {
@ -17,40 +22,30 @@ const getBuildId = () =>
false
);
const getData = async (graphqlQuery) => {
const response = await ofetch(`${baseUrl}/api/graphql`, {
export const getData = async (graphqlQuery) => {
const response = await ofetch(gqlUrl, {
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 = parseDate(createdAt);
return {
id,
title,
link: permalink,
description: summary,
author: author?.name,
itunes_item_image: image,
pubDate,
upvotes: numUpvotes,
comments: numComments,
category: tags,
};
});
const render = (data) => art(path.join(__dirname, 'templates/posts.art'), data);
const getRedirectedLink = (data) =>
Promise.all(
data.map((v) =>
cache.tryGet(v.link, async () => {
const resp = await ofetch.raw(v.link);
return { ...v, link: resp.headers.get('location') };
})
)
);
export { baseUrl, getBuildId, getData, getList, getRedirectedLink };
export const getList = (edges) =>
edges.map(({ node }) => ({
id: node.id,
title: node.title,
link: node.commentsPermalink ?? node.permalink,
guid: node.permalink,
description: render({
image: node.image,
content: node.contentHtml?.replaceAll('\n', '<br>') ?? node.summary,
}),
author: node.author?.name,
itunes_item_image: node.image,
pubDate: parseDate(node.createdAt),
upvotes: node.numUpvotes,
comments: node.numComments,
category: node.tags,
}));

View File

@ -2,7 +2,7 @@ import { Route } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import * as cheerio from 'cheerio';
import path from 'path';
import path from 'node:path';
import { getCurrentPath } from '@/utils/helpers';
import { art } from '@/utils/render';

View File

@ -3,7 +3,7 @@ import ofetch from '@/utils/ofetch';
import * as cheerio from 'cheerio';
import type { Context } from 'hono';
import { ChartTitleSearchConnection } from './types';
import path from 'path';
import path from 'node:path';
import { getCurrentPath } from '@/utils/helpers';
import { art } from '@/utils/render';

View File

@ -4,7 +4,7 @@ import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
import md5 from '@/utils/md5';
import path from 'path';
import path from 'node:path';
import { getCurrentPath } from '@/utils/helpers';
import { art } from '@/utils/render';

View File

@ -9,7 +9,7 @@ import 'dayjs/locale/zh-cn';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import path from 'path';
import path from 'node:path';
import { CrowdfundingData, CrowdfundingDetailData, CrowdfundingDetailInfo, CrowdfundingItem, CrowdfundingList, DataResponse } from './types';
dayjs.extend(localizedFormat);

View File

@ -3,7 +3,7 @@ import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import path from 'path';
import path from 'node:path';
import { art } from '@/utils/render';
import { getCurrentPath } from '@/utils/helpers';

View File

@ -3,7 +3,7 @@ import ofetch from '@/utils/ofetch';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import cache from '@/utils/cache';
import path from 'path';
import path from 'node:path';
import { getCurrentPath } from '@/utils/helpers';
import { art } from '@/utils/render';

View File

@ -7,7 +7,7 @@ import { parseDate } from '@/utils/parse-date';
import { baseUrl, getCitys, getDistricts } from './utils';
import InvalidParameterError from '@/errors/types/invalid-parameter';
import { art } from '@/utils/render';
import path from 'path';
import path from 'node:path';
import { getCurrentPath } from '@/utils/helpers';
const __dirname = getCurrentPath(import.meta.url);