UNPKG

dango-cli

Version:

A configurable minecraft BE to Discord Bot cli tool

269 lines (232 loc) 9.04 kB
const axios = require('axios'); const AF = require('prismarine-auth'); const bedrock = require('bedrock-protocol'); const config = require('./config.json'); const { log, sendEmbed } = require('./utils'); let discordClient = null; const realmConfig = config.realms[0]; if (!realmConfig) throw new Error('No realm configuration found'); if (!realmConfig.logChannels) throw new Error('No log channels configured'); const players = new Map(); const realmClients = new Map(); const devices = [ "Undefined", "Android", "iOS", "OSX", "FireOS", "GearVR", "Hololens", "Windows", "Win32", "Dedicated", "TVOS", "PlayStation", "NintendoSwitch", "Xbox", "WindowsPhone" ]; const LEAVE_THRESHOLD = 7000; function hasInvalidCharacters(name) { const validPattern = /^[a-zA-Z0-9_-]+$/; return !validPattern.test(name); } async function isAltAccount(xuid, username, auth) { try { const response = await axios.get( `https://profile.xboxlive.com/users/xuid(${xuid})/profile/settings`, { headers: { "Authorization": `XBL3.0 x=${auth.userHash};${auth.XSTSToken}`, "Accept": "application/json", "x-xbl-contract-version": 2 }, params: { settings: 'Gamerscore,People,Followers' } } ); const settings = response.data.profileUsers[0].settings.reduce((acc, s) => { acc[s.id] = s.value; return acc; }, {}); const gamerScore = parseInt(settings.Gamerscore || "0", 10); const friendsCount = parseInt(settings.People || "0", 10); const followersCount = parseInt(settings.Followers || "0", 10); log(`Alt Check for ${username}: GS=${gamerScore}, Friends=${friendsCount}, Followers=${followersCount}`); if ( gamerScore < config.altSystem.maxGamerScore || friendsCount < config.altSystem.maxFriends || followersCount < config.altSystem.maxFollowers ) { return { isAlt: true }; } return { isAlt: false }; } catch (err) { log(`Error checking alt for ${username}: ${err.message}`); return { isAlt: false }; } } function sendCommand(client, command) { try { client.write('command_request', { command, origin: { type: 'player', uuid: uuid.v4(), request_id: uuid.v4() }, internal: true, version: 52, }); } catch (err) { log(`Error sending command: ${err.message}`); } } function sendCommandToRealm(command) { const client = realmClients.get(realmConfig.realmCode); if (!client) return; sendCommand(client, command); } function setDiscordClient(client) { discordClient = client; log('Discord client set:', discordClient ? 'Connected' : 'Not connected'); } async function spawnBot() { log(`Starting bot for realm: ${realmConfig.realmName}`); log(`Whitelist mode is: ${realmConfig.modules.whitelist}`); const authFlow = new AF.Authflow(config.username, './accounts', { authTitle: AF.Titles.MinecraftNintendoSwitch, deviceType: 'Nintendo', flow: 'live', }); const client = bedrock.createClient({ username: config.username, profilesFolder: './accounts', realms: { realmInvite: realmConfig.realmCode }, conLog: log, }); const auth = await authFlow.getXboxToken(); realmClients.set(realmConfig.realmCode, client); client.on('spawn', () => { log(`Bot spawned in realm: ${realmConfig.realmName}`); }); // Listen to Minecraft chat and forward to Discord client.on('text', (packet) => { if (!packet || !packet.message) return; // Filter chat types, only normal chat or whispers if (packet.type !== 'chat' && packet.type !== 'whisper') return; const mcMessage = packet.message; const sender = packet.source_name || 'Unknown'; log(`[MC Chat] ${sender}: ${mcMessage}`); if (!discordClient) return; const discordChannel = discordClient.channels.cache.get('YOUR_DISCORD_CHANNEL_ID'); if (discordChannel) { discordChannel.send(`**${sender}**: ${mcMessage}`).catch(err => { log(`Failed to send MC chat message to Discord: ${err.message}`); }); } }); client.on('player_list', async (packet) => { if (!packet.records || !packet.records.records) return; const currentPlayers = new Set(); for (const player of packet.records.records) { if (!player || !player.username || player.username === client.username) continue; const username = player.username; const xuid = player.xbox_user_id; const os = typeof player.build_platform === 'number' ? devices[player.build_platform] : player.build_platform; currentPlayers.add(username); // === Whitelist ON: kick anyone not whitelisted === if (realmConfig.modules.whitelist === true) { if (!config.whitelist.includes(username)) { log(`Kicking ${username} - not whitelisted`); sendCommand(client, `/kick "${username}" You are not whitelisted`); sendEmbed({ title: 'Player Kicked', description: `${username} was kicked\nReason: Not on whitelist`, color: '#FF0000', timestamp: true }, realmConfig.logChannels.kicks, discordClient); players.delete(username); continue; } else { if (!players.has(username)) { players.set(username, { data: player, lastSeen: Date.now() }); log(`Whitelisted player joined: ${username}`); sendEmbed({ title: 'Whitelisted Player Joined', description: `${username} joined.\nDevice: ${os}`, color: '#00FFFF', timestamp: true }, realmConfig.logChannels.joinsAndLeaves, discordClient); } else { players.get(username).lastSeen = Date.now(); } continue; } } // === Whitelist OFF: run normal checks === if (config.altSystem) { const altCheck = await isAltAccount(xuid, username, auth); if (altCheck.isAlt) { log(`Kicking ${username} - Alt detected`); sendCommand(client, `/kick "${username}" Alt accounts are not allowed`); sendEmbed({ title: 'Player Kicked', description: `${username} was kicked\nReason: Alt account`, color: '#FF0000', timestamp: true }, realmConfig.logChannels.kicks, discordClient); players.delete(username); continue; } } // New player join if (!players.has(username)) { players.set(username, { data: player, lastSeen: Date.now() }); log(`Player joined: ${username} on ${os}`); sendEmbed({ title: 'Player Joined', description: `${username} joined the realm\nDevice: ${os}`, color: '#00FF00', timestamp: true }, realmConfig.logChannels.joinsAndLeaves, discordClient); } else { players.get(username).lastSeen = Date.now(); } } // Leave detection for (const [username, entry] of players) { if (!currentPlayers.has(username)) { setTimeout(() => { const currentEntry = players.get(username); if (currentEntry && (Date.now() - currentEntry.lastSeen >= LEAVE_THRESHOLD)) { players.delete(username); log(`Player left: ${username}`); sendEmbed({ title: 'Player Left', description: `${username} left the realm`, color: '#FFA500', timestamp: true }, realmConfig.logChannels.joinsAndLeaves, discordClient); } }, LEAVE_THRESHOLD); } } }); client.on('error', (err) => { log(`Bot error in realm ${realmConfig.realmName}: ${err.message}`); setTimeout(spawnBot, 5000); }); client.on('kick', (reason) => { log(`Bot was kicked from realm ${realmConfig.realmName}: ${reason}`); sendEmbed({ title: 'Realm Kick', description: `Bot was kicked from the realm\nReason: ${reason}`, color: '#FF0000', timestamp: true }, realmConfig.logChannels.kicks, discordClient); setTimeout(spawnBot, 5000); }); return client; } function relayMessageFromDiscordToMinecraft(message) { const client = realmClients.get(realmConfig.realmCode); if (!client) return; try { const username = message.member?.displayName || message.author.username; const cleanMessage = message.content.replace(/[§#"\\]/g, ''); const tellrawCommand = `/tellraw @a {"rawtext":[{"text":"§9[Discord] §f${username} §8» §r${cleanMessage}"}]}`; sendCommand(client, tellrawCommand); } catch (err) { log(`Error relaying Discord message: ${err.message}`); } } module.exports = { spawnBot, relayMessageFromDiscordToMinecraft, setDiscordClient, sendCommandToRealm };