Short urls 2 #93

Merged
bones7242 merged 8 commits from short-urls-2 into master 2017-07-20 08:16:30 +02:00
14 changed files with 498 additions and 443 deletions

View file

@ -3,151 +3,10 @@ const db = require('../models');
const logger = require('winston'); const logger = require('winston');
const getAllFreePublicClaims = require('../helpers/functions/getAllFreePublicClaims.js'); const getAllFreePublicClaims = require('../helpers/functions/getAllFreePublicClaims.js');
const isFreePublicClaim = require('../helpers/functions/isFreePublicClaim.js'); const isFreePublicClaim = require('../helpers/functions/isFreePublicClaim.js');
const { validateClaimId } = require('../helpers/libraries/serveHelpers.js'); const serveHelpers = require('../helpers/libraries/serveHelpers.js');
function updateFileIfNeeded (uri, claimName, claimId, localOutpoint, localHeight) {
logger.debug(`Initiating resolve to check outpoint for ${claimName}:${claimId}.`);
// 1. resolve claim
lbryApi
.resolveUri(uri)
.then(result => {
// check to make sure the result is a claim
if (!result.claim) {
logger.debug('resolve did not return a claim');
return;
}
// logger.debug('resolved result:', result);
const resolvedOutpoint = `${result.claim.txid}:${result.claim.nout}`;
const resolvedHeight = result.claim.height;
const resolvedAddress = result.claim.address;
logger.debug('database outpoint:', localOutpoint);
logger.debug('resolved outpoint:', resolvedOutpoint);
// 2. if the outpoint's match, no further work needed
if (localOutpoint === resolvedOutpoint) {
logger.debug('local outpoint matched');
// 2. if the outpoints don't match, check the height
} else if (localHeight > resolvedHeight) {
logger.debug('local height was greater than resolved height');
// 2. get the resolved claim
} else {
logger.debug(`local outpoint did not match for ${uri}. Initiating update.`);
getClaimAndUpdate(uri, resolvedAddress, resolvedHeight);
}
})
.catch(error => {
logger.error(error);
});
}
function getClaimAndUpdate (uri, address, height) {
// 1. get the claim
lbryApi
.getClaim(uri)
.then(({ name, claim_id, outpoint, file_name, download_path, mime_type, metadata }) => {
logger.debug(' Get returned outpoint: ', outpoint);
// 2. update the entry in db
db.File
.update({
outpoint,
height, // note: height is coming from the 'resolve', not 'get'.
address, // note: address is coming from the 'resolve', not 'get'.
fileName: file_name,
filePath: download_path,
fileType: mime_type,
nsfw : metadata.stream.metadata.nsfw,
}, {
where: {
name,
claimId: claim_id,
},
})
.then(result => {
logger.debug('successfully updated mysql record', result);
})
.catch(error => {
logger.error('sequelize error', error);
});
})
.catch(error => {
logger.error(`error while getting claim for ${uri} >> `, error);
});
}
function getClaimAndHandleResponse (uri, address, height, resolve, reject) {
lbryApi
.getClaim(uri)
.then(({ name, claim_id, outpoint, file_name, download_path, mime_type, metadata }) => {
// create entry in the db
logger.silly(`creating "${name}" record in File db`);
db.File
.create({
name,
claimId : claim_id,
address, // note: comes from parent 'resolve,' not this 'get' call
outpoint,
height, // note: comes from parent 'resolve,' not this 'get' call
fileName: file_name,
filePath: download_path,
fileType: mime_type,
nsfw : metadata.stream.metadata.nsfw,
})
.then(result => {
logger.debug('successfully created mysql record');
})
.catch(error => {
logger.error('sequelize create error', error);
});
// resolve the request
resolve({
name,
claimId : claim_id,
fileName: file_name,
filePath: download_path,
fileType: mime_type,
});
})
.catch(error => {
reject(error);
});
}
function getClaimAndReturnResponse (uri, address, height) {
const deferred = new Promise((resolve, reject) => {
lbryApi
.getClaim(uri)
.then(({ name, claim_id, outpoint, file_name, download_path, mime_type, metadata }) => {
// create entry in the db
logger.silly(`creating new File record`);
db.File
.create({
name,
claimId : claim_id,
address, // note: passed as an arguent, not from this 'get' call
outpoint,
height, // note: passed as an arguent, not from this 'get' call
fileName: file_name,
filePath: download_path,
fileType: mime_type,
nsfw : metadata.stream.metadata.nsfw,
})
.then(result => {
logger.debug('successfully created File record');
resolve(result); // note: result.dataValues ?
})
.catch(error => {
logger.error('sequelize create error', error);
reject(error);
});
})
.catch(error => {
reject(error);
});
});
return deferred;
}
module.exports = { module.exports = {
getClaimByName (claimName) { serveClaimByName (claimName) {
const deferred = new Promise((resolve, reject) => { const deferred = new Promise((resolve, reject) => {
// 1. get the top free, public claims // 1. get the top free, public claims
getAllFreePublicClaims(claimName) getAllFreePublicClaims(claimName)
@ -171,15 +30,14 @@ module.exports = {
// serve the file // serve the file
resolve(claim.dataValues); resolve(claim.dataValues);
// trigger update if needed // trigger update if needed
updateFileIfNeeded(uri, name, claimId, claim.dataValues.outpoint, claim.dataValues.height); serveHelpers.updateFileIfNeeded(uri, claim.dataValues.outpoint, claim.dataValues.height);
// 3. otherwise use daemon to retrieve it // 3. otherwise use daemon to retrieve it
} else { } else {
// get the claim and serve it // get the claim and serve it
getClaimAndHandleResponse(uri, address, height, resolve, reject); serveHelpers.getClaimAndHandleResponse(uri, address, height, resolve, reject);
} }
}) })
.catch(error => { .catch(error => {
logger.error('sequelize error', error);
reject(error); reject(error);
}); });
}) })
@ -189,54 +47,92 @@ module.exports = {
}); });
return deferred; return deferred;
}, },
getClaimByClaimId (name, claimId) { serveClaimByClaimId (name, claimId) {
logger.debug(`serving claim "${name}" with claimid "${claimId}"`);
const deferred = new Promise((resolve, reject) => {
// 1. check locally for the claim
const uri = `${name}#${claimId}`;
db.File
.findOne({ where: { name, claimId } })
.then(result => {
// 3. if a match is found locally, serve that claim
if (result) {
logger.debug('local result found');
// return the data for the file to be served
resolve(result.dataValues);
serveHelpers.updateFileIfNeeded(uri, result.dataValues.outpoint, result.dataValues.outpoint);
// 3. if a match was not found locally, use the daemon to retrieve the claim & return the db data once it is created
} else {
logger.debug('no local result found');
lbryApi
.resolveUri(uri)
.then(result => {
if (result.claim && isFreePublicClaim(result.claim)) { // check to see if the claim is free & public
// get claim and serve
serveHelpers
.getClaimAndReturnResponse(uri, result.claim.address, result.claim.height)
.then(result => {
resolve(result.dataValues);
})
.catch(error => reject(error));
} else {
logger.debug('Resolve did not return a free, public claim');
resolve(null);
}
})
.catch(error => {
logger.debug('resolve returned an error');
reject(error);
});
}
})
.catch(error => reject(error));
});
return deferred;
},
serveClaimByShortUrl (name, shortUrl) {
const deferred = new Promise((resolve, reject) => { const deferred = new Promise((resolve, reject) => {
let uri; let uri;
let claimId;
// 1. validate the claim id & retrieve the full claim id if needed // 1. validate the claim id & retrieve the full claim id if needed
validateClaimId(name, claimId) serveHelpers
.then(validClaimId => { .getClaimIdByShortUrl(name, shortUrl)
.then(result => {
// 2. check locally for the claim // 2. check locally for the claim
logger.debug('valid claim id:', validClaimId); uri = `${name}#${result}`;
uri = `${name}#${validClaimId}`; claimId = result;
return db.File.findOne({ where: { name, claimId: validClaimId } }); return db.File.findOne({ where: { name, claimId } });
}) })
.then(result => { .then(result => {
// 3. if a match is found locally, serve that claim // 3. if a match is found locally, serve that claim
if (result) { if (result) {
logger.debug('Result found in File table:', result.dataValues);
// return the data for the file to be served // return the data for the file to be served
resolve(result.dataValues); resolve(result.dataValues);
// update the file, as needed // update the file, as needed
updateFileIfNeeded(uri, name, claimId, result.dataValues.outpoint, result.dataValues.outpoint); serveHelpers.updateFileIfNeeded(uri, result.dataValues.outpoint, result.dataValues.outpoint);
// 3. if a match was not found locally, use the daemon to retrieve the claim & return the db data once it is created // 3. if a match was not found locally, use the daemon to retrieve the claim & return the db data once it is created
} else { } else {
logger.debug('No result found in File table');
lbryApi lbryApi
.resolveUri(uri) .resolveUri(uri)
.then(result => { .then(result => {
if (!result.claim) { // check to make sure the result is a claim if (result.claim && isFreePublicClaim(result.claim)) { // check to see if the claim is free & public
logger.debug('resolve did not return a claim');
resolve(null);
}
if (isFreePublicClaim(result.claim)) { // check to see if the claim is free & public
// get claim and serve // get claim and serve
resolve(getClaimAndReturnResponse(uri, result.claim.address, result.claim.height)); serveHelpers
.getClaimAndReturnResponse(uri, result.claim.address, result.claim.height)
.then(result => {
resolve(result.dataValues);
})
.catch(error => reject(error));
} else { } else {
logger.debug('Resolve did not return a free, public claim');
resolve(null); resolve(null);
} }
}) })
.catch(error => { .catch(error => reject(error));
reject(error);
});
} }
}) })
.catch(error => { .catch(error => reject(error));
reject(error);
});
}); });
return deferred; return deferred;
}, },
getAllClaims (claimName) {
return getAllFreePublicClaims(claimName);
},
}; };

