#6354 RSS feed for channels

(cherry picked from commit 041f420416)
This commit is contained in:
infinite-persistence 2021-07-02 13:04:44 +08:00
parent 1446ae2a29
commit 0fbf202f2b
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
7 changed files with 142 additions and 2 deletions

View file

@ -53,6 +53,7 @@
"electron-notarize": "^1.0.0",
"electron-updater": "^4.2.4",
"express": "^4.17.1",
"feed": "^4.2.2",
"if-env": "^1.0.4",
"react-datetime-picker": "^3.2.1",
"react-top-loading-bar": "^2.0.1",

View file

@ -7,7 +7,7 @@ import React from 'react';
import classnames from 'classnames';
import { Menu, MenuButton, MenuList, MenuItem } from '@reach/menu-button';
import Icon from 'component/common/icon';
import { generateShareUrl } from 'util/url';
import { generateShareUrl, generateRssUrl } from 'util/url';
import { useHistory } from 'react-router';
import { buildURI, parseURI, COLLECTIONS_CONSTS } from 'lbry-redux';
@ -103,6 +103,7 @@ function ClaimMenuList(props: Props) {
}
const shareUrl: string = generateShareUrl(SHARE_DOMAIN, uri);
const rssUrl: string = generateRssUrl(SHARE_DOMAIN, uri);
const isCollectionClaim = claim && claim.value_type === 'collection';
// $FlowFixMe
const isPlayable =
@ -182,6 +183,10 @@ function ClaimMenuList(props: Props) {
}
}
function handleCopyRssLink() {
navigator.clipboard.writeText(rssUrl);
}
function handleCopyLink() {
navigator.clipboard.writeText(shareUrl);
}
@ -350,7 +355,18 @@ function ClaimMenuList(props: Props) {
)}
</>
)}
<hr className="menu__separator" />
{isChannelPage && (
<MenuItem className="comment__menu-option" onSelect={handleCopyRssLink}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.SHARE} />
{__('Copy RSS URL')}
</div>
</MenuItem>
)}
<MenuItem className="comment__menu-option" onSelect={handleCopyLink}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.SHARE} />

View file

@ -147,3 +147,9 @@ export const generateShareUrl = (
const url = `${domain}/${lbryWebUrl}` + (urlParamsString === '' ? '' : `?${urlParamsString}`);
return url;
};
export const generateRssUrl = (domain, lbryUrl) => {
const { channelName, channelClaimId } = parseURI(lbryUrl);
const url = `${domain}/$/rss/@${channelName}/${channelClaimId}`;
return url;
};

View file

@ -9,7 +9,7 @@ const pool = mysql.createPool({
});
function queryPool(sql, params) {
return new Promise(resolve => {
return new Promise((resolve) => {
pool.query(sql, params, (error, rows) => {
if (error) {
console.log('error', error); // eslint-disable-line

View file

@ -1,4 +1,5 @@
const { getHtml } = require('./html');
const { getRss } = require('./rss');
const { generateStreamUrl } = require('../../ui/util/web');
const fetch = require('node-fetch');
const Router = require('@koa/router');
@ -42,6 +43,12 @@ router.get(`/$/stream/:claimName/:claimId`, async (ctx) => {
ctx.redirect(streamUrl);
});
router.get(`/$/rss/:claimName/:claimId`, async (ctx) => {
const xml = await getRss(ctx);
ctx.set('Content-Type', 'application/rss+xml');
ctx.body = xml;
});
router.get('*', async (ctx) => {
const html = await getHtml(ctx);
ctx.body = html;

96
web/src/rss.js Normal file
View file

@ -0,0 +1,96 @@
const { URL, SITE_NAME, LBRY_WEB_API } = require('../../config.js');
const { Lbry } = require('lbry-redux');
const Feed = require('feed').Feed;
const SDK_API_PATH = `${LBRY_WEB_API}/api/v1`;
const proxyURL = `${SDK_API_PATH}/proxy`;
Lbry.setDaemonConnectionString(proxyURL);
async function doClaimSearch(options) {
let results;
try {
results = await Lbry.claim_search(options);
} catch {}
return results ? results.items : undefined;
}
async function getChannelClaim(claimId) {
const options = {
claim_ids: [claimId],
page_size: 1,
no_totals: true,
};
const claims = await doClaimSearch(options);
return claims ? claims[0] : undefined;
}
async function getClaimsFromChannel(claimId, count) {
const options = {
channel_ids: [claimId],
page_size: count,
has_source: true,
claim_type: 'stream',
order_by: ['creation_timestamp'],
no_totals: true,
};
return await doClaimSearch(options);
}
async function getFeed(channelClaim) {
const replaceLineFeeds = (str) => str.replace(/(?:\r\n|\r|\n)/g, '<br>');
const value = channelClaim.value;
const title = value ? value.title : channelClaim.name;
const options = {
title: title + ' on ' + SITE_NAME,
description: value ? replaceLineFeeds(value.description) : '',
link: `${URL}/${channelClaim.name}:${channelClaim.claim_id}`,
favicon: URL + '/public/favicon.png',
generator: SITE_NAME + ' RSS Feed',
image: value ? value.thumbnail.url : '',
author: {
name: channelClaim.name,
link: URL + '/' + channelClaim.name + ':' + channelClaim.claim_id,
},
};
const feed = new Feed(options);
const latestClaims = await getClaimsFromChannel(channelClaim.claim_id, 50);
latestClaims.forEach((c) => {
const meta = c.meta;
const value = c.value;
feed.addItem({
guid: c.claim_id,
id: c.claim_id,
title: value ? value.title : c.name,
description: value ? replaceLineFeeds(value.description) : '',
image: value ? value.thumbnail.url : '',
link: URL + '/' + c.name + ':' + c.claim_id,
date: new Date(meta ? meta.creation_timestamp * 1000 : null),
});
});
return feed;
}
async function getRss(ctx) {
if (!ctx.params.claimName || !ctx.params.claimId) {
return 'Invalid URL';
}
const channelClaim = await getChannelClaim(ctx.params.claimId);
if (typeof channelClaim === 'string' || !channelClaim) {
return channelClaim;
}
const feed = await getFeed(channelClaim);
return feed.rss2();
}
module.exports = { getRss };

View file

@ -5220,6 +5220,13 @@ fd-slicer@~1.1.0:
dependencies:
pend "~1.2.0"
feed@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/feed/-/feed-4.2.2.tgz#865783ef6ed12579e2c44bbef3c9113bc4956a7e"
integrity sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==
dependencies:
xml-js "^1.6.11"
figgy-pudding@^3.5.1:
version "3.5.2"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
@ -12512,6 +12519,13 @@ xdg-basedir@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
xml-js@^1.6.11:
version "1.6.11"
resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
dependencies:
sax "^1.2.4"
xmlbuilder@^10.0.0:
version "10.1.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz#8cae6688cc9b38d850b7c8d3c0a4161dcaf475b0"