using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; using System.Threading; namespace MinecraftClient.ChatBots { /// /// Runs a list of commands /// public class Script : ChatBot { private string? file; private string[] lines = Array.Empty(); private string[] args = Array.Empty(); private int sleepticks = 10; private int nextline = 0; private readonly string? owner; private bool csharp; private Thread? thread; private readonly Dictionary? localVars; public Script(string filename) { ParseArguments(filename); } public Script(string filename, string? ownername, Dictionary? localVars) : this(filename) { owner = ownername; this.localVars = localVars; } private void ParseArguments(string argstr) { List args = new(); StringBuilder str = new(); bool escape = false; bool quotes = false; foreach (char c in argstr) { if (escape) { if (c != '"') str.Append('\\'); str.Append(c); escape = false; } else { if (c == '\\') escape = true; else if (c == '"') quotes = !quotes; else if (c == ' ' && !quotes) { if (str.Length > 0) args.Add(str.ToString()); str.Clear(); } else str.Append(c); } } if (str.Length > 0) args.Add(str.ToString()); if (args.Count > 0) { file = args[0]; args.RemoveAt(0); this.args = args.ToArray(); } else file = ""; } public static bool LookForScript(ref string filename) { //Automatically look in subfolders and try to add ".txt" file extension char dir_slash = Path.DirectorySeparatorChar; string[] files = new string[] { filename, filename + ".txt", filename + ".cs", "scripts" + dir_slash + filename, "scripts" + dir_slash + filename + ".txt", "scripts" + dir_slash + filename + ".cs", "config" + dir_slash + filename, "config" + dir_slash + filename + ".txt", "config" + dir_slash + filename + ".cs", }; foreach (string possible_file in files) { if (System.IO.File.Exists(possible_file)) { filename = possible_file; return true; } } if (Settings.Config.Logging.DebugMessages) { string caller = "Script"; try { StackFrame frame = new(1); MethodBase method = frame.GetMethod()!; Type type = method.DeclaringType!; caller = type.Name; } catch { } ConsoleIO.WriteLineFormatted(string.Format(Translations.bot_script_not_found, caller, filename)); } return false; } public override void Initialize() { //Load the given file from the startup parameters if (LookForScript(ref file!)) { lines = System.IO.File.ReadAllLines(file, Encoding.UTF8); csharp = file.EndsWith(".cs"); thread = null; if (!String.IsNullOrEmpty(owner)) SendPrivateMessage(owner, string.Format(Translations.bot_script_pm_loaded, file)); } else { LogToConsole(string.Format(Translations.bot_script_file_not_found, System.IO.Path.GetFullPath(file))); if (!String.IsNullOrEmpty(owner)) SendPrivateMessage(owner, string.Format(Translations.bot_script_file_not_found, file)); UnloadBot(); //No need to keep the bot active } } public override void Update() { if (csharp) //C# compiled script { //Initialize thread on first update if (thread == null) { thread = new Thread(() => { try { CSharpRunner.Run(this, lines, args, localVars, scriptName: file!); } catch (CSharpException e) { string errorMessage = string.Format(Translations.bot_script_fail, file, e.ExceptionType); LogToConsole(errorMessage); if (owner != null) SendPrivateMessage(owner, errorMessage); LogToConsole(e.InnerException); } }) { Name = "MCC Script - " + file }; thread.Start(); } //Unload bot once the thread has finished running if (thread != null && !thread.IsAlive) { UnloadBot(); } } else //Classic MCC script interpreter { if (sleepticks > 0) { sleepticks--; } else { if (nextline < lines.Length) //Is there an instruction left to interpret? { string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line nextline++; //Move the cursor so that the next time the following line will be interpreted if (instruction_line.Length > 1) { if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/') { instruction_line = Settings.Config.AppVar.ExpandVars(instruction_line, localVars); string instruction_name = instruction_line.Split(' ')[0]; switch (instruction_name.ToLower()) { case "wait": int ticks = 10; try { ticks = Convert.ToInt32(instruction_line[5..]); } catch { } sleepticks = ticks; break; default: if (!PerformInternalCommand(instruction_line)) { Update(); //Unknown command : process next line immediately } else if (instruction_name.ToLower() != "log") { LogToConsole(instruction_line); } break; } } else { Update(); } //Comment: process next line immediately } } else { //No more instructions to interpret UnloadBot(); } } } } } }