View file

@ -0,0 +1,159 @@
const lbryApi = require('../helpers/libraries/lbryApi.js');
const db = require('../models');
const logger = require('winston');
const getAllFreePublicClaims = require('../helpers/functions/getAllFreePublicClaims.js');
const isFreePublicClaim = require('../helpers/functions/isFreePublicClaim.js');
const serveHelpers = require('../helpers/libraries/serveHelpers.js');
module.exports = {
showClaimByName (claimName) {
const deferred = new Promise((resolve, reject) => {
// 1. get the top free, public claims
getAllFreePublicClaims(claimName)
.then(freePublicClaimList => {
// check to make sure some claims were found
if (!freePublicClaimList) {
resolve(null);
return;
}
const name = freePublicClaimList[0].name;
const claimId = freePublicClaimList[0].claim_id;
const uri = `${name}#${claimId}`;
const height = freePublicClaimList[0].height;
const address = freePublicClaimList[0].address;
// 2. check to see if the file is available locally
db.File
.findOne({ where: { name, claimId } })
.then(result => {
// 3. if a matching record is found locally, serve it
if (result) {
// return the data for the file to be served
serveHelpers.getShortUrlByClaimId(name, claimId)
.then(shortUrl => {
result.dataValues['shortUrl'] = shortUrl;
resolve(result.dataValues);
})
.catch(error => reject(error));
// trigger update if needed
serveHelpers.updateFileIfNeeded(uri, result.dataValues.outpoint, result.dataValues.height);
// 3. otherwise use daemon to retrieve it
} else {
// get the claim and serve it
serveHelpers.getClaimAndHandleResponse(uri, address, height, resolve, reject);
}
})
.catch(error => {
reject(error);
});
})
.catch(error => {
reject(error);
});
});
return deferred;
},
showClaimByClaimId (name, claimId) {
logger.debug(`Getting claim name: ${name} by claimid: ${claimId}`);
const deferred = new Promise((resolve, reject) => {
// 1. check locally for the claim
const uri = `${name}#${claimId}`;
db.File
.findOne({ where: { name, claimId } })
.then(result => {
// 3. if a match is found locally, serve that claim
if (result) {
logger.debug('local result found');
// return the data for the file to be served
serveHelpers.getShortUrlByClaimId(name, claimId)
.then(shortUrl => {
result.dataValues['shortUrl'] = shortUrl;
resolve(result.dataValues);
})
.catch(error => reject(error));
// update the file, as needed
serveHelpers.updateFileIfNeeded(uri, result.dataValues.outpoint, result.dataValues.outpoint);
// 3. if a match was not found locally, use the daemon to retrieve the claim & return the db data once it is created
} else {
logger.debug('no local result found');
lbryApi
.resolveUri(uri)
.then(result => {
logger.debug('resolve returned successfully');
if (result.claim && isFreePublicClaim(result.claim)) { // check to see if the claim is free & public
// get claim and serve
serveHelpers.getClaimAndReturnResponse(uri, result.claim.address, result.claim.height)
.then(result => {
logger.debug('get request returned');
serveHelpers.getShortUrlByClaimId(name, claimId)
.then(shortUrl => {
result.dataValues['shortUrl'] = shortUrl;
resolve(result.dataValues);
})
.catch(error => reject(error));
})
.catch(error => reject(error));
} else {
logger.debug('Resolve did not return a free, public claim');
resolve(null, null);
}
})
.catch(error => {
logger.debug('resolve returned an error');
reject(error);
});
}
})
.catch(error => reject(error));
});
return deferred;
},
showClaimByShortUrl (name, shortUrl) {
const deferred = new Promise((resolve, reject) => {
let uri;
let claimId;
// 1. validate the claim id & retrieve the full claim id if needed
serveHelpers.getClaimIdByShortUrl(name, shortUrl)
.then(result => {
// 2. check locally for the claim
uri = `${name}#${result}`;
claimId = result;
return db.File.findOne({ where: { name, claimId } });
})
.then(result => {
// 3. if a match is found locally, serve that claim
if (result) {
// return the data for the file to be served
result.dataValues['shortUrl'] = shortUrl;
resolve(result.dataValues);
// update the file, as needed
serveHelpers.updateFileIfNeeded(uri, result.dataValues.outpoint, result.dataValues.outpoint);
// 3. if a match was not found locally, use the daemon to retrieve the claim & return the db data once it is created
} else {
lbryApi
.resolveUri(uri)
.then(result => {
if (result.claim && isFreePublicClaim(result.claim)) { // check to see if the claim is free & public
// get claim and serve
serveHelpers.getClaimAndReturnResponse(uri, result.claim.address, result.claim.height)
.then(result => {
logger.debug('returned');
result.dataValues['shortUrl'] = shortUrl;
resolve(result.dataValues);
})
.catch(error => reject(error));
} else {
logger.debug('Resolve did not return a free, public claim');
resolve(null, null);
}
})
.catch(error => reject(error));
}
})
.catch(error => reject(error));
});
return deferred;
},
showAllClaims (claimName) {
return getAllFreePublicClaims(claimName);
},
};

