Files
OpenBullet/RuriLib/Blocks/BlockBase.cs
T

447 lines
19 KiB
C#

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
{
/// <summary>
/// A generic block used to process information in RuriLib.
/// </summary>
public abstract class BlockBase : ViewModelBase
{
private string label = "BASE";
/// <summary>The label of the block.</summary>
public string Label { get { return label; } set { label = value; OnPropertyChanged(); } }
private bool disabled = false;
/// <summary>Whether the block should be skipped or processed.</summary>
public bool Disabled { get { return disabled; } set { disabled = value; OnPropertyChanged(); } }
/// <summary>Whether the block is a selenium-related block.</summary>
[JsonIgnore]
public bool IsSelenium { get { return GetType().ToString().StartsWith("S"); } }
/// <summary>Whether the block is a captcha-related block.</summary>
[JsonIgnore]
public bool IsCaptcha { get { return GetType() == typeof(BlockImageCaptcha) || GetType() == typeof(BlockRecaptcha); } }
#region Virtual methods
/// <summary>
/// Builds a block from a line of LoliScript code.
/// </summary>
/// <param name="line">The line of LoliScript code</param>
/// <returns>The parsed block object</returns>
public virtual BlockBase FromLS(string line)
{
throw new Exception("Cannot Convert to the abstract class BlockBase");
}
/// <summary>
/// Builds a block from multiple lines of LoliScript code.
/// </summary>
/// <param name="lines">The lines of LoliScript code</param>
/// <returns>The parsed block object</returns>
public virtual BlockBase FromLS(List<string> lines)
{
throw new Exception("Cannot Convert from the abstract class BlockBase");
}
/// <summary>
/// Converts the block to LoliScript code.
/// </summary>
/// <param name="indent"></param>
/// <returns></returns>
public virtual string ToLS(bool indent = true)
{
throw new Exception("Cannot Convert from the abstract class BlockBase");
}
/// <summary>
/// Executes the actual block logic.
/// </summary>
/// <param name="data">The BotData needed for variable replacement</param>
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
/// <summary>
/// Replaces variables recursively, expanding lists or dictionaries with jolly indices.
/// </summary>
/// <param name="input">The string to replace variables into</param>
/// <param name="data">The BotData needed for variable replacement</param>
/// <returns>An array of values obtained replacing the original input with each of the possible values of the first List or Dictionary variable found</returns>
public static List<string> ReplaceValuesRecursive(string input, BotData data)
{
var toReplace = new List<string>();
// Regex parse the syntax <LIST[*]>
var matches = Regex.Matches(input, @"<([^\[]*)\[\*\]>");
var variables = new List<CVar>();
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<string>)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 <DICT(*)> (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 <DICT{*}> (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();
}
/// <summary>
/// Replaces variables in a given input string.
/// </summary>
/// <param name="input">The string to replace variables into</param>
/// <param name="data">The BotData needed for variable replacement</param>
/// <returns>The string where variables have been replaced</returns>
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("<INPUT>", data.Data.Data);
output = output.Replace("<STATUS>", data.Status.ToString());
output = output.Replace("<BOTNUM>", data.BotNumber.ToString());
if (data.Proxy != null)
output = output.Replace("<PROXY>", 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. <br> 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
/// <summary>
/// Parses an argument between two bracket delimiters.
/// </summary>
/// <param name="input">The string to parse the argument from</param>
/// <param name="delimL">The left bracket delimiter</param>
/// <param name="delimR">The right bracket delimiter</param>
/// <returns>The argument between the delimiters</returns>
public static List<string> ParseArguments(string input, char delimL, char delimR)
{
var output = new List<string>();
var matches = Regex.Matches(input, @"\" + delimL + @"([^\" + delimR + @"]*)\" + delimR);
foreach (Match match in matches) output.Add(match.Groups[1].Value);
return output;
}
/// <summary>
/// Updates the ADDRESS and SOURCE variables basing on the selenium-driven browser's URL bar and page source.
/// </summary>
/// <param name="data">The BotData containing the driver and the variables</param>
public static void UpdateSeleniumData(BotData data)
{
data.Address = data.Driver.Url;
data.ResponseSource = data.Driver.PageSource;
}
/// <summary>
/// Adds a single or list variable with the given value.
/// </summary>
/// <param name="data">The BotData used for variable replacement and insertion</param>
/// <param name="isCapture">Whether the variable should be marked for Capture</param>
/// <param name="recursive">Whether the variable to add should be a list or a single value</param>
/// <param name="values">The list of values. In case recursive is set to false, only the first value of the list will be taken.</param>
/// <param name="variableName">The name of the variable to create</param>
/// <param name="prefix">The string to add at the start of the value</param>
/// <param name="suffix">The string to add at the end of the value</param>
/// <param name="urlEncode">Whether to URLencode the values before creating the variables</param>
/// <param name="createEmpty">Whether to create an empty (single) variable if the list of values is empty</param>
public static void InsertVariables(BotData data, bool isCapture, bool recursive, List<string> 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
/// <summary>
/// Saves a Selenium screenshot to a file with automatically generated name.
/// </summary>
/// <param name="screenshot">The Selenium screenshot</param>
/// <param name="data">The BotData used for path creation</param>
public static void SaveScreenshot(OpenQA.Selenium.Screenshot screenshot, BotData data)
{
var path = MakeScreenshotPath(data);
data.Screenshots.Add(path);
screenshot.SaveAsFile(path);
}
/// <summary>
/// Saves a screenshot to a file with automatically generated name.
/// </summary>
/// <param name="screenshot">The bitmap image</param>
/// <param name="data">The BotData used for path creation</param>
public static void SaveScreenshot(Bitmap screenshot, BotData data)
{
var path = MakeScreenshotPath(data);
data.Screenshots.Add(path);
screenshot.Save(path);
}
/// <summary>
/// Builds the path for the screenshot file.
/// </summary>
/// <param name="data">The BotData for path creation</param>
/// <returns>The path of the file to save the screenshot to</returns>
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}";
}
/// <summary>
/// Gets the first available name in the given folder by incrementing a number at the end of the filename.
/// </summary>
/// <param name="basePath">The path to the folder</param>
/// <param name="fileName">The name of the file without numbers at the end</param>
/// <param name="extension">The extension of the file</param>
/// <returns>The first available filename (including extension)</returns>
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;
}
/// <summary>
/// Fixes the filename to be compatible with the filesystem indicization.
/// </summary>
/// <param name="name">The name of the file</param>
/// <param name="underscore">Whether to replace the unallowed characters with an underscore instead of removing them</param>
/// <returns>The valid filename ready to be saved to disk</returns>
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 ? "_" : "");
}
/// <summary>
/// Truncates a string to fit a given size and adds ' [...]' (5 characters) at the end to display that the string would be longer.
/// </summary>
/// <param name="input">The string to truncate</param>
/// <param name="max">The maximum length of the string</param>
/// <returns>The truncated string, or the same string if its length was not exceeding the maximum size</returns>
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
}
}