mirror of https://github.com/lupettohf/neobot
parent
115f2368bb
commit
6478a234d5
4 changed files with 365 additions and 1 deletions
@ -1,2 +1,29 @@ |
||||
# neobot |
||||
Minecraft trasparent proxy and wannabe AFK bot using mineflayer and minecraft-protocol |
||||
Minecraft trasparent (and persistant) proxy and wannabe AFK bot using mineflayer and minecraft-protocol. Tested and developed for 1.12.2. |
||||
|
||||
This project aims to be a a persistent proxy and a bot for Minecraft servers (mostly anarchy servers where automation is allowed). |
||||
|
||||
## Features |
||||
* Makes a persistent connection to the server like [2bored2wait](https://github.com/themoonisacheese/2bored2wait) |
||||
* Command system to manage the bot/proxy (wip) |
||||
* Fishing Bot (working even when connected to the proxy) |
||||
* AutoEat when disconnected (with mineflayer-auto-eat) |
||||
* AntiAFK (currently not working, see limitations) |
||||
|
||||
## Limits |
||||
* neobot uses the same packet caching code as 2bored2wait, so when you connect (or reconnect) some items/world data can appear missing. To fix this issue you have to reload the chunks by walking a few blocks or going though a portal |
||||
* Mineflayer is not intended to be used as a proxy to a real client, while this is possible it has some limitations, many API calls do not work or needs tweaks to work while the player is connected to the proxy, for example the AFK function uses `bot.setControlState('jump', true)` which require Phisics.js plugin (the mineflayer plugin that handles client physic), however the physics plugin must be off while the player is connected to the proxy. |
||||
* Right now the bot is developed for 1.12.2, i could work even on newer and older versions, hover due to the use of version specific packets some features need or might need tweaks to work in versions different than 1.12.2 |
||||
|
||||
## To-Do |
||||
* Configuration system |
||||
* A better command system/modular plugins |
||||
* Handling of crashes, disconnects, kicks, ban and reboots |
||||
* Webhook notifications to Discord or other services |
||||
* Fix bugs |
||||
* A total rewrite |
||||
|
||||
### Credits |
||||
[2bored2wait](https://github.com/themoonisacheese/2bored2wait) for the proxy caching library and some other code (filterpackets) |
||||
[AFKBot](https://github.com/DrMoraschi/AFKBot) for the idea |
||||
All the folks developing minecraft-protocol and mienflayer. |
||||
|
@ -0,0 +1,59 @@ |
||||
var chunkData = new Map(); |
||||
var abilitiesPacket; |
||||
var loginpacket; |
||||
var gChunkCaching; |
||||
var positionPacket; |
||||
var inventory = []; |
||||
module.exports = { |
||||
init: (client, chunkCaching) => { |
||||
gChunkCaching = chunkCaching; |
||||
client.on("packet", (data, meta, rawData) => { // each time 2b2t sends a packet
|
||||
switch (meta.name) { |
||||
case "map_chunk": |
||||
if(chunkCaching) chunkData.set(data.x + "_" + data.z, rawData); |
||||
break; |
||||
case "unload_chunk": |
||||
if(chunkCaching) chunkData.delete(data.chunkX + "_" + data.chunkZ); |
||||
break; |
||||
case "respawn": |
||||
Object.assign(loginpacket, data); |
||||
chunkData = new Map(); |
||||
inventory = []; |
||||
break; |
||||
case "login": |
||||
loginpacket = data; |
||||
break; |
||||
// Apparently this makes the game crash when no cheat plus or other anticheats are present
|
||||
//case "game_state_change":
|
||||
// loginpacket.gameMode = data.gameMode;
|
||||
// break;
|
||||
case "abilities": |
||||
abilitiesPacket = rawData; |
||||
break; |
||||
case "position": |
||||
positionPacket = rawData; |
||||
break; |
||||
case "set_slot": |
||||
if(data.windowId == 0) { // windowId 0 is the inventory
|
||||
inventory[data.slot] = data; |
||||
} |
||||
} |
||||
}); |
||||
|
||||
}, |
||||
join: (proxyClient) => { |
||||
proxyClient.write('login', loginpacket); |
||||
proxyClient.writeRaw(positionPacket); |
||||
proxyClient.writeRaw(abilitiesPacket); |
||||
inventory.forEach( (slot) => { |
||||
if(slot != null) { |
||||
proxyClient.write("set_slot", slot); |
||||
} |
||||
}); |
||||
setTimeout( () => { |
||||
if(gChunkCaching) chunkData.forEach((data) => { |
||||
proxyClient.writeRaw(data); |
||||
}); |
||||
}, 1000); |
||||
} |
||||
} |
@ -0,0 +1,248 @@ |
||||
var mineflayer = require('mineflayer') |
||||
const mc = require('minecraft-protocol'); |
||||
var autoeat = require("mineflayer-auto-eat") |
||||
var botConfig = require('./config.json') |
||||
const cachePackets = require('./cachePackets.js'); |
||||
|
||||
var proxyClient; // This is the real client (java)
|
||||
var bot; // This is the fake client connecting to the server
|
||||
var server; // The proxy
|
||||
|
||||
|
||||
var toggleAntiAFK = false; |
||||
let nowFishing = false |
||||
let mcData |
||||
|
||||
run() |
||||
|
||||
function run() |
||||
{ |
||||
log("Starting Minecraft Server"); |
||||
server = mc.createServer({
|
||||
'online-mode': true, |
||||
encryption: true, |
||||
host: '0.0.0.0', |
||||
port: 25565, |
||||
version: "1.12.2", |
||||
'max-players': maxPlayers = 1, |
||||
'motd': "§cl§6u§eh§af§9'§bs§5 §ca§6f§ek§a §9p§br§5o§cx§6y§e §as§9e§br§5v§ce§6r" |
||||
}); |
||||
|
||||
log("Starting mineflayer client"); |
||||
bot = mineflayer.createBot({ |
||||
host: "", |
||||
port: 25565, |
||||
username: "", |
||||
password: "", |
||||
plugins: { |
||||
physics: false |
||||
}, |
||||
hideErrors: true |
||||
}) |
||||
|
||||
bot.loadPlugin(autoeat); |
||||
//Loads item definition
|
||||
bot.on('inject_allowed', () => { |
||||
mcData = require('minecraft-data')(bot.version) |
||||
}) |
||||
|
||||
cachePackets.init(bot._client, true); |
||||
|
||||
bot._client.on("packet", (data, meta, rawData) => { |
||||
if(proxyClient){ filterPacketAndSend(rawData, meta, proxyClient); } |
||||
}); |
||||
|
||||
//Handles remote server quit
|
||||
bot._client.on('end', () => |
||||
{ |
||||
if(proxyClient)
|
||||
{ |
||||
proxyClient.end("Hurr durr, got disconnected form server.\nAttempting to restart"); |
||||
proxyClient = null; |
||||
} |
||||
//run();
|
||||
}); |
||||
|
||||
//Handles generic errors
|
||||
bot._client.on('error', (err) => { |
||||
if(proxyClient) |
||||
{ |
||||
proxyClient.end("Hurr durr, we got a thing here:\n"+err.toString()); |
||||
proxyClient = null; |
||||
} |
||||
}); |
||||
|
||||
server.on('listening', function () { |
||||
log('Server listening on port ' + server.socketServer.address().port) |
||||
}); |
||||
|
||||
// Here it handles packet switching to client
|
||||
server.on('login', (newProxyClient) => {
|
||||
let remoteAddress = newProxyClient.socket.remoteAddress |
||||
log('New incoming connection from: '+remoteAddress) |
||||
|
||||
if(bot.username == undefined) |
||||
{ |
||||
server.end("Wait for bot to connect."); |
||||
} |
||||
|
||||
newProxyClient.on('packet', (data, meta, rawData) => {
|
||||
filterPacketAndSend(rawData, meta, bot._client); |
||||
}); |
||||
cachePackets.join(newProxyClient); |
||||
proxyClient = newProxyClient; |
||||
|
||||
newProxyClient.on('end', function () { |
||||
log(remoteAddress + ' Closed connection'); |
||||
//bot.physics.physicEnabled = true;
|
||||
}) |
||||
|
||||
proxyClient.on('packet', (data, meta) => { |
||||
if(meta.name === "chat") |
||||
{ |
||||
switch(data.message) |
||||
{ |
||||
case ",antiafk": |
||||
//bunnyTheAntiAFK(2000)
|
||||
break; |
||||
case ",fishing on": |
||||
startFishing() |
||||
break; |
||||
case ",fishing off": |
||||
stopFishing() |
||||
break; |
||||
case ",ping": |
||||
sendMessage(newProxyClient, "Pong!"); |
||||
break; |
||||
case ",stats": |
||||
stats(); |
||||
break; |
||||
} |
||||
} |
||||
}); |
||||
|
||||
sendMessage(newProxyClient, "Welcome to luhf's afk proxy!") |
||||
//bot.physics.physicEnabled = false;
|
||||
}); |
||||
|
||||
// Relay chat to terminal
|
||||
bot.on('message', (msg) => { |
||||
log(msg.toString()); |
||||
}) |
||||
|
||||
// Send playerlist to terminal and setup autoeat
|
||||
bot.once('spawn', () => { |
||||
var playersList = Object.keys(bot.players).join(", ") |
||||
log(`Online players: ${playersList}`) |
||||
bot.autoEat.options = { |
||||
priority: "foodPoints",
|
||||
startAt: 19, |
||||
bannedFood: [], |
||||
}; |
||||
}); |
||||
|
||||
//Hook for autoeat
|
||||
bot.on("health", () => { |
||||
if (bot.food === 20) bot.autoEat.disable() |
||||
else { |
||||
sendMessage(proxyClient, "Proxy AutoEat running...")
|
||||
bot.autoEat.enable()
|
||||
} |
||||
})
|
||||
|
||||
} |
||||
|
||||
function filterPacketAndSend(data, meta, dest) { |
||||
if (meta.name !== "keep_alive" && meta.name !== "update_time") {
|
||||
dest.writeRaw(data); |
||||
} |
||||
} |
||||
|
||||
function stats() |
||||
{ |
||||
sendMessage(proxyClient, `Server version: ${bot.game.serverBrand} (${bot.player.ping} ms)\nExperience: ${bot.experience.pints} (level ${bot.experience.level})\n`); |
||||
} |
||||
|
||||
function onSoundEffect(packet) { |
||||
if(packet.soundId == 153) //This is the splash sound, this id is oly valid for 1.12.2
|
||||
{ |
||||
bot._client.removeListener('sound_effect', onSoundEffect) |
||||
setTimeout(()=> |
||||
{ |
||||
bot._client.write('use_item', {hand: 0}); |
||||
}, 350);
|
||||
setTimeout(()=> |
||||
{ |
||||
startFishing() |
||||
}, 550);
|
||||
return;
|
||||
}
|
||||
} |
||||
|
||||
async function startFishing () { |
||||
sendMessage(proxyClient, `Starting proxy fishing module.`); |
||||
try { |
||||
await bot.equip(mcData.itemsByName.fishing_rod.id, 'hand') |
||||
} catch (err) { |
||||
return bot.chat(err.message) |
||||
} |
||||
|
||||
nowFishing = true |
||||
|
||||
bot._client.on('sound_effect', onSoundEffect) |
||||
|
||||
try { |
||||
await bot.fish() |
||||
} catch (err) { |
||||
bot.chat(err.message) |
||||
} |
||||
nowFishing = false |
||||
} |
||||
|
||||
function stopFishing () { |
||||
sendMessage(proxyClient, `Stopped proxy fishing module.`); |
||||
bot.removeListener('sound_effect', onSoundEffect) |
||||
|
||||
if (nowFishing) { |
||||
bot.activateItem() |
||||
} |
||||
} |
||||
|
||||
|
||||
function bunnyTheAntiAFK(interval) |
||||
{ |
||||
if(toggleAntiAFK){
|
||||
sendMessage(proxyClient, "Anti AFK --> off"); |
||||
toggleAntiAFK = false;
|
||||
clearInterval(jumpTimeout);
|
||||
return; |
||||
} |
||||
sendMessage(proxyClient, "Anti AFK --> on"); |
||||
const jumpTimeout = setInterval(() => |
||||
{ |
||||
setTimeout(() => |
||||
{ |
||||
bot.setControlState('jump', false) |
||||
}, 200); |
||||
bot.setControlState('jump', true) |
||||
}, interval); |
||||
toggleAntiAFK = true; |
||||
} |
||||
|
||||
function sendMessage(client, message) |
||||
{ |
||||
if(client == undefined) return; |
||||
|
||||
var msg = { |
||||
translate: 'chat.type.announcement', |
||||
"with": [ |
||||
'§cS§6e§er§av§9e§br', |
||||
message |
||||
]};
|
||||
client.write('chat', { message: JSON.stringify(msg), position: 0, sender: '0'}); |
||||
} |
||||
|
||||
function log(message) |
||||
{ |
||||
console.log(message); |
||||
} |
@ -0,0 +1,30 @@ |
||||
{ |
||||
"name": "neobot", |
||||
"version": "0.1.2", |
||||
"description": "Trasparent and persistant Minecraft proxy with bot features.", |
||||
"main": "index.js", |
||||
"dependencies": { |
||||
"minecraft-protocol": "latest", |
||||
"mineflayer": "^2.29.1", |
||||
"mineflayer-auto-eat": "1.3.4" |
||||
}, |
||||
"devDependencies": {}, |
||||
"scripts": { |
||||
"start": "node index.js" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/lupettohf/neobot.git" |
||||
}, |
||||
"keywords": [ |
||||
"Minecraft", |
||||
"AFK Bot", |
||||
"Persistant Proxy" |
||||
], |
||||
"author": "lupettohf", |
||||
"license": "beerware", |
||||
"bugs": { |
||||
"url": "https://github.com/lupettohf/neobot/issues" |
||||
}, |
||||
"homepage": "https://github.com/lupettohf/neobot/" |
||||
} |
Loading…
Reference in new issue