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
* [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 _siteConfig = _interopRequireDefault(require("@config/siteConfig.json"));
var _createPageTitle = _interopRequireDefault(require("../../utils/createPageTitle"));
var _createMetaTags = _interopRequireDefault(require("../../utils/createMetaTags"));
var _oEmbed = _interopRequireDefault(require("../../utils/oEmbed.js"));
var _createCanonicalLink = _interopRequireDefault(require("../../utils/createCanonicalLink"));
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); }
var host = _siteConfig.default.details.host;
var SEO =
/*#__PURE__*/
function (_React$Component) {
@ -61,15 +67,15 @@ function (_React$Component) {
asset: asset,
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, {
title: pageTitle,
meta: metaTags,
link: [{
rel: 'canonical',
href: canonicalLink
}]
href: cannonicalLink
}, _oEmbed.default.json(host, cannonicalLink)]
});
}
}]);
@ -79,7 +85,6 @@ function (_React$Component) {
return SEO;
}(_react.default.Component);
;
SEO.propTypes = {
pageTitle: _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 PropTypes from 'prop-types';
import siteConfig from '@config/siteConfig.json';
import createPageTitle from '../../utils/createPageTitle';
import createMetaTags from '../../utils/createMetaTags';
import oEmbed from '../../utils/oEmbed.js';
import createCanonicalLink from '../../utils/createCanonicalLink';
const { details: { host } } = siteConfig;
class SEO extends React.Component {
render () {
// props from parent
@ -17,17 +21,23 @@ class SEO extends React.Component {
asset,
channel,
});
const canonicalLink = createCanonicalLink(asset, channel, pageUri);
const cannonicalLink = createCanonicalLink(asset, channel, pageUri);
// render results
return (
<Helmet
title={pageTitle}
meta={metaTags}
link={[{rel: 'canonical', href: canonicalLink}]}
link={[
{
rel : 'canonical',
href: cannonicalLink,
},
oEmbed.json(host, cannonicalLink),
]}
/>
);
}
};
}
SEO.propTypes = {
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 getClaimId = require('./getClaimId.js');
const getClaimId = require('../../../utils/getClaimId.js');
const NO_CHANNEL = 'NO_CHANNEL';
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 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 getClaimIdAndServeAsset = require('../utils/getClaimIdAndServeAsset.js');
@ -36,6 +36,7 @@ const serveByClaim = (req, res) => {
({ claimName } = lbryUri.parseClaim(params.claim));
logger.debug('serve request:', { headers, ip, originalUrl, params });
getClaimIdAndServeAsset(null, null, claimName, null, originalUrl, ip, res);
sendGAServeEvent(headers, ip, originalUrl);

View file

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

View file

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

View file

@ -1,6 +1,6 @@
const logger = require('winston');
const db = require('../../../../models');
const db = require('../../models/index');
const getClaimIdByChannel = (channelName, channelClaimId, claimName) => {
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 getTorList = require('../../controllers/api/tor');
const getBlockedList = require('../../controllers/api/blocked');
const getOEmbedData = require('../../controllers/api/oEmbed');
module.exports = (app) => {
@ -46,4 +47,6 @@ module.exports = (app) => {
app.get('/api/tor', torCheckMiddleware, getTorList);
// blocked
app.get('/api/blocked', torCheckMiddleware, getBlockedList);
// open embed
app.get('/api/oembed', torCheckMiddleware, getOEmbedData)
};