diff --git a/app/server/bot/events/chat.js b/app/server/bot/events/chat.js new file mode 100644 index 0000000..145c5bb --- /dev/null +++ b/app/server/bot/events/chat.js @@ -0,0 +1,10 @@ +var escapeHtml = require('../../utils').escapeHtml; + +module.exports = function(socket) { + + socket.mcbot.on('chat', function(username, message) { + var text = '<' + username + '> ' + message; + socket.emit('bot:message', escapeHtml(text)); + }); + +}; diff --git a/app/server/bot/events/message.js b/app/server/bot/events/message.js index 57fca6a..2798d06 100644 --- a/app/server/bot/events/message.js +++ b/app/server/bot/events/message.js @@ -1,4 +1,7 @@ -var stringToCode = require('../../utils').stringToCode; +var parseVanilla = require('../../parsers/vanilla'); +var parseExtra = require('../../parsers/extra'); + + var escapeHtml = require('../../utils').escapeHtml; module.exports = function(socket) { @@ -9,76 +12,30 @@ module.exports = function(socket) { // empty buffer var buffer = ''; - // modded servers match here + + + + // parse for json objects with 'extra' if (message.extra) { + buffer = parseExtra(message.extra); - // for each piece of text - message.extra.forEach(function(data) { - - // get the text out of the element - var text; - if (typeof data === 'string') { - text = data; - } else if (typeof data === 'object') { - text = data.text; - } - - // if text is available - if (text) { - text = text.replace(/§k/ig, ''); // remove crazy format - text = text.replace(/§l/ig, ''); // remove bold format - buffer += '§' + stringToCode(data.color) + text; // add the text to the buffer - } - - }); - - // vanilla server matches here - } else if (message.with) { - var text; - - switch (message.translate) { - case 'chat.type.announcement': - text = '§d[' + message.with[0].text + '] '; - message.with[1].extra.forEach(function(x) { - text += x; - }); - break; - case 'chat.type.admin': - if (message.with[1].translate === 'commands.op.success') { - text = '§eOpped ' + message.with[1].with; - } - break; - case 'commands.players.list': - text = '§eThere are ' + message.with[0] + '/' + message.with[1] + ' players online.'; - break; - case 'commands.kick.success': - text = '§e' + message.with[0] + ' kicked!'; - break; - case 'commands.whitelist.add.success': - text = '§f' + message.with[0] + ' whitelisted'; - break; - case 'commands.whitelist.remove.success': - text = '§f' + message.with[0] + ' removed from whitelist'; - break; - case 'commands.whitelist.list': - text = '§f' + message.with[0] + ' players in the whitelist'; - break; - case 'commands.generic.usage': - text = '§cInvalid command usage'; - break; - } - - buffer += text || '[i] [MinecraftChat] Unknown data received from server. [Unsuported Server]'; - + // if the text comes clean } else if (message.text) { - buffer += message.text; + buffer = message.text; + + // if the message is vanilla + } else if (message.translate) { + buffer = parseVanilla(message); + + // the message format is not handled (yet) } else { + console.log(message); return; } - if (buffer.length === 0) { - return; - } + + // if none of the parsers returned anything, stop here + if (!buffer) return; // escape any html in the buffer buffer = escapeHtml(buffer); diff --git a/app/server/bot/index.js b/app/server/bot/index.js index d657cdd..ef2b96f 100644 --- a/app/server/bot/index.js +++ b/app/server/bot/index.js @@ -4,6 +4,7 @@ module.exports = function(socket) { require('./events/login')(socket); require('./events/spawn')(socket); require('./events/message')(socket); + // require('./events/chat')(socket); require('./events/end')(socket); }; diff --git a/app/server/parsers/extra.js b/app/server/parsers/extra.js new file mode 100644 index 0000000..d947d87 --- /dev/null +++ b/app/server/parsers/extra.js @@ -0,0 +1,29 @@ +var stringToCode = require('../utils').stringToCode; + +module.exports = function(extra) { + + var string = ''; + + // for each piece of text + extra.forEach(function(data) { + + // get the text out of the element + var text; + if (typeof data === 'string') { + text = data; + } else if (typeof data === 'object') { + text = data.text; + } + + // if text is available + if (text) { + text = text.replace(/§k/ig, ''); // remove crazy format + text = text.replace(/§l/ig, ''); // remove bold format + string += '§' + stringToCode(data.color) + text; // add the color code to the string + } + + }); + + return string; + +}; diff --git a/app/server/parsers/vanilla.js b/app/server/parsers/vanilla.js new file mode 100644 index 0000000..0d1dab0 --- /dev/null +++ b/app/server/parsers/vanilla.js @@ -0,0 +1,283 @@ +var stringToCode = require('../utils').stringToCode; +var parseExtra = require('../parsers/extra'); + +module.exports = function(jsonMsg) { + + console.log(jsonMsg); + + var buffer = ''; + var color = stringToCode(jsonMsg.color); + + switch (jsonMsg.translate) { + + case 'chat.type.text': + username = jsonMsg.with[0].text; + msg = jsonMsg.with[1]; + return '§' + color + '<'+username+'> ' + msg; + + + case 'chat.type.announcement': + sender = jsonMsg.with[0]; + broadcast = parseExtra(jsonMsg.with[1].extra); + return '§' + color + '['+sender+'] ' + broadcast; + + + case 'commands.generic.notFound': + return '§' + color + 'Unknown command. Try /help for a list of commands'; + + + case 'commands.players.list': + connected = jsonMsg.with[0]; + max = jsonMsg.with[1]; + return '§' + color + 'There are '+connected+'/'+max+' players online:'; + + + case 'commands.help.header': + current = jsonMsg.with[0]; + pages = jsonMsg.with[1]; + return '§' + color + '--- Showing help page '+current+' of '+pages+' (/help ) ---'; + + + case 'commands.generic.usage': + return '§' + color + 'Usage: ' + parseCommandUsage(jsonMsg.with[0].translate); + + + case 'multiplayer.player.left': + player = jsonMsg.with[0].text; + return '§' + color + player + ' left the game'; + + + case 'multiplayer.player.joined': + player = jsonMsg.with[0].text; + return '§' + color + player + ' joined the game'; + + + case 'chat.type.admin': + return '§' + color + '[' + jsonMsg.with[0] + ': ' + parseAdmin(jsonMsg.with[1]) + ']'; + + + case 'death.attack.mob': + victim = jsonMsg.with[0].text; + killer = jsonMsg.with[1].text; + return '§' + color + victim + ' was slain by ' + killer; + + + case 'death.attack.arrow': + victim = jsonMsg.with[0].text; + killer = jsonMsg.with[1].text; + return '§' + color + victim + ' was shot by ' + killer; + + + case 'death.attack.player': + victim = jsonMsg.with[0].text; + killer = jsonMsg.with[1].text; + return '§' + color + victim + ' was slain by ' + killer; + + + case 'death.attack.explosion.player': + victim = jsonMsg.with[0].text; + killer = jsonMsg.with[1].text; + return '§' + color + victim + ' was blown up by ' + killer; + + + case 'chat.type.achievement': + player = jsonMsg.with[0].text; + achievement = jsonMsg.with[1].extra[0].translate; + return '§' + color + player + ' has just earned the achievement ' + '§a['+parseAchievement(achievement)+']' ; + + } + + // if message is a command usage + if (/^commands\..*usage$/.test(jsonMsg.translate)) { + return '§' + color + parseCommandUsage(jsonMsg.translate); + } + +}; + + +function parseAdmin(command) { + + switch (command.translate) { + + case 'commands.downfall.success': + return 'Toggled downfall'; + + case 'commands.op.success': + return 'Opped ' + command.with[0]; + + case 'commands.tp.success': + return 'Teleported ' + command.with[0] + ' to ' + command.with[1]; + + case 'commands.kill.successful': + return 'Killed ' + command.with[0]; + + case 'commands.gamemode.success.other': + player = command.with[0]; + gamemode = command.with[1].translate.split('.')[1]; + gamemode = gamemode.charAt(0).toUpperCase() + gamemode.slice(1); + return 'Set ' + player + ' game mode to ' + gamemode + ' Mode'; + + default: + return 'Unknown admin message'; + + } + +} + + + +function parseAchievement(achievementid) { + + var achievements = { + 'achievement.acquireIron': 'Acquire Hardware', + 'achievement.bakeCake': 'The Lie', + 'achievement.blazeRod': 'Into Fire', + 'achievement.bookcase': 'Librarian', + 'achievement.breedCow': 'Repopulation', + 'achievement.buildBetterPickaxe': 'Getting an Upgrade', + 'achievement.buildFurnace': 'Hot Topic', + 'achievement.buildHoe': 'Time to Farm!', + 'achievement.buildPickaxe': 'Time to Mine!', + 'achievement.buildSword': 'Time to Strike!', + 'achievement.buildWorkBench': 'Benchmarking', + 'achievement.cookFish': 'Delicious Fish', + 'achievement.diamonds': 'DIAMONDS!', + 'achievement.diamondsToYou': 'Diamonds to you!', + 'achievement.enchantments': 'Enchanter', + 'achievement.exploreAllBiomes': 'Adventuring Time', + 'achievement.flyPig': 'When Pigs Fly', + 'achievement.fullBeacon': 'Beaconator', + 'achievement.get': 'Achievement get!', + 'achievement.ghast': 'Return to Sender', + 'achievement.killCow': 'Cow Tipper', + 'achievement.killEnemy': 'Monster Hunter', + 'achievement.killWither': 'The Beginning.', + 'achievement.makeBread': 'Bake Bread', + 'achievement.mineWood': 'Getting Wood', + 'achievement.onARail': 'On A Rail', + 'achievement.openInventory': 'Taking Inventory', + 'achievement.overkill': 'Overkill', + 'achievement.overpowered': 'Overpowered', + 'achievement.portal': 'We Need to Go Deeper', + 'achievement.potion': 'Local Brewery', + 'achievement.snipeSkeleton': 'Sniper Duel', + 'achievement.spawnWither': 'The Beginning?', + 'achievement.taken': 'Taken!', + 'achievement.theEnd': 'The End?', + 'achievement.theEnd2': 'The End.', + 'achievement.unknown': '???' + }; + + return achievements[achievementid] || '???'; +} + +function parseCommandUsage(commandId) { + + var commands = { + 'commands.achievement.usage': '/achievement [player]', + 'commands.ban.usage': '/ban [reason ...]', + 'commands.banip.usage': '/ban-ip [reason ...]', + 'commands.banlist.usage': '/banlist [ips|players]', + 'commands.blockdata.usage': '/blockdata ', + 'commands.chunkinfo.usage': '/chunkinfo [ ]', + 'commands.clear.usage': '/clear [player] [item] [data] [maxCount] [dataTag]', + 'commands.clone.usage': '/clone [mode]', + 'commands.compare.usage': '/testforblocks [mode]', + 'commands.defaultgamemode.usage': '/defaultgamemode ', + 'commands.deop.usage': '/deop ', + 'commands.difficulty.usage': '/difficulty ', + 'commands.downfall.usage': '/toggledownfall', + 'commands.effect.usage': '/effect [seconds] [amplifier] [hideParticles]', + 'commands.enchant.usage': '/enchant [level]', + 'commands.entitydata.usage': '/entitydata ', + 'commands.execute.usage': '/execute OR /execute detect ', + 'commands.fill.usage': '/fill [dataValue] [oldBlockHandling] [dataTag]', + 'commands.gamemode.usage': '/gamemode [player]', + 'commands.gamerule.usage': '/gamerule [value]', + 'commands.give.usage': '/give [amount] [data] [dataTag]', + 'commands.help.usage': '/help [page|command name]', + 'commands.kick.usage': '/kick [reason ...]', + 'commands.kill.usage': '/kill [player|entity]', + 'commands.me.usage': '/me ', + 'commands.message.usage': '/tell ', + 'commands.op.usage': '/op ', + 'commands.particle.usage': '/particle [count] [mode]', + 'commands.players.usage': '/list', + 'commands.playsound.usage': '/playsound [x] [y] [z] [volume] [pitch] [minimumVolume]', + 'commands.publish.usage': '/publish', + 'commands.replaceitem.block.usage': '/replaceitem block [amount] [data] [dataTag]', + 'commands.replaceitem.entity.usage': '/replaceitem entity [amount] [data] [dataTag]', + 'commands.replaceitem.usage': '/replaceitem ...', + 'commands.save-off.usage': '/save-off', + 'commands.save-on.usage': '/save-on', + 'commands.save.usage': '/save-all', + 'commands.say.usage': '/say ', + 'commands.scoreboard.objectives.add.usage': '/scoreboard objectives add [display name ...]', + 'commands.scoreboard.objectives.remove.usage': '/scoreboard objectives remove ', + 'commands.scoreboard.objectives.setdisplay.usage': '/scoreboard objectives setdisplay [objective]', + 'commands.scoreboard.objectives.usage': '/scoreboard objectives ...', + 'commands.scoreboard.players.add.usage': '/scoreboard players add [dataTag]', + 'commands.scoreboard.players.enable.usage': '/scoreboard players enable ', + 'commands.scoreboard.players.list.usage': '/scoreboard players list [name]', + 'commands.scoreboard.players.operation.usage': '/scoreboard players operation ', + 'commands.scoreboard.players.remove.usage': '/scoreboard players remove [dataTag]', + 'commands.scoreboard.players.reset.usage': '/scoreboard players reset [objective]', + 'commands.scoreboard.players.set.usage': '/scoreboard players set [dataTag]', + 'commands.scoreboard.players.test.usage': '/scoreboard players test ', + 'commands.scoreboard.players.usage': '/scoreboard players ...', + 'commands.scoreboard.teams.add.usage': '/scoreboard teams add [display name ...]', + 'commands.scoreboard.teams.empty.usage': '/scoreboard teams empty ', + 'commands.scoreboard.teams.join.usage': '/scoreboard teams join [player]', + 'commands.scoreboard.teams.leave.usage': '/scoreboard teams leave [player]', + 'commands.scoreboard.teams.list.usage': '/scoreboard teams list [name]', + 'commands.scoreboard.teams.option.usage': '/scoreboard teams option ', + 'commands.scoreboard.teams.remove.usage': '/scoreboard teams remove ', + 'commands.scoreboard.teams.usage': '/scoreboard teams ...', + 'commands.scoreboard.usage': '/scoreboard ...', + 'commands.seed.usage': '/seed', + 'commands.setblock.usage': '/setblock [dataValue] [oldBlockHandling] [dataTag]', + 'commands.setidletimeout.usage': '/setidletimeout ', + 'commands.setworldspawn.usage': '/setworldspawn [ ]', + 'commands.spawnpoint.usage': '/spawnpoint [player] [ ]', + 'commands.spreadplayers.usage': '/spreadplayers ', + 'commands.stats.block.clear.usage': '/stats block clear ', + 'commands.stats.block.set.usage': '/stats block set ', + 'commands.stats.block.usage': '/stats block ...', + 'commands.stats.entity.clear.usage': '/stats entity clear ', + 'commands.stats.entity.set.usage': '/stats entity set ', + 'commands.stats.entity.usage': '/stats entity ', + 'commands.stats.usage': '/stats ...', + 'commands.stop.usage': '/stop', + 'commands.summon.usage': '/summon [x] [y] [z] [dataTag]', + 'commands.tellraw.usage': '/tellraw ', + 'commands.testfor.usage': '/testfor [dataTag]', + 'commands.testforblock.usage': '/testforblock [dataValue] [dataTag]', + 'commands.time.usage': '/time ', + 'commands.title.usage': '/title ...', + 'commands.title.usage.clear': '/title clear|reset', + 'commands.title.usage.times': '/title times ', + 'commands.title.usage.title': '/title title|subtitle ', + 'commands.tp.usage': '/tp [target player] OR /tp [target player] [ ]', + 'commands.trigger.usage': '/trigger ', + 'commands.unban.usage': '/pardon ', + 'commands.unbanip.usage': '/pardon-ip
', + 'commands.weather.usage': '/weather [duration in seconds]', + 'commands.whitelist.add.usage': '/whitelist add ', + 'commands.whitelist.remove.usage': '/whitelist remove ', + 'commands.whitelist.usage': '/whitelist ', + 'commands.worldborder.add.usage': '/worldborder add [timeInSeconds]', + 'commands.worldborder.center.usage': '/worldborder centre ', + 'commands.worldborder.damage.amount.usage': '/worldborder damage amount ', + 'commands.worldborder.damage.buffer.usage': '/worldborder damage buffer ', + 'commands.worldborder.damage.usage': '/worldborder damage ', + 'commands.worldborder.set.usage': '/worldborder set [timeInSeconds]', + 'commands.worldborder.usage': '/worldborder ...', + 'commands.worldborder.warning.distance.usage': '/worldborder warning distance ', + 'commands.worldborder.warning.time.usage': '/worldborder warning time ', + 'commands.worldborder.warning.usage': '/worldborder warning ', + 'commands.xp.usage': '/xp [player] OR /xp L [player]' + }; + + return commands[commandId] || 'Unknown command usage'; + +} \ No newline at end of file diff --git a/package.json b/package.json index 65dc4c6..96f7e6a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "grunt": "^0.4.5", "grunt-browserify": "^3.7.0", "grunt-contrib-uglify": "^0.9.1", - "mineflayer": "latest", + "mineflayer": "git@github.com:andrewrk/mineflayer.git", + "node-mojangson": "^0.1.0", "socket.io": "^1.3.5" }, "devDependencies": {},