UNPKG

minecraft-wrap

Version:

Download and wrap the vanilla minecraft server in node.js

162 lines (149 loc) 5.42 kB
const path = require('path') const fs = require('mz/fs') const promisify = require('es6-promisify') const mkdirp = promisify(require('mkdirp')) const spawn = require('child_process').spawn const rimraf = require('rimraf') const EventEmitter = require('events').EventEmitter const defaultServerProps = { 'generator-settings': '', 'op-permission-level': '4', 'allow-nether': 'true', 'level-name': 'world', 'enable-query': 'false', 'allow-flight': 'false', 'announce-player-achievements': true, 'server-port': '25565', 'level-type': 'DEFAULT', 'enable-rcon': 'false', 'force-gamemode': 'false', 'level-seed': '', 'server-ip': '', 'max-build-height': '256', 'spawn-npcs': 'true', 'white-list': 'false', 'spawn-animals': 'true', hardcore: 'false', 'snooper-enabled': 'true', 'online-mode': 'true', 'resource-pack': '', pvp: 'true', difficulty: '1', 'enable-command-block': 'false', gamemode: '0', 'player-idle-timeout': '0', 'max-players': '20', 'spawn-monsters': 'true', 'generate-structures': 'true', 'view-distance': '10', 'spawn-protection': '16', motd: 'A Minecraft Server', 'use-native-transport': 'false' } const TOO_OLD_JAVA_REGEX = /has been compiled by a more recent version of the Java Runtime \(class file version (\d+\.\d+)\), this version of the Java Runtime only recognizes class file versions up to (\d+\.\d+)/ class WrapServer extends EventEmitter { constructor (MC_SERVER_JAR, MC_SERVER_PATH, OPTIONS) { super() this.MC_SERVER_JAR = MC_SERVER_JAR this.MC_SERVER_PATH = MC_SERVER_PATH this.OPTIONS = { minMem: (OPTIONS && OPTIONS.minMem) ? OPTIONS.minMem : '1024', maxMem: (OPTIONS && OPTIONS.maxMem) ? OPTIONS.maxMem : '1024', doneRegex: (OPTIONS && OPTIONS.doneRegex) ? OPTIONS.doneRegex : /\[.+\]: Done/, noOverride: OPTIONS && OPTIONS.noOverride, javaPath: OPTIONS && OPTIONS.javaPath } } stopServer (done) { if (!this.mcServer) { done() return } this.mcServer.stdin.write('stop\n') const timeoutHandle = setTimeout(() => { console.warn('Server shutdown took to long. Killing process.') this.mcServer.kill() }, 10000) this.mcServer.on('close', () => { this.mcServer = null clearTimeout(timeoutHandle) done() }) } writeServer (line) { this.mcServer.stdin.write(line) } deleteServerData (done) { rimraf(this.MC_SERVER_PATH, done) } startServer (propOverrides, done) { const props = {} Object.keys(defaultServerProps).forEach(prop => { props[prop] = defaultServerProps[prop] }) Object.keys(propOverrides).forEach(prop => { props[prop] = propOverrides[prop] }) mkdirp(this.MC_SERVER_PATH) .then(() => this.OPTIONS.noOverride ? Promise.resolve() : Promise.all([ fs.writeFile(path.join(this.MC_SERVER_PATH, 'server.properties'), Object.keys(props).map(prop => prop + '=' + props[prop] + '\n').join('')), fs.writeFile(path.join(this.MC_SERVER_PATH, 'eula.txt'), 'eula=true'), fs.writeFile(path.join(this.MC_SERVER_PATH, 'banned-players.json'), '[]'), fs.writeFile(path.join(this.MC_SERVER_PATH, 'banned-ips.json'), '[]'), fs.writeFile(path.join(this.MC_SERVER_PATH, 'ops.json'), '[]'), fs.writeFile(path.join(this.MC_SERVER_PATH, 'whitelist.json'), '[]') ])) .then(() => { return fs.exists(this.MC_SERVER_JAR).then(exists => { if (!exists) { throw new Error('The file ' + this.MC_SERVER_JAR + " doesn't exist.") } }) }) .then(() => new Promise((resolve, reject) => { this.mcServer = spawn(this.OPTIONS.javaPath ?? 'java', [ '-jar', '-Xms' + this.OPTIONS.minMem + 'M', '-Xmx' + this.OPTIONS.maxMem + 'M', this.MC_SERVER_JAR, 'nogui' ], { stdio: 'pipe', detached: false, cwd: this.MC_SERVER_PATH }) this.mcServer.stdin.setEncoding('utf8') this.mcServer.stdout.setEncoding('utf8') this.mcServer.stderr.setEncoding('utf8') let buffer = '' this.mcServer.stdout.on('data', onData) this.mcServer.stderr.on('data', onData) const self = this function onData (data) { buffer += data const lines = buffer.split('\n') const len = lines.length - 1 for (let i = 0; i < len; ++i) { self.mcServer.emit('line', lines[i]) } buffer = lines[lines.length - 1] } this.mcServer.on('line', onLine) this.mcServer.on('line', (line) => { this.emit('line', line) }) function onLine (line) { const regex = self.OPTIONS.doneRegex if (regex.test(line)) { self.mcServer.removeListener('line', onLine) return resolve() } else if (/FAILED TO BIND TO PORT/.test(line)) { return reject(new Error('failed to bind to port')) } else if (TOO_OLD_JAVA_REGEX.test(line)) { const matches = line.match(TOO_OLD_JAVA_REGEX) return reject(new Error(`Java too old, have java for class files version ${matches[1]}, need java for class files version ${matches[2]}`)) } } })) .then(() => done()) .catch(err => done(err)) } } module.exports = WrapServer