diff --git a/bot.js b/bot.js index 6d901ec..7fba480 100644 --- a/bot.js +++ b/bot.js @@ -1,7 +1,10 @@ const mc = require('minecraft-protocol') +const nbt = require('prismarine-nbt') const { EventEmitter } = require('events') const fs = require('fs') const path = require('path') +const fileExists = require('./util/file_exists') +const crypto = require('crypto') const plugins = [] for (const filename of fs.readdirSync(path.resolve(__dirname, 'plugins'))) { @@ -9,6 +12,32 @@ for (const filename of fs.readdirSync(path.resolve(__dirname, 'plugins'))) { plugins.push(require(path.resolve(__dirname, 'plugins', filename))) } +const usernameSchemes = { + async random_playerdata (bot, options) { + const playerDataDir = await path.join(bot.paths.persistent, 'playerdata') + if (!await fileExists(playerDataDir)) return undefined + + let subdir + try { + const subdirs = (await fs.promises.readdir(playerDataDir)).filter(filename => !filename.endsWith('_old')) + subdir = path.join(playerDataDir, subdirs[crypto.randomInt(0, subdirs.length)]) + } catch { + return undefined + } + + let data + try { + const files = await fs.promises.readdir(subdir) + const raw = await fs.promises.readFile(path.join(subdir, files[crypto.randomInt(0, files.length)])) + data = await nbt.parse(raw) + } catch { + return undefined + } + + return data?.parsed?.value?.username?.value + } +} + function createBot (options = {}) { // defaults options.username ??= 'Bot' @@ -27,6 +56,7 @@ function createBot (options = {}) { options['online-mode'].username ??= null options['online-mode'].password ??= null + options.paths ??= {} options.paths.logs ??= 'logs' options.paths.music ??= 'music' options.paths.images ??= 'images' @@ -68,13 +98,10 @@ function createBot (options = {}) { bot.loggedIn = false bot.emit('end', reason) // auto reconnect - if (bot.autoReconnect) { + if (bot.autoReconnect && !options.client) { setTimeout(() => { - if (bot.randomizeUsername && !bot['online-mode'].enabled) { options.username = options.username.slice(0, -2) + '\u00a7' + String.fromCharCode(Math.floor(Math.random() * 65535)) } - - bot._client = mc.createClient(options) - bot.emit('set_client', bot._client) - }, 6000) + bot._initClient() + }, 6000) } }) @@ -92,11 +119,25 @@ function createBot (options = {}) { bot.emit('packet.' + meta.name, data); }) }) - bot._client = options.client ?? mc.createClient(options) - for (const plugin of plugins) plugin(bot, options) // Load plugins before emitting set_client so that plugins can listen for the event + async function _initClient () { + const usernameScheme = typeof options.usernameScheme === 'function' ? options.usernameScheme : usernameSchemes[options.usernameScheme] + const username = await usernameScheme?.(bot, options) + if (username) options.username = username - bot.emit('set_client', bot._client) + bot._client = mc.createClient(options) + bot.emit('set_client', bot._client) + } + bot._initClient = _initClient + + for (const plugin of plugins) plugin(bot, options) + + if (options.client) { + bot._client = options.client + bot.emit('set_client', bot._client) + } else { + bot._initClient() + } function loadPlugin (plugin) { try { diff --git a/index.js b/index.js index 9187dc3..2ea12a4 100644 --- a/index.js +++ b/index.js @@ -14,12 +14,12 @@ async function main () { const configPath = process.argv[2] ?? 'config.json5' if (!await fileExists(configPath)) { await fs.copyFile(path.join(__dirname, 'default.json5'), configPath) - console.info('No config file was found, so a default one was created') + globalThis.console.info('No config file was found, so a default one was created') } const config = json5.parse(await fs.readFile(configPath, 'utf-8')) // logging - const logdir = config.paths.logs ?? 'logs' + const logdir = config.paths?.logs ?? 'logs' if (!await fileExists(logdir)) await fs.mkdir(logdir) const rl = readline.createInterface({ diff --git a/package-lock.json b/package-lock.json index a2ebe18..c0e21e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "name": "chipmunkbot3", "dependencies": { "@mozilla/readability": "^0.4.1", "@skeldjs/client": "^2.15.17", @@ -2413,9 +2414,10 @@ "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" }, "node_modules/matrix-js-sdk": { - "version": "31.5.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-31.5.0.tgz", - "integrity": "sha512-d8Y/Vt6PdX8leSOQ06yoArJ1xMwCzxSb1H2GzW9mtOgXnHpeYvrAuPrYr32k5hfdUAJp0xPibSqDP+/+2kCnpg==", + "version": "31.6.1", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-31.6.1.tgz", + "integrity": "sha512-XDSafXV2mrOQvkyZ8i1eDwqmicjLVTkdA2iAw1QsfAZtERz4CAlp1QJULcN1X2WhBx7pMnsN8M/k98LruUqMMQ==", + "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.12.5", "@matrix-org/matrix-sdk-crypto-wasm": "^4.6.0", @@ -5694,9 +5696,9 @@ "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" }, "matrix-js-sdk": { - "version": "31.5.0", - "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-31.5.0.tgz", - "integrity": "sha512-d8Y/Vt6PdX8leSOQ06yoArJ1xMwCzxSb1H2GzW9mtOgXnHpeYvrAuPrYr32k5hfdUAJp0xPibSqDP+/+2kCnpg==", + "version": "31.6.1", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-31.6.1.tgz", + "integrity": "sha512-XDSafXV2mrOQvkyZ8i1eDwqmicjLVTkdA2iAw1QsfAZtERz4CAlp1QJULcN1X2WhBx7pMnsN8M/k98LruUqMMQ==", "requires": { "@babel/runtime": "^7.12.5", "@matrix-org/matrix-sdk-crypto-wasm": "^4.6.0", diff --git a/plugins/kbwl.js b/plugins/kbwl.js index b7e7271..68fc28b 100644 --- a/plugins/kbwl.js +++ b/plugins/kbwl.js @@ -3,7 +3,7 @@ function inject (bot) { bot.kbwl.enabled = false bot.kbwl.players = ['_ChipMC_'] - bot._client.on('player_info', (packet) => { + bot.on('packet.player_info', (packet) => { if (bot.kbwl.enabled && packet.action === 0) { packet.data.forEach((player) => { if (player.UUID !== bot.uuid && !bot.kbwl.players.includes(player.name)) { bot.exploits.titleKick(`@a[name="${player.name.replace(/"/, '\\"')}"]`) } diff --git a/plugins/player_data.js b/plugins/player_data.js index f4c59d2..bea5da4 100644 --- a/plugins/player_data.js +++ b/plugins/player_data.js @@ -11,18 +11,9 @@ async function inject (bot) { const persistentDir = bot.paths.persistent const playerDataDir = path.join(persistentDir, 'playerdata') - if (!await fileExists(playerDataDir)) await fs.mkdir(playerDataDir) - bot.playerData = playerData bot.loadPlayerData = loadPlayerData - if (bot.loggedIn && bot.players) { - for (const player of bot.players) { - // If we logged in while the async file operations were happening (highly unlikely but possible), load the player data for the players on the server - handlePlayerAdded(player) - } - } - bot.on('player_added', handlePlayerAdded) async function handlePlayerAdded (player) { @@ -49,7 +40,7 @@ async function inject (bot) { uuid = getOfflineUUID(uuid) } - const data = new PlayerData(path.join(playerDataDir, uuid + '.dat')) + const data = new PlayerData(path.join(playerDataDir, uuid.substring(0, 2) + '/' + uuid.substring(2) + '.dat')) await data.load() return data } diff --git a/util/persistent_data.js b/util/persistent_data.js index ee20f47..c9fcccc 100644 --- a/util/persistent_data.js +++ b/util/persistent_data.js @@ -1,4 +1,5 @@ const fs = require('fs/promises') +const path = require('path') const { createWriteStream } = require('fs') const zlib = require('zlib') const stream = require('stream/promises') @@ -38,6 +39,8 @@ class PersistentData { } async save () { + await fs.mkdir(path.dirname(this.filepath), { recursive: true }) + const data = this.unparse(this.data) if (await fileExists(this.filepath)) await fs.copyFile(this.filepath, this.filepath + '_old') // Back up our data before a save