mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-09-01 14:57:14 +08:00
feat(route): add back matters (#15687)
This commit is contained in:
@@ -594,12 +594,6 @@ router.get('/zucc/cssearch/latest/:webVpn/:key', lazyloadRouteHandler('./routes/
|
||||
// checkee
|
||||
router.get('/checkee/:dispdate', lazyloadRouteHandler('./routes/checkee/index'));
|
||||
|
||||
// Matters
|
||||
router.get('/matters/latest/:type?', lazyloadRouteHandler('./routes/matters/latest'));
|
||||
router.redirect('/matters/hot', '/matters/latest/heat'); // Deprecated
|
||||
router.get('/matters/tags/:tid', lazyloadRouteHandler('./routes/matters/tags'));
|
||||
router.get('/matters/author/:uid', lazyloadRouteHandler('./routes/matters/author'));
|
||||
|
||||
// 古诗文网
|
||||
router.get('/gushiwen/recommend/:annotation?', lazyloadRouteHandler('./routes/gushiwen/recommend'));
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
const got = require('@/utils/got');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const uid = ctx.params.uid;
|
||||
const host = `https://matters.news`;
|
||||
const url = `https://server.matters.news/graphql`;
|
||||
const response = await got({
|
||||
method: 'post',
|
||||
url,
|
||||
json: {
|
||||
query: `
|
||||
{
|
||||
user(input: { userName: "${uid}" }) {
|
||||
displayName
|
||||
info {
|
||||
description
|
||||
}
|
||||
articles(input: { first: 20 }) {
|
||||
edges {
|
||||
node {
|
||||
slug
|
||||
mediaHash
|
||||
title
|
||||
content
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const user = response.data.data.user;
|
||||
|
||||
ctx.state.data = {
|
||||
title: `Matters | ${user.displayName}`,
|
||||
link: `${host}/@${uid}`,
|
||||
description: user.info.description,
|
||||
item: user.articles.edges.map(({ node: article }) => ({
|
||||
title: article.title,
|
||||
author: user.displayName,
|
||||
description: article.content,
|
||||
link: `${host}/@${uid}/${article.slug}-${article.mediaHash}`,
|
||||
pubDate: parseDate(article.createdAt),
|
||||
})),
|
||||
};
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
const got = require('@/utils/got');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const type = ctx.params.type || 'latest';
|
||||
const url = `https://server.matters.news/graphql`;
|
||||
const options = {
|
||||
latest: {
|
||||
title: '最新',
|
||||
apiType: 'newest',
|
||||
},
|
||||
heat: {
|
||||
title: '熱議',
|
||||
apiType: 'hottest',
|
||||
},
|
||||
essence: {
|
||||
title: '精華',
|
||||
apiType: 'icymi',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await got({
|
||||
method: 'post',
|
||||
url,
|
||||
json: {
|
||||
query: `
|
||||
{
|
||||
viewer {
|
||||
id
|
||||
recommendation {
|
||||
feed: ${options[type].apiType}(input: { first: 10 }) {
|
||||
edges {
|
||||
node {
|
||||
author {
|
||||
userName
|
||||
displayName
|
||||
}
|
||||
slug
|
||||
mediaHash
|
||||
title
|
||||
content
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const item = response.data.data.viewer.recommendation.feed.edges.map(({ node }) => {
|
||||
const link = `https://matters.news/@${node.author.userName}/${node.slug}-${node.mediaHash}`;
|
||||
const article = node.content;
|
||||
return {
|
||||
title: node.title,
|
||||
link,
|
||||
description: article,
|
||||
author: node.author.displayName,
|
||||
pubDate: parseDate(node.createdAt),
|
||||
};
|
||||
});
|
||||
|
||||
ctx.state.data = {
|
||||
title: `Matters | ${options[type].title}`,
|
||||
link: 'https://matters.news/',
|
||||
item,
|
||||
};
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
const got = require('@/utils/got');
|
||||
const { parseDate } = require('@/utils/parse-date');
|
||||
|
||||
module.exports = async (ctx) => {
|
||||
const tid = ctx.params.tid;
|
||||
const host = `https://matters.news`;
|
||||
const url = `https://server.matters.news/graphql`;
|
||||
const response = await got({
|
||||
method: 'post',
|
||||
url,
|
||||
json: {
|
||||
query: `
|
||||
{
|
||||
node(input: { id: "${tid}" }) {
|
||||
... on Tag {
|
||||
content
|
||||
articles(input:{first: 20}){
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
slug
|
||||
cover
|
||||
summary
|
||||
mediaHash
|
||||
content
|
||||
createdAt
|
||||
author {
|
||||
id
|
||||
userName
|
||||
displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const node = response.data.data.node;
|
||||
|
||||
ctx.state.data = {
|
||||
title: `Matters | ${node.content}`,
|
||||
link: `${host}/tags/${tid}`,
|
||||
description: node.content,
|
||||
item: node.articles.edges.map(({ node: article }) => ({
|
||||
title: article.title,
|
||||
author: article.author.displayName,
|
||||
description: article.content,
|
||||
link: `${host}/@${article.author.id}/${article.slug}-${article.mediaHash}`,
|
||||
pubDate: parseDate(article.createdAt),
|
||||
})),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Route } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { baseUrl, gqlEndpoint, parseItem } from './utils';
|
||||
|
||||
const handler = async (ctx) => {
|
||||
const { uid } = ctx.req.param();
|
||||
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20;
|
||||
const response = await ofetch(gqlEndpoint, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
query: `{
|
||||
user(input: {userName: "${uid}"}) {
|
||||
displayName
|
||||
avatar
|
||||
info {
|
||||
description
|
||||
}
|
||||
articles(input: {first: ${limit}}) {
|
||||
edges {
|
||||
node {
|
||||
shortHash
|
||||
title
|
||||
content
|
||||
createdAt
|
||||
author {
|
||||
displayName
|
||||
}
|
||||
tags {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const user = response.data.user;
|
||||
|
||||
return {
|
||||
title: `Matters | ${user.displayName}`,
|
||||
link: `${baseUrl}/@${uid}`,
|
||||
description: user.info.description,
|
||||
image: user.avatar,
|
||||
item: user.articles.edges.map(({ node }) => parseItem(node)),
|
||||
};
|
||||
};
|
||||
|
||||
export const route: Route = {
|
||||
path: '/author/:uid',
|
||||
name: 'Author',
|
||||
example: '/matters/author/robertu',
|
||||
parameters: { uid: "Author id, can be found at author's homepage url" },
|
||||
maintainers: ['Cerebrater', 'xosdy'],
|
||||
handler,
|
||||
radar: [
|
||||
{
|
||||
source: ['matters.town/:uid'],
|
||||
target: (params) => `/matters/author/${params.uid.slice(1)}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Route } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import { baseUrl, gqlEndpoint, parseItem } from './utils';
|
||||
|
||||
const handler = async (ctx) => {
|
||||
const { type = 'latest' } = ctx.req.param();
|
||||
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20;
|
||||
const options = {
|
||||
latest: {
|
||||
title: '最新',
|
||||
apiType: 'newest',
|
||||
},
|
||||
heat: {
|
||||
title: '熱議',
|
||||
apiType: 'hottest',
|
||||
},
|
||||
essence: {
|
||||
title: '精華',
|
||||
apiType: 'icymi',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await ofetch(gqlEndpoint, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
query: `{
|
||||
viewer {
|
||||
recommendation {
|
||||
feed: ${options[type].apiType}(input: {first: ${limit}}) {
|
||||
edges {
|
||||
node {
|
||||
shortHash
|
||||
title
|
||||
content
|
||||
createdAt
|
||||
author {
|
||||
displayName
|
||||
}
|
||||
tags {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const item = response.data.viewer.recommendation.feed.edges.map(({ node }) => parseItem(node));
|
||||
|
||||
return {
|
||||
title: `Matters | ${options[type].title}`,
|
||||
link: baseUrl,
|
||||
item,
|
||||
};
|
||||
};
|
||||
export const route: Route = {
|
||||
path: '/latest/:type?',
|
||||
name: 'Latest, heat, essence',
|
||||
example: '/matters/latest/heat',
|
||||
parameters: { uid: 'Defaults to latest, see table below' },
|
||||
maintainers: ['xyqfer', 'Cerebrater', 'xosdy'],
|
||||
handler,
|
||||
radar: [
|
||||
{
|
||||
source: ['matters.town'],
|
||||
},
|
||||
],
|
||||
description: `| 最新 | 热门 | 精华 |
|
||||
| ------ | ---- | ------- |
|
||||
| latest | heat | essence |`,
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: 'Matters',
|
||||
url: 'matters.town',
|
||||
categories: ['new-media'],
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Route } from '@/types';
|
||||
import ofetch from '@/utils/ofetch';
|
||||
import * as cheerio from 'cheerio';
|
||||
import cache from '@/utils/cache';
|
||||
import { baseUrl, gqlEndpoint, parseItem } from './utils';
|
||||
|
||||
interface Tag {
|
||||
type: string;
|
||||
generated: boolean;
|
||||
id: string;
|
||||
typename: string;
|
||||
}
|
||||
|
||||
const getTagId = (tid: string) =>
|
||||
cache.tryGet(`matters:tags:${tid}`, async () => {
|
||||
const response = await ofetch(`${baseUrl}/tags/${tid}`);
|
||||
const $ = cheerio.load(response);
|
||||
const nextData = JSON.parse($('script#__NEXT_DATA__').text());
|
||||
|
||||
const node = Object.entries(nextData.props.apolloState.data.ROOT_QUERY)
|
||||
.find(([key]) => key.startsWith('node'))
|
||||
?.pop() as Tag;
|
||||
|
||||
return node?.id.split(':')[1];
|
||||
});
|
||||
|
||||
const handler = async (ctx) => {
|
||||
const { tid } = ctx.req.param();
|
||||
const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20;
|
||||
|
||||
const tagId = await getTagId(tid);
|
||||
|
||||
const gqlResponse = await ofetch(gqlEndpoint, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
query: `{
|
||||
node(input: {id: "${tagId}"}) {
|
||||
... on Tag {
|
||||
content
|
||||
description
|
||||
articles(input: {first: ${limit}}) {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
shortHash
|
||||
content
|
||||
createdAt
|
||||
author {
|
||||
displayName
|
||||
}
|
||||
tags {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
});
|
||||
|
||||
const node = gqlResponse.data.node;
|
||||
|
||||
return {
|
||||
title: `Matters | ${node.content}`,
|
||||
link: `${baseUrl}/tags/${tid}`,
|
||||
description: node.description,
|
||||
item: node.articles.edges.map(({ node }) => parseItem(node)),
|
||||
};
|
||||
};
|
||||
|
||||
export const route: Route = {
|
||||
path: '/tags/:tid',
|
||||
name: 'Tags',
|
||||
example: '/matters/tags/972-哲學',
|
||||
parameters: { tid: 'Tag id, can be found in the url of the tag page' },
|
||||
maintainers: ['Cerebrater'],
|
||||
handler,
|
||||
radar: [
|
||||
{
|
||||
source: ['matters.town/tags/:tid'],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
|
||||
export const baseUrl = 'https://matters.town';
|
||||
export const gqlEndpoint = 'https://server.matters.town/graphql';
|
||||
|
||||
interface Tag {
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface Author {
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
interface Article {
|
||||
shortHash: string;
|
||||
title: string;
|
||||
content: string;
|
||||
createdAt: string;
|
||||
author: Author;
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
export const parseItem = (node: Article) => ({
|
||||
title: node.title,
|
||||
description: node.content,
|
||||
link: `${baseUrl}/a/${node.shortHash}`,
|
||||
author: node.author.displayName,
|
||||
pubDate: parseDate(node.createdAt),
|
||||
category: node.tags.map((tag) => tag.content),
|
||||
});
|
||||
Reference in New Issue
Block a user