using OpenQA.Selenium; using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Remote; using RuriLib.LS; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Media; namespace RuriLib.LS { /// /// Parses a MOUSEACTION command. /// class MouseActionParser { /// /// Gets the Action that needs to be executed. /// /// The data line to parse /// The BotData needed for variable replacement /// The Action to execute public static Action Parse(string line, BotData data) { // Trim the line var input = line.Trim(); // Initialize the action chain Actions actions = null; try { actions = new Actions(data.Driver); } catch { throw new Exception("No Browser initialized!"); } // Build it var offsetX = 0; var offsetY = 0; var point1X = 0; var point1Y = 0; var point2X = 0; var point2Y = 0; var key = ""; var gravity = 1; var wind = 1; var qty = 0; IWebElement elem1 = null; IWebElement elem2 = null; Line newLine = null; while (input != "") { var parsed = LineParser.ParseToken(ref input, TokenType.Parameter, true).ToUpper(); switch (parsed) { case "SPAWN": // Spawn a div in a certain position so you can hook to it later via id SpawnDiv(data.Driver, LineParser.ParseInt(ref input, "X"), LineParser.ParseInt(ref input, "Y"), LineParser.ParseLiteral(ref input, "ID")); break; case "CLICK": if (!LineParser.CheckIdentifier(ref input, "ELEMENT")) actions.Click(); else actions.Click(ParseElement(ref input, data)); break; case "CLICKANDHOLD": if (!LineParser.CheckIdentifier(ref input, "ELEMENT")) actions.ClickAndHold(); else actions.ClickAndHold(ParseElement(ref input, data)); break; case "RIGHTCLICK": if (!LineParser.CheckIdentifier(ref input, "ELEMENT")) actions.ContextClick(); else actions.ContextClick(ParseElement(ref input, data)); break; case "DOUBLECLICK": if (!LineParser.CheckIdentifier(ref input, "ELEMENT")) actions.DoubleClick(); else actions.DoubleClick(ParseElement(ref input, data)); break; case "DRAGANDDROP": elem1 = ParseElement(ref input, data); LineParser.ParseToken(ref input, TokenType.Arrow, true); elem2 = ParseElement(ref input, data); actions.DragAndDrop(elem1, elem2); break; case "DRAGANDDROPWITHOFFSET": offsetX = LineParser.ParseInt(ref input, "OFFSET X"); offsetY = LineParser.ParseInt(ref input, "OFFSET Y"); actions.DragAndDropToOffset(ParseElement(ref input, data), offsetX, offsetY); break; case "KEYDOWN": key = LineParser.ParseLiteral(ref input, "KEY", true, data); if (!LineParser.CheckIdentifier(ref input, "ELEMENT")) actions.KeyDown(key); else actions.KeyDown(ParseElement(ref input, data), key); break; case "KEYUP": key = LineParser.ParseLiteral(ref input, "KEY", true, data); if (!LineParser.CheckIdentifier(ref input, "ELEMENT")) actions.KeyUp(key); else actions.KeyUp(ParseElement(ref input, data), key); break; case "MOVEBY": offsetX = LineParser.ParseInt(ref input, "OFFSET X"); offsetY = LineParser.ParseInt(ref input, "OFFSET Y"); actions.MoveByOffset(offsetX, offsetY); break; case "MOVETO": actions.MoveToElement(ParseElement(ref input, data)); break; case "RELEASE": if (!LineParser.CheckIdentifier(ref input, "ELEMENT")) actions.Release(); else actions.Release(ParseElement(ref input, data)); break; case "SENDKEYS": key = LineParser.ParseLiteral(ref input, "KEY", true, data); if (!LineParser.CheckIdentifier(ref input, "ELEMENT")) actions.SendKeys(key); else actions.SendKeys(ParseElement(ref input, data), key); break; case "DRAWPOINTS": offsetX = LineParser.ParseInt(ref input, "MAX WIDTH"); offsetY = LineParser.ParseInt(ref input, "MAX HEIGHT"); var amount = LineParser.ParseInt(ref input, "AMOUNT"); Random rand = new Random(); var previousx = 0; var previousy = 0; // Move to the first point actions.MoveToElement(data.Driver.FindElementByTagName("body"), point1X, point1Y); List points = new List(); for (int i = 0; i < amount; i++) { var x = rand.Next(0, offsetX); var y = rand.Next(0, offsetY); actions.MoveByOffset(x - previousx, y - previousy); previousx = x; previousy = y; points.Add(new Point(x, y)); } if (data.GlobalSettings.Selenium.DrawMouseMovement) DrawRedDots(data.Driver, points.ToArray(), 5); break; case "DRAWLINE": if (LineParser.Lookahead(ref input) == TokenType.Integer) { point1X = LineParser.ParseInt(ref input, "X1"); point1Y = LineParser.ParseInt(ref input, "Y1"); LineParser.ParseToken(ref input, TokenType.Arrow, true); point2X = LineParser.ParseInt(ref input, "X2"); point2Y = LineParser.ParseInt(ref input, "Y2"); } else { elem1 = ParseElement(ref input, data); point1X = elem1.Location.X; point1Y = elem1.Location.Y; LineParser.ParseToken(ref input, TokenType.Arrow, true); elem2 = ParseElement(ref input, data); point2X = elem2.Location.X; point2Y = elem2.Location.Y; } LineParser.EnsureIdentifier(ref input, ":"); qty = LineParser.ParseInt(ref input, "QUANTITY"); // Move to the first point actions.MoveToElement(data.Driver.FindElementByTagName("body"), point1X, point1Y); newLine = new Line(new Point(point1X, point1Y), new Point(point2X, point2Y)); if (data.GlobalSettings.Selenium.DrawMouseMovement) DrawRedDots(data.Driver, newLine.getPoints(qty), 5); foreach (var p in newLine.getOffsets(qty)) { actions.MoveByOffset(p.X, p.Y); } break; case "DRAWLINEHUMAN": if (LineParser.Lookahead(ref input) == TokenType.Integer) { point1X = LineParser.ParseInt(ref input, "X1"); point1Y = LineParser.ParseInt(ref input, "Y1"); LineParser.ParseToken(ref input, TokenType.Arrow, true); point2X = LineParser.ParseInt(ref input, "X2"); point2Y = LineParser.ParseInt(ref input, "Y2"); } else { elem1 = ParseElement(ref input, data); point1X = elem1.Location.X; point1Y = elem1.Location.Y; LineParser.ParseToken(ref input, TokenType.Arrow, true); elem2 = ParseElement(ref input, data); point2X = elem2.Location.X; point2Y = elem2.Location.Y; } LineParser.EnsureIdentifier(ref input, ":"); qty = LineParser.ParseInt(ref input, "QUANTITY"); if (LineParser.Lookahead(ref input) == TokenType.Integer) { gravity = LineParser.ParseInt(ref input, "GRAVITY"); wind = LineParser.ParseInt(ref input, "WIND"); } // Move to the first point actions.MoveToElement(data.Driver.FindElementByTagName("body"), point1X, point1Y); newLine = new Line(new Point(point1X, point1Y), new Point(point2X, point2Y)); var array = newLine.HumanWindMouse(point1X, point1Y, point2X, point2Y, gravity, wind, 1); var shrinked = ShrinkArray(array, qty); if (data.GlobalSettings.Selenium.DrawMouseMovement) DrawRedDots(data.Driver, shrinked, 5); foreach (var p in GetOffsets(shrinked)) { actions.MoveByOffset(p.X, p.Y); } break; } } return new Action(() => { actions.Build(); actions.Perform(); data.Log(new LogEntry("Executed Mouse Actions", Colors.White)); }); } #region Action Creators /// /// Draws a red dot at the specified coordinates. /// /// The selenium driver /// The x coordinate of the dot /// The y coordinate of the dot /// The thickness in pixels of the dot private static void DrawRedDot(RemoteWebDriver driver, int x, int y, int thickness) { try { var executor = driver as IJavaScriptExecutor; executor.ExecuteScript(RedDotScript(x, y, thickness)); } catch { } } /// /// Shrinks an array of points by removing random elements to fit a target size. /// /// The original array of points /// The target size of the array being returned /// An array of points with the target size public static Point[] ShrinkArray(Point[] originArray, int targetSize) { Random rand = new Random(); List origin = originArray.ToList(); while (origin.Count > targetSize) { origin.RemoveAt(rand.Next(1, origin.Count - 2)); } return origin.ToArray(); } /// /// Given an array of points, gets the offsets between one point and the next. /// /// The original array of points /// The array of offsets private static Point[] GetOffsets(Point[] originArray) { List points = new List(); var prev = originArray[0]; foreach (var point in originArray) { points.Add(new Point(point.X - prev.X, point.Y - prev.Y)); prev = point; } return points.ToArray(); } /// /// Builds the js needed to draw a red dot on the screen with the specified options. /// /// The x coordinate of the dot /// The y coordinate of the dot /// The thickness of the dot /// The id of the created div element /// The script to execute inside the the browser private static string RedDotScript(int x, int y, int thickness, string id = "reddot") { return " var div = document.createElement('div');" + " div.style.backgroundColor = 'red';" + " div.id = '" + id + "';" + " div.style.position = 'absolute';" + " div.style.left = '" + x + "px';" + " div.style.top = '" + y + "px';" + " div.style.height = '" + thickness + "px';" + " div.style.width = '" + thickness + "px';" + " document.getElementsByTagName('body')[0].appendChild(div);"; } /// /// Spawns a 1x1 div element at the specified coordinates. /// /// /// The selenium driver /// The x coordinate of the div /// The y coordinate of the div /// /// The id of the div element private static void SpawnDiv(RemoteWebDriver driver, int x, int y, string id) { try { var executor = driver as IJavaScriptExecutor; executor.ExecuteScript(RedDotScript(x, y, 1, id)); } catch { } } /// /// Draws an array of red dots on the screen. /// /// The selenium driver /// The array of points to draw /// The thickness of the points public static void DrawRedDots(RemoteWebDriver driver, Point[] points, int thickness) { var script = ""; for (int i = 0; i < points.Count(); i++) script += RedDotScript(points[i].X, points[i].Y, thickness); try { var executor = driver as IJavaScriptExecutor; executor.ExecuteScript(script); } catch { } } /// /// Parses an html element from LoliScript code. /// /// The reference to the line of code /// The BotData needed for variable replacement /// The parsed IWebElement public static IWebElement ParseElement(ref string input, BotData data) { LineParser.EnsureIdentifier(ref input, "ELEMENT"); var locator = (ElementLocator)LineParser.ParseEnum(ref input, "Element Locator", typeof(ElementLocator)); var elemstring = LineParser.ParseLiteral(ref input, "Element Identifier"); var index = 0; if (LineParser.Lookahead(ref input) == TokenType.Integer) index = LineParser.ParseInt(ref input, "Element Index"); switch (locator) { case ElementLocator.Id: return data.Driver.FindElementsById(elemstring)[index]; case ElementLocator.Class: return data.Driver.FindElementsByClassName(elemstring)[index]; case ElementLocator.Name: return data.Driver.FindElementsByName(elemstring)[index]; case ElementLocator.Selector: return data.Driver.FindElementsByCssSelector(elemstring)[index]; case ElementLocator.Tag: return data.Driver.FindElementsByTagName(elemstring)[index]; case ElementLocator.XPath: return data.Driver.FindElementsByXPath(elemstring)[index]; default: throw new Exception("Element not found on the page"); } } #endregion } #region Line Class /// /// Represents a line drawn between two points. /// This class provides useful methods to simulate mouse movement across the screen. /// public class Line { private Point p1, p2; private Random rand; /// /// Creates a line between two points. /// /// /// public Line(Point p1, Point p2) { this.p1 = p1; this.p2 = p2; rand = new Random(); } /// /// Gets a given number of equally distant points in the line. /// /// The amount of points to generate /// The array of the generated points public Point[] getPoints(int quantity) { var points = new Point[quantity]; int ydiff = p2.Y - p1.Y, xdiff = p2.X - p1.X; double slope = (double)(p2.Y - p1.Y) / (p2.X - p1.X); double x, y; --quantity; for (double i = 0; i < quantity; i++) { y = slope == 0 ? 0 : ydiff * (i / quantity); x = slope == 0 ? xdiff * (i / quantity) : y / slope; points[(int)i] = new Point((int)Math.Round(x) + p1.X, (int)Math.Round(y) + p1.Y); } points[quantity] = p2; return points; } /// /// Gets a given number of offsets from equally distant points in the line. /// /// The amount of offsets to generate /// The array of the generated offsets public Point[] getOffsets(int quantity) { var points = new Point[quantity]; int ydiff = p2.Y - p1.Y, xdiff = p2.X - p1.X; double slope = (double)(p2.Y - p1.Y) / (p2.X - p1.X); double x, y; --quantity; for (double i = 0; i < quantity; i++) { y = slope == 0 ? 0 : ydiff * (i / quantity); x = slope == 0 ? xdiff * (i / quantity) : y / slope; points[(int)i] = new Point((int)Math.Round(x), (int)Math.Round(y)); } points[quantity] = p2; return points; } /// /// The carthesian distance between two points. /// /// The x coordinate of the first point /// The y coordinate of the first point /// The x coordinate of the second point /// The y coordinate of the second point /// The distance between the points private static double Distance(double x1, double y1, double x2, double y2) { return Math.Sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2)); } /// /// Gets the hypotenuse of a triangle given its legs. /// /// The first leg of the triangle /// The second leg of the triangle /// The value of the hypotenuse private static double Hypot(double x, double y) { return Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2)); } /// /// Gets the points drawn by a mouse when a human moves it across the screen. /// /// The x coordinate of the starting point /// The y coordinate of the starting point /// The x coordinate of the ending point /// The y coordinate of the ending point /// The gravity the movement is subject to /// The non linearity of the movement /// The target area to reach /// The set of points to draw in order to emulate a humanlike movement of the mouse public Point[] HumanWindMouse(double xs, double ys, double xe, double ye, double gravity, double wind, double targetArea) { double veloX = 0, veloY = 0, windX = 0, windY = 0; var sqrt2 = Math.Sqrt(2); var sqrt3 = Math.Sqrt(3); var sqrt5 = Math.Sqrt(5); var tDist = (int)Distance(Math.Round(xs), Math.Round(ys), Math.Round(xe), Math.Round(ye)); var t = (uint)(Environment.TickCount + 10000); List points = new List(); do { if (Environment.TickCount > t) break; var dist = Hypot(xs - xe, ys - ye); wind = Math.Min(wind, dist); if (dist < 1) dist = 1; var d = (Math.Round(Math.Round((double)tDist) * 0.3) / 7); if (d > 25) d = 25; if (d < 5) d = 5; double rCnc = rand.Next(6); if (rCnc == 1) d = 2; double maxStep; if (d <= Math.Round(dist)) maxStep = d; else maxStep = Math.Round(dist); if (dist >= targetArea) { windX = windX / sqrt3 + (rand.Next((int)(Math.Round(wind) * 2 + 1)) - wind) / sqrt5; windY = windY / sqrt3 + (rand.Next((int)(Math.Round(wind) * 2 + 1)) - wind) / sqrt5; } else { windX = windX / sqrt2; windY = windY / sqrt2; } veloX = veloX + windX; veloY = veloY + windY; veloX = veloX + gravity * (xe - xs) / dist; veloY = veloY + gravity * (ye - ys) / dist; if (Hypot(veloX, veloY) > maxStep) { var randomDist = maxStep / 2.0 + rand.Next((int)(Math.Round(maxStep) / 2)); var veloMag = Math.Sqrt(veloX * veloX + veloY * veloY); veloX = (veloX / veloMag) * randomDist; veloY = (veloY / veloMag) * randomDist; } var lastX = (int)Math.Round(xs); var lastY = (int)Math.Round(ys); xs = xs + veloX; ys = ys + veloY; // Set cursor on next step if (lastX != Math.Round(xs) || (lastY != Math.Round(ys))) points.Add(new Point((int)Math.Round(xs), (int)Math.Round(ys))); } while (!(Hypot(xs - xe, ys - ye) < 1)); // Set cursor on end if (Math.Round(xe) != Math.Round(xs) || (Math.Round(ye) != Math.Round(ys))) points.Add(new Point((int)Math.Round(xe), (int)Math.Round(ye))); return points.ToArray(); } } #endregion }