fix(route): 修复bangumi吐槽箱无法获取评分的问题 (#10780)

* 修复bangumi吐槽箱无法获取评分的问题

* refactor: migrate to v2

* fix: subject date parsing

* fix: old routes

* fix: sort maintainer
This commit is contained in:
Axojhf 2022-09-15 04:16:29 +08:00 committed by GitHub
parent 6251a2dc02
commit 2600c1ab16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 302 additions and 275 deletions

View File

@ -827,20 +827,6 @@
{ title: '日记评论区', docs: 'https://docs.rsshub.app/social-media.html#fur-affinity', source: '/journal/:id/', target: '/furaffinity/journal_comments/:id' },
],
},
'bgm.tv': {
_name: 'Bangumi',
'.': [
{ title: '小组话题', docs: 'https://docs.rsshub.app/anime.html#bangumi', source: '/group/:id', target: '/bangumi/group/:id' },
{ title: '小组话题的新回复', docs: 'https://docs.rsshub.app/anime.html#bangumi', source: '/group/topic/:id', target: '/bangumi/topic/:id' },
{ title: '现实人物的新作品', docs: 'https://docs.rsshub.app/anime.html#bangumi', source: '/person/:id', target: '/bangumi/person/:id' },
{ title: '用户日志', docs: 'https://docs.rsshub.app/anime.html#bangumi', source: '/user/:id', target: '/bangumi/user/blog/:id' },
{ title: '条目的讨论', docs: 'https://docs.rsshub.app/anime.html#bangumi', source: '/subject/:id', target: '/bangumi/subject/:id/topics' },
{ title: '条目的评论', docs: 'https://docs.rsshub.app/anime.html#bangumi', source: '/subject/:id', target: '/bangumi/subject/:id/blogs' },
{ title: '条目的章节', docs: 'https://docs.rsshub.app/anime.html#bangumi', source: '/subject/:id', target: '/bangumi/subject/:id' },
{ title: '条目的吐槽箱', docs: 'https://docs.rsshub.app/anime.html#bangumi', source: '/subject/:id', target: '/bangumi/subject/:id/comments' },
{ title: '放送列表', docs: 'https://docs.rsshub.app/anime.html#bangumi', source: '/calendar', target: '/bangumi/calendar/today' },
],
},
'e-hentai.org/': {
_name: 'E-Hentai',
'.': [

View File

@ -204,43 +204,43 @@ pageClass: routes
<Route author="ranpox" path="/anitama/:channel?" example="/anitama" :paramsDesc="['频道id从频道的地址栏中查看']"/>
## Bangumi
## Bangumi 番组计划
### 放送列表
<Route author="magic-akari" example="/bangumi/calendar/today" path="/bangumi/calendar/today" radar="1"/>
<Route author="magic-akari" example="/bangumi/tv/calendar/today" path="/bangumi/tv/calendar/today" radar="1"/>
### 条目的章节
<Route author="SettingDust" example="/bangumi/subject/240038" path="/bangumi/subject/:id" :paramsDesc="['条目 id, 在条目页面的地址栏查看']" radar="1"/>
<Route author="SettingDust" example="/bangumi/tv/subject/240038" path="/bangumi/tv/subject/:id" :paramsDesc="['条目 id, 在条目页面的地址栏查看']" radar="1"/>
### 条目的吐槽箱
<Route author="ylc395" example="/bangumi/subject/214265/comments?minLength=100" path="/bangumi/subject/:id/comments" :paramsDesc="['条目 id, 在条目页面的地址栏查看. minLength: 以查询字符串query string的形式指定. 用于过滤掉内容长度小于指定值的吐槽']" radar="1"/>
<Route author="ylc395" example="/bangumi/tv/subject/214265/comments?minLength=100" path="/bangumi/tv/subject/:id/comments" :paramsDesc="['条目 id, 在条目页面的地址栏查看. minLength: 以查询字符串query string的形式指定. 用于过滤掉内容长度小于指定值的吐槽']" radar="1"/>
### 条目的评论
<Route author="ylc395" example="/bangumi/subject/214265/blogs" path="/bangumi/subject/:id/blogs" :paramsDesc="['条目 id, 在条目页面的地址栏查看']" radar="1"/>
<Route author="ylc395" example="/bangumi/tv/subject/214265/blogs" path="/bangumi/tv/subject/:id/blogs" :paramsDesc="['条目 id, 在条目页面的地址栏查看']" radar="1"/>
### 条目的讨论
<Route author="ylc395" example="/bangumi/subject/214265/topics" path="/bangumi/subject/:id/topics" :paramsDesc="['条目 id, 在条目页面的地址栏查看']" radar="1"/>
<Route author="ylc395" example="/bangumi/tv/subject/214265/topics" path="/bangumi/tv/subject/:id/topics" :paramsDesc="['条目 id, 在条目页面的地址栏查看']" radar="1"/>
### 现实人物的新作品
<Route author="ylc395" example="/bangumi/person/32943" path="/bangumi/person/:id" :paramsDesc="['人物 id, 在人物页面的地址栏查看']" radar="1"/>
<Route author="ylc395" example="/bangumi/tv/person/32943" path="/bangumi/tv/person/:id" :paramsDesc="['人物 id, 在人物页面的地址栏查看']" radar="1"/>
### 小组话题的新回复
<Route author="ylc395" example="/bangumi/topic/24657" path="/bangumi/topic/:id" :paramsDesc="['话题 id, 在话题页面地址栏查看']" radar="1"/>
<Route author="ylc395" example="/bangumi/tv/topic/367032" path="/bangumi/tv/topic/:id" :paramsDesc="['话题 id, 在话题页面地址栏查看']" radar="1"/>
### 小组话题
<Route author="SettingDust" example="/bangumi/group/boring" path="/bangumi/group/:id" :paramsDesc="['小组 id, 在小组页面地址栏查看']" radar="1"/>
<Route author="SettingDust" example="/bangumi/tv/group/boring" path="/bangumi/tv/group/:id" :paramsDesc="['小组 id, 在小组页面地址栏查看']" radar="1"/>
### 用户日志
<Route author="nczitzk" example="/bangumi/user/blog/sai" path="/bangumi/user/blog/:id" :paramsDesc="['用户 id, 在用户页面地址栏查看']" radar="1"/>
<Route author="nczitzk" example="/bangumi/tv/user/blog/sai" path="/bangumi/tv/user/blog/:id" :paramsDesc="['用户 id, 在用户页面地址栏查看']" radar="1"/>
## bilibili
@ -616,11 +616,11 @@ Sources
### 最新
<Route author="nczitzk" example="/bangumi" path="/bangumi"/>
<Route author="nczitzk" example="/bangumi/moe" path="/bangumi/moe"/>
### 标签
<Route author="nczitzk" example="/bangumi/简体中文/1080p" path="/bangumi/:tags?" :paramsDesc="['标签,默认为空,多个标签用 `/` 分隔']">
<Route author="nczitzk" example="/bangumi/moe/简体中文/1080p" path="/bangumi/moe/:tags?" :paramsDesc="['标签,默认为空,多个标签用 `/` 分隔']">
更多标签请前往 [搜索种子](https://bangumi.moe/search/index)

View File

@ -8,11 +8,11 @@ pageClass: routes
### Latest
<RouteEn author="nczitzk" example="/bangumi" path="/bangumi"/>
<RouteEn author="nczitzk" example="/bangumi/moe" path="/bangumi/moe"/>
### Tags
<RouteEn author="nczitzk" example="/bangumi/chs/1080p" path="/bangumi/:tags?" :paramsDesc="['Tags, empty by default, multiple tags separated by `/`']">
<RouteEn author="nczitzk" example="/bangumi/moe/chs/1080p" path="/bangumi/moe/:tags?" :paramsDesc="['Tags, empty by default, multiple tags separated by `/`']">
For more tags, please go to [Search torrent](https://bangumi.moe/search/index)

View File

@ -1672,65 +1672,6 @@ module.exports = {
},
],
},
'bgm.tv': {
_name: 'Bangumi',
'.': [
{
title: '小组话题',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/group/:id',
target: '/bangumi/group/:id',
},
{
title: '小组话题的新回复',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/group/topic/:id',
target: '/bangumi/topic/:id',
},
{
title: '现实人物的新作品',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/person/:id',
target: '/bangumi/person/:id',
},
{
title: '用户日志',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/user/:id',
target: '/bangumi/user/blog/:id',
},
{
title: '条目的讨论',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/subject/:id',
target: '/bangumi/subject/:id/topics',
},
{
title: '条目的评论',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/subject/:id',
target: '/bangumi/subject/:id/blogs',
},
{
title: '条目的章节',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/subject/:id',
target: '/bangumi/subject/:id',
},
{
title: '条目的吐槽箱',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/subject/:id',
target: '/bangumi/subject/:id/comments',
},
{
title: '放送列表',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/calendar',
target: '/bangumi/calendar/today',
},
],
},
'e-hentai.org/': {
_name: 'E-Hentai',
'.': [

View File

@ -34,13 +34,13 @@ router.get('/1draw', lazyloadRouteHandler('./routes/1draw/index'));
router.get('/benedictevans', lazyloadRouteHandler('./routes/benedictevans/recent.js'));
// bangumi
router.get('/bangumi/calendar/today', lazyloadRouteHandler('./routes/bangumi/calendar/today'));
router.get('/bangumi/subject/:id/:type', lazyloadRouteHandler('./routes/bangumi/subject'));
router.get('/bangumi/person/:id', lazyloadRouteHandler('./routes/bangumi/person'));
router.get('/bangumi/topic/:id', lazyloadRouteHandler('./routes/bangumi/group/reply'));
router.get('/bangumi/group/:id', lazyloadRouteHandler('./routes/bangumi/group/topic'));
router.get('/bangumi/subject/:id', lazyloadRouteHandler('./routes/bangumi/subject'));
router.get('/bangumi/user/blog/:id', lazyloadRouteHandler('./routes/bangumi/user/blog'));
// router.get('/bangumi/calendar/today', lazyloadRouteHandler('./routes/bangumi/calendar/today'));
// router.get('/bangumi/subject/:id/:type', lazyloadRouteHandler('./routes/bangumi/subject'));
// router.get('/bangumi/person/:id', lazyloadRouteHandler('./routes/bangumi/person'));
// router.get('/bangumi/topic/:id', lazyloadRouteHandler('./routes/bangumi/group/reply'));
// router.get('/bangumi/group/:id', lazyloadRouteHandler('./routes/bangumi/group/topic'));
// router.get('/bangumi/subject/:id', lazyloadRouteHandler('./routes/bangumi/subject'));
// router.get('/bangumi/user/blog/:id', lazyloadRouteHandler('./routes/bangumi/user/blog'));
// 報導者 migrated to v2
// router.get('/twreporter/newest', lazyloadRouteHandler('./routes/twreporter/newest'));

View File

@ -1,43 +0,0 @@
const got = require('@/utils/got');
module.exports = async (ctx) => {
const bgmCalendarUrl = 'https://api.bgm.tv/calendar';
const bgmDataUrl = 'https://cdn.jsdelivr.net/npm/bangumi-data/dist/data.json';
const url = [bgmCalendarUrl, bgmDataUrl];
const cache = await Promise.all(url.map(ctx.cache.get));
const result = await Promise.all(
cache.map(async (c, i) => {
if (c) {
return Promise.resolve(JSON.parse(c));
} else {
const response = await got({
method: 'get',
url: url[i],
});
const data = response.data;
if (i === 1) {
// 只保留有 bangumi id 的番剧
const length = data.items.length;
const items = [];
for (let index = 0; index < length; index++) {
const item = data.items[index];
const bgm_site = item.sites.find((s) => s.site === 'bangumi');
if (bgm_site) {
item.bgm_id = bgm_site.id;
items.push(item);
}
}
data.items = items;
}
ctx.cache.set(url[i], JSON.stringify(data));
return Promise.resolve(data);
}
})
);
return result;
};

View File

@ -1,26 +0,0 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const resolve_url = require('url').resolve;
const base_url = 'https://bgm.tv/';
module.exports = async (ctx) => {
const groupID = ctx.params.id;
const link = 'https://bgm.tv/group/' + groupID + '/forum';
const html = (await got.get(link)).data;
const $ = cheerio.load(html);
const title = 'Bangumi - ' + $('.SecondaryNavTitle').text();
ctx.state.data = {
title,
link,
item: $('.topic_list .topic')
.map((_, elem) => ({
link: resolve_url(base_url, $('.subject a', elem).attr('href')),
title: $('.subject a', elem).attr('title'),
pubDate: new Date($('.lastpost .time', elem).text()).toUTCString(),
description: 'Author: ' + $('.author a', elem).text() + '<br>' + 'Reply: ' + $('.posts', elem).text(),
}))
.get(),
};
};

View File

@ -1,26 +0,0 @@
const got = require('@/utils/got');
module.exports = async (subjectID) => {
const url = `https://api.bgm.tv/subject/${subjectID}?responseGroup=large`;
const epsInfo = (await got.get(url)).data;
const activeEps = [];
epsInfo.eps.forEach((e) => {
if (e.status === 'Air') {
activeEps.push(e);
}
});
return {
title: epsInfo.name_cn,
link: `https://bgm.tv/subject/${subjectID}`,
description: epsInfo.summary,
item: activeEps.reverse().map((e) => ({
title: `ep.${e.sort} ${e.name_cn}`,
description: `<img src="${epsInfo.images.common}" alt="ep.${e.sort} ${e.name_cn}"><p>${e.desc.replace(/\n+/g, '<br>')}</p>`,
pubDate: new Date(e.airdate).toUTCString(),
guid: e.id,
link: e.url,
})),
};
};

View File

@ -1,5 +1,17 @@
module.exports = {
// bangumi.moe
'/moe': ['nczitzk'],
'/moe/:tags?': ['nczitzk'],
// bangumi.online
'/online': ['devinmugen'],
'/': ['nczitzk'],
'/:tags?': ['nczitzk'],
// bangumi.tv
'/tv/calendar/today': ['magic-akari'],
'/tv/group/:id': ['SettingDust'],
'/tv/person/:id': ['ylc395'],
'/tv/subject/:id': ['SettingDust'],
'/tv/subject/:id/blogs': ['ylc395'],
'/tv/subject/:id/comments': ['ylc395'],
'/tv/subject/:id/topics': ['ylc395'],
'/tv/topic/:id': ['ylc395'],
'/tv/user/blog/:id': ['nczitzk'],
};

View File

@ -2,7 +2,7 @@ const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
module.exports = async (ctx) => {
const isLatest = ctx.path === '/';
const isLatest = ctx.path === '/moe';
const rootUrl = 'https://bangumi.moe';
let response;
@ -19,7 +19,7 @@ module.exports = async (ctx) => {
const tagUrl = `${rootUrl}/api/tag/search`;
const torrentUrl = `${rootUrl}/api/torrent/search`;
const params = ctx.path.split('/').slice(1);
const params = ctx.path.split('/').slice(2);
tag_id = await Promise.all(
params.map((param) =>

View File

@ -11,7 +11,7 @@ module.exports = async (ctx) => {
const items = list.map((item) => ({
title: `${item.title_zh} - 第 ${item.volume}`,
description: art(path.join(__dirname, 'templates/image.art'), {
description: art(path.join(__dirname, '../templates/online/image.art'), {
src: `https:${item.cover}`,
alt: `${item.title_zh} - 第 ${item.volume}`,
}),

View File

@ -1,3 +1,63 @@
const bangumiTV = {
_name: 'Bangumi 番组计划',
'.': [
{
title: '小组话题',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/group/:id',
target: '/bangumi/tv/group/:id',
},
{
title: '小组话题的新回复',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/group/topic/:id',
target: '/bangumi/tv/topic/:id',
},
{
title: '现实人物的新作品',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/person/:id',
target: '/bangumi/tv/person/:id',
},
{
title: '用户日志',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/user/:id',
target: '/bangumi/tv/user/blog/:id',
},
{
title: '条目的讨论',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/subject/:id',
target: '/bangumi/tv/subject/:id/topics',
},
{
title: '条目的评论',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/subject/:id',
target: '/bangumi/tv/subject/:id/blogs',
},
{
title: '条目的章节',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/subject/:id',
target: '/bangumi/tv/subject/:id',
},
{
title: '条目的吐槽箱',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/subject/:id',
target: '/bangumi/tv/subject/:id/comments',
},
{
title: '放送列表',
docs: 'https://docs.rsshub.app/anime.html#bangumi',
source: '/calendar',
target: '/bangumi/tv/calendar/today',
},
],
};
module.exports = {
'bangumi.moe': {
_name: '萌番組',
@ -6,13 +66,13 @@ module.exports = {
title: '最新',
docs: 'https://docs.rsshub.app/anime.html#meng-fan-zu-zui-xin',
source: ['/'],
target: '/bangumi',
target: '/bangumi/moe',
},
{
title: '标签',
docs: 'https://docs.rsshub.app/anime.html#meng-fan-zu-biao-qian',
source: ['/search/index'],
target: '/bangumi/:tags?',
target: '/bangumi/moe/:tags?',
},
],
},
@ -27,4 +87,6 @@ module.exports = {
},
],
},
'bangumi.tv': bangumiTV,
'bgm.tv': bangumiTV,
};

View File

@ -1,4 +1,14 @@
module.exports = function (router) {
router.get('/online', require('./online'));
router.get(/([\w-/]+)?/, require('./index'));
// bangumi.moe
router.get(/moe([\w-/]+)?/, require('./moe/index'));
// bangumi.online
router.get('/online', require('./online/online'));
// bangumi.tv
router.get('/tv/calendar/today', require('./tv/calendar/today'));
router.get('/tv/group/:id', require('./tv/group/topic'));
router.get('/tv/person/:id', require('./tv/person'));
router.get('/tv/subject/:id', require('./tv/subject'));
router.get('/tv/subject/:id/:type', require('./tv/subject'));
router.get('/tv/topic/:id', require('./tv/group/reply'));
router.get('/tv/user/blog/:id', require('./tv/user/blog'));
};

View File

@ -0,0 +1,2 @@
<img src="{{ epsInfo.images.large || epsInfo.images.common }}" alt="ep.{{ e.sort }} {{ e.name_cn || e.name }}">
<p>{{@ e.desc.replace(/\r\n/g, '<br>') }}</p>

View File

@ -0,0 +1,12 @@
<img src={{ bgm.image }}>
<ul>
{{ each bgm.sites site }}
{{ set url }}
{{ if site.url }}
{{ url = site.url }}
{{ else }}
<% url = siteMeta[site.site].urlTemplate.replace('{{id}}', site.id) %>
{{ /if }}
<li><a href="{{ url }}">{{ siteMeta[site.site].title }}</a></li>
{{ /each }}
</ul>

View File

@ -0,0 +1,37 @@
const got = require('@/utils/got');
const config = require('@/config').value;
module.exports = (tryGet) => {
const bgmCalendarUrl = 'https://api.bgm.tv/calendar';
const bgmDataUrl = 'https://cdn.jsdelivr.net/npm/bangumi-data/dist/data.json';
const urls = [bgmCalendarUrl, bgmDataUrl];
return Promise.all(
urls.map((item, i) =>
tryGet(
item,
async () => {
const { data } = await got(item);
if (i === 1) {
// 只保留有 bangumi id 的番剧
const items = [];
for (const item of data.items) {
const bgmSite = item.sites.find((s) => s.site === 'bangumi');
if (bgmSite) {
item.bgmId = bgmSite.id;
items.push(item);
}
}
data.items = items;
}
return data;
},
config.cache.contentExpire,
false
)
)
);
};

View File

@ -1,27 +1,25 @@
const getData = require('./_base');
const { art } = require('@/utils/render');
const path = require('path');
module.exports = async (ctx) => {
const [list, data] = await getData(ctx);
const [list, data] = await getData(ctx.cache.tryGet);
const siteMeta = data.siteMeta;
const today = new Date(Date.now());
// 将 UTC 时间向前移动9小时即可在数值上表示东京时间
today.setUTCHours(today.getUTCHours() + 9);
const day = today.getUTCDay();
const todayList = list.filter((l) => l.weekday.id % 7 === day)[0];
const todayBgmId = todayList.items.map((t) => t.id);
const todayList = list.find((l) => l.weekday.id % 7 === day);
const todayBgmId = todayList.items.map((t) => t.id.toString());
const images = todayList.items.reduce((p, c) => {
p[c.id] = (c.images || {}).large;
return p;
}, {});
const todayBgm = data.items.filter((d) => todayBgmId.includes(d.bgm_id));
const todayBgm = data.items.filter((d) => todayBgmId.includes(d.bgmId));
todayBgm.forEach((bgm) => {
bgm.image = images[bgm.bgm_id];
bgm.image = images[bgm.bgmId];
});
ctx.state.data = {
@ -35,25 +33,13 @@ module.exports = async (ctx) => {
updated.setMinutes(begin.getMinutes());
updated.setSeconds(begin.getSeconds());
const link = `http://bangumi.tv/subject/${bgm.bgm_id}`;
const link = `https://bangumi.tv/subject/${bgm.bgmId}`;
const id = `${link}#${new Intl.DateTimeFormat('zh-CN').format(updated)}`;
const image = `<img src=${bgm.image} />`;
const html =
image +
'<ul>' +
bgm.sites
.map((s) => {
let url;
if (s.url) {
url = s.url;
} else {
url = siteMeta[s.site].urlTemplate.replace('{{id}}', s.id);
}
return `<li><a href="${url}">${siteMeta[s.site].title}</a></li>`;
})
.join('') +
'</ul>';
const html = art(path.resolve(__dirname, '../../templates/tv/today.art'), {
bgm,
siteMeta,
});
return {
id,
@ -63,7 +49,9 @@ module.exports = async (ctx) => {
Object.values(bgm.titleTranslate)
.map((t) => t.join(''))
.join(''),
].join(''),
]
.filter((t) => t) // don't join if empty
.join(''),
updated: updated.toISOString(),
pubDate: updated.toUTCString(),
link,

View File

@ -1,16 +1,18 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const timezone = require('@/utils/timezone');
module.exports = async (ctx) => {
// bangumi.tv未提供获取小组话题的API因此仍需要通过抓取网页来获取
const topicID = ctx.params.id;
const link = `https://bgm.tv/group/topic/${topicID}`;
const html = (await got.get(link)).data;
const { data: html } = await got(link);
const $ = cheerio.load(html);
const title = $('#pageHeader h1').text();
const latestReplies = $('.row_reply')
.slice(-10)
.map((i, el) => {
.toArray()
.map((el) => {
const $el = $(el);
return {
id: $el.attr('id'),
@ -18,19 +20,28 @@ module.exports = async (ctx) => {
content: $el.find('.reply_content .message').html(),
date: $el.children().first().find('small').children().remove().end().text().slice(3),
};
})
.get()
.reverse();
});
const postTopic = {
title,
description: $('.postTopic .topic_content').html(),
author: $('.postTopic .inner strong a').first().text(),
pubDate: timezone(parseDate($('.postTopic .re_info small').text().trim().slice(5)), +8),
link,
};
ctx.state.data = {
title: `${title}的最新回复`,
link,
item: latestReplies.map((c) => ({
title: `${c.author}回复了小组话题《${title}`,
description: c.content,
guid: c.id,
pubDate: new Date(c.date).toUTCString(),
link,
})),
item: [
...latestReplies.map((c) => ({
title: `${c.author} 回复了小组话题《${title}`,
description: c.content,
pubDate: timezone(parseDate(c.date), +8),
author: c.author,
link: `${link}#${c.id}`,
})),
postTopic,
],
};
};

View File

@ -0,0 +1,26 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const base_url = 'https://bgm.tv';
module.exports = async (ctx) => {
const groupID = ctx.params.id;
const link = `${base_url}/group/${groupID}/forum`;
const { data: html } = await got(link);
const $ = cheerio.load(html);
const title = 'Bangumi - ' + $('.SecondaryNavTitle').text();
ctx.state.data = {
title,
link,
item: $('.topic_list .topic')
.toArray()
.map((elem) => ({
link: new URL($('.subject a', elem).attr('href'), base_url).href,
title: $('.subject a', elem).attr('title'),
pubDate: parseDate($('.lastpost .time', elem).text()),
description: 'Reply: ' + $('.posts', elem).text(),
author: $('.author a', elem).text(),
})),
};
};

View File

@ -1,15 +1,17 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
module.exports = async (ctx) => {
// bangumi.tv未提供获取“人物信息”的API因此仍需要通过抓取网页来获取
const personID = ctx.params.id;
const link = `https://bgm.tv/person/${personID}/works?sort=date`;
const html = (await got.get(link)).data;
const { data: html } = await got(link);
const $ = cheerio.load(html);
const personName = $('.nameSingle a').text();
const works = $('.item')
.map((i, el) => {
.toArray()
.map((el) => {
const $el = $(el);
const $workEl = $el.find('.l');
return {
@ -18,17 +20,19 @@ module.exports = async (ctx) => {
workInfo: $el.find('p.info').text(),
job: $el.find('.badge_job').text(),
};
})
.get();
});
ctx.state.data = {
title: `${personName}参与的作品`,
link,
item: works.map((c) => ({
title: `${personName}${c.job}的身份参与了作品《${c.work}`,
description: c.workInfo,
link: c.workURL,
pubDate: c.workInfo.substring(0, c.workInfo.indexOf(' /')) === '' ? new Date(0).toUTCString() : new Date(c.workInfo.substring(0, c.workInfo.indexOf(' /'))).toUTCString(),
})),
item: works.map((c) => {
const match = c.workInfo.match(/(\d{4}(?:-|年)\d{1,2}(?:-|月)\d{1,2})/);
return {
title: `${personName}${c.job}的身份参与了作品《${c.work}`,
description: c.workInfo,
link: c.workURL,
pubDate: match ? parseDate(match[1], ['YYYY-MM-DD', 'YYYY-M-D']) : null,
};
}),
};
};

View File

@ -1,34 +1,32 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate, parseRelativeDate } = require('@/utils/parse-date');
module.exports = async (subjectID, minLength) => {
// bangumi.tv未提供获取“吐槽comments”的API因此仍需要通过抓取网页来获取
const link = `https://bgm.tv/subject/${subjectID}/comments`;
const html = (await got.get(link)).data;
const { data: html } = await got(link);
const $ = cheerio.load(html);
const title = $('.nameSingle').find('a').text();
const comments = $('.item')
.map((i, el) => {
.toArray()
.map((el) => {
const $el = $(el);
const $rateEl = $el.find('.starsinfo');
const $rateEl = $el.find('.starlight');
let rate = null;
if ($rateEl.length > 0) {
rate = $rateEl.attr('class').match(/sstars(\d)/)[1];
rate = $rateEl.attr('class').match(/stars(\d)/)[1];
}
const dateString = $el.find('small.grey').text().slice(2);
let date;
if (dateString.includes('ago')) {
// 处理表示相对日期的字符串
const list = dateString
.match(/(\dd )?(\d{1,2}h )?(\d{1,2}m )?ago/)
.slice(1)
.map((s) => (s ? Number(s.slice(0, -2)) : 0));
date = new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * list[0] - 1000 * 60 * 60 * list[1] - 1000 * 60 * list[2]).toUTCString();
date = parseRelativeDate(dateString);
} else {
// 表示绝对日期的字符串
date = new Date(dateString).toUTCString();
date = parseDate(dateString);
}
return {
@ -38,15 +36,15 @@ module.exports = async (subjectID, minLength) => {
date,
};
})
.get()
.filter((obj) => obj.content.length >= minLength);
return {
title: `${title}Bangumi吐槽箱`,
title: `${title} Bangumi 吐槽箱`,
link,
item: comments.map((c) => ({
title: `${c.user}的吐槽`,
description: `【评分:${c.rate}${c.content}`,
guid: c.user,
guid: `${link}#${c.user}`,
pubDate: c.date,
link,
})),

View File

@ -0,0 +1,31 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const { art } = require('@/utils/render');
const path = require('path');
module.exports = async (subjectID) => {
const url = `https://api.bgm.tv/subject/${subjectID}?responseGroup=large`;
const { data: epsInfo } = await got(url);
const activeEps = [];
epsInfo.eps.forEach((e) => {
if (e.status === 'Air') {
activeEps.push(e);
}
});
return {
title: epsInfo.name_cn || epsInfo.name,
link: `https://bgm.tv/subject/${subjectID}`,
description: epsInfo.summary,
item: activeEps.map((e) => ({
title: `ep.${e.sort} ${e.name_cn || e.name}`,
description: art(path.resolve(__dirname, '../../templates/tv/ep.art'), {
e,
epsInfo,
}),
pubDate: parseDate(e.airdate),
link: e.url.replace('http:', 'https:'),
})),
};
};

View File

@ -1,6 +1,5 @@
const getComments = require('./comments.js');
const getFromAPI = require('./offcial-subject-api.js');
const getEps = require('./ep.js');
module.exports = async (ctx) => {
@ -9,7 +8,7 @@ module.exports = async (ctx) => {
if (ctx.params.type) {
switch (ctx.params.type) {
case 'comments':
response = await getComments(id, Number(ctx.request.query.minLength) || 0);
response = await getComments(id, Number(ctx.query.minLength) || 0);
break;
case 'blogs':
response = await getFromAPI('blog')(id);

View File

@ -1,4 +1,5 @@
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
module.exports = (type) => {
const mapping = {
@ -13,17 +14,18 @@ module.exports = (type) => {
};
return async (subjectID) => {
// 官方提供的条目API文档见https://github.com/bangumi/api/blob/master/docs-raw/Subject-API.md
// 官方提供的条目API文档见 https://github.com/bangumi/api/blob/3f3fa6390c468816f9883d24be488e41f8946159/docs-raw/Subject-API.md
const url = `https://api.bgm.tv/subject/${subjectID}?responseGroup=large`;
const subjectInfo = (await got.get(url)).data;
const { data: subjectInfo } = await got(url);
return {
title: `${subjectInfo.name_cn || subjectInfo.name}Bangumi${mapping[type].cn}`,
title: `${subjectInfo.name_cn || subjectInfo.name} Bangumi ${mapping[type].cn}`,
link: `https://bgm.tv/subject/${subjectInfo.id}/${mapping[type].en}`,
item: subjectInfo[type].map((article) => ({
title: `${article.user.nickname}${article.title}`,
description: article.summary || '',
link: article.url,
pubDate: new Date(article.timestamp * 1000).toUTCString(),
link: article.url.replace('http:', 'https:'),
pubDate: parseDate(article.timestamp, 'X'),
author: article.user.nickname,
})),
};
};

View File

@ -1,6 +1,7 @@
const url = require('url');
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const timezone = require('@/utils/timezone');
module.exports = async (ctx) => {
const currentUrl = `https://bgm.tv/user/${ctx.params.id}/blog`;
@ -11,16 +12,16 @@ module.exports = async (ctx) => {
const $ = cheerio.load(response.data);
const list = $('#entry_list div.item')
.find('h2.title')
.map((_, item) => {
.toArray()
.map((item) => {
item = $(item);
const a = item.find('a');
return {
title: a.text(),
link: url.resolve(`https://bgm.tv/`, a.attr('href')),
pubDate: new Date(item.parent().find('small.time').text() + ' GMT+8').toUTCString(),
link: new URL(a.attr('href'), 'https://bgm.tv').href,
pubDate: timezone(parseDate(item.parent().find('small.time').text()), 0),
};
})
.get();
});
const items = await Promise.all(
list.map((item) =>