Merge pull request #546 from lbryio/358-oEmbed

add oEmbed support
This commit is contained in:
Bill Bittner 2018-07-31 10:38:16 -07:00 committed by GitHub
commit 54dcd84529
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 318 additions and 16 deletions

View file

@ -80,3 +80,21 @@ Issues with lbry (e.g. the spee.ch wallet, lbrynet configuration, etc.) that req
* client * client
* [react](https://reactjs.org/) * [react](https://reactjs.org/)
### URL formats
Below is a list of all possible urls for the content on spee.ch
* controlling, free `LBRY` claim
* spee.ch/claim (show)
* spee.ch/claim.ext (serve)
* specific `LBRY` claim
* spee.ch/claim_id/claim
* spee.ch/claim_id/claim.ext
* all free contents for the controlling `LBRY` channel
* spee.ch/@channel
* a specific `LBRY` channel
* spee.ch/@channel:channel_id
* a specific claim within the controlling `LBRY` channel
* spee.ch/@channel/claim (show)
* spee.ch/@channel/claim.ext (serve)
* a specific claim within a specific `LBRY` channel
* spee.ch/@channel:channel_id/claim
* spee.ch/@channel:channel_id/claim.ext

View file

@ -11,10 +11,14 @@ var _reactHelmet = _interopRequireDefault(require("react-helmet"));
var _propTypes = _interopRequireDefault(require("prop-types")); var _propTypes = _interopRequireDefault(require("prop-types"));
var _siteConfig = _interopRequireDefault(require("@config/siteConfig.json"));
var _createPageTitle = _interopRequireDefault(require("../../utils/createPageTitle")); var _createPageTitle = _interopRequireDefault(require("../../utils/createPageTitle"));
var _createMetaTags = _interopRequireDefault(require("../../utils/createMetaTags")); var _createMetaTags = _interopRequireDefault(require("../../utils/createMetaTags"));
var _oEmbed = _interopRequireDefault(require("../../utils/oEmbed.js"));
var _createCanonicalLink = _interopRequireDefault(require("../../utils/createCanonicalLink")); var _createCanonicalLink = _interopRequireDefault(require("../../utils/createCanonicalLink"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@ -37,6 +41,8 @@ function _assertThisInitialized(self) { if (self === void 0) { throw new Referen
function _getPrototypeOf(o) { _getPrototypeOf = Object.getPrototypeOf || function _getPrototypeOf(o) { return o.__proto__; }; return _getPrototypeOf(o); } function _getPrototypeOf(o) { _getPrototypeOf = Object.getPrototypeOf || function _getPrototypeOf(o) { return o.__proto__; }; return _getPrototypeOf(o); }
var host = _siteConfig.default.details.host;
var SEO = var SEO =
/*#__PURE__*/ /*#__PURE__*/
function (_React$Component) { function (_React$Component) {
@ -61,15 +67,15 @@ function (_React$Component) {
asset: asset, asset: asset,
channel: channel channel: channel
}); });
var canonicalLink = (0, _createCanonicalLink.default)(asset, channel, pageUri); // render results var cannonicalLink = (0, _createCanonicalLink.default)(asset, channel, pageUri); // render results
return _react.default.createElement(_reactHelmet.default, { return _react.default.createElement(_reactHelmet.default, {
title: pageTitle, title: pageTitle,
meta: metaTags, meta: metaTags,
link: [{ link: [{
rel: 'canonical', rel: 'canonical',
href: canonicalLink href: cannonicalLink
}] }, _oEmbed.default.json(host, cannonicalLink)]
}); });
} }
}]); }]);
@ -79,7 +85,6 @@ function (_React$Component) {
return SEO; return SEO;
}(_react.default.Component); }(_react.default.Component);
;
SEO.propTypes = { SEO.propTypes = {
pageTitle: _propTypes.default.string, pageTitle: _propTypes.default.string,
pageUri: _propTypes.default.string, pageUri: _propTypes.default.string,

View file

@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var rel = 'alternate';
var title = 'spee.ch oEmbed profile';
var formatUrlForQuery = function formatUrlForQuery(url) {
return url.replace(/\//g, '%2F').replace(/:/g, '%3A');
};
var createJsonLinkData = function createJsonLinkData(host, canonicalUrl) {
return {
rel: rel,
type: 'application/json+oembed',
href: "".concat(host, "/api/oembed?url=").concat(formatUrlForQuery(canonicalUrl), "%2F&format=json"),
title: title
};
};
var createXmlLinkData = function createXmlLinkData(host, canonicalUrl) {
return {
rel: rel,
type: 'application/xml+oembed',
href: "".concat(host, "/api/oembed?url=").concat(formatUrlForQuery(canonicalUrl), "%2F&format=xml"),
title: title
};
};
var _default = {
json: createJsonLinkData,
xml: createXmlLinkData
};
exports.default = _default;

View file

@ -2,10 +2,14 @@ import React from 'react';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import siteConfig from '@config/siteConfig.json';
import createPageTitle from '../../utils/createPageTitle'; import createPageTitle from '../../utils/createPageTitle';
import createMetaTags from '../../utils/createMetaTags'; import createMetaTags from '../../utils/createMetaTags';
import oEmbed from '../../utils/oEmbed.js';
import createCanonicalLink from '../../utils/createCanonicalLink'; import createCanonicalLink from '../../utils/createCanonicalLink';
const { details: { host } } = siteConfig;
class SEO extends React.Component { class SEO extends React.Component {
render () { render () {
// props from parent // props from parent
@ -17,17 +21,23 @@ class SEO extends React.Component {
asset, asset,
channel, channel,
}); });
const canonicalLink = createCanonicalLink(asset, channel, pageUri); const cannonicalLink = createCanonicalLink(asset, channel, pageUri);
// render results // render results
return ( return (
<Helmet <Helmet
title={pageTitle} title={pageTitle}
meta={metaTags} meta={metaTags}
link={[{rel: 'canonical', href: canonicalLink}]} link={[
{
rel : 'canonical',
href: cannonicalLink,
},
oEmbed.json(host, cannonicalLink),
]}
/> />
); );
} }
}; }
SEO.propTypes = { SEO.propTypes = {
pageTitle: PropTypes.string, pageTitle: PropTypes.string,

View file

@ -0,0 +1,29 @@
const rel = 'alternate';
const title = 'spee.ch oEmbed profile';
const formatUrlForQuery = (url) => {
return url.replace(/\//g, '%2F').replace(/:/g, '%3A');
};
const createJsonLinkData = (host, canonicalUrl) => {
return {
rel,
type: 'application/json+oembed',
href: `${host}/api/oembed?url=${formatUrlForQuery(canonicalUrl)}%2F&format=json`,
title,
};
};
const createXmlLinkData = (host, canonicalUrl) => {
return {
rel,
type: 'application/xml+oembed',
href: `${host}/api/oembed?url=${formatUrlForQuery(canonicalUrl)}%2F&format=xml`,
title,
};
};
export default {
json: createJsonLinkData,
xml : createXmlLinkData,
};

View file

@ -2,7 +2,7 @@ const db = require('../../../../models');
const { handleErrorResponse } = require('../../../utils/errorHandlers.js'); const { handleErrorResponse } = require('../../../utils/errorHandlers.js');
const getClaimId = require('./getClaimId.js'); const getClaimId = require('../../../utils/getClaimId.js');
const NO_CHANNEL = 'NO_CHANNEL'; const NO_CHANNEL = 'NO_CHANNEL';
const NO_CLAIM = 'NO_CLAIM'; const NO_CLAIM = 'NO_CLAIM';

View file

@ -0,0 +1,67 @@
const logger = require('winston');
const db = require('../../../models');
const getClaimId = require('../../utils/getClaimId');
const {
details: {
host,
title: siteTitle,
},
} = require('@config/siteConfig');
const getOEmbedDataForAsset = (channelName, channelClaimId, claimName, claimId) => {
let fileData, claimData;
let data = {
version : '1.0',
provider_name: siteTitle,
provider_url : host,
cache_age : 86400, // one day in seconds
};
return getClaimId(channelName, channelClaimId, claimName, claimId)
.then(fullClaimId => {
claimId = fullClaimId;
return db.Claim.findOne({
where: {
name : claimName,
claimId: fullClaimId,
},
});
})
.then(claimRecord => {
claimData = claimRecord.dataValues;
return db.Blocked.isNotBlocked(claimData.outpoint);
})
.then(() => {
return db.File.findOne({
where: {
name: claimName,
claimId,
},
});
})
.then(fileRecord => {
fileData = fileRecord.dataValues;
logger.debug('file data:', fileData);
const serveUrl = `${host}/${fileData.claimId}/${fileData.name}.${fileData.fileType.substring(fileData.fileType.indexOf('/') + 1)}`;
// set the resource type
if (fileData.fileType === 'video/mp4') {
data['type'] = 'video';
data['html'] = `<video width="100%" controls poster="${claimData.thumbnail}" src="${serveUrl}"/></video>`;
} else {
data['type'] = 'picture';
data['url'] = serveUrl;
}
// get the data
data['title'] = claimData.title;
data['width'] = fileData.width || 600;
data['height'] = fileData.height || 400;
data['author_name'] = siteTitle;
data['author_url'] = host;
})
.then(() => {
return data;
});
};
module.exports = getOEmbedDataForAsset;

View file

@ -0,0 +1,33 @@
const db = require('../../../models');
const {
details: {
host,
title: siteTitle,
},
} = require('@config/siteConfig');
const getOEmbedDataForChannel = (channelName, channelClaimId) => {
return db.Certificate
.findOne({
where: {
name : channelName,
claimId: channelClaimId,
},
})
.then(certificateRecord => {
const certificateData = certificateRecord.dataValues;
return {
version : 1.0,
provider_name: siteTitle,
provider_url : host,
type : 'link',
author_name : certificateData.name,
title : `${certificateData.name}'s channel on Spee.ch`,
author_url : `${host}/${certificateData.name}:${certificateData.claimId}`,
cache_age : 86400, // one day in seconds
};
});
};
module.exports = getOEmbedDataForChannel;

View file

@ -0,0 +1,68 @@
const logger = require('winston');
const lbryUri = require('../../utils/lbryUri');
const getOEmbedDataForChannel = require('./getOEmbedDataForChannel');
const getOEmbedDataForAsset = require('./getOEmbedDataForAsset');
const parseSpeechUrl = require('./parseSpeechUrl');
const getOEmbedData = (req, res) => {
const { query: { url, format } } = req;
logger.debug('req url', url);
logger.debug('req format', format);
const { paramOne, paramTwo } = parseSpeechUrl(url);
let claimName, isChannel, channelName, channelClaimId, claimId;
if (paramTwo) {
({ isChannel, channelName, channelClaimId, claimId } = lbryUri.parseIdentifier(paramOne));
({ claimName } = lbryUri.parseClaim(paramTwo));
} else {
({ isChannel, channelName, channelClaimId } = lbryUri.parseIdentifier(paramOne));
if (!isChannel ) {
({ claimName } = lbryUri.parseClaim(paramOne));
}
}
if (isChannel && !paramTwo) {
getOEmbedDataForChannel(channelName, channelClaimId)
.then(data => {
if (format === 'xml'){
return res.status(503).json({
success: false,
message: 'xml format is not implemented yet',
})
} else {
return res.status(200).json(data);
}
})
.catch((error) => {
return res.status(404).json({
success: false,
message: error,
});
})
} else {
getOEmbedDataForAsset(channelName, channelClaimId, claimName, claimId)
.then(data => {
if (format === 'xml'){
return res.status(503).json({
success: false,
message: 'xml format is not implemented yet',
})
} else {
return res.status(200).json(data);
}
})
.catch((error) => {
return res.status(404).json({
success: false,
message: error,
});
})
}
};
module.exports = getOEmbedData;

View file

@ -0,0 +1,24 @@
const logger = require('winston');
const parseSpeechUrl = (url) => {
const componentsRegex = new RegExp(
'([^:/?#]+://)' +
'([^/?#]*)' +
'(/)' +
'([^/?#]*)' +
'(/)' +
'([^/?#]*)'
);
const [, , , , paramOne, , paramTwo] = componentsRegex
.exec(url)
.map(match => match || null);
logger.debug(`params from speech url: ${paramOne} ${paramTwo}`);
return {
paramOne,
paramTwo,
};
};
module.exports = parseSpeechUrl;

View file

@ -3,7 +3,7 @@ const logger = require('winston');
const { sendGAServeEvent } = require('../../../utils/googleAnalytics'); const { sendGAServeEvent } = require('../../../utils/googleAnalytics');
const handleShowRender = require('../../../render/build/handleShowRender.js'); const handleShowRender = require('../../../render/build/handleShowRender.js');
const lbryUri = require('../utils/lbryUri.js'); const lbryUri = require('../../utils/lbryUri.js');
const determineRequestType = require('../utils/determineRequestType.js'); const determineRequestType = require('../utils/determineRequestType.js');
const getClaimIdAndServeAsset = require('../utils/getClaimIdAndServeAsset.js'); const getClaimIdAndServeAsset = require('../utils/getClaimIdAndServeAsset.js');
@ -36,6 +36,7 @@ const serveByClaim = (req, res) => {
({ claimName } = lbryUri.parseClaim(params.claim)); ({ claimName } = lbryUri.parseClaim(params.claim));
logger.debug('serve request:', { headers, ip, originalUrl, params }); logger.debug('serve request:', { headers, ip, originalUrl, params });
getClaimIdAndServeAsset(null, null, claimName, null, originalUrl, ip, res); getClaimIdAndServeAsset(null, null, claimName, null, originalUrl, ip, res);
sendGAServeEvent(headers, ip, originalUrl); sendGAServeEvent(headers, ip, originalUrl);

View file

@ -3,7 +3,7 @@ const logger = require('winston');
const { sendGAServeEvent } = require('../../../utils/googleAnalytics'); const { sendGAServeEvent } = require('../../../utils/googleAnalytics');
const handleShowRender = require('../../../render/build/handleShowRender.js'); const handleShowRender = require('../../../render/build/handleShowRender.js');
const lbryUri = require('../utils/lbryUri.js'); const lbryUri = require('../../utils/lbryUri.js');
const determineRequestType = require('../utils/determineRequestType.js'); const determineRequestType = require('../utils/determineRequestType.js');
const getClaimIdAndServeAsset = require('../utils/getClaimIdAndServeAsset.js'); const getClaimIdAndServeAsset = require('../utils/getClaimIdAndServeAsset.js');
@ -37,6 +37,7 @@ const serverByIdentifierAndClaim = (req, res) => {
} }
logger.debug('serve request:', { headers, ip, originalUrl, params }); logger.debug('serve request:', { headers, ip, originalUrl, params });
getClaimIdAndServeAsset(channelName, channelClaimId, claimName, claimId, originalUrl, ip, res); getClaimIdAndServeAsset(channelName, channelClaimId, claimName, claimId, originalUrl, ip, res);
sendGAServeEvent(headers, ip, originalUrl); sendGAServeEvent(headers, ip, originalUrl);

View file

@ -2,10 +2,9 @@ const logger = require('winston');
const db = require('../../../models'); const db = require('../../../models');
const getClaimId = require('../../api/claim/longId/getClaimId.js'); const getClaimId = require('../../utils/getClaimId.js');
const { handleErrorResponse } = require('../../utils/errorHandlers.js'); const { handleErrorResponse } = require('../../utils/errorHandlers.js');
const getLocalFileRecord = require('./getLocalFileRecord.js');
const serveFile = require('./serveFile.js'); const serveFile = require('./serveFile.js');
const NO_CHANNEL = 'NO_CHANNEL'; const NO_CHANNEL = 'NO_CHANNEL';
@ -25,10 +24,18 @@ const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId
return db.Blocked.isNotBlocked(outpoint); return db.Blocked.isNotBlocked(outpoint);
}) })
.then(() => { .then(() => {
return getLocalFileRecord(claimId, claimName); return db.File.findOne({
where: {
claimId,
name: claimName,
},
});
}) })
.then(fileRecord => { .then(fileRecord => {
serveFile(fileRecord, res); if (!fileRecord) {
throw NO_FILE;
}
serveFile(fileRecord.dataValues, res);
}) })
.catch(error => { .catch(error => {
if (error === NO_CLAIM) { if (error === NO_CLAIM) {
@ -53,7 +60,7 @@ const getClaimIdAndServeAsset = (channelName, channelClaimId, claimName, claimId
}); });
} }
if (error === NO_FILE) { if (error === NO_FILE) {
logger.debug('claim was blocked'); logger.debug('no file available');
return res.status(307).redirect(`/api/claim/get/${name}/${claimId}`); return res.status(307).redirect(`/api/claim/get/${name}/${claimId}`);
} }
handleErrorResponse(originalUrl, ip, error, res); handleErrorResponse(originalUrl, ip, error, res);

View file

@ -1,6 +1,6 @@
const logger = require('winston'); const logger = require('winston');
const db = require('../../../../models'); const db = require('../../models/index');
const getClaimIdByChannel = (channelName, channelClaimId, claimName) => { const getClaimIdByChannel = (channelName, channelClaimId, claimName) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View file

@ -19,6 +19,7 @@ const userPassword = require('../../controllers/api/user/password');
const publishingConfig = require('../../controllers/api/config/site/publishing'); const publishingConfig = require('../../controllers/api/config/site/publishing');
const getTorList = require('../../controllers/api/tor'); const getTorList = require('../../controllers/api/tor');
const getBlockedList = require('../../controllers/api/blocked'); const getBlockedList = require('../../controllers/api/blocked');
const getOEmbedData = require('../../controllers/api/oEmbed');
module.exports = (app) => { module.exports = (app) => {
@ -46,4 +47,6 @@ module.exports = (app) => {
app.get('/api/tor', torCheckMiddleware, getTorList); app.get('/api/tor', torCheckMiddleware, getTorList);
// blocked // blocked
app.get('/api/blocked', torCheckMiddleware, getBlockedList); app.get('/api/blocked', torCheckMiddleware, getBlockedList);
// open embed
app.get('/api/oembed', torCheckMiddleware, getOEmbedData)
}; };