Use get instead of generating the stream url (1499)
This commit is contained in:
commit
f79f346c94
8 changed files with 52 additions and 56 deletions
|
@ -9,7 +9,6 @@ WEB_SERVER_PORT=1337
|
||||||
|
|
||||||
# -- APIs ---
|
# -- APIs ---
|
||||||
LBRY_WEB_API=https://api.na-backend.odysee.com
|
LBRY_WEB_API=https://api.na-backend.odysee.com
|
||||||
LBRY_WEB_STREAMING_API=https://player.odycdn.com
|
|
||||||
LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video
|
LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video
|
||||||
COMMENT_SERVER_API=https://comments.odysee.tv/api/v2
|
COMMENT_SERVER_API=https://comments.odysee.tv/api/v2
|
||||||
SEARCH_SERVER_API_ALT=https://recsys.odysee.tv/search
|
SEARCH_SERVER_API_ALT=https://recsys.odysee.tv/search
|
||||||
|
|
|
@ -10,7 +10,6 @@ const config = {
|
||||||
LBRY_WEB_PUBLISH_API: process.env.LBRY_WEB_PUBLISH_API,
|
LBRY_WEB_PUBLISH_API: process.env.LBRY_WEB_PUBLISH_API,
|
||||||
LBRY_WEB_PUBLISH_API_V2: process.env.LBRY_WEB_PUBLISH_API_V2,
|
LBRY_WEB_PUBLISH_API_V2: process.env.LBRY_WEB_PUBLISH_API_V2,
|
||||||
LBRY_API_URL: process.env.LBRY_API_URL, // api.odysee.com',
|
LBRY_API_URL: process.env.LBRY_API_URL, // api.odysee.com',
|
||||||
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, // player.odycdn.com
|
|
||||||
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
|
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
|
||||||
SEARCH_SERVER_API: process.env.SEARCH_SERVER_API,
|
SEARCH_SERVER_API: process.env.SEARCH_SERVER_API,
|
||||||
SEARCH_SERVER_API_ALT: process.env.SEARCH_SERVER_API_ALT,
|
SEARCH_SERVER_API_ALT: process.env.SEARCH_SERVER_API_ALT,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { generateStreamUrl } from 'util/web';
|
|
||||||
import { MISSING_THUMB_DEFAULT } from 'config';
|
import { MISSING_THUMB_DEFAULT } from 'config';
|
||||||
|
|
||||||
export default function useGetThumbnail(
|
export default function useGetThumbnail(
|
||||||
|
@ -18,35 +17,28 @@ export default function useGetThumbnail(
|
||||||
const isFree = claim && claim.value && (!claim.value.fee || Number(claim.value.fee.amount) <= 0);
|
const isFree = claim && claim.value && (!claim.value.fee || Number(claim.value.fee.amount) <= 0);
|
||||||
const isCollection = claim && claim.value_type === 'collection';
|
const isCollection = claim && claim.value_type === 'collection';
|
||||||
const thumbnailInClaim = claim && claim.value && claim.value.thumbnail && claim.value.thumbnail.url;
|
const thumbnailInClaim = claim && claim.value && claim.value.thumbnail && claim.value.thumbnail.url;
|
||||||
|
let shouldFetchFileInfo = false;
|
||||||
|
|
||||||
// @if TARGET='web'
|
|
||||||
if (thumbnailInClaim) {
|
if (thumbnailInClaim) {
|
||||||
thumbnailToUse = thumbnailInClaim;
|
thumbnailToUse = thumbnailInClaim;
|
||||||
} else if (claim && isImage && isFree) {
|
} else if (claim && isImage && isFree) {
|
||||||
thumbnailToUse = generateStreamUrl(claim.name, claim.claim_id);
|
if (streamingUrl) {
|
||||||
|
thumbnailToUse = streamingUrl;
|
||||||
|
} else if (!shouldHide) {
|
||||||
|
shouldFetchFileInfo = true;
|
||||||
|
}
|
||||||
} else if (isCollection) {
|
} else if (isCollection) {
|
||||||
thumbnailToUse = MISSING_THUMB_DEFAULT;
|
thumbnailToUse = MISSING_THUMB_DEFAULT;
|
||||||
}
|
}
|
||||||
// @endif
|
|
||||||
|
|
||||||
// @if TARGET='app'
|
|
||||||
thumbnailToUse = thumbnailInClaim;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Temporarily disabled until we can call get with "save_blobs: off"
|
|
||||||
//
|
|
||||||
// React.useEffect(() => {
|
|
||||||
// if (hasClaim && isImage && isFree) {
|
|
||||||
// if (streamingUrl) {
|
|
||||||
// setThumbnail(streamingUrl);
|
|
||||||
// } else if (!shouldHide) {
|
|
||||||
// getFile(uri);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }, [hasClaim, isFree, isImage, streamingUrl, uri, shouldHide]);
|
|
||||||
// @endif
|
|
||||||
|
|
||||||
const [thumbnail, setThumbnail] = React.useState(thumbnailToUse);
|
const [thumbnail, setThumbnail] = React.useState(thumbnailToUse);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (shouldFetchFileInfo) {
|
||||||
|
getFile(uri);
|
||||||
|
}
|
||||||
|
}, [shouldFetchFileInfo, uri]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setThumbnail(thumbnailToUse);
|
setThumbnail(thumbnailToUse);
|
||||||
}, [thumbnailToUse]);
|
}, [thumbnailToUse]);
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
const { URL, LBRY_WEB_STREAMING_API, THUMBNAIL_CARDS_CDN_URL } = require('../../config');
|
const { URL, THUMBNAIL_CARDS_CDN_URL } = require('../../config');
|
||||||
|
|
||||||
const CONTINENT_COOKIE = 'continent';
|
const CONTINENT_COOKIE = 'continent';
|
||||||
|
|
||||||
function generateStreamUrl(claimName, claimId) {
|
|
||||||
return `${LBRY_WEB_STREAMING_API}/content/claims/${encodeURIComponent(claimName)
|
|
||||||
.replace(/'/g, '%27')
|
|
||||||
.replace(/\(/g, '%28')
|
|
||||||
.replace(/\)/g, '%29')}/${claimId}/${encodeURIComponent(claimName)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateEmbedUrl(claimName, claimId, startTime, referralLink) {
|
function generateEmbedUrl(claimName, claimId, startTime, referralLink) {
|
||||||
let urlParams = new URLSearchParams();
|
let urlParams = new URLSearchParams();
|
||||||
|
|
||||||
|
@ -98,7 +91,6 @@ module.exports = {
|
||||||
generateEmbedIframeData,
|
generateEmbedIframeData,
|
||||||
generateEmbedUrl,
|
generateEmbedUrl,
|
||||||
generateEmbedUrlEncoded,
|
generateEmbedUrlEncoded,
|
||||||
generateStreamUrl,
|
|
||||||
getParameterByName,
|
getParameterByName,
|
||||||
getThumbnailCdnUrl,
|
getThumbnailCdnUrl,
|
||||||
escapeHtmlProperty,
|
escapeHtmlProperty,
|
||||||
|
|
15
web/src/fetchStreamUrl.js
Normal file
15
web/src/fetchStreamUrl.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const { lbryProxy: Lbry } = require('../lbry');
|
||||||
|
const { buildURI } = require('./lbryURI');
|
||||||
|
|
||||||
|
async function fetchStreamUrl(claimName, claimId) {
|
||||||
|
const uri = buildURI({ claimName, claimId });
|
||||||
|
return await Lbry.get({ uri })
|
||||||
|
.then(({ streaming_url }) => streaming_url)
|
||||||
|
.catch((error) => {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fetchStreamUrl,
|
||||||
|
};
|
|
@ -14,12 +14,12 @@ const {
|
||||||
const {
|
const {
|
||||||
generateDirectUrl,
|
generateDirectUrl,
|
||||||
generateEmbedUrl,
|
generateEmbedUrl,
|
||||||
generateStreamUrl,
|
|
||||||
getParameterByName,
|
getParameterByName,
|
||||||
getThumbnailCdnUrl,
|
getThumbnailCdnUrl,
|
||||||
escapeHtmlProperty,
|
escapeHtmlProperty,
|
||||||
unscapeHtmlProperty,
|
unscapeHtmlProperty,
|
||||||
} = require('../../ui/util/web');
|
} = require('../../ui/util/web');
|
||||||
|
const { fetchStreamUrl } = require('./fetchStreamUrl');
|
||||||
const { getJsBundleId } = require('../bundle-id.js');
|
const { getJsBundleId } = require('../bundle-id.js');
|
||||||
const { lbryProxy: Lbry } = require('../lbry');
|
const { lbryProxy: Lbry } = require('../lbry');
|
||||||
const { getHomepageJsonV1 } = require('./getHomepageJSON');
|
const { getHomepageJsonV1 } = require('./getHomepageJSON');
|
||||||
|
@ -151,7 +151,7 @@ function buildBasicOgMetadata() {
|
||||||
// Metadata used for urls that need claim information
|
// Metadata used for urls that need claim information
|
||||||
// Also has option to override defaults
|
// Also has option to override defaults
|
||||||
//
|
//
|
||||||
function buildClaimOgMetadata(uri, claim, overrideOptions = {}, referrerQuery) {
|
async function buildClaimOgMetadata(uri, claim, overrideOptions = {}, referrerQuery) {
|
||||||
// Initial setup for claim based og metadata
|
// Initial setup for claim based og metadata
|
||||||
const { claimName } = parseURI(uri);
|
const { claimName } = parseURI(uri);
|
||||||
const { meta, value, signing_channel } = claim;
|
const { meta, value, signing_channel } = claim;
|
||||||
|
@ -181,7 +181,7 @@ function buildClaimOgMetadata(uri, claim, overrideOptions = {}, referrerQuery) {
|
||||||
let imageThumbnail;
|
let imageThumbnail;
|
||||||
|
|
||||||
if (fee <= 0 && mediaType && mediaType.startsWith('image/')) {
|
if (fee <= 0 && mediaType && mediaType.startsWith('image/')) {
|
||||||
imageThumbnail = generateStreamUrl(claim.name, claim.claim_id);
|
imageThumbnail = await fetchStreamUrl(claim.name, claim.claim_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const claimThumbnail =
|
const claimThumbnail =
|
||||||
|
@ -392,7 +392,7 @@ async function getHtml(ctx) {
|
||||||
const inviteChannel = requestPath.slice(invitePath.length);
|
const inviteChannel = requestPath.slice(invitePath.length);
|
||||||
const inviteChannelUrl = normalizeClaimUrl(inviteChannel);
|
const inviteChannelUrl = normalizeClaimUrl(inviteChannel);
|
||||||
const claim = await resolveClaimOrRedirect(ctx, inviteChannelUrl);
|
const claim = await resolveClaimOrRedirect(ctx, inviteChannelUrl);
|
||||||
const invitePageMetadata = buildClaimOgMetadata(inviteChannelUrl, claim, {
|
const invitePageMetadata = await buildClaimOgMetadata(inviteChannelUrl, claim, {
|
||||||
title: `Join ${claim.name} on ${SITE_NAME}`,
|
title: `Join ${claim.name} on ${SITE_NAME}`,
|
||||||
description: `Join ${claim.name} on ${SITE_NAME}, a content wonderland owned by everyone (and no one).`,
|
description: `Join ${claim.name} on ${SITE_NAME}, a content wonderland owned by everyone (and no one).`,
|
||||||
});
|
});
|
||||||
|
@ -414,7 +414,7 @@ async function getHtml(ctx) {
|
||||||
const claim = await resolveClaimOrRedirect(ctx, claimUri, true);
|
const claim = await resolveClaimOrRedirect(ctx, claimUri, true);
|
||||||
|
|
||||||
if (claim) {
|
if (claim) {
|
||||||
const ogMetadata = buildClaimOgMetadata(claimUri, claim);
|
const ogMetadata = await buildClaimOgMetadata(claimUri, claim);
|
||||||
const googleVideoMetadata = buildGoogleVideoMetadata(claimUri, claim);
|
const googleVideoMetadata = buildGoogleVideoMetadata(claimUri, claim);
|
||||||
return insertToHead(html, ogMetadata.concat('\n', googleVideoMetadata));
|
return insertToHead(html, ogMetadata.concat('\n', googleVideoMetadata));
|
||||||
}
|
}
|
||||||
|
@ -438,7 +438,7 @@ async function getHtml(ctx) {
|
||||||
const referrerQuery = escapeHtmlProperty(getParameterByName('r', ctx.request.url));
|
const referrerQuery = escapeHtmlProperty(getParameterByName('r', ctx.request.url));
|
||||||
|
|
||||||
if (claim) {
|
if (claim) {
|
||||||
const ogMetadata = buildClaimOgMetadata(claimUri, claim, {}, referrerQuery);
|
const ogMetadata = await buildClaimOgMetadata(claimUri, claim, {}, referrerQuery);
|
||||||
const googleVideoMetadata = buildGoogleVideoMetadata(claimUri, claim);
|
const googleVideoMetadata = buildGoogleVideoMetadata(claimUri, claim);
|
||||||
return insertToHead(html, ogMetadata.concat('\n', googleVideoMetadata));
|
return insertToHead(html, ogMetadata.concat('\n', googleVideoMetadata));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { generateStreamUrl } = require('../../ui/util/web');
|
const { fetchStreamUrl } = require('./fetchStreamUrl');
|
||||||
const { getHtml } = require('./html');
|
const { getHtml } = require('./html');
|
||||||
const { getOEmbed } = require('./oEmbed');
|
const { getOEmbed } = require('./oEmbed');
|
||||||
const { getRss } = require('./rss');
|
const { getRss } = require('./rss');
|
||||||
|
@ -13,11 +13,9 @@ global.fetch = fetch;
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
function getStreamUrl(ctx) {
|
async function getStreamUrl(ctx) {
|
||||||
const { claimName, claimId } = ctx.params;
|
const { claimName, claimId } = ctx.params;
|
||||||
|
return await fetchStreamUrl(claimName, claimId);
|
||||||
const streamUrl = generateStreamUrl(claimName, claimId);
|
|
||||||
return streamUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rssMiddleware = async (ctx) => {
|
const rssMiddleware = async (ctx) => {
|
||||||
|
@ -43,7 +41,7 @@ router.get(`/$/api/content/v1/get`, async (ctx) => getHomepage(ctx, 1));
|
||||||
router.get(`/$/api/content/v2/get`, async (ctx) => getHomepage(ctx, 2));
|
router.get(`/$/api/content/v2/get`, async (ctx) => getHomepage(ctx, 2));
|
||||||
|
|
||||||
router.get(`/$/download/:claimName/:claimId`, async (ctx) => {
|
router.get(`/$/download/:claimName/:claimId`, async (ctx) => {
|
||||||
const streamUrl = getStreamUrl(ctx);
|
const streamUrl = await getStreamUrl(ctx);
|
||||||
const downloadUrl = `${streamUrl}?download=1`;
|
const downloadUrl = `${streamUrl}?download=1`;
|
||||||
ctx.redirect(downloadUrl);
|
ctx.redirect(downloadUrl);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { generateStreamUrl } = require('../../ui/util/web');
|
const { fetchStreamUrl } = require('./fetchStreamUrl');
|
||||||
const { lbryProxy: Lbry } = require('../lbry');
|
const { lbryProxy: Lbry } = require('../lbry');
|
||||||
const { URL, SITE_NAME, PROXY_URL } = require('../../config.js');
|
const { URL, SITE_NAME, PROXY_URL } = require('../../config.js');
|
||||||
const Mime = require('mime-types');
|
const Mime = require('mime-types');
|
||||||
|
@ -62,7 +62,7 @@ function encodeWithSpecialCharEncode(string) {
|
||||||
return encodeURIComponent(string).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
|
return encodeURIComponent(string).replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateEnclosureForClaimContent = (claim) => {
|
async function generateEnclosureForClaimContent(claim) {
|
||||||
const value = claim.value;
|
const value = claim.value;
|
||||||
if (!value || !value.stream_type) {
|
if (!value || !value.stream_type) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -72,27 +72,27 @@ const generateEnclosureForClaimContent = (claim) => {
|
||||||
switch (value.stream_type) {
|
switch (value.stream_type) {
|
||||||
case 'video':
|
case 'video':
|
||||||
return {
|
return {
|
||||||
url: generateStreamUrl(claim.name, claim.claim_id) + (fileExt || '.mp4'),
|
url: (await fetchStreamUrl(claim.name, claim.claim_id)) + (fileExt || '.mp4'),
|
||||||
type: (value.source && value.source.media_type) || 'video/mp4',
|
type: (value.source && value.source.media_type) || 'video/mp4',
|
||||||
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
|
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'audio':
|
case 'audio':
|
||||||
return {
|
return {
|
||||||
url: generateStreamUrl(claim.name, claim.claim_id) + ((fileExt === '.mpga' ? '.mp3' : fileExt) || '.mp3'),
|
url: (await fetchStreamUrl(claim.name, claim.claim_id)) + ((fileExt === '.mpga' ? '.mp3' : fileExt) || '.mp3'),
|
||||||
type: (value.source && value.source.media_type) || 'audio/mpeg',
|
type: (value.source && value.source.media_type) || 'audio/mpeg',
|
||||||
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
|
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
|
||||||
};
|
};
|
||||||
case 'image':
|
case 'image':
|
||||||
return {
|
return {
|
||||||
url: generateStreamUrl(claim.name, claim.claim_id) + (fileExt || '.jpeg'),
|
url: (await fetchStreamUrl(claim.name, claim.claim_id)) + (fileExt || '.jpeg'),
|
||||||
type: (value.source && value.source.media_type) || 'image/jpeg',
|
type: (value.source && value.source.media_type) || 'image/jpeg',
|
||||||
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
|
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
|
||||||
};
|
};
|
||||||
case 'document':
|
case 'document':
|
||||||
case 'software':
|
case 'software':
|
||||||
return {
|
return {
|
||||||
url: generateStreamUrl(claim.name, claim.claim_id),
|
url: await fetchStreamUrl(claim.name, claim.claim_id),
|
||||||
type: (value.source && value.source.media_type) || undefined,
|
type: (value.source && value.source.media_type) || undefined,
|
||||||
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
|
size: (value.source && value.source.size) || 0, // Per spec, 0 is a valid fallback.
|
||||||
};
|
};
|
||||||
|
@ -100,7 +100,7 @@ const generateEnclosureForClaimContent = (claim) => {
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const getLanguageValue = (claim) => {
|
const getLanguageValue = (claim) => {
|
||||||
const {
|
const {
|
||||||
|
@ -215,7 +215,7 @@ const getFormattedDescription = (claim) => replaceLineFeeds(claim.value.descript
|
||||||
// Generate
|
// Generate
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
|
|
||||||
function generateFeed(feedLink, channelClaim, claimsInChannel) {
|
async function generateFeed(feedLink, channelClaim, claimsInChannel) {
|
||||||
// --- Channel ---
|
// --- Channel ---
|
||||||
let channelTitle = (channelClaim.value && channelClaim.value.title) || channelClaim.name;
|
let channelTitle = (channelClaim.value && channelClaim.value.title) || channelClaim.name;
|
||||||
let channelURL = URL + '/' + channelClaim.canonical_url.replace('lbry://', '').replace(/#/g, ':');
|
let channelURL = URL + '/' + channelClaim.canonical_url.replace('lbry://', '').replace(/#/g, ':');
|
||||||
|
@ -245,7 +245,8 @@ function generateFeed(feedLink, channelClaim, claimsInChannel) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Content ---
|
// --- Content ---
|
||||||
claimsInChannel.forEach((c) => {
|
for (let i = 0; i < claimsInChannel.length; ++i) {
|
||||||
|
const c = claimsInChannel[i];
|
||||||
const title = (c.value && c.value.title) || c.name;
|
const title = (c.value && c.value.title) || c.name;
|
||||||
const thumbnailUrl = (c.value && c.value.thumbnail && c.value.thumbnail.url) || '';
|
const thumbnailUrl = (c.value && c.value.thumbnail && c.value.thumbnail.url) || '';
|
||||||
const thumbnailHtml = thumbnailUrl
|
const thumbnailHtml = thumbnailUrl
|
||||||
|
@ -264,7 +265,7 @@ function generateFeed(feedLink, channelClaim, claimsInChannel) {
|
||||||
guid: undefined, // defaults to 'url'
|
guid: undefined, // defaults to 'url'
|
||||||
author: undefined, // defaults feed author property
|
author: undefined, // defaults feed author property
|
||||||
date: new Date(date),
|
date: new Date(date),
|
||||||
enclosure: generateEnclosureForClaimContent(c),
|
enclosure: await generateEnclosureForClaimContent(c),
|
||||||
custom_elements: [
|
custom_elements: [
|
||||||
{ 'itunes:title': title },
|
{ 'itunes:title': title },
|
||||||
{ 'itunes:author': channelTitle },
|
{ 'itunes:author': channelTitle },
|
||||||
|
@ -273,7 +274,7 @@ function generateFeed(feedLink, channelClaim, claimsInChannel) {
|
||||||
generateItunesExplicitElement(c),
|
generateItunesExplicitElement(c),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
return feed;
|
return feed;
|
||||||
}
|
}
|
||||||
|
@ -289,7 +290,7 @@ async function getRss(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const latestClaimsInChannel = await getClaimsFromChannel(channelClaim.claim_id, NUM_ENTRIES);
|
const latestClaimsInChannel = await getClaimsFromChannel(channelClaim.claim_id, NUM_ENTRIES);
|
||||||
const feed = generateFeed(`${URL}${ctx.request.url}`, channelClaim, latestClaimsInChannel);
|
const feed = await generateFeed(`${URL}${ctx.request.url}`, channelClaim, latestClaimsInChannel);
|
||||||
return feed.xml();
|
return feed.xml();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue