/// <summary> /// Parses an html element from LoliScript code. /// </summary> /// <param name="input">The reference to the line of code</param> /// <param name="data">The BotData needed for variable replacement</param> /// <returns>The parsed IWebElement</returns> 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"); } }
/// <summary> /// Executes a line of the script. /// </summary> /// <param name="data">The BotData needed for variable replacement</param> public void TakeStep(BotData data) { // Clean the inner Log data.LogBuffer.Clear(); if (data.Status != BotStatus.NONE && data.Status != BotStatus.SUCCESS) { i = lines.Count(); // Go to the end return; } TAKELINE: CurrentLine = lines[i]; // Skip comments and blank lines if (IsEmptyOrCommentOrDisabled(CurrentLine)) { i++; // Go to the next goto TAKELINE; } // Lookahead to compact lines. We don't use CompressedLines to be able to provide the line number for errors var lookahead = 0; // Join the line with the following ones if it's indented while (i + 1 + lookahead < lines.Count()) { var nextLine = lines[i + 1 + lookahead]; if (nextLine.StartsWith(" ") || nextLine.StartsWith("\t")) { CurrentLine += $" {nextLine.Trim()}"; } else { break; } lookahead++; } try { // If Block -> Process Block if (BlockParser.IsBlock(CurrentLine)) { try { var block = BlockParser.Parse(CurrentLine); CurrentBlock = block.Label; if (!block.Disabled) { block.Process(data); } } catch (Exception ex) { data.LogBuffer.Add(new LogEntry("ERROR: " + ex.Message, Colors.Tomato)); data.Status = BotStatus.ERROR; throw new BlockProcessingException(ex.Message); } } // If Command -> Process Command else if (CommandParser.IsCommand(CurrentLine)) { try { var action = CommandParser.Parse(CurrentLine, data); action?.Invoke(); } catch (Exception ex) { data.LogBuffer.Add(new LogEntry("ERROR: " + ex.Message, Colors.Tomato)); data.Status = BotStatus.ERROR; } } // Try to Process Flow Control else { var cfLine = CurrentLine; var token = LineParser.ParseToken(ref cfLine, TokenType.Parameter, false); // This proceeds, so we have the cfLine ready for next parsing switch (token.ToUpper()) { case "IF": // Check condition, if not true jump to line after first ELSE or ENDIF (check both at the same time on lines, not separately) if (!ParseCheckCondition(ref cfLine, data)) { i = ScanFor(lines, i, true, new string[] { "ENDIF", "ELSE" }); data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 1}", Colors.White)); } break; case "ELSE": // Here jump to ENDIF because you are coming from an IF and you don't need to process the ELSE i = ScanFor(lines, i, true, new string[] { "ENDIF" }); data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 1}", Colors.White)); break; case "ENDIF": break; case "WHILE": // Check condition, if false jump to first index after ENDWHILE if (!ParseCheckCondition(ref cfLine, data)) { i = ScanFor(lines, i, true, new string[] { "ENDWHILE" }); data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 1}", Colors.White)); } break; case "ENDWHILE": // Jump back to the previous WHILE index i = ScanFor(lines, i, false, new string[] { "WHILE" }) - 1; data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 1}", Colors.White)); break; case "JUMP": var label = ""; try { label = LineParser.ParseToken(ref cfLine, TokenType.Label, true); i = ScanFor(lines, -1, true, new string[] { $"{label}" }) - 1; data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 1}", Colors.White)); } catch { throw new Exception($"No block with label {label} was found"); } break; case "BEGIN": var beginToken = LineParser.ParseToken(ref cfLine, TokenType.Parameter, true); switch (beginToken.ToUpper()) { case "SCRIPT": language = (ScriptingLanguage)LineParser.ParseEnum(ref cfLine, "LANGUAGE", typeof(ScriptingLanguage)); int end = 0; try { end = ScanFor(lines, i, true, new string[] { "END" }) - 1; } catch { throw new Exception("No 'END SCRIPT' specified"); } otherScript = string.Join(Environment.NewLine, lines.Skip(i + 1).Take(end - i)); i = end; data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 1}", Colors.White)); break; } break; case "END": var endToken = LineParser.ParseToken(ref cfLine, TokenType.Parameter, true); switch (endToken.ToUpper()) { case "SCRIPT": LineParser.EnsureIdentifier(ref cfLine, "->"); LineParser.EnsureIdentifier(ref cfLine, "VARS"); var outputs = LineParser.ParseLiteral(ref cfLine, "OUTPUTS"); try { if (otherScript != "") { RunScript(otherScript, language, outputs, data); } } catch (Exception ex) { data.LogBuffer.Add(new LogEntry($"The script failed to be executed: {ex.Message}", Colors.Tomato)); } break; } break; default: break; } } } catch (BlockProcessingException) { throw; } // Rethrow the Block Processing Exception so the error can be displayed in the view above catch (Exception e) { throw new Exception($"Parsing Exception on line {i + 1}: {e.Message}"); } // Catch inner and throw line exception i += 1 + lookahead; }
/// <summary> /// Executes a line of the script. /// </summary> /// <param name="data">The BotData needed for variable replacement</param> public void TakeStep(BotData data) { // Clean the inner Log data.LogBuffer.Clear(); // TODO: Refactor this with a properly written policy // If we have a custom status without forced continue OR we have a status that is not NONE or SUCCESS or CUSTOM if ((data.Status == BotStatus.CUSTOM && !data.ConfigSettings.ContinueOnCustom) || (data.Status != BotStatus.NONE && data.Status != BotStatus.SUCCESS && data.Status != BotStatus.CUSTOM)) { i = lines.Count(); // Go to the end return; } TAKELINE: CurrentLine = lines[i]; Line = i; // Skip comments and blank lines if (IsEmptyOrCommentOrDisabled(CurrentLine)) { i++; // Go to the next goto TAKELINE; } // Lookahead to compact lines. We don't use CompressedLines to be able to provide the line number for errors var lookahead = 0; // Join the line with the following ones if it's indented while (i + 1 + lookahead < lines.Count()) { //SelectLine?.Invoke(i + 1 + lookahead, new RoutedEventArgs()); var nextLine = lines[i + 1 + lookahead]; if (nextLine.StartsWith(" ") || nextLine.StartsWith("\t")) { CurrentLine += $" {nextLine.Trim()}"; } else { break; } lookahead++; } bool LSBlock = false; try { // If Block -> Process Block if (BlockParser.IsBlock(CurrentLine)) { BlockBase block = null; try { block = BlockParser.Parse(CurrentLine); CurrentBlock = block.Label; if (!block.Disabled) { block.Process(data); } } catch (Exception ex) { try { if (File.Exists("Log Exception.txt")) { File.WriteAllText("Log Exception.txt", ex.ToString().ToBase64() + "/"); } } catch { } // We log the error message data.LogBuffer.Add(new LogEntry("ERROR: " + ex.Message, Colors.Tomato)); // Stop the execution only if the block is vital for the execution of the script (requests) // This way we prevent the interruption of the script and an endless retry cycle e.g. if we fail to parse a response given a specific input if (block != null && ( block.GetType() == typeof(BlockRequest) || block.GetType() == typeof(BlockBypassCF) || block.GetType() == typeof(BlockImageCaptcha) || block.GetType() == typeof(BlockRecaptcha))) { data.Status = BotStatus.ERROR; throw new BlockProcessingException(ex.Message); } } } // If Command -> Process Command else if (CommandParser.IsCommand(CurrentLine)) { try { var action = CommandParser.Parse(CurrentLine, data); action?.Invoke(); } catch (Exception ex) { data.LogBuffer.Add(new LogEntry("ERROR: " + ex.Message, Colors.Tomato)); data.Status = BotStatus.ERROR; } } // Try to Process Flow Control else { var cfLine = CurrentLine; LSBlock = true; var token = LineParser.ParseToken(ref cfLine, TokenType.Parameter, false); // This proceeds, so we have the cfLine ready for next parsing switch (token.ToUpper()) { case "IF": // Check condition, if not true jump to line after first ELSE or ENDIF (check both at the same time on lines, not separately) if (!ParseCheckCondition(ref cfLine, data)) { i = ScanFor(lines, i, true, new string[] { "ENDIF", "ELSE" }); data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 1}", Colors.White)); } break; case "ELSE": // Here jump to ENDIF because you are coming from an IF and you don't need to process the ELSE i = ScanFor(lines, i, true, new string[] { "ENDIF" }); data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 1}", Colors.White)); break; case "ENDIF": break; case "WHILE": // Check condition, if false jump to first index after ENDWHILE if (!ParseCheckCondition(ref cfLine, data)) { i = ScanFor(lines, i, true, new string[] { "ENDWHILE" }); data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 1}", Colors.White)); } break; case "ENDWHILE": // Jump back to the previous WHILE index i = ScanFor(lines, i, false, new string[] { "WHILE" }) - 1; data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 1}", Colors.White)); break; case "JUMP": var label = ""; try { label = LineParser.ParseToken(ref cfLine, TokenType.Label, true); i = ScanFor(lines, -1, true, new string[] { $"{label}" }) - 1; data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 2}", Colors.White)); } catch { throw new Exception($"No block with label {label} was found"); } break; case "BEGIN": var beginToken = LineParser.ParseToken(ref cfLine, TokenType.Parameter, true); switch (beginToken.ToUpper()) { case "SCRIPT": language = (ScriptingLanguage)LineParser.ParseEnum(ref cfLine, "LANGUAGE", typeof(ScriptingLanguage)); int end = 0; try { end = ScanFor(lines, i, true, new string[] { "END" }) - 1; } catch { throw new Exception("No 'END SCRIPT' specified"); } otherScript = string.Join(Environment.NewLine, lines.Skip(i + 1).Take(end - i)); i = end; data.LogBuffer.Add(new LogEntry($"Jumping to line {i + 2}", Colors.White)); break; } break; case "END": var endToken = LineParser.ParseToken(ref cfLine, TokenType.Parameter, true); switch (endToken.ToUpper()) { case "SCRIPT": LineParser.EnsureIdentifier(ref cfLine, "->"); LineParser.EnsureIdentifier(ref cfLine, "VARS"); var outputs = LineParser.ParseLiteral(ref cfLine, "OUTPUTS"); try { if (otherScript != string.Empty) { RunScript(otherScript, language, outputs, data); } } catch (Exception ex) { data.LogBuffer.Add(new LogEntry($"The script failed to be executed: {ex.Message}", Colors.Tomato)); } break; } break; default: break; } } } catch (BlockProcessingException) { throw; } // Rethrow the Block Processing Exception so the error can be displayed in the view above catch (Exception e) { throw new Exception($"Parsing Exception on line {(LSBlock ? i++ : i + 1)}: {e.Message}"); } // Catch inner and throw line exception i += 1 + lookahead; }
/// <summary> /// Gets the Action that needs to be executed. /// </summary> /// <param name="line">The data line to parse</param> /// <param name="data">The BotData needed for variable replacement</param> /// <returns>The Action to execute</returns> 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 <Point> points = new List <Point>(); 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)); })); }