"use strict"; // P A C K A G E S import async from "async"; import color from "colorette"; import Octokit from "@octokit/rest"; import redis from "redis"; // U T I L S import messageSlack from "~helper/slack"; import relativeDate from "~module/relative-date"; let octokit; // R E D I S let client; if (process.env.GITHUB_OAUTH_TOKEN) { octokit = new Octokit({ auth: `token ${process.env.GITHUB_OAUTH_TOKEN}` }); } else process.stdout.write(`${color.red("[missing]")} GitHub token\n`); if (process.env.REDISCLOUD_URL) { client = redis.createClient(process.env.REDISCLOUD_URL); client.on("error", redisError => { process.env.NODE_ENV === "development" ? process.stdout.write(`\n${color.yellow("Unable to connect to Redis client.")}\nYou may be missing an .env file or your connection was reset.`) : messageSlack( "\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 process.stdout.write(`${color.red("[missing]")} Redis client URL\n`); // P R O G R A M function generateEvent(event) { switch(event.type) { case "CommitCommentEvent": return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> commented on <a href="${generateUrl("comment", event)}" target="_blank" title="View this comment on GitHub" rel="noopener noreferrer" >commit</a> in `; case "CreateEvent": return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> created ${event.payload.ref_type} <code><a href="${generateUrl("create", event)}" rel="noopener noreferrer" target="_blank" title="View this branch on GitHub" >${refToBranch(event.payload.ref)}</a></code> in `; case "DeleteEvent": return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> deleted ${event.payload.ref_type} <code>${event.payload.ref}</code> in `; case "ForkEvent": return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> forked <strong><a href="${generateUrl("repo", event)}" rel="noopener noreferrer" target="_blank" title="View this repo on GitHub" >${event.repo.name}</a></strong> to <strong><a href="${generateUrl("forkee", event)}" rel="noopener noreferrer" target="_blank" title="View this repo fork on GitHub" >${event.payload.forkee.full_name}</a></strong> `; case "IssueCommentEvent": if (event.payload.issue.pull_request) { return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> commented on pull request <em><a href="${generateUrl("issue", event)}" rel="noopener noreferrer" target="_blank" title="View this comment on GitHub" >${escapeSpecialCharacters(event.payload.issue.title)}</a></em> in `; } else { return ` <strong>${event.actor.display_login}</strong> commented on issue <em><a href="${generateUrl("issue", event)}" rel="noopener noreferrer" target="_blank" title="View this comment on GitHub" >${escapeSpecialCharacters(event.payload.issue.title)}</a></em> in `; } case "IssuesEvent": return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> ${event.payload.action} issue <em><a href="${generateUrl("issue", event)}" rel="noopener noreferrer" target="_blank" title="View this issue on GitHub" >${escapeSpecialCharacters(event.payload.issue.title)}</a></em> in `; case "PullRequestEvent": return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> pull request <em><a href="${generateUrl("pull_request", event)}" rel="noopener noreferrer" target="_blank" title="View this pull request on GitHub" >${escapeSpecialCharacters(event.payload.pull_request.title)}</a></em> in `; case "PullRequestReviewCommentEvent": return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> commented on pull request <em><a href="${generateUrl("pull_request", event)}" rel="noopener noreferrer" target="_blank" title="View this comment on GitHub" >${escapeSpecialCharacters(event.payload.pull_request.title)}</a></em> in `; case "PushEvent": return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> pushed to <code><a href="${generateUrl("push", event)}" rel="noopener noreferrer" target="_blank" title="View this branch on GitHub" >${refToBranch(event.payload.ref)}</a></code> in `; case "ReleaseEvent": return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> released <em><a href="${generateUrl("release", event)}" title="View this release on GitHub" target="_blank" rel="noopener noreferrer" >${event.payload.release.tag_name}</a></em> in `; case "WatchEvent": return ` <strong><a href="${generateUrl("actor", event)}" rel="noopener noreferrer" target="_blank" title="Visit ${event.actor.login}'s profile on GitHub" >${event.actor.display_login}</a></strong> starred the repo `; default: break; } } function generateGitHubFeed(displayGitHubFeed) { if (process.env.REDISCLOUD_URL) { 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) { const repoName = ` <a href="${generateUrl("repo", event)}" title="View this repo on GitHub" target="_blank" rel="noopener noreferrer"><strong>${event.repo.name}</strong></a> `; renderedEvents.push(` <div class='github-feed__event'> <a href="${generateUrl("actor", event)}" target="_blank" rel="noopener noreferrer"> <img src="${event.actor.avatar_url}" class="github-feed__event__avatar" alt="${event.actor.login}'s avatar"/> </a> <p> ${generateEvent(event)} ${event.type !== "ForkEvent" ? repoName : ""} <em class="github-feed__event__time">${relativeDate(new Date(event.created_at))}</em> </p> </div> `); } updateGithubFeed(); // TODO: Update `.last-updated` every minute displayGitHubFeed(` <h3>GitHub</h3> <h5 class="last-updated">Last updated: ${new Date().format("YYYY-MM-DD") .replace(/-/g, "·")} at ${new Date().add(-4, "hours") .format("UTC:H:mm:ss A") .toLowerCase()} EST</h5> ${renderedEvents.join("")} `); }); } } function generateUrl(type, event) { switch(type) { case "actor": return `https://github.com/${event.actor.display_login}`; case "comment": return event.payload.comment.html_url; case "create": return `https://github.com/${event.repo.name}/tree/${event.payload.ref}`; case "forkee": return `https://github.com/${event.payload.forkee.full_name}`; case "issue": return event.payload.issue.html_url; case "pull_request": return event.payload.pull_request.html_url; case "push": return `https://github.com/${event.repo.name}/tree/${event.payload.ref.replace("refs/heads/", "")}`; case "release": return event.payload.release.html_url; case "repo": return `https://github.com/${event.repo.name}`; default: break; } } function updateGithubFeed() { octokit.activity.listPublicEventsForOrg({ 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 (err) return; if (reply === null) client.zadd("events", item.id, eventString, callback); else callback(); }); }, () => client.zremrangebyrank("events", 0, -51)); // Keep the latest 50 events }) .catch(err => { messageSlack( "\n" + "> *GITHUB FEED ERROR:* ```" + JSON.parse(JSON.stringify(err)) + "```" + "\n" + "> _Cause: GitHub feed refresh_\n" ); }); } // H E L P E R function escapeSpecialCharacters(contentToEscape) { const tagsToReplace = { "&": "&", "<": "<", ">": ">" }; return contentToEscape.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag); } function refToBranch(ref) { if (ref) return ref.replace("refs/heads/", ""); } // E X P O R T S export { generateEvent, generateGitHubFeed, generateUrl, updateGithubFeed };