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":