From ed910aa9c7fc2b7f4f3282d98591098c59aa42e4 Mon Sep 17 00:00:00 2001 From: Milutinke Date: Thu, 20 Oct 2022 19:29:35 +0200 Subject: [PATCH 1/5] Added the Web Socket Chat Bot. --- .gitmodules | 3 + MinecraftClient/ChatBots/WebSocketBot.cs | 1183 ++++++++++++++++++++++ MinecraftClient/Json.cs | 57 +- MinecraftClient/McClient.cs | 1 + MinecraftClient/MinecraftClient.csproj | 2 + MinecraftClient/Resources/lang/en.ini | 17 + MinecraftClient/Scripting/ChatBot.cs | 2 +- MinecraftClient/Settings.cs | 9 +- websocket-sharp | 1 + 9 files changed, 1272 insertions(+), 3 deletions(-) create mode 100644 MinecraftClient/ChatBots/WebSocketBot.cs create mode 160000 websocket-sharp diff --git a/.gitmodules b/.gitmodules index 5ef4ed0f..d927b7f7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = ConsoleInteractive url = https://github.com/breadbyte/ConsoleInteractive branch = main +[submodule "websocket-sharp"] + path = websocket-sharp + url = https://github.com/sta/websocket-sharp.git diff --git a/MinecraftClient/ChatBots/WebSocketBot.cs b/MinecraftClient/ChatBots/WebSocketBot.cs new file mode 100644 index 00000000..3e96b75a --- /dev/null +++ b/MinecraftClient/ChatBots/WebSocketBot.cs @@ -0,0 +1,1183 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.RegularExpressions; +using MinecraftClient.Inventory; +using MinecraftClient.Mapping; +using Newtonsoft.Json; +using Tomlet.Attributes; +using WebSocketSharp; +using WebSocketSharp.Server; + +namespace MinecraftClient.ChatBots +{ + public class WsServer + { + private WebSocketServer _server; + + public static event EventHandler? OnNewSession; + public static event EventHandler? OnSessionClose; + public static event EventHandler? OnMessageRecived; + + public WsServer(string ip, int port) + { + _server = new WebSocketServer(IPAddress.Parse(ip), port); + _server.AddWebSocketService("/mcc"); + _server.Start(); + } + + public void Stop() + { + _server.Stop(); + } + + public class WsBehavior : WebSocketBehavior + { + private string _sessionId { get; set; } = ""; + public bool _authenticated { get; set; } = false; + + public WsBehavior() + { + IgnoreExtensions = true; + } + + protected override void OnOpen() + { + _sessionId = Guid.NewGuid().ToString(); + OnNewSession!.Invoke(this, _sessionId); + } + + protected override void OnClose(CloseEventArgs e) + { + OnSessionClose!.Invoke(this, _sessionId); + } + + protected override void OnMessage(MessageEventArgs websocketEvent) + { + WsServer.OnMessageRecived!.Invoke(this, websocketEvent.Data); + } + + public void SendToClient(string text) + { + Send(text); + } + + public void SetSessionId(string newSessionId) + { + _sessionId = newSessionId; + } + + public string GetSessionId() + { + return _sessionId; + } + + public void SetAuthenticated(bool authenticated) + { + _authenticated = authenticated; + } + + public bool IsAuthenticated() + { + return _authenticated; + } + } + } + + internal class WsChatBotCommand + { + [JsonProperty("command")] + public string Command { get; set; } = ""; + + [JsonProperty("requestId")] + public string RequestId { get; set; } = ""; + + [JsonProperty("parameters")] + public object[]? Parameters { get; set; } + } + + internal class WsCommandResponder + { + private WebSocketBot _bot; + private WsServer.WsBehavior _session; + private string _command; + private string _requestId; + + public WsCommandResponder(WebSocketBot bot, WsServer.WsBehavior session, string command, string requestId) + { + _bot = bot; + _session = session; + _command = command; + _requestId = requestId; + } + + private void SendCommandResponse(bool success, string result, bool overrideAuth = false) + { + _bot.SendCommandResponse(_session, success, _requestId, _command, result, overrideAuth); + } + + public void SendErrorResponse(string error, bool overrideAuth = false) + { + SendCommandResponse(false, error, overrideAuth); + } + + public void SendSuccessResponse(string result, bool overrideAuth = false) + { + SendCommandResponse(true, result, overrideAuth); + } + + public void SendSuccessResponse(bool overrideAuth = false) + { + SendSuccessResponse(JsonConvert.SerializeObject(true), overrideAuth); + } + + public string Quote(string text) + { + return "\"" + text + "\""; + } + } + + internal class NbtData + { + public NBT? nbt { get; set; } + } + + internal class NBT + { + public Dictionary? nbt { get; set; } + } + + internal class NbtDictionaryConverter : JsonConverter> + { + public override void WriteJson(JsonWriter writer, Dictionary? value, JsonSerializer serializer) + => throw new NotImplementedException(); + + public override Dictionary? ReadJson(JsonReader reader, Type objectType, Dictionary? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var keyValuePairs = serializer.Deserialize>>(reader); + return new Dictionary(keyValuePairs!); + } + } + + public class WebSocketBot : ChatBot + { + private string? _ip; + private int _port; + private string? _password; + private WsServer? _server; + private Dictionary? _sessions; + + public static Configs Config = new(); + + [TomlDoNotInlineObject] + public class Configs + { + public WebSocketBot? botInstance { get; set; } + + [NonSerialized] + private const string BotName = "Websocket"; + + public bool Enabled = false; + + [TomlInlineComment("$config.ChatBot.WebSocketBot.Ip$")] + public string? Ip = "127.0.0.1"; + + [TomlInlineComment("$config.ChatBot.WebSocketBot.Port$")] + public int Port = 8043; + + [TomlInlineComment("$config.ChatBot.WebSocketBot.Password$")] + public string? Password = "wspass12345"; + + public void OnSettingsUpdated() + { + if (botInstance == null) + return; + + Match match = Regex.Match(Ip!, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"); + + if (!match.Success) + { + botInstance!.LogToConsole(Translations.TryGet("bot.WebSocketBot.failed_to_start.ip")); + botInstance!.UnloadBot(); + return; + } + + if (Port > 65535) + { + botInstance!.LogToConsole(Translations.TryGet("bot.WebSocketBot.failed_to_start.port")); + botInstance!.UnloadBot(); + return; + } + + botInstance!._ip = Ip; + botInstance!._port = Port; + botInstance!._password = Password; + + botInstance!.Initialize(); + } + } + + public WebSocketBot() + { + Config.botInstance = this; + + Match match = Regex.Match(Config.Ip!, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"); + + if (!match.Success) + { + LogToConsole(Translations.TryGet("bot.WebSocketBot.failed_to_start.ip")); + return; + } + + if (Config.Port > 65535) + { + LogToConsole(Translations.TryGet("bot.WebSocketBot.failed_to_start.port")); + return; + } + + _ip = Config.Ip; + _port = Config.Port; + _password = Config.Password; + } + + public override void Initialize() + { + if (_server != null) + { + SendEvent("OnWsRestarting", ""); + _server.Stop(); + } + + try + { + LogToConsole(Translations.TryGet("bot.WebSocketBot.starting")); + _server = new WsServer(_ip!, _port); + _sessions = new Dictionary(); + + LogToConsole(Translations.TryGet("bot.WebSocketBot.started", _ip, _port)); + } + catch (Exception e) + { + LogToConsole(Translations.TryGet("bot.WebSocketBot.failed_to_start.custom", e)); + return; + } + + WsServer.OnNewSession += (sender, id) => + { + LogToConsole(Translations.TryGet("bot.WebSocketBot.new_session", id)); + _sessions[id] = (WsServer.WsBehavior)sender!; + }; + + WsServer.OnSessionClose += (sender, id) => + { + LogToConsole(Translations.TryGet("bot.WebSocketBot.session_disconnected", id)); + _sessions.Remove(id); + }; + + WsServer.OnMessageRecived += (sender, message) => + { + var session = (WsServer.WsBehavior)sender!; + + if (!ProcessWebsocketCommand(session, _password!, message)) + return; + + string? result = ""; + PerformInternalCommand(message, ref result); + SendSessionEvent(session, "OnMccCommandResponse", "{\"response\": \"" + result + "\"}"); + }; + } + + public bool ProcessWebsocketCommand(WsServer.WsBehavior session, string password, string message) + { + message = message.Trim(); + + if (string.IsNullOrEmpty(message)) + return false; + + if (message.StartsWith('{')) + { + try + { + LogDebugToConsole("\n\n\tGot command\n\n\t" + message + "\n\n"); + + WsChatBotCommand cmd = JsonConvert.DeserializeObject(message)!; + WsCommandResponder responder = new WsCommandResponder(this, session, cmd.Command, cmd.RequestId); + + // Authentication and session commands + if (password.Length != 0) + { + if (!session.IsAuthenticated()) + { + // Allow session name changing before login for easier identification + if (cmd.Command.Equals("ChangeSessionId", StringComparison.OrdinalIgnoreCase)) + { + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expected 1 (newSessionid)!"), true); + return false; + } + + string newId = (cmd.Parameters[0] as string)!; + + if (newId.Length == 0) + { + responder.SendErrorResponse(responder.Quote("Please provide a valid session ID!"), true); + return false; + } + + if (newId.Length > 32) + { + responder.SendErrorResponse(responder.Quote("The session ID can't be longer than 32 characters!"), true); + return false; + } + + string oldId = session.GetSessionId(); + + _sessions!.Remove(oldId); + _sessions[newId] = session; + session.SetSessionId(newId); + + responder.SendSuccessResponse(responder.Quote("The session ID was successfully changed to: '" + newId + "'"), true); + LogToConsole(Translations.TryGet("bot.WebSocketBot.session_id_changed", oldId, newId)); + return false; + } + + // Special case for authentication + if (cmd.Command.Equals("Authenticate", StringComparison.OrdinalIgnoreCase)) + { + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expected 1 (password)!"), true); + return false; + } + + string pass = (cmd.Parameters[0] as string)!; + + if (pass.Length == 0) + { + responder.SendErrorResponse(responder.Quote("Please provide a valid password! (Example: 'Authenticate password123')"), true); + return false; + } + + if (!pass.Equals(password)) + { + responder.SendErrorResponse(responder.Quote("Incorrect password provided!"), true); + return false; + } + + session.SetAuthenticated(true); + responder.SendSuccessResponse(responder.Quote("Succesfully authenticated!"), true); + LogToConsole(Translations.TryGet("bot.WebSocketBot.session_authenticated", session.GetSessionId())); + return false; + } + + responder.SendErrorResponse(responder.Quote("You must authenticate in order to send and recieve data!"), true); + return false; + } + } + else + { + if (!session.IsAuthenticated()) + { + responder.SendSuccessResponse(responder.Quote("Succesfully authenticated!")); + LogToConsole(Translations.TryGet("bot.WebSocketBot.session_authenticated", session.GetSessionId())); + session.SetAuthenticated(true); + return false; + } + } + + // Process other commands + switch (cmd.Command) + { + case "LogToConsole": + if (cmd.Parameters == null || cmd.Parameters.Length > 1 || cmd.Parameters.Length < 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting a single parameter!")); + return false; + } + + LogToConsole((cmd.Parameters[0] as string)!); + responder.SendSuccessResponse(); + break; + + case "LogDebugToConsole": + if (cmd.Parameters == null || cmd.Parameters.Length > 1 || cmd.Parameters.Length < 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting a single parameter!")); + return false; + } + + LogDebugToConsole((cmd.Parameters[0] as string)!); + responder.SendSuccessResponse(); + break; + + case "LogToConsoleTranslated": + if (cmd.Parameters == null || cmd.Parameters.Length > 1 || cmd.Parameters.Length < 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting a single parameter!")); + return false; + } + + LogToConsoleTranslated((cmd.Parameters[0] as string)!); + responder.SendSuccessResponse(); + break; + + case "LogDebugToConsoleTranslated": + if (cmd.Parameters!.Length > 1 || cmd.Parameters.Length < 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting a single parameter!")); + return false; + } + + LogDebugToConsoleTranslated((cmd.Parameters[0] as string)!); + responder.SendSuccessResponse(); + break; + + case "ReconnectToTheServer": + if (cmd.Parameters == null || cmd.Parameters.Length != 2) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 2 parameters (extraAttempts, delaySeconds)!")); + return false; + } + + ReconnectToTheServer(Convert.ToInt32(cmd.Parameters[0]), Convert.ToInt32(cmd.Parameters[1])); + responder.SendSuccessResponse(); + break; + + case "DisconnectAndExit": + responder.SendSuccessResponse(); + DisconnectAndExit(); + break; + + case "SendPrivateMessage": + if (cmd.Parameters == null || cmd.Parameters.Length != 2) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 2 parameters (player, message)!")); + return false; + } + + SendPrivateMessage((cmd.Parameters[0] as string)!, (cmd.Parameters[1] as string)!); + responder.SendSuccessResponse(); + break; + + case "RunScript": + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 parameter (filename)!")); + return false; + } + + RunScript((cmd.Parameters[0] as string)!); + responder.SendSuccessResponse(); + break; + + case "GetTerrainEnabled": + responder.SendSuccessResponse(GetTerrainEnabled().ToString().ToLower()); + break; + + case "SetTerrainEnabled": + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 parameter (enabled)!")); + return false; + } + + SetTerrainEnabled((bool)cmd.Parameters[0]); + responder.SendSuccessResponse(); + break; + + case "GetEntityHandlingEnabled": + responder.SendSuccessResponse(GetEntityHandlingEnabled().ToString().ToLower()); + break; + + case "Sneak": + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 parameter (on)!")); + return false; + } + + Sneak((bool)cmd.Parameters[0]); + responder.SendSuccessResponse(); + break; + + case "SendEntityAction": + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 parameter (actionType)!")); + return false; + } + + SendEntityAction(((Protocol.EntityActionType)(Convert.ToInt32(cmd.Parameters[0])))); + responder.SendSuccessResponse(); + break; + + case "DigBlock": + if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 3 || cmd.Parameters.Length > 5) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 or 3 parameter(s) (location, swingArms?, lookAtBlock?)!")); + return false; + } + + Location location = new Location(Convert.ToInt32(cmd.Parameters[0]), Convert.ToInt32(cmd.Parameters[1]), Convert.ToInt32(cmd.Parameters[2])); + + if (location.DistanceSquared(GetCurrentLocation().EyesLocation()) > 25) + { + responder.SendErrorResponse(responder.Quote("The block you're trying to dig is too far away!")); + return false; + } + + if (GetWorld().GetBlock(location).Type == Material.Air) + { + responder.SendErrorResponse(responder.Quote("The block you're trying to dig is is air!")); + return false; + } + + bool resullt = false; + + if (cmd.Parameters.Length == 3) + resullt = DigBlock(location); + else if (cmd.Parameters.Length == 4) + resullt = DigBlock(location, (bool)cmd.Parameters[3]); + else if (cmd.Parameters.Length == 5) + resullt = DigBlock(location, (bool)cmd.Parameters[3], (bool)cmd.Parameters[4]); + + responder.SendSuccessResponse(JsonConvert.SerializeObject(resullt)); + break; + + case "SetSlot": + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 parameter (slotNumber)!")); + return false; + } + + SetSlot(Convert.ToInt32(cmd.Parameters[0])); + responder.SendSuccessResponse(); + break; + + case "GetWorld": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetWorld())); + break; + + case "GetEntities": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetEntities())); + break; + + case "GetPlayersLatency": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetPlayersLatency())); + break; + + case "GetCurrentLocation": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetCurrentLocation())); + break; + + case "MoveToLocation": + if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 3 || cmd.Parameters.Length > 8) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 or 7 parameter(s) (x, y, z, allowUnsafe?, allowDirectTeleport?, maxOffset?, minoffset?, timeout?)!")); + return false; + } + + bool allowUnsafe = false; + bool allowDirectTeleport = false; + int maxOffset = 0; + int minOffset = 0; + TimeSpan? timeout = null; + + if (cmd.Parameters.Length >= 4) + allowUnsafe = (bool)cmd.Parameters[3]; + + if (cmd.Parameters.Length >= 5) + allowDirectTeleport = (bool)cmd.Parameters[4]; + + if (cmd.Parameters.Length >= 6) + maxOffset = Convert.ToInt32(cmd.Parameters[5]); + + if (cmd.Parameters.Length >= 7) + minOffset = Convert.ToInt32(cmd.Parameters[6]); + + if (cmd.Parameters.Length == 8) + timeout = TimeSpan.FromSeconds(Convert.ToInt32(cmd.Parameters[7])); + + bool canMove = MoveToLocation( + new Location(Convert.ToInt32(cmd.Parameters[0]), + Convert.ToInt32(cmd.Parameters[1]), + Convert.ToInt32(cmd.Parameters[2])), + allowUnsafe, + allowDirectTeleport, + maxOffset, + minOffset, + timeout); + + responder.SendSuccessResponse(JsonConvert.SerializeObject(canMove)); + break; + + case "ClientIsMoving": + responder.SendSuccessResponse(JsonConvert.SerializeObject(ClientIsMoving())); + break; + + case "LookAtLocation": + if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 3 || cmd.Parameters.Length > 3) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 3 parameter(s) (x, y, z)!")); + return false; + } + + LookAtLocation(new Location(Convert.ToInt32(cmd.Parameters[0]), Convert.ToInt32(cmd.Parameters[1]), Convert.ToInt32(cmd.Parameters[2]))); + responder.SendSuccessResponse(); + break; + + case "GetTimestamp": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetTimestamp())); + break; + + case "GetServerPort": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetServerPort())); + break; + + case "GetServerHost": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetServerHost())); + break; + + case "GetUsername": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetUsername())); + break; + + case "GetGamemode": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GameModeString(GetGamemode()))); + break; + + case "GetYaw": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetYaw())); + break; + + case "GetPitch": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetPitch())); + break; + + case "GetUserUUID": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetUserUUID())); + break; + + case "GetOnlinePlayers": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetOnlinePlayers())); + break; + + case "GetOnlinePlayersWithUUID": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetOnlinePlayersWithUUID())); + break; + + case "GetServerTPS": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetServerTPS())); + break; + + case "InteractEntity": + if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 2 || cmd.Parameters.Length > 3) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting at least 2 and at most 3 parameter(s) (entityId, interactionType, hand?)!")); + return false; + } + + InteractType interactionType = (InteractType)Convert.ToInt32(cmd.Parameters[1]); + + Hand interactionHand = Hand.MainHand; + + if (cmd.Parameters.Length == 3) + interactionHand = (Hand)Convert.ToInt32(cmd.Parameters[2]); + + responder.SendSuccessResponse(JsonConvert.SerializeObject(InteractEntity(Convert.ToInt32(cmd.Parameters[0]), interactionType, interactionHand))); + break; + + case "CreativeGive": + if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 3 || cmd.Parameters.Length > 4) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting at least 3 and at most 4 parameter(s) (slotId, itemType, count, nbt?)!")); + return false; + } + + NBT? nbt = null; + + if (cmd.Parameters.Length == 4) + nbt = JsonConvert.DeserializeObject(cmd.Parameters[3].ToString()!, new NbtDictionaryConverter())!; + + responder.SendSuccessResponse( + JsonConvert.SerializeObject(CreativeGive( + Convert.ToInt32(cmd.Parameters[0]), + (ItemType)Convert.ToInt32(cmd.Parameters[1]), + Convert.ToInt32(cmd.Parameters[2]), + nbt == null ? new Dictionary() : nbt!.nbt!) + )); + + break; + + case "CreativeDelete": + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting at 1 parameter (slotId)!")); + return false; + } + + responder.SendSuccessResponse(JsonConvert.SerializeObject(CreativeDelete(Convert.ToInt32(cmd.Parameters[0])))); + break; + + case "SendAnimation": + Hand hand = Hand.MainHand; + + if (cmd.Parameters != null && cmd.Parameters.Length == 1) + hand = (Hand)Convert.ToInt32(cmd.Parameters[0]); + + responder.SendSuccessResponse(JsonConvert.SerializeObject(SendAnimation(hand))); + break; + + case "SendPlaceBlock": + if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length < 4 || cmd.Parameters.Length > 4) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting at least 4 and at most 5 parameters (x, y, z, blockFace, hand?)!")); + return false; + } + + Location blockLocation = new Location(Convert.ToInt32(cmd.Parameters[0]), Convert.ToInt32(cmd.Parameters[1]), Convert.ToInt32(cmd.Parameters[2])); + + Direction blockFacingDirection = (Direction)Convert.ToInt32(cmd.Parameters[3]); + Hand handToUse = Hand.MainHand; + + if (cmd.Parameters.Length == 4) + handToUse = (Hand)Convert.ToInt32(cmd.Parameters[4]); + + responder.SendSuccessResponse(JsonConvert.SerializeObject(SendPlaceBlock(blockLocation, blockFacingDirection, handToUse))); + break; + + case "UseItemInHand": + responder.SendSuccessResponse(JsonConvert.SerializeObject(UseItemInHand())); + break; + + case "GetInventoryEnabled": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetInventoryEnabled())); + break; + + case "GetPlayerInventory": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetPlayerInventory())); + break; + + case "GetInventories": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetInventories())); + break; + + case "WindowAction": + if (cmd.Parameters == null || cmd.Parameters.Length == 0 || cmd.Parameters.Length != 3) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 3 parameters (inventoryId, slotId, windowActionType)!")); + return false; + } + + responder.SendSuccessResponse( + JsonConvert.SerializeObject(WindowAction( + Convert.ToInt32(cmd.Parameters[0]), + Convert.ToInt32(cmd.Parameters[1]), + (WindowActionType)Convert.ToInt32(cmd.Parameters[2]) + ))); + break; + + case "ChangeSlot": + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 parameter (slotId)!")); + return false; + } + + responder.SendSuccessResponse(JsonConvert.SerializeObject(ChangeSlot((short)Convert.ToInt32(cmd.Parameters[0])))); + break; + + case "GetCurrentSlot": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetCurrentSlot())); + break; + + case "ClearInventories": + responder.SendSuccessResponse(JsonConvert.SerializeObject(ClearInventories())); + break; + + case "UpdateSign": + if (cmd.Parameters == null || cmd.Parameters.Length != 7) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 parameter (x, y, z, line1, line2, line3, line4)!")); + return false; + } + + Location signLocation = new Location(Convert.ToInt32(cmd.Parameters[0]), Convert.ToInt32(cmd.Parameters[1]), Convert.ToInt32(cmd.Parameters[2])); + + responder.SendSuccessResponse( + JsonConvert.SerializeObject(UpdateSign(signLocation, + (string)cmd.Parameters[3], + (string)cmd.Parameters[4], + (string)cmd.Parameters[5], + (string)cmd.Parameters[6] + ))); + break; + + case "SelectTrade": + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 parameter (selectedSlot)!")); + return false; + } + + responder.SendSuccessResponse(JsonConvert.SerializeObject(SelectTrade(Convert.ToInt32(cmd.Parameters[0])))); + break; + + case "UpdateCommandBlock": + if (cmd.Parameters == null || cmd.Parameters.Length != 6) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 parameter (x, y, z, command, commandBlockMode, commandBlockFlags)!")); + return false; + } + + Location commandBlockLocation = new Location(Convert.ToInt32(cmd.Parameters[0]), Convert.ToInt32(cmd.Parameters[1]), Convert.ToInt32(cmd.Parameters[2])); + + responder.SendSuccessResponse( + UpdateCommandBlock(commandBlockLocation, + (string)cmd.Parameters[3], + (CommandBlockMode)Convert.ToInt32(cmd.Parameters[4]), + (CommandBlockFlags)Convert.ToInt32(cmd.Parameters[5]) + ).ToString().ToLower()); + break; + + case "CloseInventory": + if (cmd.Parameters == null || cmd.Parameters.Length != 1) + { + responder.SendErrorResponse(responder.Quote("Invalid number of parameters, expecting 1 parameter (inventoryId)!")); + return false; + } + + responder.SendSuccessResponse(CloseInventory(Convert.ToInt32(cmd.Parameters[0])).ToString().ToLower()); + break; + + case "GetMaxChatMessageLength": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetMaxChatMessageLength())); + break; + + case "Respawn": + responder.SendSuccessResponse(JsonConvert.SerializeObject(Respawn())); + break; + + case "GetProtocolVersion": + responder.SendSuccessResponse(JsonConvert.SerializeObject(GetProtocolVersion())); + break; + } + } + catch (Exception e) + { + LogDebugToConsole(e.Message); + SendSessionEvent(session, "OnWsCommandResponse", "{\"success\": false, \"message\": \"An error occured, possible reasons: mailformed json, type conversion, internal error\", \"stackTrace\": \"" + Json.EscapeString(e.ToString()) + "\"}", true); + return false; + } + + return false; + } + else + { + if (password.Length != 0) + { + if (!session.IsAuthenticated()) + { + SendSessionEvent(session, "OnWsCommandResponse", "{\"error\": true, \"message\": \"You must authenticate in order to send and recieve data!\"}", true); + return false; + } + } + else + { + if (!session.IsAuthenticated()) + { + SendSessionEvent(session, "OnWsCommandResponse", "{\"success\": true, \"message\": \"Succesfully authenticated!\"}", true); + LogToConsole(Translations.TryGet("bot.WebSocketBot.session_authenticated", session.GetSessionId())); + session.SetAuthenticated(true); + return true; + } + } + } + + return true; + } + + public override void OnUnload() + { + if (_server != null) + { + SendEvent("OnWsConnectionClose", ""); + _server.Stop(); + } + } + + // ========================================================================================== + // Bot Events + // ========================================================================================== + public override void AfterGameJoined() + { + SendEvent("OnGameJoined", ""); + } + + public override void OnBlockBreakAnimation(Entity entity, Location location, byte stage) + { + SendEvent("OnBlockBreakAnimation", new { entity, location, stage }); + } + + public override void OnEntityAnimation(Entity entity, byte animation) + { + SendEvent("OnEntityAnimation", new { entity, animation }); + } + + public override void GetText(string text) + { + text = GetVerbatim(text).Trim(); + + string message = ""; + string username = ""; + + if (IsPrivateMessage(text, ref message, ref username)) + SendEvent("OnChatPrivate", new { sender = username, message, rawText = text }); + else if (IsChatMessage(text, ref message, ref username)) + SendEvent("OnChatPublic", new { username, message, rawText = text }); + else if (IsTeleportRequest(text, ref username)) + SendEvent("OnTeleportRequest", new { sender = username, rawText = text }); + } + + public override void GetText(string text, string? json) + { + SendEvent("OnChatRaw", new { text, json }); + } + + public override bool OnDisconnect(DisconnectReason reason, string message) + { + string reasonString = "Unknown"; + + switch (reason) + { + case DisconnectReason.ConnectionLost: + reasonString = "Connection Lost"; + break; + + case DisconnectReason.UserLogout: + reasonString = "User Logout"; + break; + + case DisconnectReason.InGameKick: + reasonString = "In-Game Kick"; + break; + + case DisconnectReason.LoginRejected: + reasonString = "Login Rejected"; + break; + } + + SendEvent("OnDisconnect", new { reason = reasonString, message }); + return false; + } + + public override void OnPlayerProperty(Dictionary prop) + { + SendEvent("OnPlayerProperty", prop); + } + + public override void OnServerTpsUpdate(Double tps) + { + SendEvent("OnServerTpsUpdate", new { tps }); + } + + public override void OnTimeUpdate(long WorldAge, long TimeOfDay) + { + SendEvent("OnTimeUpdate", new { worldAge = WorldAge, timeOfDay = TimeOfDay }); + } + + public override void OnEntityMove(Entity entity) + { + SendEvent("OnEntityMove", entity); + } + + public override void OnInternalCommand(string commandName, string commandParams, string Result) + { + SendEvent("OnInternalCommand", new { command = commandName, parameters = commandParams, result = Result.Replace("\"", "'") }); + } + + public override void OnEntitySpawn(Entity entity) + { + SendEvent("OnEntitySpawn", entity); + } + + public override void OnEntityDespawn(Entity entity) + { + SendEvent("OnEntityDespawn", entity); + } + + public override void OnHeldItemChange(byte slot) + { + SendEvent("OnHeldItemChange", new { itemSlot = slot }); + } + + public override void OnHealthUpdate(float health, int food) + { + SendEvent("OnHealthUpdate", new { health, food }); + } + + public override void OnExplosion(Location explode, float strength, int recordcount) + { + SendEvent("OnExplosion", new { location = explode, strength, recordCount = recordcount }); + } + + public override void OnSetExperience(float Experiencebar, int Level, int TotalExperience) + { + SendEvent("OnSetExperience", new { experienceBar = Experiencebar, level = Level, totalExperience = TotalExperience }); + } + + public override void OnGamemodeUpdate(string playername, Guid uuid, int gamemode) + { + SendEvent("OnGamemodeUpdate", new { playerName = playername, uuid, gameMode = GameModeString(gamemode) }); + } + + public override void OnLatencyUpdate(string playername, Guid uuid, int latency) + { + SendEvent("OnLatencyUpdate", new { playerName = playername, uuid, latency }); + } + + public override void OnMapData(int mapid, byte scale, bool trackingPosition, bool locked, List icons, byte columnsUpdated, byte rowsUpdated, byte mapCoulmnX, byte mapRowZ, byte[]? colors) + { + SendEvent("OnMapData", new { mapId = mapid, scale, trackingPosition, locked, icons, columnsUpdated, rowsUpdated, mapCoulmnX, mapRowZ, colors }); + } + + public override void OnTradeList(int windowID, List trades, VillagerInfo villagerInfo) + { + SendEvent("OnTradeList", new { windowId = windowID, trades, villagerInfo }); + } + + public override void OnTitle(int action, string titletext, string subtitletext, string actionbartext, int fadein, int stay, int fadeout, string json_) + { + SendEvent("OnTitle", new { action, titleText = titletext, subtitleText = subtitletext, actionBarText = actionbartext, fadeIn = fadein, stay, rawJson = json_ }); + } + + public override void OnEntityEquipment(Entity entity, int slot, Item? item) + { + SendEvent("OnEntityEquipment", new { entity, slot, item }); + } + + public override void OnEntityEffect(Entity entity, Effects effect, int amplifier, int duration, byte flags) + { + SendEvent("OnEntityEffect", new { entity, effect, amplifier, duration, flags }); + } + + public override void OnScoreboardObjective(string objectivename, byte mode, string objectivevalue, int type, string json_) + { + SendEvent("OnScoreboardObjective", new { objectiveName = objectivename, mode, objectiveValue = objectivevalue, type, rawJson = json_ }); + } + + public override void OnUpdateScore(string entityname, int action, string objectivename, int value) + { + SendEvent("OnUpdateScore", new { entityName = entityname, action, objectiveName = objectivename, type = value }); + } + + public override void OnInventoryUpdate(int inventoryId) + { + SendEvent("OnInventoryUpdate", new { inventoryId }); + } + + public override void OnInventoryOpen(int inventoryId) + { + SendEvent("OnInventoryOpen", new { inventoryId }); + } + + public override void OnInventoryClose(int inventoryId) + { + SendEvent("OnInventoryClose", new { inventoryId }); + } + + public override void OnPlayerJoin(Guid uuid, string name) + { + SendEvent("OnPlayerJoin", new { uuid, name }); + } + + public override void OnPlayerLeave(Guid uuid, string? name) + { + SendEvent("OnPlayerLeave", new { uuid, name = (name == null ? "null" : name) }); + } + + public override void OnDeath() + { + SendEvent("OnDeath", ""); + } + + public override void OnRespawn() + { + SendEvent("OnRespawn", ""); + } + + public override void OnEntityHealth(Entity entity, float health) + { + SendEvent("OnEntityHealth", new { entity, health }); + } + + public override void OnEntityMetadata(Entity entity, Dictionary? metadata) + { + SendEvent("OnEntityMetadata", new { entity, metadata }); + } + + public override void OnPlayerStatus(byte statusId) + { + SendEvent("OnPlayerStatus", new { statusId }); + } + + public override void OnNetworkPacket(int packetID, List packetData, bool isLogin, bool isInbound) + { + SendEvent("OnNetworkPacket", new { packetId = packetID, isLogin, isInbound, packetData }); + } + + // ========================================================================================== + // Helper methods + // ========================================================================================== + + private void SendEvent(string type, object data, bool overrideAuth = false) + { + foreach (KeyValuePair pair in _sessions!) + SendSessionEvent(pair.Value, type, JsonConvert.SerializeObject(data), overrideAuth); + } + + private void SendEvent(string type, string data, bool overrideAuth = false) + { + foreach (KeyValuePair pair in _sessions!) + SendSessionEvent(pair.Value, type, data, overrideAuth); + } + + private void SendSessionEvent(WsServer.WsBehavior session, string type, string data, bool overrideAuth = false) + { + if (session != null && (overrideAuth || session.IsAuthenticated())) + { + session.SendToClient("{\"event\": \"" + type + "\", \"data\": " + (data.IsNullOrEmpty() ? "null" : "\"" + Json.EscapeString(data) + "\"") + "}"); + + if (!(type.Contains("Entity") || type.Equals("OnTimeUpdate") || type.Equals("OnServerTpsUpdate"))) + LogDebugToConsole( + "\n\n\tSending:\n\n\t{\"event\": \"" + type + + "\", \"data\": " + (data.IsNullOrEmpty() ? "null" : "\"" + + Json.EscapeString(data) + "\"") + "}\n\n"); + } + } + + public void SendCommandResponse(WsServer.WsBehavior session, bool success, string requestId, string command, string result, bool overrideAuth = false) + { + SendSessionEvent(session, "OnWsCommandResponse", "{\"success\": " + success.ToString().ToLower() + ", \"requestId\": \"" + requestId + "\", \"command\": \"" + command + "\", \"result\": " + (string.IsNullOrEmpty(result) ? "null" : result) + "}", overrideAuth); + } + + public string GameModeString(int gameMode) + { + if (gameMode == 0) + return "survival"; + + if (gameMode == 1) + return "creative"; + + if (gameMode == 2) + return "adventure"; + + if (gameMode == 3) + return "spectator"; + + return "unknown"; + } + } +} diff --git a/MinecraftClient/Json.cs b/MinecraftClient/Json.cs index e77e32bf..b4b0008f 100644 --- a/MinecraftClient/Json.cs +++ b/MinecraftClient/Json.cs @@ -103,7 +103,7 @@ namespace MinecraftClient && IsHex(toparse[cursorpos + 5])) { //"abc\u0123abc" => "0123" => 0123 => Unicode char n°0123 => Add char to string - data.StringValue += char.ConvertFromUtf32(int.Parse(toparse.Substring(cursorpos + 2, 4), + data.StringValue += char.ConvertFromUtf32(int.Parse(toparse.Substring(cursorpos + 2, 4), System.Globalization.NumberStyles.HexNumber)); cursorpos += 6; continue; } @@ -221,5 +221,60 @@ namespace MinecraftClient || toparse[cursorpos] == '\n')) cursorpos++; } + + // Original: https://github.com/mono/mono/blob/master/mcs/class/System.Json/System.Json/JsonValue.cs + private static bool NeedEscape(string src, int i) + { + char c = src[i]; + return c < 32 || c == '"' || c == '\\' + // Broken lead surrogate + || (c >= '\uD800' && c <= '\uDBFF' && + (i == src.Length - 1 || src[i + 1] < '\uDC00' || src[i + 1] > '\uDFFF')) + // Broken tail surrogate + || (c >= '\uDC00' && c <= '\uDFFF' && + (i == 0 || src[i - 1] < '\uD800' || src[i - 1] > '\uDBFF')) + // To produce valid JavaScript + || c == '\u2028' || c == '\u2029' + // Escape " tags + || (c == '/' && i > 0 && src[i - 1] == '<'); + } + + public static string EscapeString(string src) + { + StringBuilder sb = new StringBuilder(); + + int start = 0; + + for (int i = 0; i < src.Length; i++) + { + if (NeedEscape(src, i)) + { + sb.Append(src, start, i - start); + + switch (src[i]) + { + case '\b': sb.Append("\\b"); break; + case '\f': sb.Append("\\f"); break; + case '\n': sb.Append("\\n"); break; + case '\r': sb.Append("\\r"); break; + case '\t': sb.Append("\\t"); break; + case '\"': sb.Append("\\\""); break; + case '\\': sb.Append("\\\\"); break; + case '/': sb.Append("\\/"); break; + + default: + sb.Append("\\u"); + sb.Append(((int)src[i]).ToString("x04")); + break; + } + + start = i + 1; + } + } + + sb.Append(src, start, src.Length - start); + + return sb.ToString(); + } } } diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 677cad72..fafc781f 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -273,6 +273,7 @@ namespace MinecraftClient if (Config.ChatBot.RemoteControl.Enabled) { BotLoad(new RemoteControl()); } if (Config.ChatBot.ReplayCapture.Enabled && reload) { BotLoad(new ReplayCapture()); } if (Config.ChatBot.ScriptScheduler.Enabled) { BotLoad(new ScriptScheduler()); } + if (Config.ChatBot.WebSocketBot.Enabled) { BotLoad(new WebSocketBot()); } //Add your ChatBot here by uncommenting and adapting //BotLoad(new ChatBots.YourBot()); } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index b7f45d16..fb2e19b8 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -39,6 +39,7 @@ + @@ -72,6 +73,7 @@ + diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini index 3543b494..0c7fdbe0 100644 --- a/MinecraftClient/Resources/lang/en.ini +++ b/MinecraftClient/Resources/lang/en.ini @@ -700,6 +700,17 @@ bot.scriptScheduler.running_inverval=Interval / Running action: {0} bot.scriptScheduler.running_login=Login / Running action: {0} bot.scriptScheduler.task=triggeronfirstlogin: {0}\n triggeronlogin: {1}\n triggerontime: {2}\n triggeroninterval: {3}\n timevalue: {4}\n timeinterval: {5}\n action: {6} +# WebSocketBot +bot.WebSocketBot.session_id_changed=§bSession with an id §a{0}§b has been renamed to: §a{1}§b! +bot.WebSocketBot.session_authenticated=§bSession with an id §a{0}§b has been succesfully authenticated! +bot.WebSocketBot.failed_to_start.ip=§cFailed to start a server! The provided IP address is not a valid one! +bot.WebSocketBot.failed_to_start.port=§cFailed to start a server! The port number provided is out of the range, it must be 65535 or bellow it! +bot.WebSocketBot.failed_to_start.custom=§cFailed to start a server:\n\n{0}\n\n +bot.WebSocketBot.starting=Starting the Websocket server... +bot.WebSocketBot.started=§bServer started on ip §a{0}§b port: §a{1} +bot.WebSocketBot.new_session=§bNew session connected: §a{0} +bot.WebSocketBot.session_disconnected=§bSession with an id §a{0}§b has disconnected! + # TestBot bot.testBot.told=Bot: {0} told me : {1} bot.testBot.said=Bot: {0} said : {1} @@ -940,3 +951,9 @@ config.ChatBot.ReplayCapture.Backup_Interval=How long should replay file be auto # ChatBot.ScriptScheduler config.ChatBot.ScriptScheduler=Schedule commands and scripts to launch on various events such as server join, date/time or time interval\n# See https://mccteam.github.io/guide/chat-bots.html#script-scheduler for more info + +# ChatBot.WebSocketBot +config.ChatBot.WebSocketBot=Remotely control the client using Web Sockets.\n# This is useful if you want to implement an application that can remotely and asynchronously execute procedures in MCC.\n# Example implementation written in JavaScript: https://github.com/milutinke/MCC.js.git\n# The protocol specification will be available in the documentation soon. +config.ChatBot.WebSocketBot.Ip=The IP address that Websocket server will be bounded to. +config.ChatBot.WebSocketBot.Port=The Port that Websocket server will be bounded to. +config.ChatBot.WebSocketBot.Password=A password that will be used to authenticate on thw Websocket server (It is recommended to change the default password and to set a strong one). diff --git a/MinecraftClient/Scripting/ChatBot.cs b/MinecraftClient/Scripting/ChatBot.cs index 14fefcde..8ce7de16 100644 --- a/MinecraftClient/Scripting/ChatBot.cs +++ b/MinecraftClient/Scripting/ChatBot.cs @@ -1009,7 +1009,7 @@ namespace MinecraftClient /// /// Send Entity Action /// - private bool SendEntityAction(Protocol.EntityActionType entityAction) + protected bool SendEntityAction(Protocol.EntityActionType entityAction) { return Handler.SendEntityAction(entityAction); } diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 7880ead0..9671c235 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -567,7 +567,7 @@ namespace MinecraftClient public enum ForgeConfigType { no, auto, force }; - public enum TerminalColorDepthType { bit_4, bit_8, bit_24}; + public enum TerminalColorDepthType { bit_4, bit_8, bit_24 }; } public struct AccountInfoConfig @@ -1152,6 +1152,13 @@ namespace MinecraftClient get { return ChatBots.ScriptScheduler.Config; } set { ChatBots.ScriptScheduler.Config = value; ChatBots.ScriptScheduler.Config.OnSettingUpdate(); } } + + [TomlPrecedingComment("$config.ChatBot.WebSocketBot$")] + public ChatBots.WebSocketBot.Configs WebSocketBot + { + get { return ChatBots.WebSocketBot.Config!; } + set { ChatBots.WebSocketBot.Config = value; } + } } } diff --git a/websocket-sharp b/websocket-sharp new file mode 160000 index 00000000..8e175845 --- /dev/null +++ b/websocket-sharp @@ -0,0 +1 @@ +Subproject commit 8e175845420d7dd707d7636747a71a0b5037ba91 From 315029b0e81bb65fd60611b77cac1b8f877b1c11 Mon Sep 17 00:00:00 2001 From: Milutinke Date: Thu, 20 Oct 2022 19:42:23 +0200 Subject: [PATCH 2/5] Fixed the TOML error --- MinecraftClient/ChatBots/WebSocketBot.cs | 32 ------------------------ 1 file changed, 32 deletions(-) diff --git a/MinecraftClient/ChatBots/WebSocketBot.cs b/MinecraftClient/ChatBots/WebSocketBot.cs index 3e96b75a..cef17043 100644 --- a/MinecraftClient/ChatBots/WebSocketBot.cs +++ b/MinecraftClient/ChatBots/WebSocketBot.cs @@ -172,8 +172,6 @@ namespace MinecraftClient.ChatBots [TomlDoNotInlineObject] public class Configs { - public WebSocketBot? botInstance { get; set; } - [NonSerialized] private const string BotName = "Websocket"; @@ -187,40 +185,10 @@ namespace MinecraftClient.ChatBots [TomlInlineComment("$config.ChatBot.WebSocketBot.Password$")] public string? Password = "wspass12345"; - - public void OnSettingsUpdated() - { - if (botInstance == null) - return; - - Match match = Regex.Match(Ip!, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"); - - if (!match.Success) - { - botInstance!.LogToConsole(Translations.TryGet("bot.WebSocketBot.failed_to_start.ip")); - botInstance!.UnloadBot(); - return; - } - - if (Port > 65535) - { - botInstance!.LogToConsole(Translations.TryGet("bot.WebSocketBot.failed_to_start.port")); - botInstance!.UnloadBot(); - return; - } - - botInstance!._ip = Ip; - botInstance!._port = Port; - botInstance!._password = Password; - - botInstance!.Initialize(); - } } public WebSocketBot() { - Config.botInstance = this; - Match match = Regex.Match(Config.Ip!, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"); if (!match.Success) From 4338bef440844caa16410ecb466a786e08589db2 Mon Sep 17 00:00:00 2001 From: Milutinke Date: Mon, 24 Oct 2022 22:41:56 +0200 Subject: [PATCH 3/5] Fixed chat messages not being sent. --- MinecraftClient/ChatBots/WebSocketBot.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/ChatBots/WebSocketBot.cs b/MinecraftClient/ChatBots/WebSocketBot.cs index cef17043..6ef934c9 100644 --- a/MinecraftClient/ChatBots/WebSocketBot.cs +++ b/MinecraftClient/ChatBots/WebSocketBot.cs @@ -859,9 +859,10 @@ namespace MinecraftClient.ChatBots SendSessionEvent(session, "OnWsCommandResponse", "{\"success\": true, \"message\": \"Succesfully authenticated!\"}", true); LogToConsole(Translations.TryGet("bot.WebSocketBot.session_authenticated", session.GetSessionId())); session.SetAuthenticated(true); - return true; } } + + SendText(message); } return true; From 46e8818b5b93963268ddf2152e94fd6c8365c5a4 Mon Sep 17 00:00:00 2001 From: Milutinke Date: Mon, 24 Oct 2022 22:47:11 +0200 Subject: [PATCH 4/5] Fixed a mintor warning in the Map chat bot. --- MinecraftClient/ChatBots/Map.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinecraftClient/ChatBots/Map.cs b/MinecraftClient/ChatBots/Map.cs index a004b82f..e6d4137a 100644 --- a/MinecraftClient/ChatBots/Map.cs +++ b/MinecraftClient/ChatBots/Map.cs @@ -313,7 +313,7 @@ namespace MinecraftClient.ChatBots if (File.Exists(newFileName)) File.Delete(newFileName); } - catch (IOException e) { } + catch (IOException) { } } }); } From 7b19a1137fc9c4ac79140ba97daad68358684858 Mon Sep 17 00:00:00 2001 From: Milutinke Date: Mon, 24 Oct 2022 22:52:49 +0200 Subject: [PATCH 5/5] Added a Debug mode setting to the Websocket chat bot. --- MinecraftClient/ChatBots/WebSocketBot.cs | 8 ++++++-- MinecraftClient/Resources/lang/en.ini | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/ChatBots/WebSocketBot.cs b/MinecraftClient/ChatBots/WebSocketBot.cs index 6ef934c9..fb43fc5a 100644 --- a/MinecraftClient/ChatBots/WebSocketBot.cs +++ b/MinecraftClient/ChatBots/WebSocketBot.cs @@ -185,6 +185,9 @@ namespace MinecraftClient.ChatBots [TomlInlineComment("$config.ChatBot.WebSocketBot.Password$")] public string? Password = "wspass12345"; + + [TomlInlineComment("$config.ChatBot.WebSocketBot.DebugMode$")] + public bool DebugMode = false; } public WebSocketBot() @@ -266,7 +269,8 @@ namespace MinecraftClient.ChatBots { try { - LogDebugToConsole("\n\n\tGot command\n\n\t" + message + "\n\n"); + if (Config.DebugMode) + LogDebugToConsole("\n\n\tGot command\n\n\t" + message + "\n\n"); WsChatBotCommand cmd = JsonConvert.DeserializeObject(message)!; WsCommandResponder responder = new WsCommandResponder(this, session, cmd.Command, cmd.RequestId); @@ -1119,7 +1123,7 @@ namespace MinecraftClient.ChatBots { session.SendToClient("{\"event\": \"" + type + "\", \"data\": " + (data.IsNullOrEmpty() ? "null" : "\"" + Json.EscapeString(data) + "\"") + "}"); - if (!(type.Contains("Entity") || type.Equals("OnTimeUpdate") || type.Equals("OnServerTpsUpdate"))) + if (!(type.Contains("Entity") || type.Equals("OnTimeUpdate") || type.Equals("OnServerTpsUpdate")) && Config.DebugMode) LogDebugToConsole( "\n\n\tSending:\n\n\t{\"event\": \"" + type + "\", \"data\": " + (data.IsNullOrEmpty() ? "null" : "\"" diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini index b4d2d2c5..de943033 100644 --- a/MinecraftClient/Resources/lang/en.ini +++ b/MinecraftClient/Resources/lang/en.ini @@ -1047,3 +1047,4 @@ config.ChatBot.WebSocketBot=Remotely control the client using Web Sockets.\n# Th config.ChatBot.WebSocketBot.Ip=The IP address that Websocket server will be bounded to. config.ChatBot.WebSocketBot.Port=The Port that Websocket server will be bounded to. config.ChatBot.WebSocketBot.Password=A password that will be used to authenticate on thw Websocket server (It is recommended to change the default password and to set a strong one). +config.ChatBot.WebSocketBot.DebugMode=If enabled the chat bot will print what is it recieving and sending when the "DebugMessages" are enabled in "Logging" section. This is useful when making your own library or trying to figure what is wrong with an existing one. \ No newline at end of file