using Newtonsoft.Json;
using RuriLib.Models;
using RuriLib.ViewModels;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Media;
namespace RuriLib
{
///
/// A generic block used to process information in RuriLib.
///
public abstract class BlockBase : ViewModelBase
{
private string label = "BASE";
/// The label of the block.
public string Label { get { return label; } set { label = value; OnPropertyChanged(); } }
private bool disabled = false;
/// Whether the block should be skipped or processed.
public bool Disabled { get { return disabled; } set { disabled = value; OnPropertyChanged(); } }
/// Whether the block is a selenium-related block.
[JsonIgnore]
public bool IsSelenium { get { return GetType().ToString().StartsWith("S"); } }
/// Whether the block is a captcha-related block.
[JsonIgnore]
public bool IsCaptcha { get { return GetType() == typeof(BlockImageCaptcha) || GetType() == typeof(BlockRecaptcha); } }
#region Virtual methods
///
/// Builds a block from a line of LoliScript code.
///
/// The line of LoliScript code
/// The parsed block object
public virtual BlockBase FromLS(string line)
{
throw new Exception("Cannot Convert to the abstract class BlockBase");
}
///
/// Builds a block from multiple lines of LoliScript code.
///
/// The lines of LoliScript code
/// The parsed block object
public virtual BlockBase FromLS(List lines)
{
throw new Exception("Cannot Convert from the abstract class BlockBase");
}
///
/// Converts the block to LoliScript code.
///
///
///
public virtual string ToLS(bool indent = true)
{
throw new Exception("Cannot Convert from the abstract class BlockBase");
}
///
/// Executes the actual block logic.
///
/// The BotData needed for variable replacement
public virtual void Process(BotData data)
{
// Clear log buffer
data.LogBuffer.Clear();
data.Log(new LogEntry(string.Format("<--- Executing Block {0} --->", Label),Colors.Orange));
}
#endregion
#region Variable Replacement
///
/// Replaces variables recursively, expanding lists or dictionaries with jolly indices.
///
/// The string to replace variables into
/// The BotData needed for variable replacement
/// An array of values obtained replacing the original input with each of the possible values of the first List or Dictionary variable found
public static List ReplaceValuesRecursive(string input, BotData data)
{
var toReplace = new List();
// Regex parse the syntax
var matches = Regex.Matches(input, @"<([^\[]*)\[\*\]>");
var variables = new List();
foreach (Match m in matches)
{
var name = m.Groups[1].Value;
// Retrieve the dict
var variable = data.Variables.Get(name);
if (variable == null)
{
variable = data.GlobalVariables.Get(name);
if (variable == null) continue;
}
if (variable.Type == CVar.VarType.List)
{
variables.Add(variable);
}
}
// If there's no corresponding variable, just readd the input string and proceed with normal replacement
if (variables.Count > 0)
{
var max = variables.OrderBy(v => v.Value.Count).Last().Value.Count;
for (var i = 0; i < max; i++)
{
var replaced = input;
foreach(var variable in variables)
{
var list = (List)variable.Value;
if (list.Count > i)
{
replaced = replaced.Replace($"<{variable.Name}[*]>", list[i]);
}
else
{
replaced = replaced.Replace($"<{variable.Name}[*]>", "NULL");
}
}
toReplace.Add(replaced);
}
goto END;
}
// Regex parse the syntax (wildcard key -> returns list of all values)
var match = Regex.Match(input, @"<([^\(]*)\(\*\)>");
if (match.Success)
{
var full = match.Groups[0].Value;
var name = match.Groups[1].Value;
// Retrieve the list
var dict = data.Variables.GetDictionary(name);
if (dict == null) dict = data.GlobalVariables.GetDictionary(name);
// If there's no corresponding variable, just readd the input string and proceed with normal replacement
if (dict == null) toReplace.Add(input);
else
{
foreach (var item in dict)
toReplace.Add(input.Replace(full, item.Value));
}
goto END;
}
// Regex parse the syntax (wildcard value -> returns list of all keys)
match = Regex.Match(input, @"<([^\{]*)\{\*\}>");
if (match.Success)
{
var full = match.Groups[0].Value;
var name = match.Groups[1].Value;
// Retrieve the dict
var dict = data.Variables.GetDictionary(name);
if (dict == null) dict = data.GlobalVariables.GetDictionary(name);
// If there's no corresponding variable, just readd the input string and proceed with normal replacement
if (dict == null) toReplace.Add(input);
else
{
foreach (var item in dict)
toReplace.Add(input.Replace(full, item.Key));
}
goto END;
}
// If no other match was a success, it means there's no recursive value and we simply add the input to the list
toReplace.Add(input);
END:
// Now for each item in the list, do the normal replacement and return the replaced list of strings
return toReplace.Select(i => ReplaceValues(i, data)).ToList();
}
///
/// Replaces variables in a given input string.
///
/// The string to replace variables into
/// The BotData needed for variable replacement
/// The string where variables have been replaced
public static string ReplaceValues(string input, BotData data)
{
if (!input.Contains("<") && !input.Contains(">")) return input;
var previous = "";
var output = input;
do
{
previous = output;
// Replace all the fixed quantities (this needs to go away inside BotData.cs, initialized as hidden vars)
output = output.Replace("", data.Data.Data);
output = output.Replace("", data.Status.ToString());
output = output.Replace("", data.BotNumber.ToString());
if (data.Proxy != null)
output = output.Replace("", data.Proxy.Proxy);
// Get all the inner (max. 1 level of nesting) variables
var matches = Regex.Matches(output, @"<([^<>]*)>");
foreach(Match match in matches)
{
var full = match.Groups[0].Value;
var m = match.Groups[1].Value;
// Parse the variable name
var name = Regex.Match(m, @"^[^\[\{\(]*").Value;
// Try to get the variable (first local, then global, then if none was found go to the next iteration)
// We don't throw an error here because it could be some HTML or XML code e.g. that triggers this, and we dont' want to spam the user with unneeded errors
var v = data.Variables.Get(name);
if (v == null) v = data.GlobalVariables.Get(name);
if (v == null) continue;
// Parse the arguments
var args = m.Replace(name, "");
switch (v.Type)
{
case CVar.VarType.Single:
output = output.Replace(full, v.Value);
break;
case CVar.VarType.List:
// If it's just the list name, replace it with its string representation
if (string.IsNullOrEmpty(args))
{
output = output.Replace(full, v.ToString());
break;
}
var index = 0;
int.TryParse(ParseArguments(args, '[', ']')[0], out index);
var item = v.GetListItem(index); // Can return null
if (item != null) output = output.Replace(full, item);
break;
case CVar.VarType.Dictionary:
if (args.Contains("(") && args.Contains(")"))
{
var dicKey = ParseArguments(args, '(', ')')[0];
try { output = output.Replace(full, v.GetDictValue(dicKey)); } catch { }
}
else if (args.Contains("{") && args.Contains("}"))
{
var dicVal = ParseArguments(args, '{', '}')[0];
try { output = output.Replace(full, v.GetDictKey(dicVal)); } catch { }
}
else // If it's just the dictionary name, replace it with its string representation
{
output = output.Replace(full, v.ToString());
break;
}
break;
}
}
}
while (input.Contains("<") && input.Contains(">") && output != previous);
return output;
}
#endregion
///
/// Parses an argument between two bracket delimiters.
///
/// The string to parse the argument from
/// The left bracket delimiter
/// The right bracket delimiter
/// The argument between the delimiters
public static List ParseArguments(string input, char delimL, char delimR)
{
var output = new List();
var matches = Regex.Matches(input, @"\" + delimL + @"([^\" + delimR + @"]*)\" + delimR);
foreach (Match match in matches) output.Add(match.Groups[1].Value);
return output;
}
///
/// Updates the ADDRESS and SOURCE variables basing on the selenium-driven browser's URL bar and page source.
///
/// The BotData containing the driver and the variables
public static void UpdateSeleniumData(BotData data)
{
data.Address = data.Driver.Url;
data.ResponseSource = data.Driver.PageSource;
}
///
/// Adds a single or list variable with the given value.
///
/// The BotData used for variable replacement and insertion
/// Whether the variable should be marked for Capture
/// Whether the variable to add should be a list or a single value
/// The list of values. In case recursive is set to false, only the first value of the list will be taken.
/// The name of the variable to create
/// The string to add at the start of the value
/// The string to add at the end of the value
/// Whether to URLencode the values before creating the variables
/// Whether to create an empty (single) variable if the list of values is empty
public static void InsertVariables(BotData data, bool isCapture, bool recursive, List values, string variableName, string prefix, string suffix, bool urlEncode, bool createEmpty)
{
var list = values.Select(v => ReplaceValues(prefix, data) + v.Trim() + ReplaceValues(suffix, data)).ToList();
if (urlEncode) list = list.Select(v => System.Uri.EscapeDataString(v)).ToList();
CVar variable = null;
if (recursive)
{
if (list.Count == 0)
{
if (createEmpty)
{
variable = new CVar(variableName, list, isCapture);
}
}
else
{
variable = new CVar(variableName, list, isCapture);
}
}
else
{
if (list.Count == 0)
{
if (createEmpty)
{
variable = new CVar(variableName, "", isCapture);
}
}
else
{
variable = new CVar(variableName, list.First(), isCapture);
}
}
if (variable != null)
{
data.Variables.Set(variable);
data.Log(new LogEntry("Parsed variable" + " | Name: " + variable.Name + " | Value: " + variable.ToString() + Environment.NewLine, isCapture ? Colors.OrangeRed : Colors.Gold));
}
else
{
data.Log(new LogEntry("Could not parse any data. The variable was not created.", Colors.White));
}
}
#region File Utilities
///
/// Saves a Selenium screenshot to a file with automatically generated name.
///
/// The Selenium screenshot
/// The BotData used for path creation
public static void SaveScreenshot(OpenQA.Selenium.Screenshot screenshot, BotData data)
{
var path = MakeScreenshotPath(data);
data.Screenshots.Add(path);
screenshot.SaveAsFile(path);
}
///
/// Saves a screenshot to a file with automatically generated name.
///
/// The bitmap image
/// The BotData used for path creation
public static void SaveScreenshot(Bitmap screenshot, BotData data)
{
var path = MakeScreenshotPath(data);
data.Screenshots.Add(path);
screenshot.Save(path);
}
///
/// Builds the path for the screenshot file.
///
/// The BotData for path creation
/// The path of the file to save the screenshot to
private static string MakeScreenshotPath(BotData data)
{
var folderName = MakeValidFileName(data.ConfigSettings.Name);
var originalFilename = MakeValidFileName(data.Data.Data);
// Check if you have to make the folder
if (!Directory.Exists($"Screenshots\\{folderName}")) Directory.CreateDirectory($"Screenshots\\{folderName}");
// Save the file inside the folder
var filename = GetFirstAvailableFileName($"Screenshots\\{folderName}\\", originalFilename, "bmp");
return $"Screenshots\\{folderName}\\{filename}";
}
///
/// Gets the first available name in the given folder by incrementing a number at the end of the filename.
///
/// The path to the folder
/// The name of the file without numbers at the end
/// The extension of the file
/// The first available filename (including extension)
public static string GetFirstAvailableFileName(string basePath, string fileName, string extension)
{
int i;
for (i = 1; File.Exists(basePath + fileName + i + "." + extension); i++) { }
return fileName + i + "." + extension;
}
///
/// Fixes the filename to be compatible with the filesystem indicization.
///
/// The name of the file
/// Whether to replace the unallowed characters with an underscore instead of removing them
/// The valid filename ready to be saved to disk
public static string MakeValidFileName(string name, bool underscore = true)
{
string invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
string invalidRegStr = string.Format(@"([{0}]*\.+$)|([{0}]+)", invalidChars);
return Regex.Replace(name, invalidRegStr, underscore ? "_" : "");
}
///
/// Truncates a string to fit a given size and adds ' [...]' (5 characters) at the end to display that the string would be longer.
///
/// The string to truncate
/// The maximum length of the string
/// The truncated string, or the same string if its length was not exceeding the maximum size
public static string TruncatePretty(string input, int max)
{
input = input.Replace("\r\n", "").Replace("\n", "");
if (input.Length < max) return input;
return input.Substring(0, max) + " [...]";
}
#endregion
}
}