Last active
August 22, 2019 07:08
-
-
Save z-------------/cfd952688351ca96ba1c9111b2d488d2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| const needle = require("needle"); | |
| const fs = require("fs"); | |
| /* set up yargs */ | |
| const argv = require("yargs") | |
| .version(false) | |
| .usage("Usage: $0 [options]") | |
| .describe("r", "rename downloaded jar file") | |
| .alias("r", "replace") | |
| .boolean("r") | |
| .describe("k", "keep old jar file (only applicable with -r)") | |
| .alias("k", "keep") | |
| .boolean("k") | |
| .describe("d", "only list updates without downloading") | |
| .alias("d", "dry") | |
| .boolean("d") | |
| .describe("build", "specify a build number to download") | |
| .describe("R", "ignore state file") | |
| .boolean("R") | |
| .describe("v", "enable verbose output") | |
| .alias("v", "verbose") | |
| .boolean("v") | |
| .argv; | |
| /* consts */ | |
| const MSG_NO_NEW_VERSION = "No matching new version available."; | |
| /* helper functions */ | |
| const print = function(msg) { | |
| return process.stdout.write(msg); | |
| }; | |
| const pad = function(s, n, c) { | |
| s = s.toString(); | |
| return repStr(c || "0", n - s.length) + s; | |
| }; | |
| const repStr = function(c, n) { | |
| return Array(n + 1).join(c); | |
| }; | |
| const progressBar = function(p, c1, c2, l) { | |
| l = l || 75; | |
| c1 = c1 || "#"; | |
| c2 = c2 || "-"; | |
| const lm = l - 2 - 5; // 2 for the [], 5 for _000% | |
| return `[${Array(Math.round(p * lm) + 1).join(c1)}${Array(Math.round((1 - p) * lm) + 1).join(c2)}] ${pad(Math.round(p * 100), 3, " ")}%`; | |
| }; | |
| const semverGetMajor = function(v) { | |
| return v.split(".").slice(0, 2).join("."); | |
| }; | |
| const semverSplit = function(v) { | |
| return v.split(".").map(Number); | |
| }; | |
| const semverEQ = function(ver, n) { | |
| n = n || 3; | |
| if (!state.apiVersion) return true; | |
| const splitA = semverSplit(state.apiVersion); | |
| const splitB = semverSplit(ver); | |
| for (let i = 0; i < n; ++i) { | |
| if (splitA[i] !== splitB[i]) return false; | |
| } | |
| return true; | |
| }; | |
| const buildNumberGT = function(buildNumber) { | |
| return !state.buildNumber || buildNumber > state.buildNumber; | |
| }; | |
| const formatDate = date => { | |
| return { | |
| date: [pad(date.getFullYear(), 4), pad(date.getMonth() + 1, 2), pad(date.getDate(), 2)].join("-"), | |
| time: [pad(date.getHours(), 2), pad(date.getMinutes(), 2)].join(":") | |
| }; | |
| }; | |
| const formatBuildInfo = function(build) { | |
| const commits = build.changeSet.items; | |
| const dateFormatted = formatDate(new Date(build.timestamp)); | |
| let lines = []; | |
| for (let i = 0; i < commits.length; ++i) { | |
| let commentLines = commits[i].comment.split("\n").filter(commentLine => commentLine.trim().length); | |
| if (!argv.v) { | |
| commentLines.length = Math.min(commentLines.length, 1); | |
| } else if (i === 0) { | |
| commentLines[0] += " - " + dateFormatted.date + " " + dateFormatted.time; | |
| } | |
| for (let j = 1; j < commentLines.length; ++j) { | |
| commentLines[j] = repStr(" ", 15) + commentLines[j]; | |
| } | |
| lines.push(`${i === 0 ? `#${pad(build.number, 3)} ` : ""}[${commits[i].commitId.substring(0, 7)}] ${commentLines.join("\n")}\n`); | |
| } | |
| for (let i = 1; i < lines.length; ++i) { | |
| lines[i] = repStr(" ", 5) + lines[i]; | |
| } | |
| if (!lines.length) return ""; | |
| return lines.join("\n"); | |
| }; | |
| const underline = function(text) { | |
| return text + "\n" + repStr("=", text.length); | |
| }; | |
| const logVerbose = function() { | |
| if (argv.v) return console.log(...arguments); | |
| }; | |
| /* globals */ | |
| let isDownloadInProgress = false; | |
| let filename; | |
| let readStream; | |
| /* get state */ | |
| let state = { | |
| apiVersion: null, | |
| buildNumber: null | |
| }; | |
| if (!argv.R) { | |
| try { | |
| const vHistFileContents = fs.readFileSync("version_history.json", "utf-8"); | |
| const versionInfo = JSON.parse(vHistFileContents).currentVersion; | |
| const patApiVersion = /(?<=MC: )\d+\.\d+(\.\d+)?/; | |
| const patBuildNumber = /(?<=git-Paper-)\d+/; | |
| const matchApiVersion = versionInfo.match(patApiVersion); | |
| const matchBuildNumber = versionInfo.match(patBuildNumber); | |
| if (matchApiVersion) state.apiVersion = matchApiVersion[0]; | |
| if (matchBuildNumber) state.buildNumber = Number(matchBuildNumber[0]); | |
| } catch (e) { | |
| console.log("Couldn't read version history file."); | |
| } | |
| } | |
| /* hey ho */ | |
| needle("get", "https://papermc.io/js/downloads.js") | |
| .then(response => { | |
| let openCount = 0, closeCount = 0; | |
| let startIndex = -1, endIndex = -1; | |
| const body = response.body.toString(); | |
| const chars = body.split(""); | |
| for (let i = 0; i < chars.length; ++i) { | |
| const char = chars[i]; | |
| if (char === "{") { | |
| ++openCount; | |
| if (startIndex === -1) startIndex = i; | |
| } | |
| else if (char === "}") ++closeCount; | |
| if (openCount > 1 && openCount == closeCount) { | |
| endIndex = i + 1; | |
| const sub = body.substring(startIndex, endIndex) | |
| .replace(/\/\/.*/g, "") | |
| .replace(/,\s*(?=})/g, ""); | |
| const json = JSON.parse(sub); | |
| return gotDownloadsList(json); | |
| } | |
| } | |
| }); | |
| function gotDownloadsList(json) { | |
| // find matching version | |
| let matchingVersion; | |
| for (let key in json) { | |
| const apiVersion = json[key].api_version; | |
| if (json[key].api_endpoint === "paper" && semverEQ(apiVersion, 2)) { | |
| matchingVersion = apiVersion; | |
| break; | |
| } | |
| } | |
| if (!matchingVersion) return console.log(MSG_NO_NEW_VERSION); | |
| let major = semverGetMajor(matchingVersion); | |
| // get build numbers for matching version | |
| needle("get", `https://papermc.io/ci/job/Paper-${major}/api/json?tree=builds[number,timestamp,changeSet[items[comment,commitId,msg]]]`, { json: true }) | |
| .then(response => { | |
| let newerBuilds = response.body.builds | |
| .filter(build => buildNumberGT(build.number)) | |
| .filter(build => { | |
| for (let commit of build.changeSet.items) { | |
| if (commit.comment.indexOf("[CI-SKIP]") !== -1) return false; | |
| } | |
| return true; | |
| }); | |
| if (!newerBuilds.length) return console.log(MSG_NO_NEW_VERSION); | |
| print(`\n${repStr(" ", 5)}Paper ${matchingVersion}\n\n`); | |
| for (let build of newerBuilds) { | |
| const formatted = formatBuildInfo(build); | |
| if (formatted.trim().length) print(formatted + "\n"); | |
| } | |
| if (!argv.d) { | |
| const buildNumber = argv.build || newerBuilds[0].number; | |
| const url = `https://papermc.io/api/v1/paper/${matchingVersion}/${buildNumber}/download`; | |
| filename = `paper-${buildNumber}.jar`; | |
| // start downloading jar | |
| print(`Downloading ${matchingVersion} #${pad(buildNumber, 3)}...\n`); | |
| needle("head", url) | |
| .then(response => { | |
| const contentLength = Number(response.headers["content-length"]); | |
| let writeStream = fs.createWriteStream(filename); | |
| logVerbose(`Writing to ${filename}...`); | |
| readStream = needle.get(url); | |
| readStream.pipe(writeStream); | |
| readStream.on("data", () => { | |
| isDownloadInProgress = true; | |
| process.stdout.write("\r" + progressBar(writeStream.bytesWritten / contentLength, null, null, process.stdout.columns)); | |
| }); | |
| readStream.on("end", () => { | |
| if (isDownloadInProgress) { | |
| isDownloadInProgress = false; | |
| print("\nDownload complete.\n"); | |
| if (argv.r) { | |
| fs.rename("paper.jar", "paper.temp.jar", err => { | |
| if (err) { | |
| if (err.code === "ENOENT") logVerbose("No old jar to rename. Continuing."); | |
| else return console.error("Couldn't rename paper.jar.", err); | |
| } else { | |
| logVerbose("Renamed old jar to paper.temp.jar."); | |
| } | |
| fs.rename(filename, "paper.jar", err => { | |
| if (err) return console.error(`Couldn't rename ${filename}.`); | |
| logVerbose("Renamed new jar."); | |
| if (argv.k) { | |
| fs.rename("paper.temp.jar", "paper.old.jar", err => { | |
| if (err) { | |
| if (err.code === "ENOENT") logVerbose("No temp jar to rename. Continuing."); | |
| else return console.error("Couldn't rename paper.temp.jar.", err); | |
| } else { | |
| logVerbose("Renamed temp jar to paper.old.jar."); | |
| } | |
| }); | |
| } else { | |
| fs.unlink("paper.temp.jar", err => { | |
| if (err) { | |
| if (err.code === "ENOENT") logVerbose("No temp jar to delete. Continuing."); | |
| else return console.error("Couldn't delete paper.temp.jar.", err); | |
| } else { | |
| logVerbose("Deleted temp jar."); | |
| } | |
| }); | |
| } | |
| }); | |
| }); | |
| } | |
| } | |
| }); | |
| }) | |
| .catch(() => console.error(`Error downloading from ${url}.`)); | |
| } | |
| }); | |
| } | |
| process.on("SIGINT", () => { | |
| if (isDownloadInProgress) { | |
| isDownloadInProgress = false; | |
| readStream.destroy(); | |
| logVerbose("Destroyed stream."); | |
| logVerbose("Deleting partially downloaded file..."); | |
| fs.unlink(filename, err => { | |
| if (err) console.error(`Failed to delete ${filename}.`); | |
| else logVerbose(`Deleted ${filename}.`); | |
| process.exit(); | |
| }); | |
| } | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment