"use strict"; require("dotenv").config(); require("date-format-lite"); // P A C K A G E S const async = require("async"); const color = require("colorette"); const cors = require("cors"); const dedent = require("dedent"); const fastify = require("fastify")({ logger: { level: "warn", prettyPrint: process.env.NODE_ENV === "development" ? true : false } }); const html = require("choo/html"); const local = require("app-root-path").require; const octokit = require("@octokit/rest")(); const redis = require("redis"); const request = require("request-promise-native"); // V A R I A B L E S const fetchMetadata = local("app/helpers/fetch-metadata"); const github = local("app/helpers/github"); const log = console.log; // eslint-disable-line const logSlackError = local("app/helpers/slack"); const relativeDate = local("app/modules/relative-date"); let client; if (typeof process.env.GITHUB_OAUTH_TOKEN !== "undefined") { octokit.authenticate({ type: "oauth", token: process.env.GITHUB_OAUTH_TOKEN }); } else log(`${color.red("[missing]")} GitHub token`); if (typeof process.env.REDISCLOUD_URL !== "undefined") { client = redis.createClient(process.env.REDISCLOUD_URL); client.on("error", redisError => { process.env.NODE_ENV === "development" ? log(`\n${color.yellow("Unable to connect to Redis client.")}\nYou may be missing an .env file or your connection was reset.`) : logSlackError( "\n" + "> *REDIS ERROR:* ```" + JSON.parse(JSON.stringify(redisError)) + "```" + "\n" + "> _Cause: Someone is trying to run LBRY.tech locally without environment variables OR Heroku is busted_\n" ) ; }); } else log(`${color.red("[missing]")} Redis client URL`); // P R O G R A M fastify.use(cors()); fastify.register(require("fastify-compress")); fastify.register(require("fastify-ws")); fastify.register(require("fastify-helmet"), { hidePoweredBy: { setTo: "LBRY" } }); fastify.register(require("fastify-static"), { prefix: "/assets/", root: `${__dirname}/app/dist/` }); fastify.register(require("choo-ssr/fastify"), { app: require("./app") }); fastify.ready(err => { if (err) throw err; fastify.ws.on("connection", socket => { socket.on("message", data => { data = JSON.parse(data); switch(data.message) { case "fetch metadata": fetchMetadata(data, socket); break; case "landed on homepage": generateGitHubFeed(result => { socket.send(JSON.stringify({ "html": result, "message": "updated html", "selector": "#github-feed" })); }); break; case "landed on tour": generateContent(1, result => { socket.send(JSON.stringify({ "html": result, "message": "updated html", "selector": "#tour-loader" })); }); break; case "request for tour, example 1": generateContent(1, result => { socket.send(JSON.stringify({ "html": result, "message": "updated html", "selector": "#tour-loader" })); }); break; case "request for tour, example 2": generateMemeCreator(socket); break; case "request for tour, example 3": generateContent(3, result => { socket.send(JSON.stringify({ "html": result, "message": "updated html", "selector": "#tour-loader" })); }); break; case "subscribe": newsletterSubscribe(data, socket); break; default: log(data); break; } }); socket.on("close", () => { // console.log(socket); return socket.terminate(); }); }); }); // B E G I N const start = async () => { try { await fastify.listen(process.env.PORT || 8080, process.env.IP || "0.0.0.0"); /* await fastify.listen( process.env.NODE_ENV === "development" ? 8080 : process.env.PORT ); */ } catch (err) { fastify.log.error(err); process.exit(1); } process.env.NODE_ENV === "development" ? log(`\n— ${color.green("⚡")} ${fastify.server.address().port}\n`) : logSlackError(`Server started at port \`${fastify.server.address().port}\``) ; }; start(); // H E L P E R S function generateGitHubFeed(displayGitHubFeed) { if (typeof process.env.REDISCLOUD_URL !== "undefined") { client.zrevrange("events", 0, 9, (err, reply) => { if (err) return; // TODO: Render a div with nice error message const events = []; const renderedEvents = []; reply.forEach(item => events.push(JSON.parse(item))); for (const event of events) { renderedEvents.push(`

${github.generateEvent(event)} ${event.repo.name} ${relativeDate(new Date(event.created_at))}

`); } updateGithubFeed(); // TODO: Update `.last-updated` every minute displayGitHubFeed(dedent`

GitHub

Last updated: ${new Date().format("YYYY-MM-DD").replace(/-/g, "·")} at ${new Date().add(-4, "hours").format("UTC:H:mm:ss A").toLowerCase()} EST
${renderedEvents.join("")} `); }); } } function generateMemeCreator(socket) { const images = [ { alt: "Carl Sagan", src: "/assets/media/images/carlsagan2.jpg" }, { alt: "Doge", src: "/assets/media/images/doge-meme.jpg" }, { alt: "LBRY Logo With Green Background", src: "/assets/media/images/lbry-green.png" } ]; const memePlaceholderData = { bottomLine: { placeholder: "Top line", value: "that I made" }, description: { placeholder: "Description", value: "Check out this image I published to LBRY via lbry.tech" }, topLine: { placeholder: "Top line", value: "This is an example meme" }, title: { placeholder: "Title", value: "Dank Meme Supreme da Cheese" } }; const renderedImages = []; for (const image of images) { renderedImages.push(`${image.alt}`); } const memeCreator = html`
Unfortunately, it looks like canvas is not supported in your browser ${renderedImages}

Image Text

Metadata

