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 (only the first one! This doesn't support multiple arrays because they can have different sizes)
var match = Regex.Match(input, @"<([^\[]*)\[\*\]>");
if (match.Success)
{
var full = match.Groups[0].Value;
var name = match.Groups[1].Value;
// Retrieve the dict
var list = data.Variables.GetList(name);
if (list == null) list = data.GlobalVariables.GetList(name);
// If there's no corresponding variable, just readd the input string and proceed with normal replacement
if (list == null) toReplace.Add(input);
else
{
foreach (var item in list)
toReplace.Add(input.Replace(full, item));
}
goto END;
}
// Regex parse the syntax (wildcard key -> returns list of all values)
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:
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 { }
}
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
public static void InsertVariables(BotData data, bool isCapture, bool recursive, List values, string variableName, string prefix, string suffix)
{
var list = values.Select(v => ReplaceValues(prefix, data) + v.Trim() + ReplaceValues(suffix, data));
CVar variable;
if (recursive) variable = new CVar(variableName, list.ToList(), isCapture);
else variable = new CVar(variableName, list.First(), isCapture);
data.Variables.Set(variable);
data.Log(new LogEntry("Parsed variable" + " | Name: " + variable.Name + " | Value: " + variable.ToString() + Environment.NewLine, isCapture ? Colors.OrangeRed : Colors.Gold));
}
#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
}
}