diff --git a/MinecraftClient/ChatBots/FileInputBot.cs b/MinecraftClient/ChatBots/FileInputBot.cs new file mode 100644 index 00000000..16c65cd8 --- /dev/null +++ b/MinecraftClient/ChatBots/FileInputBot.cs @@ -0,0 +1,105 @@ +using System; +using System.IO; +using System.Threading; +using MinecraftClient.CommandHandler; +using MinecraftClient.Scripting; + +namespace MinecraftClient.ChatBots +{ + /// + /// Debug-only ChatBot that monitors a text file for commands. + /// Write lines to the file from any external tool (e.g. Cursor Shell) + /// and this bot will execute them as MCC internal commands. + /// + /// Usage from Cursor Shell: + /// Add-Content mcc_input.txt "inventory" + /// Add-Content mcc_input.txt "send /give @s diamond_sword 1" + /// + /// Lines starting with "/" are sent as server chat; others are treated + /// as MCC internal commands (same as typing in the MCC console). + /// + public class FileInputBot : ChatBot + { + private const string BotName = "FileInput"; + private string _filePath = string.Empty; + private long _lastPosition; + private int _tickCounter; + + public override void Initialize() + { + _filePath = Path.GetFullPath( + Environment.GetEnvironmentVariable("MCC_INPUT_FILE") ?? "mcc_input.txt"); + + if (File.Exists(_filePath)) + _lastPosition = new FileInfo(_filePath).Length; + else + File.WriteAllText(_filePath, ""); + + LogToConsole(BotName, $"Watching: {_filePath}"); + LogToConsole(BotName, "Write commands to this file to execute them."); + } + + public override void Update() + { + // Poll every ~500ms (Update is called every ~100ms) + if (++_tickCounter < 5) + return; + _tickCounter = 0; + + try + { + if (!File.Exists(_filePath)) + return; + + var info = new FileInfo(_filePath); + if (info.Length <= _lastPosition) + return; + + string newContent; + using (var fs = new FileStream(_filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + fs.Seek(_lastPosition, SeekOrigin.Begin); + using var reader = new StreamReader(fs); + newContent = reader.ReadToEnd(); + } + _lastPosition = info.Length; + + foreach (var rawLine in newContent.Split('\n')) + { + var line = rawLine.Trim(); + if (string.IsNullOrEmpty(line)) + continue; + + LogToConsole(BotName, $"> {line}"); + + if (line.StartsWith("/")) + { + SendText(line); + } + else + { + CmdResult result = new(); + if (PerformInternalCommand(line, ref result)) + { + if (!string.IsNullOrEmpty(result.ToString())) + LogToConsole(BotName, result.ToString()); + } + else + { + // Not an internal command — send as chat + SendText(line); + } + } + } + } + catch (IOException) + { + // File may be temporarily locked by the writer + } + catch (Exception ex) + { + LogToConsole(BotName, $"Error: {ex.Message}"); + } + } + } +} diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index e9b0b38c..c632569b 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -425,8 +425,8 @@ namespace MinecraftClient if (Config.ChatBot.ScriptScheduler.Enabled) { BotLoad(new ScriptScheduler()); } if (Config.ChatBot.TelegramBridge.Enabled) { BotLoad(new TelegramBridge()); } if (Config.ChatBot.ItemsCollector.Enabled) { BotLoad(new ItemsCollector()); } - //Add your ChatBot here by uncommenting and adapting - //BotLoad(new ChatBots.YourBot()); + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MCC_FILE_INPUT"))) + BotLoad(new FileInputBot()); } /// diff --git a/MinecraftClient/Protocol/Message/ChatParser.cs b/MinecraftClient/Protocol/Message/ChatParser.cs index d3db4e38..f907eadd 100644 --- a/MinecraftClient/Protocol/Message/ChatParser.cs +++ b/MinecraftClient/Protocol/Message/ChatParser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -510,8 +510,7 @@ namespace MinecraftClient.Protocol.Message { if (nbt.Count == 1 && nbt.TryGetValue("", out object? rootMessage)) { - // Nameless root tag - return (string)rootMessage; + return rootMessage?.ToString() ?? string.Empty; } string message = string.Empty; @@ -526,7 +525,7 @@ namespace MinecraftClient.Protocol.Message { case "text": { - message = (string)value; + message = value?.ToString() ?? string.Empty; } break; case "extra":