View file

@ -10,11 +10,10 @@ module.exports = {
if (result && (typeof result !== 'string')) { if (result && (typeof result !== 'string')) {
result = result.toString(); result = result.toString();
} }
// // make sure the ip address(es) are a string // make sure the ip address(es) are a string
if (ipAddress && (typeof ipAddress !== 'string')) { if (ipAddress && (typeof ipAddress !== 'string')) {
ipAddress = ipAddress.toString(); ipAddress = ipAddress.toString();
} }
logger.silly(name, claimId);
db.File db.File
.findOne({where: { name, claimId }}) .findOne({where: { name, claimId }})
.then(file => { .then(file => {
@ -25,7 +24,6 @@ module.exports = {
} else { } else {
FileId = null; FileId = null;
} }
logger.silly('file id:', FileId);
return db.Request return db.Request
.create({ .create({
action, action,

View file

@ -59,6 +59,7 @@ module.exports = {
resolve(data.result); resolve(data.result);
}) })
.catch(error => { .catch(error => {
logger.debug("axios.post 'get' error");
reject(error); reject(error);
}); });
}); });
@ -90,12 +91,11 @@ module.exports = {
params: { uri }, params: { uri },
}) })
.then(({ data }) => { .then(({ data }) => {
// check for errors if (data.result[uri].error) { // check for errors
if (data.result[uri].error) {
reject(data.result[uri].error); reject(data.result[uri].error);
return; } else { // if no errors, resolve
}
resolve(data.result[uri]); resolve(data.result[uri]);
}
}) })
.catch(error => { .catch(error => {
reject(error); reject(error);

View file

@ -1,5 +1,74 @@
const logger = require('winston'); const logger = require('winston');
const db = require('../../models'); const db = require('../../models');
const lbryApi = require('./lbryApi');
function determineShortUrl (claimId, claimList) {
logger.debug('determining short url based on claim id and claim list');
const thisClaim = claimList.filter(claim => { // find this claim in the list & store it
return claim.claim_id === claimId;
})[0];
claimList = claimList.filter(claim => { // remove this claim from the claim list
return claim.claim_id !== claimId;
});
if (claimList.length === 0) { // if there are no other claims, return the first letter of the claim id
return claimId.substring(0, 1);
} else {
let i = 0;
const claimListCopy = claimList;
while (claimList.length !== 0) { // filter out matching claims
i++;
claimList = claimList.filter(claim => {
return (claim.claim_id.substring(0, i) === claimId.substring(0, i));
});
}
i -= 1;
const lastMatch = claimId.substring(0, i);
const matchingClaims = claimListCopy.filter(claim => {
return (claim.claim_id.substring(0, i) === lastMatch);
});
for (let j = 0; j < matchingClaims.length; j++) {
if (matchingClaims[j].height < thisClaim.height) {
return claimId.substring(0, i + 1);
}
}
return claimId.substring(0, i);
}
}
function getClaimAndUpdate (uri, address, height) {
// 1. get the claim
lbryApi
.getClaim(uri)
.then(({ name, claim_id, outpoint, file_name, download_path, mime_type, metadata }) => {
logger.debug(' Get returned outpoint: ', outpoint);
// 2. update the entry in db
db.File
.update({
outpoint,
height, // note: height is coming from the 'resolve', not 'get'.
address, // note: address is coming from the 'resolve', not 'get'.
fileName: file_name,
filePath: download_path,
fileType: mime_type,
nsfw : metadata.stream.metadata.nsfw,
}, {
where: {
name,
claimId: claim_id,
},
})
.then(result => {
logger.debug('successfully updated mysql record', result);
})
.catch(error => {
logger.error('sequelize error', error);
});
})
.catch(error => {
logger.error(`error while getting claim for ${uri} >> `, error);
});
}
module.exports = { module.exports = {
serveFile ({ fileName, fileType, filePath }, res) { serveFile ({ fileName, fileType, filePath }, res) {
@ -29,39 +98,157 @@ module.exports = {
// send the file // send the file
res.status(200).sendFile(filePath, options); res.status(200).sendFile(filePath, options);
}, },
validateClaimId (name, claimId) { getClaimIdByShortUrl (name, shortUrl) {
const deferred = new Promise((resolve, reject) => { const deferred = new Promise((resolve, reject) => {
logger.debug('claim id length:', claimId.length); lbryApi.getClaimsList(name)
// make sure the claim id is 40 characters .then(({ claims }) => {
if (claimId.length === 40) { const regex = new RegExp(`^${shortUrl}`);
logger.debug('Claim Id length is valid.'); logger.debug('regex:', regex);
resolve(claimId); const filteredClaimsList = claims.filter(claim => {
// if the claim id is shorter than 40, check the db for the full claim id return regex.test(claim.claim_id);
} else if (claimId.length === 1) { });
logger.debug(`Finding claim id for "${name}" "${claimId}"`); logger.debug('filtered claims list', filteredClaimsList);
db.File switch (filteredClaimsList.length) {
.findOne({ case 0:
where: { reject(new Error('That is an invalid short url'));
name, break;
claimId: { $like: `${claimId}%` }, case 1:
}, resolve(filteredClaimsList[0].claim_id);
}) break;
.then(file => { default:
// if no results were found, throw an error const sortedClaimsList = filteredClaimsList.sort((a, b) => {
if (!file) { return a.height > b.height;
reject(new Error('That is not a valid short URL.')); });
resolve(sortedClaimsList[0].claim_id);
break;
} }
// if a result was found, resolve with the full claim id
logger.debug('Full claim id:', file.dataValues.claimId);
resolve(file.dataValues.claimId);
}) })
.catch(error => { .catch(error => {
reject(error); reject(error);
}); });
} else { });
logger.error('The Claim Id was neither 40 nor 1 character in length'); return deferred;
reject(new Error('That Claim Id is not valid.')); },
getShortUrlByClaimId (name, claimId) {
const deferred = new Promise((resolve, reject) => {
// get a list of all the claims
lbryApi.getClaimsList(name)
// find the smallest possible unique url for this claim
.then(({ claims }) => {
const shortUrl = determineShortUrl(claimId, claims);
resolve(shortUrl);
})
.catch(error => {
reject(error);
});
});
return deferred;
},
determineShortUrl (claimId, claimList) {
return determineShortUrl(claimId, claimList);
},
updateFileIfNeeded (uri, localOutpoint, localHeight) {
logger.debug(`Initiating resolve to check outpoint for ${uri}`);
// 1. resolve claim
lbryApi
.resolveUri(uri)
.then(result => {
// check to make sure the result is a claim
if (!result.claim) {
logger.debug('resolve did not return a claim');
return;
} }
// logger.debug('resolved result:', result);
const resolvedOutpoint = `${result.claim.txid}:${result.claim.nout}`;
const resolvedHeight = result.claim.height;
const resolvedAddress = result.claim.address;
logger.debug('database outpoint:', localOutpoint);
logger.debug('resolved outpoint:', resolvedOutpoint);
// 2. if the outpoint's match, no further work needed
if (localOutpoint === resolvedOutpoint) {
logger.debug('local outpoint matched');
// 2. if the outpoints don't match, check the height
} else if (localHeight > resolvedHeight) {
logger.debug('local height was greater than resolved height');
// 2. get the resolved claim
} else {
logger.debug(`local outpoint did not match for ${uri}. Initiating update.`);
getClaimAndUpdate(uri, resolvedAddress, resolvedHeight);
}
})
.catch(error => {
logger.error(error);
});
},
getClaimAndHandleResponse (uri, address, height, resolve, reject) {
lbryApi
.getClaim(uri)
.then(({ name, claim_id, outpoint, file_name, download_path, mime_type, metadata }) => {
// create entry in the db
logger.silly(`creating "${name}" record in File db`);
db.File
.create({
name,
claimId : claim_id,
address, // note: comes from parent 'resolve,' not this 'get' call
outpoint,
height, // note: comes from parent 'resolve,' not this 'get' call
fileName: file_name,
kauffj commented 2017-07-20 16:02:12 +02:00 (Migrated from github.com)
Review

Technically two claims could have the same height, though this is rather unlikely.

Technically two claims could have the same height, though this is rather unlikely.
filePath: download_path,
fileType: mime_type,
nsfw : metadata.stream.metadata.nsfw,
})
.then(result => {
logger.debug('successfully created mysql record');
})
.catch(error => {
logger.error('sequelize create error', error);
});
// resolve the request
resolve({
name,
claimId : claim_id,
fileName: file_name,
filePath: download_path,
fileType: mime_type,
});
})
.catch(error => {
reject(error);
});
},
getClaimAndReturnResponse (uri, address, height) {
const deferred = new Promise((resolve, reject) => {
lbryApi
.getClaim(uri)
.then(({ name, claim_id, outpoint, file_name, download_path, mime_type, metadata }) => {
// create entry in the db
logger.silly(`Creating new File record`);
db.File
.create({
name,
claimId : claim_id,
address, // note: passed as an arguent, not from this 'get' call
outpoint,
height, // note: passed as an arguent, not from this 'get' call
fileName: file_name,
filePath: download_path,
fileType: mime_type,
nsfw : metadata.stream.metadata.nsfw,
})
.then(result => {
logger.debug('Successfully created File record');
resolve(result); // note: result.dataValues ?
})
.catch(error => {
logger.debug('db.File.create error');
reject(error);
});
})
.catch(error => {
logger.debug('lbryApi.getClaim error');
reject(error);
});
}); });
return deferred; return deferred;
}, },

View file

@ -1,96 +0,0 @@
var canvas = document.getElementById('meme-canvas');
var img = document.getElementById('start-image');
var canvasWidth;
var canvasHeight;
var fontSize = 28;
var topText = document.getElementById('top-text');
var bottomText = document.getElementById('bottom-text');
var ctx = canvas.getContext('2d');
// create the canvas
img.onload = function() {
// get dimensions of the start img
canvasWidth = img.width;
canvasHeight = img.height;
// hide start image
img.hidden = true;
// size the canvas
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// draw the starting meme
drawMeme()
}
function newCanvas(image){
// hide start image
img = image;
// get dimensions of the start img
canvasHeight = canvasWidth * (img.height / img.width);
// size the canvas
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// draw the meme
drawMeme()
}
// if the text changes, re-draw the meme
topText.addEventListener('keyup', drawMeme);
bottomText.addEventListener('keyup', drawMeme);
// draw the image and draw the text over it
function drawMeme() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
ctx.lineWidth = 4;
ctx.font = fontSize + 'px sans-serif';
ctx.strokeStyle = 'black';
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
var text1 = topText.value;
text1 = text1.toUpperCase();
x = canvasWidth / 2;
y = 5;
wrapText(ctx, text1, x, y, canvasWidth, fontSize, false);
ctx.textBaseline = 'bottom';
var text2 = bottomText.value;
text2 = text2.toUpperCase();
y = canvasHeight - 5;
wrapText(ctx, text2, x, y, canvasHeight, fontSize, true);
}
function wrapText(context, text, x, y, maxWidth, lineHeight, fromBottom) {
var pushMethod = (fromBottom)?'unshift':'push';
lineHeight = (fromBottom)?-lineHeight:lineHeight;
var lines = [];
var y = y;
var line ='';
var words = text.split(' ');
for (var i = 0; i < words.length; i++) {
var testLine = line + ' ' + words[i];
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth) {
lines[pushMethod](line);
line = words[i] + ' ';
} else {
line = testLine;
}
}
lines[pushMethod](line);
for (var k in lines ) {
context.strokeText(lines[k], x, y + lineHeight * k);
context.fillText(lines[k], x, y + lineHeight * k);
}
}

View file

@ -1,50 +0,0 @@
// define variables
var socket = io();
var uploader = new SocketIOFileUpload(socket);
var stagedFiles = null;
var license = 'Creative Commons';
var nsfw = false;
var nameInput = document.getElementById("publish-name");
/* socketio-file-upload listeners */
uploader.addEventListener('start', function(event){
event.file.meta.name = nameInput.value;
event.file.meta.license = license;
event.file.meta.nsfw = nsfw;
event.file.meta.type = stagedFiles[0].type;
// re-set the html in the publish area
document.getElementById('publish-active-area').innerHTML = '<div id="publish-status"></div><div id="progress-bar"></div>';
// start a progress animation
createProgressBar(document.getElementById('progress-bar'), 12);
// google analytics
ga('send', {
hitType: 'event',
eventCategory: 'publish',
eventAction: nameInput.value
});
});
uploader.addEventListener('progress', function(event){
var percent = event.bytesLoaded / event.file.size * 100;
updatePublishStatus('File is ' + percent.toFixed(2) + '% loaded to the server');
});
/* socket.io message listeners */
socket.on('publish-status', function(msg){
updatePublishStatus(msg);
});
socket.on('publish-failure', function(msg){
document.getElementById('publish-active-area').innerHTML = '<p>' + JSON.stringify(msg) + '</p><p> --(✖╭╮✖)→ </p><strong>For help, post the above error text in the #speech channel on the <a href="https://lbry.slack.com/" target="_blank">LBRY slack</a></strong>';
});
socket.on('publish-complete', function(msg){
var publishResults;
var directUrl = '/' + msg.name + '/' + msg.result.claim_id;
// build new publish area
publishResults = '<p>Your publish is complete! View it here:</p>';
publishResults += '<p><a target="_blank" href="' + directUrl + '">spee.ch' + directUrl + '</a></p>';
publishResults += '<p><button class="copy-button">Copy to clipboard</button></p>';
publishResults += '<p><a target="_blank" href="https://explorer.lbry.io/#!/transaction/' + msg.result.txid + '">View the transaction details</a></p>';
publishResults += '<a href="/meme-fodder/play"><button>Reload</button></a></p>';
// update publish area
document.getElementById('publish-active-area').innerHTML = publishResults;
});

View file

@ -78,7 +78,6 @@ module.exports = (app, hostedContentPath) => {
res.status(400).send(error.message); res.status(400).send(error.message);
return; return;
} }
// prepare the publish parameters // prepare the publish parameters
const fileName = file.name; const fileName = file.name;
const filePath = file.path; const filePath = file.path;

