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 "";
}
}
}
}