Minecraft-Console-Client/MinecraftClient/ChatBots/FileInputBot.cs
BruceChen 967f67190c Add FileInputBot for non-interactive debugging and fix NbtToString crash
FileInputBot (ChatBots/FileInputBot.cs):
- New ChatBot that monitors a text file (default: mcc_input.txt) for
  commands, enabling MCC control from Cursor Shell or any non-interactive
  environment where stdin is not available
- Activated by setting MCC_FILE_INPUT env var (e.g. MCC_FILE_INPUT=1)
- Polls every ~500ms for new lines appended to the file
- Lines starting with "/" are sent as server chat/commands
- Other lines are executed as MCC internal commands (same as console input)
- File path overridable via MCC_INPUT_FILE env var

McClient.cs:
- Load FileInputBot when MCC_FILE_INPUT environment variable is set

ChatParser.cs - NbtToString:
- Fix InvalidCastException when NBT "text" or nameless root tag values
  are Int32 instead of String (happens with 1.20.6 SystemChat packets
  containing numeric values in the chat component tree)
- Replace direct (string) casts with ?.ToString() ?? string.Empty

Made-with: Cursor
2026-03-19 00:45:51 +08:00

105 lines
3.5 KiB
C#

using System;
using System.IO;
using System.Threading;
using MinecraftClient.CommandHandler;
using MinecraftClient.Scripting;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// 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).
/// </summary>
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}");
}
}
}
}