using RuriLib.LS; using RuriLib.Models; using RuriLib.ViewModels; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Numerics; using System.Text; using System.Windows.Media; namespace RuriLib { /// /// The available utility groups. /// public enum UtilityGroup { /// The group of actions performed on list variables. List, /// The group of actions performed on single variables. Variable, /// The group of action to convert an encoded string. Conversion, /// The group of actions that interact with files. File } /// /// Actions executed on single variables. /// public enum VarAction { /// Splits a variable into a list given a separator. Split } /// /// Actions executed on list variables. /// public enum ListAction { /// Creates an empty list variable. Create, /// Calculates the length of a list. Length, /// Joins a list into a single string, separating the elements with a separator. Join, /// Sorts a list alphabetically, in ascending or descending order. Sort, /// Concatenates two lists into a longer list variable. Concat, /// Zips two lists into a new list variable where the elements are joined two by two. Zip, /// Maps two lists into a dictionary variable. Map, /// Adds an element to a list variable. Add, /// Removes an element from a list variable. Remove, /// Removes duplicate elements from a list variable, keeping only the first one. RemoveDuplicates, /// Picks a random element from a list variable. Random } /// /// The available conversion formats. /// public enum Conversion { /// A hexadecimal representation of a byte array. HEX, /// A binary representation of a byte array, containing a multiple of 8 binary digits. BIN, /// A base64 representation of a byte array. BASE64 } /// /// Actions that can be performed on files. /// public enum FileAction { /// Reads a file to a single variable. Read, /// Reads a file to a list variable. ReadLines, /// Writes a single variable to a file. Write, /// Writes a list variable to a file. WriteLines, /// Appends a single variable to a file. Append, /// Appends a list variable to a file. AppendLines } /// /// A block that performs actions on variables, converts values and operates on files. /// public class BlockUtility : BlockBase { // General private UtilityGroup group = UtilityGroup.List; /// The utility group. public UtilityGroup Group { get { return group; } set { group = value; OnPropertyChanged(); } } private string variableName = ""; /// The name of the output variable. public string VariableName { get { return variableName; } set { variableName = value; OnPropertyChanged(); } } private bool isCapture = false; /// Whether the output variable should be marked for Capture. public bool IsCapture { get { return isCapture; } set { isCapture = value; OnPropertyChanged(); } } private string inputString = ""; /// The input to process. public string InputString { get { return inputString; } set { inputString = value; OnPropertyChanged(); } } // Lists private ListAction listAction = ListAction.Join; /// The action to be performed on a list variable. public ListAction ListAction { get { return listAction; } set { listAction = value; OnPropertyChanged(); } } private string listName = ""; /// The name of the target list variable. public string ListName { get { return listName; } set { listName = value; OnPropertyChanged(); } } #region ListUtility Specific Variables private string separator = ","; // Join /// The separator for joining a list. public string Separator { get { return separator; } set { separator = value; OnPropertyChanged(); } } private bool ascending = true; /// Whether the sort should happen in ascending order. public bool Ascending { get { return ascending; } set { ascending = value; OnPropertyChanged(); } } private bool numeric = false; /// Whether a list is made of numeric values. public bool Numeric { get { return numeric; } set { numeric = value; OnPropertyChanged(); } } private string secondListName = ""; // Zip /// The name of the second list variable. public string SecondListName { get { return secondListName; } set { secondListName = value; OnPropertyChanged(); } } private string listItem = ""; // Add /// The list item to add. public string ListItem { get { return listItem; } set { listItem = value; OnPropertyChanged(); } } private string listIndex = "-1"; // Add (-1 = end, 0 = start) /// The list index where an item can be added/removed. -1 = end, 0 = start. public string ListIndex { get { return listIndex; } set { listIndex = value; OnPropertyChanged(); } } #endregion // Variables private VarAction varAction = VarAction.Split; /// The action to be performed on a single variable. public VarAction VarAction { get { return varAction; } set { varAction = value; OnPropertyChanged(); } } private string varName = ""; /// The name of the single variable. public string VarName { get { return varName; } set { varName = value; OnPropertyChanged(); } } #region Variables Specific private string splitSeparator = ""; /// The separator to split a string into a list. public string SplitSeparator { get { return splitSeparator; } set { splitSeparator = value; OnPropertyChanged(); } } #endregion // Conversion private Conversion conversionFrom = Conversion.HEX; /// The encoding to convert from. public Conversion ConversionFrom { get { return conversionFrom; } set { conversionFrom = value; OnPropertyChanged(); } } private Conversion conversionTo = Conversion.BASE64; /// The encoding to convert to. public Conversion ConversionTo { get { return conversionTo; } set { conversionTo = value; OnPropertyChanged(); } } // Files private string filePath = "test.txt"; /// The path to the file to read/write. public string FilePath { get { return filePath; } set { filePath = value; OnPropertyChanged(); } } private FileAction fileAction = FileAction.Read; /// The action to be performed on the file. public FileAction FileAction { get { return fileAction; } set { fileAction = value; OnPropertyChanged(); } } /// /// Creates a Utility block. /// public BlockUtility() { Label = "UTILITY"; } /// public override BlockBase FromLS(string line) { // Trim the line var input = line.Trim(); // Parse the label if (input.StartsWith("#")) Label = LineParser.ParseLabel(ref input); // Parse the function Group = (UtilityGroup)LineParser.ParseEnum(ref input, "Group", typeof(UtilityGroup)); // Parse specific function parameters switch (Group) { case UtilityGroup.List: ListName = LineParser.ParseLiteral(ref input, "List Name"); ListAction = (ListAction)LineParser.ParseEnum(ref input, "List Action", typeof(ListAction)); switch (ListAction) { case ListAction.Join: Separator = LineParser.ParseLiteral(ref input, "Separator"); break; case ListAction.Sort: while (LineParser.Lookahead(ref input) == TokenType.Boolean) LineParser.SetBool(ref input, this); break; case ListAction.Concat: case ListAction.Zip: case ListAction.Map: SecondListName = LineParser.ParseLiteral(ref input, "Second List Name"); break; case ListAction.Add: ListItem = LineParser.ParseLiteral(ref input, "Item"); ListIndex = LineParser.ParseLiteral(ref input, "Index"); break; case ListAction.Remove: ListIndex = LineParser.ParseLiteral(ref input, "Index"); break; } break; case UtilityGroup.Variable: VarName = LineParser.ParseLiteral(ref input, "Var Name"); VarAction = (VarAction)LineParser.ParseEnum(ref input, "Var Action", typeof(VarAction)); switch (VarAction) { case VarAction.Split: SplitSeparator = LineParser.ParseLiteral(ref input, "SplitSeparator"); break; } break; case UtilityGroup.Conversion: ConversionFrom = (Conversion)LineParser.ParseEnum(ref input, "Conversion From", typeof(Conversion)); ConversionTo = (Conversion)LineParser.ParseEnum(ref input, "Conversion To", typeof(Conversion)); InputString = LineParser.ParseLiteral(ref input, "Input"); break; case UtilityGroup.File: FilePath = LineParser.ParseLiteral(ref input, "File Name"); FileAction = (FileAction)LineParser.ParseEnum(ref input, "File Action", typeof(FileAction)); switch (FileAction) { case FileAction.Write: case FileAction.WriteLines: case FileAction.Append: case FileAction.AppendLines: InputString = LineParser.ParseLiteral(ref input, "Input String"); break; } break; } // Try to parse the arrow, otherwise just return the block as is with default var name and var / cap choice if (LineParser.ParseToken(ref input, TokenType.Arrow, false) == "") return this; // Parse the VAR / CAP try { var varType = LineParser.ParseToken(ref input, TokenType.Parameter, true); if (varType.ToUpper() == "VAR" || varType.ToUpper() == "CAP") IsCapture = varType.ToUpper() == "CAP"; } catch { throw new ArgumentException("Invalid or missing variable type"); } // Parse the variable/capture name try { VariableName = LineParser.ParseToken(ref input, TokenType.Literal, true); } catch { throw new ArgumentException("Variable name not specified"); } return this; } /// public override string ToLS(bool indent = true) { var writer = new BlockWriter(GetType(), indent, Disabled); writer .Label(Label) .Token("UTILITY") .Token(Group); switch (Group) { case UtilityGroup.List: writer .Literal(ListName) .Token(ListAction); switch (ListAction) { case ListAction.Join: writer .Literal(Separator); break; case ListAction.Sort: writer .Boolean(Ascending, "Ascending") .Boolean(Numeric, "Numeric"); break; case ListAction.Concat: case ListAction.Zip: case ListAction.Map: writer .Literal(SecondListName); break; case ListAction.Add: writer .Literal(ListItem) .Literal(ListIndex); break; case ListAction.Remove: writer .Literal(ListIndex); break; } break; case UtilityGroup.Variable: writer .Literal(VarName) .Token(VarAction); switch (VarAction) { case VarAction.Split: writer .Literal(SplitSeparator); break; } break; case UtilityGroup.Conversion: writer .Token(ConversionFrom) .Token(ConversionTo) .Literal(InputString); break; case UtilityGroup.File: writer .Literal(FilePath) .Token(FileAction); switch (FileAction) { case FileAction.Write: case FileAction.WriteLines: case FileAction.Append: case FileAction.AppendLines: writer .Literal(InputString); break; } break; } if (!writer.CheckDefault(VariableName, "VariableName")) writer .Arrow() .Token(IsCapture ? "CAP" : "VAR") .Literal(VariableName); return writer.ToString(); } /// public override void Process(BotData data) { base.Process(data); try { switch (group) { case UtilityGroup.List: var list = data.Variables.GetList(listName); var list2 = data.Variables.GetList(secondListName); var item = ReplaceValues(listItem, data); var index = int.Parse(ReplaceValues(listIndex, data)); CVar variable = null; switch (listAction) { case ListAction.Create: data.Variables.Set(new CVar(variableName, new List(), isCapture)); break; case ListAction.Length: data.Variables.Set(new CVar(variableName, list.Count().ToString(), isCapture)); break; case ListAction.Join: data.Variables.Set(new CVar(variableName, string.Join(separator, list), isCapture)); break; case ListAction.Sort: var sorted = list.Select(e => e).ToList(); // Clone the list so we don't edit the original one if (Numeric) { var nums = sorted.Select(e => double.Parse(e, CultureInfo.InvariantCulture)).ToList(); nums.Sort(); sorted = nums.Select(e => e.ToString()).ToList(); } else { sorted.Sort(); } if (!Ascending) sorted.Reverse(); data.Variables.Set(new CVar(variableName, sorted, isCapture)); break; case ListAction.Concat: data.Variables.Set(new CVar(variableName, list.Concat(list2).ToList(), isCapture)); break; case ListAction.Zip: data.Variables.Set(new CVar(variableName, list.Zip(list2, (a, b) => a + b).ToList(), isCapture)); break; case ListAction.Map: data.Variables.Set(new CVar(variableName, list.Zip(list2, (k, v) => new { k, v }).ToDictionary(x => x.k, x => x.v), isCapture)); break; case ListAction.Add: variable = data.Variables.Get(listName, CVar.VarType.List); if (variable == null) variable = data.GlobalVariables.Get(listName, CVar.VarType.List); if (variable == null) break; if (variable.Value.Count == 0) index = 0; else if (index < 0) index += variable.Value.Count; variable.Value.Insert(index, item); break; case ListAction.Remove: variable = data.Variables.Get(listName, CVar.VarType.List); if (variable == null) variable = data.GlobalVariables.Get(listName, CVar.VarType.List); if (variable == null) break; if (variable.Value.Count == 0) index = 0; else if (index < 0) index += variable.Value.Count; variable.Value.RemoveAt(index); break; case ListAction.RemoveDuplicates: data.Variables.Set(new CVar(variableName, list.Distinct().ToList(), isCapture)); break; case ListAction.Random: data.Variables.Set(new CVar(variableName, list[data.rand.Next(list.Count)], isCapture)); break; default: break; } data.Log(new LogEntry(string.Format("Executed action {0} on list {1}", listAction, listName), Colors.White)); break; case UtilityGroup.Variable: string single; switch (varAction) { case VarAction.Split: single = data.Variables.GetSingle(varName); data.Variables.Set(new CVar(variableName, single.Split(new string[] { ReplaceValues(splitSeparator, data) }, StringSplitOptions.None).ToList(), isCapture)); break; } data.Log(new LogEntry(string.Format("Executed action {0} on variable {1}", varAction, varName), Colors.White)); break; case UtilityGroup.Conversion: byte[] convertedBytes = ConvertFrom(ReplaceValues(inputString, data), conversionFrom); data.Variables.Set(new CVar(variableName, ConvertTo(convertedBytes, conversionTo), isCapture)); data.Log(new LogEntry(string.Format("Converted input from {0} to {1}", conversionFrom, conversionTo), Colors.White)); break; case UtilityGroup.File: var file = ReplaceValues(filePath, data); var input = ReplaceValues(inputString, data).Replace("\\r\\n", "\r\n").Replace("\\n", "\n"); var inputs = ReplaceValuesRecursive(inputString, data); switch (fileAction) { case FileAction.Read: data.Variables.Set(new CVar(variableName, File.ReadAllText(file), isCapture)); break; case FileAction.ReadLines: data.Variables.Set(new CVar(variableName, File.ReadAllLines(file).ToList(), isCapture)); break; case FileAction.Write: File.WriteAllText(file, input); break; case FileAction.WriteLines: File.WriteAllLines(file, inputs); break; case FileAction.Append: File.AppendAllText(file, input); break; case FileAction.AppendLines: File.AppendAllLines(file, inputs); break; } break; default: break; } } catch(Exception ex) { data.Log(new LogEntry(ex.Message, Colors.Tomato)); } } /// /// Converts an encoded input to a byte array. /// /// The encoded input /// The encoding /// The converted byte array public byte[] ConvertFrom(string input, Conversion type) { switch (type) { case Conversion.BASE64: return Convert.FromBase64String(input); case Conversion.HEX: input = new string(input.ToCharArray() .Where(c => !char.IsWhiteSpace(c)) .ToArray()).Replace("0x", ""); return Enumerable.Range(0, input.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(input.Substring(x, 2), 16)) .ToArray(); case Conversion.BIN: int numOfBytes = input.Length / 8; byte[] bytes = new byte[numOfBytes]; for (int i = 0; i < numOfBytes; ++i) { bytes[i] = Convert.ToByte(input.Substring(8 * i, 8), 2); } return bytes; default: return new byte[0]; } } /// /// Converts a byte array to an encoded string. /// /// The byte array to encode /// The encoding /// The encoded string public string ConvertTo(byte[] input, Conversion type) { StringBuilder sb = new StringBuilder(); switch (type) { case Conversion.BASE64: return Convert.ToBase64String(input); case Conversion.HEX: foreach (byte b in input) sb.AppendFormat("{0:x2}", b); return sb.ToString().ToUpper(); case Conversion.BIN: return string.Concat(input.Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))); default: return ""; } } } }