`; return socket.send(JSON.stringify({ "html": memeCreator, "message": "updated html", "selector": "#tour-loader" })); } function generateContent(exampleNumber, displayTrendingContent) { if (exampleNumber === 1) { return getTrendingContent().then(response => { if (!response || !response.success || response.success !== true || !response.data) return ""; const rawContentCollection = []; const renderedContentCollection = []; const trendingContentData = response.data; for (const data of trendingContentData) { rawContentCollection.push(fetchMetadata({ claim: data.url, method: "resolve", example: exampleNumber })); } Promise.all(rawContentCollection).then(collection => { for (const part of collection) { if ( !part.value.stream.metadata.nsfw && part.value.stream.metadata.thumbnail && part.channel_name ) { renderedContentCollection.push(`
${part.name}
${part.value.stream.metadata.title} ${part.channel_name}
`); } } displayTrendingContent(renderedContentCollection.join("")); }); }); } if (exampleNumber === 3) { const approvedUrls = [ "LBRY#3db81c073f82fd1bb670c65f526faea3b8546720", "correlation-can-imply-causation#173412f5b1b7aa63a752e8832406aafd9f1ecb4e", "thanos-is-the-protagonist-how-infinity#2a7f5db2678177435b1dee6c9e38e035ead450b6nyte", "epic-arcade-mode-duos-nickatnyte-molt#d81bac6d49b1f92e58c37a5f633a27a45b43405e", "political-correctness-a-force-for-good-a#b4668c0bd096317b44c40738c099b6618095e75f", "10-secrets-hidden-inside-famous-logos#007789cc45cbb4255cf02ba77cbf84ca8e3d7561", "ever-wonder-how-bitcoin-and-other#1ac47b8b3def40a25850dc726a09ce23d09e7009", "bankrupt-pan-am#784b3c215a6f06b663fc1aa292bcb19f29c489bb", "minecraft-in-real-life-iron-man#758dd6497cdfc401ae1f25984738d024d47b50af", "ethan-shows-kyle-warframe-skyvault#8a7401b88d5ed0376d98f16808194d4dcb05b284" ]; const rawContentCollection = []; const renderedContentCollection = []; for (const url of approvedUrls) { rawContentCollection.push(fetchMetadata({ claim: url, method: "resolve", example: exampleNumber })); } Promise.all(rawContentCollection).then(collection => { for (const part of collection) { if ( part && part.value && part.value.stream.metadata.thumbnail && part.channel_name ) { renderedContentCollection.push(`
${part.name}
${part.value.stream.metadata.title} ${part.channel_name}
`); } } displayTrendingContent(renderedContentCollection.join("")); }); } } function getTrendingContent() { return new Promise((resolve, reject) => { // eslint-disable-line request({ method: "GET", url: "https://api.lbry.io/file/list_trending" }, (error, response, body) => { if (error || !JSON.parse(body)) resolve("Issue fetching content"); // error body = JSON.parse(body); resolve(body); }); }); } function newsletterSubscribe(data, socket) { const email = data.email; if (!validateEmail(email)) return socket.send(JSON.stringify({ "html": "Your email is invalid", "message": "updated html", "selector": "#emailMessage" })); return new Promise((resolve, reject) => { request({ method: "POST", url: `https://api.lbry.io/list/subscribe?email=${email}&tag=developer` }).then(body => { if (!body || !JSON.parse(body)) { logSlackError( "\n" + "> *NEWSLETTER ERROR:* ```¯\\_(ツ)_/¯ This should be an unreachable error```" + "\n" + `> _Cause: ${email} interacted with the form_\n` ); return resolve(socket.send(JSON.stringify({ "html": "Something is terribly wrong", "message": "updated html", "selector": "#emailMessage" }))); } body = JSON.parse(body); if (!body.success) { logSlackError( "\n" + "> *NEWSLETTER ERROR:* ```" + JSON.parse(JSON.stringify(body.error)) + "```" + "\n" + `> _Cause: ${email} interacted with the form_\n` ); return reject(socket.send(JSON.stringify({ "html": body.error, "message": "updated html", "selector": "#emailMessage" }))); } return resolve(socket.send(JSON.stringify({ "html": "Thank you! Please confirm subscription in your inbox.", "message": "updated html", "selector": "#emailMessage" }))); }).catch(welp => { if (welp.statusCode === 409) { logSlackError( "\n" + "> *NEWSLETTER ERROR:* ```" + JSON.parse(JSON.stringify(welp.error)) + "```" + "\n" + `> _Cause: ${email} interacted with the form_\n` ); return resolve(socket.send(JSON.stringify({ "html": "You have already subscribed!", "message": "updated html", "selector": "#emailMessage" }))); } }); }); } function updateGithubFeed() { octokit.activity.getEventsForOrg({ org: "lbryio", per_page: 20, page: 1 }).then(({ data }) => { async.eachSeries(data, (item, callback) => { const eventString = JSON.stringify(item); client.zrank("events", eventString, (err, reply) => { if (reply === null) client.zadd("events", item.id, eventString, callback); else callback(); }); }, () => client.zremrangebyrank("events", 0, -51)); // Keep the latest 50 events }).catch(err => { logSlackError( "\n" + "> *GITHUB FEED ERROR:* ```" + JSON.parse(JSON.stringify(err)) + "```" + "\n" + "> _Cause: GitHub feed refresh_\n" ); }); } function validateEmail(email) { const re = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\\.,;:\s@"]{2,})$/i; return re.test(String(email)); }