diff --git a/lib/v2/chuanliu/maintainer.js b/lib/v2/chuanliu/maintainer.js
new file mode 100644
index 00000000000000..0abf523583e0a6
--- /dev/null
+++ b/lib/v2/chuanliu/maintainer.js
@@ -0,0 +1,3 @@
+module.exports = {
+ '/nice': ['nczitzk'],
+};
diff --git a/lib/v2/chuanliu/nice.js b/lib/v2/chuanliu/nice.js
new file mode 100644
index 00000000000000..7d08ed3a071773
--- /dev/null
+++ b/lib/v2/chuanliu/nice.js
@@ -0,0 +1,76 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const { parseDate } = require('@/utils/parse-date');
+const { art } = require('@/utils/render');
+const path = require('path');
+const md = require('markdown-it')({
+ html: true,
+});
+
+module.exports = async (ctx) => {
+ const limit = ctx.query.limit ? Number.parseInt(ctx.query.limit, 10) : 100;
+
+ const rootUrl = 'https://chuanliu.org';
+ const apiRootUrl = 'https://s.chuanliu.org';
+ const currentUrl = new URL('nice/', rootUrl).href;
+ const apiUrl = new URL('api/dis/api/v1/memo', apiRootUrl).href;
+
+ const { data: response } = await got(apiUrl, {
+ searchParams: {
+ creatorId: 1,
+ offset: 0,
+ limit,
+ },
+ });
+
+ const items = response.slice(0, limit).map((item) => {
+ const contents = item.content.split(/\n/);
+
+ const category = contents?.[0].replace(/^#/, '') ?? undefined;
+ const title = contents?.[1] ?? undefined;
+ const link = contents?.[2] ?? undefined;
+ const author = contents?.[3] ?? undefined;
+ const isStar = (contents?.[5] && contents[5] === 'star') ?? false;
+
+ if (isStar) {
+ contents.splice(5, 1);
+ }
+
+ return {
+ title: `${isStar ? '[STAR] ' : ''}${title}`,
+ link,
+ description: art(path.join(__dirname, 'templates/description.art'), {
+ description: md.render(contents?.join('\n\n') ?? ''),
+ images: item.resourceList.map((i) => ({
+ src: i.externalLink,
+ alt: i.filename,
+ type: i.type,
+ })),
+ }),
+ author,
+ category: [category, isStar ? 'STAR' : undefined].filter(Boolean),
+ guid: `chuanliu-nice#${item.id}`,
+ pubDate: parseDate(item.createdTs, 'X'),
+ updated: parseDate(item.updatedTs, 'X'),
+ };
+ });
+
+ const { data: currentResponse } = await got(currentUrl);
+
+ const $ = cheerio.load(currentResponse);
+
+ const icon = new URL($('link[rel="shortcut icon"]').prop('href'), currentUrl).href;
+
+ ctx.state.data = {
+ item: items,
+ title: $('title').text(),
+ link: currentUrl,
+ description: $('span.rainbow-text').first().text(),
+ language: $('html').prop('lang'),
+ icon,
+ logo: icon,
+ subtitle: $('title').text(),
+ author: $('meta[name="author"]').prop('content'),
+ allowEmpty: true,
+ };
+};
diff --git a/lib/v2/chuanliu/radar.js b/lib/v2/chuanliu/radar.js
new file mode 100644
index 00000000000000..b8072da6d07496
--- /dev/null
+++ b/lib/v2/chuanliu/radar.js
@@ -0,0 +1,13 @@
+module.exports = {
+ 'chuanliu.org': {
+ _name: '川流',
+ '.': [
+ {
+ title: '严选',
+ docs: 'https://docs.rsshub.app/routes/blog#chuan-liu-yan-xuan',
+ source: ['/nice'],
+ target: '/chuanliu/nice',
+ },
+ ],
+ },
+};
diff --git a/lib/v2/chuanliu/router.js b/lib/v2/chuanliu/router.js
new file mode 100644
index 00000000000000..d082d9a1c37c9f
--- /dev/null
+++ b/lib/v2/chuanliu/router.js
@@ -0,0 +1,3 @@
+module.exports = (router) => {
+ router.get('/nice', require('./nice'));
+};
diff --git a/lib/v2/chuanliu/templates/description.art b/lib/v2/chuanliu/templates/description.art
new file mode 100644
index 00000000000000..dfd22a693d90ce
--- /dev/null
+++ b/lib/v2/chuanliu/templates/description.art
@@ -0,0 +1,23 @@
+{{ if images }}
+ {{ each images image }}
+ {{ if image?.src }}
+
+
+
+ {{ /if }}
+ {{ /each }}
+{{ /if }}
+
+{{ if description }}
+ {{@ description }}
+{{ /if }}
\ No newline at end of file
diff --git a/website/docs/routes/blog.mdx b/website/docs/routes/blog.mdx
index 04ef21213bdf59..b1ee3e6e55a4b7 100644
--- a/website/docs/routes/blog.mdx
+++ b/website/docs/routes/blog.mdx
@@ -284,6 +284,12 @@
通过提取文章全文,以提供比官方源更佳的阅读体验.
+## 川流 {#chuan-liu}
+
+### 严选 {#chuan-liu-yan-xuan}
+
+
+
## 大侠阿木 {#da-xia-a-mu}
### 首页 {#da-xia-a-mu-shou-ye}