From 2ab44224b4a1e5ed5e5d65d0c524f3f0b7d08804 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Fri, 13 Sep 2024 15:09:11 -0400 Subject: [PATCH] AI --- nodejs/controller/ai.js | 67 ++++++++++++++++++++++++ nodejs/controller/mc-bot.js | 75 +++++++++++++++++++++++---- nodejs/model/minecraft.js | 100 +++++++++++++++++++++--------------- nodejs/package-lock.json | 9 ++++ nodejs/package.json | 1 + 5 files changed, 200 insertions(+), 52 deletions(-) create mode 100644 nodejs/controller/ai.js diff --git a/nodejs/controller/ai.js b/nodejs/controller/ai.js new file mode 100644 index 0000000..17fc4b2 --- /dev/null +++ b/nodejs/controller/ai.js @@ -0,0 +1,67 @@ +'use strict'; + +const conf = require('../conf'); + +const { + GoogleGenerativeAI, + HarmCategory, + HarmBlockThreshold, +} = require("@google/generative-ai"); + +const genAI = new GoogleGenerativeAI(conf.ai.key); + +const model = genAI.getGenerativeModel({ + model: "gemini-1.5-flash", +}); + +const generationConfig = { + temperature: 1, + topP: 0.95, + topK: 64, + maxOutputTokens: 8192, + responseMimeType: "application/json", +}; + +async function run(name, interval, players) { + const chatSession = model.startChat({ + generationConfig, + safetySettings:[ + // See https://ai.google.dev/gemini-api/docs/safety-settings + { + category: HarmCategory.HARM_CATEGORY_HARASSMENT, + threshold: HarmBlockThreshold.BLOCK_NONE, + }, + { + category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, + threshold: HarmBlockThreshold.BLOCK_NONE, + }, + { + category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT, + threshold: HarmBlockThreshold.BLOCK_NONE, + }, + { + category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, + threshold: HarmBlockThreshold.BLOCK_NONE, + }, + ], + history: [ + { + role: "user", + parts: [ + {text: conf.ai.prompt(name, interval, players)}, + ], + }, + { + role: "model", + parts: [ + {text: "Chat stuff"}, + ], + } + ], + }); + + return chatSession; +} + +module.exports = {run}; +// run(); \ No newline at end of file diff --git a/nodejs/controller/mc-bot.js b/nodejs/controller/mc-bot.js index b4af70f..72bce66 100644 --- a/nodejs/controller/mc-bot.js +++ b/nodejs/controller/mc-bot.js @@ -7,7 +7,9 @@ const {CJbot} = require('../model/minecraft'); const inventoryViewer = require('mineflayer-web-inventory'); const commands = require('./commands'); -const {onJoin} = require('./player_list') +const {onJoin} = require('./player_list'); +const ai = require('./ai'); + for(let name in conf.mc.bots){ if(CJbot.bots[name]) continue; @@ -22,21 +24,72 @@ for(let name in conf.mc.bots){ bot.on('onReady', async function(argument) { // inventoryViewer(bot.bot); + try{ + // onJoin(bot); + // await sleep(1000); + // bot.bot.setControlState('jump', true); + // setTimeout(()=> bot.bot.setControlState('jump', false), 5000); + if(bot.hasAi){ + console.log(`${bot.bot.entity.username} has AI`); + let messages = []; - onJoin(bot); - await sleep(1000); - bot.bot.setControlState('jump', true); - setTimeout(()=> bot.bot.setControlState('jump', false), 5000) + bot.bot.on('message', (message, type)=>{ + if(type === 'game_info') return; + if(message.toString().startsWith('<') && message.toString().split('>')[0].includes(bot.bot.entity.username)) return; + console.log(`Message ${type}: ${message.toString()}`) + messages.push('>', message.toString()); + }); - }) + await sleep(500); + + let aiChat = await ai.run( + bot.bot.entity.username, + conf.ai.interval, + Object.values(bot.getPlayers()).map(player=>`<[${player.lvl}] ${player.username}>`).join('\n') + ); + + setInterval(async ()=>{ + let result; + if(messages.length ===0) return; + + try{ + result = await aiChat.sendMessage(messages); + }catch(error){ + console.log('error AI API', error, result); + messages = []; + return ; + } - // bot.on('message', function(...args){ - // console.log('message | ', ...args) - // }) + try{ + messages = []; + if(!result.response.text()) return; + + for(let message of JSON.parse(result.response.text())){ + console.log('toSay', message.delay, message.text); + if(message.text === '___') return; + setTimeout(async (message)=>{ + await bot.sayAiSafe(message.text); + }, message.delay*1000, message); + } + }catch(error){ + console.log('Error in AI message loop', error, result); + } + }, conf.ai.interval*1000); + } + + }catch(error){ + console.log('error in onReady', error); + } + }); + + bot.on('error', console.log); + + // bot.on('message', function(message, type){ + // console.log(`${type}: ${message.toString()}`) + // }); } -(async ()=>{ -try{ +(async ()=>{try{ for(let name in CJbot.bots){ let bot = CJbot.bots[name]; if(bot.autoConnect){ diff --git a/nodejs/model/minecraft.js b/nodejs/model/minecraft.js index 74b9108..da42a42 100644 --- a/nodejs/model/minecraft.js +++ b/nodejs/model/minecraft.js @@ -1,5 +1,7 @@ 'use strict'; +process.env.DEBUG = 'mineflayer:*'; // Enables all debugging logs + const mineflayer = require('mineflayer'); const minecraftData = require('minecraft-data'); const { pathfinder, Movements, goals: { GoalNear } } = require('mineflayer-pathfinder'); @@ -46,6 +48,8 @@ class CJbot{ this.auth = args.auth || 'microsoft'; this.version = args.version || '1.20.1'; + this.hasAi = args.hasAi; + // States if the bot should connect when its loaded this.autoReConnect = 'autoConnect' in args ? args.autoReConnect : true; this.autoConnect = 'autoConnect' in args ? args.autoConnect : true; @@ -57,44 +61,48 @@ class CJbot{ connect(){ return new Promise((resolve, reject) =>{ + try{ + // Create the mineflayer instance + this.bot = mineflayer.createBot({ + host: this.host, + username: this.username, + password: this.password, + version: this.version, + auth: this.auth, + }); - // Create the mineflayer instance - this.bot = mineflayer.createBot({ - host: this.host, - username: this.username, - password: this.password, - version: this.version, - auth: this.auth, - }); + // If an error happens before the login event, toss an error back to + // the caller of the function + let onError = this.bot.on('error', (m)=>{ + console.log(this.name, m.toString()) + reject(m) + }) - // If an error happens before the login event, toss an error back to - // the caller of the function - let onError = this.bot.on('error', (m)=>{ - console.log(this.bot.version, m.toString()) - reject(m) - }) + // If the connection ends before the login event, toss an error back + // to the caller of the function + this.bot.on('end', (reason, ...args)=>{ + console.log(this.name, 'Connection ended:', reason, ...args); + this.isReady = false; + reject(reason); + }); - // If the connection ends before the login event, toss an error back - // to the caller of the function - this.bot.on('end', (m)=>{ - console.log(this.name, 'Connection ended:', m); - this.isReady = false; - reject(m); - }); + // When the bot is ready, return to the caller success + this.bot.on('login', ()=>{ + this.__onReady() + resolve() + }); - // When the bot is ready, return to the caller success - this.bot.on('login', ()=>{ - this.__onReady() - resolve() - }); - - // Set a timer to try to connect again in 30 seconds if the bot is - // not connected - setTimeout(async ()=>{try{ - if(this.autoReConnect && !this.isReady) await this.connect(); + // Set a timer to try to connect again in 30 seconds if the bot is + // not connected + setTimeout(async ()=>{try{ + if(this.autoReConnect && !this.isReady) await this.connect(); + }catch(error){ + console.error('minecraft.js | connect | setTimeout |', this.name, ' ', error) + }}, 30000); }catch(error){ - console.error('minecraft.js | connect | setTimeout |', this.name, ' ', error) - }}, 30000); + reject(error); + } + }); } @@ -119,25 +127,25 @@ class CJbot{ // Call the internal listeners when the bot is ready for(let callback of this.listeners.onReady || []){ - console.log('calling listener', callback) - await callback.call(this) + console.log('calling listener', callback); + await callback.call(this); } this.isReady = true; - this.__error() + this.__error(); console.log('Bot is ready', this.bot.entity.username, this.username); // Start chat listeners this.__listen(); }catch(error){ - console.error('minecraft.js | __onReady | ', this.name, ' ', error) + console.error('minecraft.js | __onReady | ', this.name, ' ', error); }} __startListeners(){ for(let event in this.listeners){ for(let callback of this.listeners[event]){ - this.bot.on(event, callback) + this.bot.on(event, callback); } } } @@ -264,7 +272,7 @@ class CJbot{ } __chatCoolDown(){ - return Math.floor(Math.random() * (3000 - 1500) + 1500); + return Math.floor(Math.random() * (3000 - 2000) + 2000); } async say(...messages){ @@ -278,6 +286,16 @@ class CJbot{ } } + async sayAiSafe(...messages){ + for(let message of messages){ + if(message.startsWith('/') && !(message.startsWith('/msg') || message.startsWith('/help'))){ + console.log('bot tried to execute bad command', message); + message = '.'+message; + } + await this.say(message); + } + } + async whisper(to, ...messages){ await this.say(...messages.map(message=>`/msg ${to} ${message}`)); } @@ -314,9 +332,9 @@ class CJbot{ console.error(`Chat command error on ${cmd} from ${from}\n`, error); } this.__unLockCommand(); - }else{ + }/*else{ this.whisper(from, `I dont know anything about ${cmd}`); - } + }*/ }catch(error){ console.error('minecraft.js | __doCommand |', this.name, ' ', error) }} diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index ce21ccc..f2a8790 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@google/generative-ai": "^0.17.1", "axios": "^0.27.2", "dotenv": "^16.0.1", "extend": "^3.0.2", @@ -45,6 +46,14 @@ "node": "10 || 12 || 14 || 16 || 18" } }, + "node_modules/@google/generative-ai": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.17.1.tgz", + "integrity": "sha512-TgWz02c5l2XJlEDys81UVat5+Qg9xqmYah7tQt6xlsBwFvzIFPz64aZFGd1av2sxT22NsssqLATjNsusAIJICA==", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@types/component-emitter": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", diff --git a/nodejs/package.json b/nodejs/package.json index 6aa97cd..fadccc0 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -17,6 +17,7 @@ }, "homepage": "https://github.com/wmantly/mc-cj-bot#readme", "dependencies": { + "@google/generative-ai": "^0.17.1", "axios": "^0.27.2", "dotenv": "^16.0.1", "extend": "^3.0.2",