feat: more readable douban user broadcast (#6053)
This commit is contained in:
parent
3442ca9196
commit
ab843e4e0a
Binary file not shown.
|
After Width: | Height: | Size: 416 KiB |
|
|
@ -694,7 +694,7 @@ Tiny Tiny RSS 会给所有 iframe 元素添加 `sandbox="allow-scripts"` 属性
|
|||
|
||||
### 用户广播
|
||||
|
||||
<Route author="alfredcai" example="/douban/people/62759792/status" path="douban/people/:userid/status" :paramsDesc="['整数型用户 id']" radar="1">
|
||||
<Route author="alfredcai" example="/douban/people/62759792/status" path="douban/people/:userid/status/:routeParams" :paramsDesc="['整数型用户 id', '额外参数;见下']" radar="1">
|
||||
|
||||
::: tip 提示
|
||||
|
||||
|
|
@ -704,6 +704,32 @@ Tiny Tiny RSS 会给所有 iframe 元素添加 `sandbox="allow-scripts"` 属性
|
|||
|
||||
:::
|
||||
|
||||
对于豆瓣用户广播内容,在 `routeParams` 参数中以 query string 格式设置如下选项可以控制输出的样式
|
||||
|
||||
| 键 | 含义 | 接受的值 | 默认值 |
|
||||
| -------------------------- | -------------------------------------------------------------- | -------------- | ------ |
|
||||
| readable | 是否开启细节排版可读性优化 | 0/1/true/false | false |
|
||||
| authorNameBold | 是否加粗作者名字 | 0/1/true/false | false |
|
||||
| showAuthorInTitle | 是否在标题处显示作者 | 0/1/true/false | true |
|
||||
| showAuthorInDesc | 是否在正文处显示作者 | 0/1/true/false | false |
|
||||
| showAuthorAvatarInDesc | 是否在正文处显示作者头像(若阅读器会提取正文图片,不建议开启) | 0/1/true/false | false |
|
||||
| showEmojiForRetweet | 显示 “🔁” 取代 “Fw”(转发) | 0/1/true/false | false |
|
||||
| showRetweetTextInTitle | 在标题出显示转发评论(置为 false 则在标题只显示被转发的广播) | 0/1/true/false | false |
|
||||
| addLinkForPics | 为图片添加可点击的链接 | 0/1/true/false | false |
|
||||
| showTimestampInDescription | 在正文处显示广播的时间戳 | 0/1/true/false | false |
|
||||
| showComments | 在正文处显示评论 | 0/1/true/false | false |
|
||||
| widthOfPics | 广播配图宽(生效取决于阅读器) | 不指定 / 数字 | 不指定 |
|
||||
| heightOfPics | 广播配图高(生效取决于阅读器) | 不指定 / 数字 | 不指定 |
|
||||
| sizeOfAuthorAvatar | 作者头像大小 | 数字 | 48 |
|
||||
|
||||
指定更多与默认值不同的参数选项可以改善 RSS 的可读性,如
|
||||
|
||||
https://rsshub.app/douban/people/113894409/status/readable=1&authorNameBold=1&showAuthorInTitle=1&showAuthorInDesc=1&showAuthorAvatarInDesc=1&showEmojiForRetweet=1&showRetweetTextInTitle=1&addLinkForPics=1&showTimestampInDescription=1&showComments=1&widthOfPics=100
|
||||
|
||||
的效果为
|
||||
|
||||
<img src="/readable-douban.png" alt="豆瓣读书的可读豆瓣广播 RSS">
|
||||
|
||||
</Route>
|
||||
|
||||
### 日记最新回应
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ router.get('/douban/bookstore', require('./routes/douban/bookstore'));
|
|||
router.get('/douban/book/rank/:type?', require('./routes/douban/book/rank'));
|
||||
router.get('/douban/doulist/:id', require('./routes/douban/doulist'));
|
||||
router.get('/douban/explore/column/:id', require('./routes/douban/explore_column'));
|
||||
router.get('/douban/people/:userid/status', require('./routes/douban/people/status.js'));
|
||||
router.get('/douban/people/:userid/status/:routeParams?', require('./routes/douban/people/status.js'));
|
||||
router.get('/douban/replies/:uid', require('./routes/douban/replies'));
|
||||
router.get('/douban/topic/:id/:sort?', require('./routes/douban/topic.js'));
|
||||
router.get('/douban/channel/:id/:nav?', require('./routes/douban/channel/topic.js'));
|
||||
|
|
|
|||
|
|
@ -1,56 +1,310 @@
|
|||
const querystring = require('querystring');
|
||||
const got = require('@/utils/got');
|
||||
const { fallback, queryToBoolean, queryToInteger } = require('@/utils/readable-social');
|
||||
|
||||
function getContentByActivity(status) {
|
||||
function getContentByActivity(ctx, item, params = {}, picsPrefixes = []) {
|
||||
const routeParams = querystring.parse(ctx.params.routeParams);
|
||||
|
||||
const mergedParams = {
|
||||
readable: fallback(params.readable, queryToBoolean(routeParams.readable), false),
|
||||
authorNameBold: fallback(params.authorNameBold, queryToBoolean(routeParams.authorNameBold), false),
|
||||
showAuthorInTitle: fallback(params.showAuthorInTitle, queryToBoolean(routeParams.showAuthorInTitle), true),
|
||||
showAuthorInDesc: fallback(params.showAuthorInDesc, queryToBoolean(routeParams.showAuthorInDesc), false),
|
||||
showAuthorAvatarInDesc: fallback(params.showAuthorAvatarInDesc, queryToBoolean(routeParams.showAuthorAvatarInDesc), false),
|
||||
showEmojiForRetweet: fallback(params.showEmojiForRetweet, queryToBoolean(routeParams.showEmojiForRetweet), false),
|
||||
showRetweetTextInTitle: fallback(params.showRetweetTextInTitle, queryToBoolean(routeParams.showRetweetTextInTitle), false),
|
||||
addLinkForPics: fallback(params.addLinkForPics, queryToBoolean(routeParams.addLinkForPics), false),
|
||||
showTimestampInDescription: fallback(params.showTimestampInDescription, queryToBoolean(routeParams.showTimestampInDescription), false),
|
||||
showComments: fallback(params.showComments, queryToBoolean(routeParams.showComments), false),
|
||||
|
||||
showColonInDesc: fallback(params.showColonInDesc, null, false),
|
||||
|
||||
widthOfPics: fallback(params.widthOfPics, queryToInteger(routeParams.widthOfPics), -1),
|
||||
heightOfPics: fallback(params.heightOfPics, queryToInteger(routeParams.heightOfPics), -1),
|
||||
sizeOfAuthorAvatar: fallback(params.sizeOfAuthorAvatar, queryToInteger(routeParams.sizeOfAuthorAvatar), 48),
|
||||
};
|
||||
|
||||
params = mergedParams;
|
||||
|
||||
const {
|
||||
readable,
|
||||
authorNameBold,
|
||||
showAuthorInTitle,
|
||||
showAuthorInDesc,
|
||||
showAuthorAvatarInDesc,
|
||||
showEmojiForRetweet,
|
||||
showRetweetTextInTitle,
|
||||
addLinkForPics,
|
||||
showTimestampInDescription,
|
||||
showComments,
|
||||
|
||||
showColonInDesc,
|
||||
|
||||
widthOfPics,
|
||||
heightOfPics,
|
||||
sizeOfAuthorAvatar,
|
||||
} = params;
|
||||
|
||||
const { status, comments } = item;
|
||||
let description = '';
|
||||
let title = '';
|
||||
switch (status.activity) {
|
||||
case '说':
|
||||
title = `${status.author.name} ${status.activity}:${status.text}`;
|
||||
description = status.text;
|
||||
status.images.forEach((image) => {
|
||||
description += `<p><img src="${image.large.url}"/></p>`;
|
||||
});
|
||||
break;
|
||||
case '转发':
|
||||
description = `${status.text}<br>`;
|
||||
if (status.reshared_status.deleted) {
|
||||
title = `${status.author.name} ${status.activity} 广播:原动态已被发布者删除`;
|
||||
description += `原动态已被发布者删除`;
|
||||
} else {
|
||||
description += getContentByActivity(status.reshared_status).title;
|
||||
title = `${status.author.name} ${status.activity} ${status.reshared_status.author.name} 的广播:${status.reshared_status.text}`;
|
||||
status.reshared_status.images.forEach((image) => {
|
||||
description += `<p><img src="${image.large.url}"/></p>`;
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
description = `${status.text ? `${status.text}<br><br>` : ''}`;
|
||||
title = `${status.author.name}${status.activity}: ${description}`;
|
||||
if (status.card) {
|
||||
let image;
|
||||
if (status.card.image && (status.card.image.large || status.card.image.normal)) {
|
||||
image = (status.card.image.large || status.card.image.normal).url;
|
||||
}
|
||||
if (status.card.rating) {
|
||||
description += `豆瓣评分:${status.card.rating}<br>${status.card.subtitle}${image ? `<br><img src="${image}">` : ''}<br><a href="${status.card.url}">《${status.card.title}》</a>`;
|
||||
title = `${status.author.name}${status.activity}:《${status.card.title}》`;
|
||||
} else {
|
||||
description += `${status.card.subtitle}${image ? `<br><img src="${image}">` : ''}<br>${status.card.url ? `<a href="${status.card.url}">${status.card.title}</a>` : ''}`;
|
||||
title = `${status.author.name} ${status.activity}:「${status.card.title}」${status.text}`;
|
||||
}
|
||||
let activityInDesc;
|
||||
let activityInTitle;
|
||||
|
||||
if (status.activity !== '转发') {
|
||||
activityInDesc = status.activity;
|
||||
activityInTitle = status.activity;
|
||||
} else {
|
||||
if (status.reshared_status.deleted) {
|
||||
activityInDesc = `转发广播`;
|
||||
activityInTitle = `转发广播`;
|
||||
} else {
|
||||
activityInDesc = '转发 ';
|
||||
if (readable) {
|
||||
activityInDesc += `<a href="${status.reshared_status.author.url}" target="_blank" rel="noopener noreferrer">`;
|
||||
}
|
||||
if (status.video_card) {
|
||||
const videoCover = status.video_card.video_info && status.video_card.video_info.cover_url;
|
||||
const videoSrc = status.video_card.video_info && status.video_card.video_info.video_url;
|
||||
description += `${videoSrc ? `<br><video src="${videoSrc}" ${videoCover ? `poster="${videoCover}"` : ''}></video>` : ''}<br><br>${
|
||||
status.video_card.title ? `<a href="${status.video_card.url}">${status.video_card.title}</a>` : ''
|
||||
}`;
|
||||
title = `${status.author.name}${status.activity}:《${status.video_card.title}》`;
|
||||
if (authorNameBold) {
|
||||
activityInDesc += `<strong>`;
|
||||
}
|
||||
break;
|
||||
activityInDesc += status.reshared_status.author.name;
|
||||
if (authorNameBold) {
|
||||
activityInDesc += `</strong>`;
|
||||
}
|
||||
if (readable) {
|
||||
activityInDesc += `</a>`;
|
||||
}
|
||||
activityInDesc += ` 的广播`;
|
||||
activityInTitle = `转发 ${status.reshared_status.author.name} 的广播`;
|
||||
}
|
||||
}
|
||||
|
||||
if (showAuthorInDesc) {
|
||||
let usernameAndAvatar = '';
|
||||
if (readable) {
|
||||
usernameAndAvatar += `<a href="${status.author.url}" target="_blank" rel="noopener noreferrer">`;
|
||||
}
|
||||
if (showAuthorAvatarInDesc) {
|
||||
usernameAndAvatar += `<img width="${sizeOfAuthorAvatar}" height="${sizeOfAuthorAvatar}" src="${status.author.avatar}" ${readable ? 'hspace="8" vspace="8" align="left"' : ''} />`;
|
||||
}
|
||||
if (authorNameBold) {
|
||||
usernameAndAvatar += `<strong>`;
|
||||
}
|
||||
usernameAndAvatar += status.author.name;
|
||||
if (authorNameBold) {
|
||||
usernameAndAvatar += `</strong>`;
|
||||
}
|
||||
if (readable) {
|
||||
usernameAndAvatar += `</a>`;
|
||||
}
|
||||
usernameAndAvatar += ` `;
|
||||
description += usernameAndAvatar + activityInDesc + (showColonInDesc ? ': ' : '');
|
||||
}
|
||||
|
||||
if (showAuthorInTitle) {
|
||||
title += `${status.author.name} `;
|
||||
}
|
||||
title += `${activityInTitle}: `;
|
||||
|
||||
if (showTimestampInDescription) {
|
||||
description += `<br><small>${status.create_time}</small><br>`;
|
||||
}
|
||||
|
||||
let text = status.text;
|
||||
let lastIndex = 0;
|
||||
const replacedTextSegements = [];
|
||||
for (const entity of status.entities) {
|
||||
replacedTextSegements.push(text.slice(lastIndex, entity.start));
|
||||
replacedTextSegements.push(`<a href="${entity.uri.replace('douban://douban.com', 'https://www.douban.com/doubanapp/dispatch?uri=')}" target="_blank" rel="noopener noreferrer">${entity.title}</a>`);
|
||||
lastIndex = entity.end;
|
||||
}
|
||||
replacedTextSegements.push(text.slice(lastIndex));
|
||||
text = replacedTextSegements.join('');
|
||||
|
||||
// text // images // video_info // parent status
|
||||
|
||||
description += text;
|
||||
|
||||
if (status.card) {
|
||||
if (status.card.rating) {
|
||||
title += `《${status.card.title}》`;
|
||||
} else {
|
||||
title += `「${status.card.title}」`;
|
||||
}
|
||||
}
|
||||
|
||||
if (status.activity !== '转发' || showRetweetTextInTitle) {
|
||||
title += status.text.replace('\n', '');
|
||||
}
|
||||
|
||||
if (status.images.length) {
|
||||
if (readable) {
|
||||
description += `<br clear="both" /><div style="clear: both"></div>`;
|
||||
} else {
|
||||
description += `<br>`;
|
||||
}
|
||||
|
||||
// 一些RSS Reader会识别所有<img>标签作为内含图片显示,我们不想要头像也作为内含图片之一
|
||||
// 让所有配图在description的最前面再次出现一次,但宽高设为0
|
||||
let picsPrefix = '';
|
||||
status.images.forEach(function (image) {
|
||||
picsPrefix += `<img width="0" height="0" hidden="true" src="${image.large.url}">`;
|
||||
});
|
||||
picsPrefixes.push(picsPrefix);
|
||||
|
||||
status.images.forEach((image) => {
|
||||
if (addLinkForPics) {
|
||||
description += '<a href="' + image.large.url + '" target="_blank" rel="noopener noreferrer">';
|
||||
}
|
||||
if (!readable) {
|
||||
description += '<br>';
|
||||
}
|
||||
let style = '';
|
||||
description += '<img ';
|
||||
if (widthOfPics >= 0) {
|
||||
description += ` width="${widthOfPics}"`;
|
||||
style += `width: ${widthOfPics}px;`;
|
||||
}
|
||||
if (heightOfPics >= 0) {
|
||||
description += `height="${heightOfPics}" `;
|
||||
style += `height: ${heightOfPics}px;`;
|
||||
}
|
||||
description += ` style="${style}" ` + (readable ? 'vspace="8" hspace="4" ' : '') + ' src="' + image.large.url + '">';
|
||||
if (addLinkForPics) {
|
||||
description += '</a>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (status.video_info) {
|
||||
if (readable) {
|
||||
description += `<br clear="both" /><div style="clear: both"></div>`;
|
||||
} else {
|
||||
description += `<br>`;
|
||||
}
|
||||
const videoCover = status.video_info.cover_url;
|
||||
const videoSrc = status.video_info.video_url;
|
||||
description += `${videoSrc ? `<video src="${videoSrc}" ${videoCover ? `poster="${videoCover}"` : ''}></video>` : ''}`;
|
||||
}
|
||||
|
||||
if (status.parent_status) {
|
||||
description += showEmojiForRetweet ? ' 🔁 ' : ' Fw: ';
|
||||
if (showRetweetTextInTitle) {
|
||||
title += showEmojiForRetweet ? ' 🔁 ' : ' Fw: ';
|
||||
}
|
||||
|
||||
let usernameAndAvatar = '';
|
||||
|
||||
if (readable) {
|
||||
usernameAndAvatar += `<a href="${status.parent_status.author.url}">`;
|
||||
}
|
||||
if (authorNameBold) {
|
||||
usernameAndAvatar += `<strong>`;
|
||||
}
|
||||
usernameAndAvatar += status.parent_status.author.name;
|
||||
if (authorNameBold) {
|
||||
usernameAndAvatar += `</strong>`;
|
||||
}
|
||||
if (readable) {
|
||||
usernameAndAvatar += `</a>`;
|
||||
}
|
||||
usernameAndAvatar += `: `;
|
||||
description += usernameAndAvatar + status.parent_status.text;
|
||||
if (showRetweetTextInTitle) {
|
||||
title += status.parent_status.author.name + ': ' + status.parent_status.text;
|
||||
}
|
||||
}
|
||||
|
||||
// card
|
||||
if (status.card) {
|
||||
let image;
|
||||
if (status.card.image && (status.card.image.large || status.card.image.normal)) {
|
||||
image = status.card.image.large || status.card.image.normal;
|
||||
}
|
||||
|
||||
if (readable) {
|
||||
description += `<br clear="both" /><div style="clear: both"></div><blockquote style="background: #80808010;border-top:1px solid #80808030;border-bottom:1px solid #80808030;margin:0;padding:5px 20px;">`;
|
||||
} else {
|
||||
description += `<br>`;
|
||||
}
|
||||
if (image) {
|
||||
description += `<img src="${image.url}" ${readable ? 'vspace="0" hspace="12" align="left" height="75" style="height: 75px;"' : ''} />`;
|
||||
}
|
||||
description += `<a href="${status.card.url}" target="_blank" rel="noopener noreferrer"><strong>${status.card.title}</strong><br><small>${status.card.subtitle}</small>`;
|
||||
if (status.card.rating) {
|
||||
description += `<br><small>评分:${status.card.rating}</small>`;
|
||||
}
|
||||
description += `</a>`;
|
||||
if (readable) {
|
||||
description += `<br clear="both" /><div style="clear: both"></div></blockquote>`;
|
||||
}
|
||||
}
|
||||
|
||||
// video_card
|
||||
if (status.video_card) {
|
||||
if (readable) {
|
||||
description += `<br clear="both" /><div style="clear: both"></div><blockquote style="background: #80808010;border-top:1px solid #80808030;border-bottom:1px solid #80808030;margin:0;padding:5px 20px;">`;
|
||||
} else {
|
||||
description += `<br>`;
|
||||
}
|
||||
const videoCover = status.video_card.video_info && status.video_card.video_info.cover_url;
|
||||
const videoSrc = status.video_card.video_info && status.video_card.video_info.video_url;
|
||||
description += `${videoSrc ? `<video src="${videoSrc}" ${videoCover ? `poster="${videoCover}"` : ''}></video>` : ''}<br>${status.video_card.title ? `<a href="${status.video_card.url}">${status.video_card.title}</a>` : ''}`;
|
||||
if (readable) {
|
||||
description += `</blockquote>`;
|
||||
}
|
||||
}
|
||||
|
||||
// reshared_status
|
||||
if (status.reshared_status) {
|
||||
if (readable) {
|
||||
description += `<br clear="both" /><div style="clear: both"></div><blockquote style="background: #80808010;border-top:1px solid #80808030;border-bottom:1px solid #80808030;margin:0;padding:5px 20px;">`;
|
||||
} else {
|
||||
description += `<br>`;
|
||||
}
|
||||
|
||||
if (showRetweetTextInTitle) {
|
||||
title += ' | ';
|
||||
}
|
||||
|
||||
if (status.reshared_status.deleted) {
|
||||
description += `原动态不可访问`;
|
||||
title += `原动态不可访问`;
|
||||
} else {
|
||||
description += getContentByActivity(
|
||||
ctx,
|
||||
{ status: status.reshared_status, comments: [] },
|
||||
{
|
||||
showAuthorInDesc: true,
|
||||
showAuthorAvatarInDesc: false,
|
||||
showComments: false,
|
||||
showColonInDesc: true,
|
||||
},
|
||||
picsPrefixes
|
||||
).description;
|
||||
title += status.reshared_status.text;
|
||||
const reshared_url = status.reshared_status.uri.replace('douban://douban.com', 'https://www.douban.com/doubanapp/dispatch?uri=');
|
||||
|
||||
if (readable) {
|
||||
description += `<br><small>原动态:<a href="${reshared_url}" target="_blank" rel="noopener noreferrer">${reshared_url}</a></small><br clear="both" /><div style="clear: both"></div></blockquote>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// comments
|
||||
if (showComments) {
|
||||
if (comments.length > 0) {
|
||||
description += '<hr>';
|
||||
}
|
||||
for (const comment of comments) {
|
||||
description += `<br>${comment.text} - <a href="${comment.author.url}" target="_blank" rel="noopener noreferrer">${comment.author.name}</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (showAuthorInDesc && showAuthorAvatarInDesc) {
|
||||
description = picsPrefixes.join('') + description;
|
||||
}
|
||||
description = description.trim().replace(/\n/g, '<br>');
|
||||
return { title, description };
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +332,7 @@ module.exports = async (ctx) => {
|
|||
items
|
||||
.filter((item) => !item.deleted)
|
||||
.map((item) => {
|
||||
const r = getContentByActivity(item.status);
|
||||
const r = getContentByActivity(ctx, item);
|
||||
return {
|
||||
title: r.title,
|
||||
link: item.status.sharing_url,
|
||||
|
|
|
|||
Loading…
Reference in New Issue