commit
54dcd84529
16 changed files with 318 additions and 16 deletions
18
README.md
18
README.md
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
36
client/build/utils/oEmbed.js
Normal file
36
client/build/utils/oEmbed.js
Normal 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;
|
|
@ -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,
|
||||
|
|
29
client/src/utils/oEmbed.js
Normal file
29
client/src/utils/oEmbed.js
Normal 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,
|
||||
};
|
|
@ -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';
|
||||
|
|
67
server/controllers/api/oEmbed/getOEmbedDataForAsset.js
Normal file
67
server/controllers/api/oEmbed/getOEmbedDataForAsset.js
Normal 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;
|
33
server/controllers/api/oEmbed/getOEmbedDataForChannel.js
Normal file
33
server/controllers/api/oEmbed/getOEmbedDataForChannel.js
Normal 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;
|
68
server/controllers/api/oEmbed/index.js
Normal file
68
server/controllers/api/oEmbed/index.js
Normal 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;
|
24
server/controllers/api/oEmbed/parseSpeechUrl.js
Normal file
24
server/controllers/api/oEmbed/parseSpeechUrl.js
Normal 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;
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) => {
|
|
@ -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)
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue