diff --git a/docs/other.md b/docs/other.md
index ff0d3aecb5..8893105f19 100644
--- a/docs/other.md
+++ b/docs/other.md
@@ -729,6 +729,36 @@ type 为 all 时,category 参数不支持 cost 和 free
+## 品玩
+
+### 实时要闻
+
+
+
+### 话题动态
+
+
+
+内容类型
+
+| 最新 | 最热 |
+| ---- | ---- |
+| 1 | 2 |
+
+
+
+### 用户
+
+
+
+内容类型
+
+| 文章 | 动态 |
+| ------- | ----- |
+| article | state |
+
+
+
## 且听风吟福利
### 分类
diff --git a/lib/router.js b/lib/router.js
index 407107f8b7..867ecfbdbf 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -1618,6 +1618,11 @@ router.get('/mofcom/article/:suffix', require('./routes/mofcom/article'));
// 字幕库
router.get('/zimuku/:type?', require('./routes/zimuku/index'));
+// 品玩
+router.get('/pingwest/status', require('./routes/pingwest/status'));
+router.get('/pingwest/tag/:tag/:type', require('./routes/pingwest/tag'));
+router.get('/pingwest/user/:uid/:type?', require('./routes/pingwest/user'));
+
// Hanime
router.get('/hanime/video', require('./routes/hanime/video'));
diff --git a/lib/routes/hpoi/info.js b/lib/routes/hpoi/info.js
index 8d9b066be5..a3bf9a1f3f 100644
--- a/lib/routes/hpoi/info.js
+++ b/lib/routes/hpoi/info.js
@@ -38,6 +38,7 @@ module.exports = async (ctx) => {
return {
title: infoTitle,
link,
+ category: infoType,
description: [`类型:${typeName}`, infoTitle, `更新内容: ${infoType}`, `
`].join('
'),
};
})
diff --git a/lib/routes/pingwest/status.js b/lib/routes/pingwest/status.js
new file mode 100644
index 0000000000..db4a978347
--- /dev/null
+++ b/lib/routes/pingwest/status.js
@@ -0,0 +1,43 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+
+module.exports = async (ctx) => {
+ const baseUrl = 'https://www.pingwest.com';
+ const url = `${baseUrl}/api/state/list`;
+ const response = await got(url, {
+ query: {
+ page: 1,
+ },
+ headers: {
+ Referer: baseUrl,
+ },
+ });
+ const $ = cheerio.load(response.data.data.list);
+ const items = $('section.item')
+ .map((_, ele) => {
+ const timestamp = ele.attribs['data-t'];
+ const $item = cheerio.load(ele);
+ const rightNode = $item('.news-info');
+ const tag = rightNode.find('.item-tag-list').text();
+ const title = rightNode.find('.title').text();
+ const link = rightNode
+ .find('a')
+ .last()
+ .attr('href');
+ const description = rightNode.text();
+ return {
+ title: title || tag,
+ link: link.startsWith('http') ? link : `https:${link}`,
+ description,
+ pubDate: new Date(timestamp * 1000).toUTCString(),
+ };
+ })
+ .get();
+
+ ctx.state.data = {
+ title: '品玩 - 实时要闻',
+ description: '品玩 - 实时要闻',
+ link: `${baseUrl}/status`,
+ item: items,
+ };
+};
diff --git a/lib/routes/pingwest/tag.js b/lib/routes/pingwest/tag.js
new file mode 100644
index 0000000000..c29747df63
--- /dev/null
+++ b/lib/routes/pingwest/tag.js
@@ -0,0 +1,50 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const utils = require('./utils');
+
+module.exports = async (ctx) => {
+ const { tag, type } = ctx.params;
+ const baseUrl = 'https://www.pingwest.com';
+ const tagUrl = `${baseUrl}/tag/${tag}`;
+ const tagId = await ctx.cache.tryGet(`pingwest-tag-${tag}`, async () => {
+ const res = await got(encodeURI(tagUrl), {
+ headers: {
+ Referer: baseUrl,
+ },
+ });
+ const $ = cheerio.load(res.data);
+ const tagId = $('.tag-detail').attr('data-id');
+ return tagId;
+ });
+ const url = `${baseUrl}/api/tag_article_list`;
+ const response = await got(url, {
+ query: {
+ page: 1,
+ id: tagId,
+ type,
+ },
+ headers: {
+ Referer: baseUrl,
+ },
+ });
+ const $ = cheerio.load(response.data.data.list);
+ const items = $('.item')
+ .map((_, ele) => {
+ const className = ele.attribs.class;
+ if (className.includes('state')) {
+ return utils.statusListParser(cheerio.load(ele))[0];
+ }
+ if (className.includes('wire')) {
+ return utils.wireListParser(cheerio.load(ele))[0];
+ }
+ return utils.articleListParser(cheerio.load(ele))[0];
+ })
+ .get();
+
+ ctx.state.data = {
+ title: `品玩 - ${tag}`,
+ description: `品玩 - ${tag}`,
+ link: tagUrl,
+ item: items,
+ };
+};
diff --git a/lib/routes/pingwest/user.js b/lib/routes/pingwest/user.js
new file mode 100644
index 0000000000..81204ddd38
--- /dev/null
+++ b/lib/routes/pingwest/user.js
@@ -0,0 +1,55 @@
+const got = require('@/utils/got');
+const cheerio = require('cheerio');
+const utils = require('./utils');
+
+module.exports = async (ctx) => {
+ const { uid, type = 'article' } = ctx.params;
+ const baseUrl = 'https://www.pingwest.com';
+ const aimUrl = `${baseUrl}/user/${uid}/${type}`;
+ const { userName, realUid } = await ctx.cache.tryGet(`pingwest-user-info-${uid}`, async () => {
+ const res = await got(aimUrl, {
+ headers: {
+ Referer: baseUrl,
+ },
+ });
+ const $ = cheerio.load(res.data);
+ const userInfoNode = $('#J_userId');
+ return {
+ userName: userInfoNode.text(),
+ realUid: userInfoNode.attr('data-user-id'),
+ };
+ });
+ const url = `${baseUrl}/api/user_data`;
+ const response = await got(url, {
+ query: {
+ page: 1,
+ user_id: realUid,
+ tab: type,
+ },
+ headers: {
+ Referer: baseUrl,
+ },
+ });
+ const $ = cheerio.load(response.data.data.list);
+
+ let item = [];
+ switch (type) {
+ case 'article':
+ item = utils.articleListParser($);
+ break;
+ case 'state':
+ item = utils.statusListParser($);
+ break;
+ }
+
+ const typeToLabel = {
+ article: '文章',
+ state: '动态',
+ };
+ ctx.state.data = {
+ title: `品玩 - ${userName} - ${typeToLabel[type]}`,
+ description: `品玩 - ${userName} - ${typeToLabel[type]}`,
+ link: aimUrl,
+ item,
+ };
+};
diff --git a/lib/routes/pingwest/utils.js b/lib/routes/pingwest/utils.js
new file mode 100644
index 0000000000..730d3f0e16
--- /dev/null
+++ b/lib/routes/pingwest/utils.js
@@ -0,0 +1,79 @@
+const cheerio = require('cheerio');
+
+const statusListParser = ($) => {
+ const items = $('.item')
+ .map((_, ele) => {
+ const timestamp = ele.attribs.pt;
+ const $item = cheerio.load(ele);
+ const mainNode = $item('.news-detail');
+ const imgsStr = mainNode
+ .find('img')
+ .map((_, ele) => `
`)
+ .get()
+ .join('
');
+ const link = mainNode
+ .find('.content a')
+ .first()
+ .attr('href');
+ const content = mainNode
+ .text()
+ .trim()
+ .replace(/展开全文$/, '');
+ return {
+ title: content,
+ link: link.startsWith('http') ? link : `https:${link}`,
+ description: [content, imgsStr].filter((s) => !!s).join('
'),
+ pubDate: new Date(timestamp * 1000).toUTCString(),
+ };
+ })
+ .get();
+ return items;
+};
+
+const articleListParser = ($) => {
+ const items = $('.item')
+ .map((_, ele) => {
+ const $item = cheerio.load(ele);
+ const timestamp = ele.attribs.pt;
+ const imgUrl = $item('.news-img img').attr('src');
+ const titleNode = $item('.title').first();
+ const title = titleNode.text();
+ const link = titleNode.find('a').attr('href');
+ const description = $item('.desc').text();
+ const author = $item('.author a').text();
+ return {
+ title,
+ link: link.startsWith('http') ? link : `https:${link}`,
+ description: [description, `
`].join('
'),
+ author,
+ pubDate: new Date(timestamp * 1000).toUTCString(),
+ };
+ })
+ .get();
+ return items;
+};
+
+const wireListParser = ($) => {
+ const items = $('.item')
+ .map((_, ele) => {
+ const $item = cheerio.load(ele);
+ const timestamp = ele.attribs.pt;
+ const titleNode = $item('.text').first();
+ const title = titleNode.text();
+ const link = titleNode.find('a').attr('href');
+ return {
+ title,
+ link: link.startsWith('http') ? link : `https:${link}`,
+ description: title,
+ pubDate: new Date(timestamp * 1000).toUTCString(),
+ };
+ })
+ .get();
+ return items;
+};
+
+module.exports = {
+ articleListParser,
+ statusListParser,
+ wireListParser,
+};