View file

@ -1,25 +1,38 @@
const logger = require('winston'); const { serveClaimByName, serveClaimByClaimId, serveClaimByShortUrl } = require('../controllers/serveController.js');
const { getClaimByClaimId, getClaimByName } = require('../controllers/serveController.js');
const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js'); const { postToStats, sendGoogleAnalytics } = require('../controllers/statsController.js');
const errorHandlers = require('../helpers/libraries/errorHandlers.js'); const errorHandlers = require('../helpers/libraries/errorHandlers.js');
const { serveFile } = require('../helpers/libraries/serveHelpers.js'); const { serveFile } = require('../helpers/libraries/serveHelpers.js');
function retrieveAssetInfo (name, claimId) {
const deferred = new Promise((resolve, reject) => {
// if claim id is full 40 chars, retrieve the shortest possible url
if (claimId.length === 40) {
resolve(serveClaimByClaimId(name, claimId));
// if the claim id is shorter than 40, retrieve the full claim id & shortest possible url
} else if (claimId.length < 40) {
resolve(serveClaimByShortUrl(name, claimId));
} else {
reject(new Error('That Claim Id is longer than 40 characters.'));
}
});
return deferred;
}
module.exports = (app) => { module.exports = (app) => {
// route to serve a specific asset // route to serve a specific asset
app.get('/:name/:claim_id', ({ headers, ip, originalUrl, params }, res) => { app.get('/:name/:claim_id', ({ headers, ip, originalUrl, params }, res) => {
// google analytics // google analytics
sendGoogleAnalytics('serve', headers, ip, originalUrl); sendGoogleAnalytics('serve', headers, ip, originalUrl);
// begin image-serve processes // begin image-serve processes
getClaimByClaimId(params.name, params.claim_id) retrieveAssetInfo(params.name, params.claim_id)
.then(fileInfo => { .then((fileInfo) => {
logger.debug('file info:', fileInfo);
// check to make sure a file was found // check to make sure a file was found
if (!fileInfo) { if (!fileInfo) {
res.status(307).render('noClaims'); res.status(307).render('noClaims');
return; return;
} }
// serve the file or the show route // serve the file or the show route
if (headers['accept']) { // note: added b/c some requests errored out due to no accept param in header if (headers['accept']) {
const mimetypes = headers['accept'].split(','); const mimetypes = headers['accept'].split(',');
if (mimetypes.includes('text/html')) { if (mimetypes.includes('text/html')) {
postToStats('show', originalUrl, ip, fileInfo.name, fileInfo.claimId, 'success'); postToStats('show', originalUrl, ip, fileInfo.name, fileInfo.claimId, 'success');
@ -42,7 +55,7 @@ module.exports = (app) => {
// google analytics // google analytics
sendGoogleAnalytics('serve', headers, ip, originalUrl); sendGoogleAnalytics('serve', headers, ip, originalUrl);
// begin image-serve processes // begin image-serve processes
getClaimByName(params.name) serveClaimByName(params.name)
.then(fileInfo => { .then(fileInfo => {
// check to make sure a file was found // check to make sure a file was found
if (!fileInfo) { if (!fileInfo) {

View file

@ -1,7 +1,22 @@
const errorHandlers = require('../helpers/libraries/errorHandlers.js'); const errorHandlers = require('../helpers/libraries/errorHandlers.js');
const { getClaimByClaimId, getClaimByName, getAllClaims } = require('../controllers/serveController.js'); const { showClaimByName, showClaimByClaimId, showClaimByShortUrl, showAllClaims } = require('../controllers/showController.js');
const { postToStats, getStatsSummary, getTrendingClaims } = require('../controllers/statsController.js'); const { postToStats, getStatsSummary, getTrendingClaims } = require('../controllers/statsController.js');
function retrieveAssetInfo (name, claimId) {
const deferred = new Promise((resolve, reject) => {
// if claim id is full 40 chars, retrieve the shortest possible url
if (claimId.length === 40) {
resolve(showClaimByClaimId(name, claimId));
// if the claim id is shorter than 40, retrieve the full claim id & shortest possible url
} else if (claimId.length < 40) {
resolve(showClaimByShortUrl(name, claimId));
} else {
reject(new Error('That Claim Id is longer than 40 characters.'));
}
});
return deferred;
}
module.exports = (app) => { module.exports = (app) => {
// route to show 'about' page for spee.ch // route to show 'about' page for spee.ch
app.get('/about', ({ ip, originalUrl }, res) => { app.get('/about', ({ ip, originalUrl }, res) => {
@ -34,22 +49,10 @@ module.exports = (app) => {
errorHandlers.handleRequestError(error, res); errorHandlers.handleRequestError(error, res);
}); });
}); });
// route to show the meme-fodder meme maker
app.get('/meme-fodder/play', ({ ip, originalUrl }, res) => {
// get and render the content
getAllClaims('meme-fodder')
.then(orderedFreePublicClaims => {
postToStats('show', originalUrl, ip, null, null, 'success');
res.status(200).render('memeFodder', { claims: orderedFreePublicClaims });
})
.catch(error => {
errorHandlers.handleRequestError('show', originalUrl, ip, error, res);
});
});
// route to display all free public claims at a given name // route to display all free public claims at a given name
app.get('/:name/all', ({ ip, originalUrl, params }, res) => { app.get('/:name/all', ({ ip, originalUrl, params }, res) => {
// get and render the content // get and render the content
getAllClaims(params.name) showAllClaims(params.name)
.then(orderedFreePublicClaims => { .then(orderedFreePublicClaims => {
if (!orderedFreePublicClaims) { if (!orderedFreePublicClaims) {
res.status(307).render('noClaims'); res.status(307).render('noClaims');
@ -65,8 +68,9 @@ module.exports = (app) => {
// route to show a specific asset // route to show a specific asset
app.get('/show/:name/:claim_id', ({ ip, originalUrl, params }, res) => { app.get('/show/:name/:claim_id', ({ ip, originalUrl, params }, res) => {
// begin image-serve processes // begin image-serve processes
getClaimByClaimId(params.name, params.claim_id) retrieveAssetInfo(params.name, params.claim_id)
.then(fileInfo => { .then((fileInfo) => {
console.log('SHORT URL:', fileInfo.shortUrl);
// check to make sure a file was found // check to make sure a file was found
if (!fileInfo) { if (!fileInfo) {
res.status(307).render('noClaims'); res.status(307).render('noClaims');
@ -77,13 +81,13 @@ module.exports = (app) => {
res.status(200).render('show', { fileInfo }); res.status(200).render('show', { fileInfo });
}) })
.catch(error => { .catch(error => {
errorHandlers.handleRequestError('serve', originalUrl, ip, error, res); errorHandlers.handleRequestError('show', originalUrl, ip, error, res);
}); });
}); });
// route to show the winning free, public claim // route to show the winning free, public claim
app.get('/show/:name', ({ ip, originalUrl, params }, res) => { app.get('/show/:name', ({ ip, originalUrl, params }, res) => {
// get and render the content // get and render the content
getClaimByName(params.name) showClaimByName(params.name)
.then(fileInfo => { .then(fileInfo => {
// check to make sure a file was found // check to make sure a file was found
if (!fileInfo) { if (!fileInfo) {
@ -95,7 +99,7 @@ module.exports = (app) => {
res.status(200).render('show', { fileInfo }); res.status(200).render('show', { fileInfo });
}) })
.catch(error => { .catch(error => {
errorHandlers.handleRequestError('serve', originalUrl, ip, error, res); errorHandlers.handleRequestError('show', originalUrl, ip, error, res);
}); });
}); });
}; };

View file

@ -1,15 +0,0 @@
<div class="wrapper">
{{> topBar}}
<div class="full">
{{> memeFodderMaker}}
{{> memeFodderResults}}
</div>
{{> footer}}
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="/siofu/client.js"></script>
<script src="/assets/js/generalFunctions.js"></script>
<script src="/assets/js/memeFodder-draw.js"></script>
<script src="/assets/js/memeFodder-publish.js"></script>

View file

@ -6,19 +6,21 @@
<h2 class="subheader">Links</h2> <h2 class="subheader">Links</h2>
{{!--short direct link to asset--}} {{!--short direct link to asset--}}
<div class="share-option"> <div class="share-option">
<a href="/{{fileInfo.name}}/{{firstCharacter fileInfo.claimId}}">Direct Link</a> <a href="/{{fileInfo.name}}/{{fileInfo.shortUrl}}">Permanent Short Link</a>
<div class="input-error" id="input-error-copy-direct-link" hidden="true"></div> (most convenient)
<div class="input-error" id="input-error-copy-short-link" hidden="true"></div>
<br/> <br/>
<input type="text" id="direct-link" class="link" readonly spellcheck="false" value="https://spee.ch/{{fileInfo.name}}/{{firstCharacter fileInfo.claimId}}" onclick="select()"/> <input type="text" id="short-link" class="link" readonly spellcheck="false" value="https://spee.ch/{{fileInfo.name}}/{{fileInfo.shortUrl}}" onclick="select()"/>
<button class="copy-button" data-elementtocopy="direct-link" onclick="copyToClipboard(event)">copy</button> <button class="copy-button" data-elementtocopy="short-link" onclick="copyToClipboard(event)">copy</button>
</div> </div>
{{!-- link to show route for asset--}} {{!-- link to show route for asset--}}
<div class="share-option"> <div class="share-option">
<a href="/show/{{fileInfo.name}}/{{firstCharacter fileInfo.claimId}}">Details Link</a> <a href="/{{fileInfo.name}}/{{fileInfo.claimId}}">Permanent Long Link</a>
<div class="input-error" id="input-error-copy-show-link" hidden="true"></div> (fastest service)
<div class="input-error" id="input-error-copy-long-link" hidden="true"></div>
</br> </br>
<input type="text" id="show-link" class="link" readonly onclick="select()" spellcheck="false" value="https://spee.ch/show/{{fileInfo.name}}/{{firstCharacter fileInfo.claimId}}"/> <input type="text" id="long-link" class="link" readonly onclick="select()" spellcheck="false" value="https://spee.ch/{{fileInfo.name}}/{{fileInfo.claimId}}"/>
<button class="copy-button" data-elementtocopy="show-link" onclick="copyToClipboard(event)">copy</button> <button class="copy-button" data-elementtocopy="long-link" onclick="copyToClipboard(event)">copy</button>
</div> </div>
{{!-- html text for embedding asset--}} {{!-- html text for embedding asset--}}
<div class="share-option"> <div class="share-option">

View file

@ -1,31 +0,0 @@
<div class="panel" id="meme-fodder-maker">
<div>
<h2>#LBRYMemeFodder</h2>
<h3>Congratulations, you found the /meme-fodder game!</h3>
<p>Create a meme based on the current <i>lbry://meme-fodder</i> claims using the tool below.</p>
</div>
<div class="col-left">
<canvas id="meme-canvas">
If you can see this, the meme generator is not supported by your browser.
</canvas>
<img id="start-image" src="/meme-fodder" alt="a picture to make your meme with"/>
</div>
<div class="col-right">
<textarea id="direct-link-holder" hidden="true">No URL yet</textarea>
<div id="publish-active-area">
<p>
<label>Meme:</label><br/>
<input id="top-text" type="text" value="Hello" /><br/>
<input id="bottom-text" type="text" value="world!" /><br/>
</p>
<p>
<label for="publish-name">Claim Name:</label></br>
<input id="publish-name" type="text" placeholder="Your claim name" />
</p>
<p>
<button onclick="startPublish()">Save and Publish</button>
</p>
</div>
</div>
<p class="stop-float">Got a masterpiece? <a href="https://twitter.com/hashtag/LBRYMemeFodder" target="_blank">Share it with the community</a> and see what they think!</p>
</div>

View file

@ -1,11 +0,0 @@
<div class="stop-float panel">
<h2>Recent Meme Fodder</h2>
<div class="row">
<p>Below are the free, public images published to <a href="lbry://meme-fodder">lbry://meme-fodder</a>. Want to put a different image on the chopping block? Go publish it!</p>
</div>
<div class="row">
{{#each claims}}
<img class="asset-small" src="/{{this.name}}/{{this.claim_id}}" onclick="newCanvas(this)"/>
{{/each}}
</div>
</div>