// <summary>Returns the next block to be processed. Empty if the script has no more blocks to execute.</summary> private string GetNextBlock() { for (var j = i; j < lines.Length; j++) { var line = lines[j]; if (IsEmptyOrCommentOrDisabled(line) || !BlockParser.IsBlock(line)) { continue; } var label = ""; if (lines[j].StartsWith("#")) { label = LineParser.ParseLabel(ref line); } var blockName = LineParser.ParseToken(ref line, TokenType.Parameter, false, false); return(string.IsNullOrEmpty(label) ? blockName : $"{blockName} ({label})"); } return(string.Empty); }
/// <summary> /// Gets a command Action from a command line. /// </summary> internal static Action Parse(string line, LSGlobals ls) { // Trim the line var input = line.Trim(); // Return an exception if the line is empty if (input == string.Empty) { throw new ArgumentNullException(); } var label = LineParser.ParseToken(ref input, TokenType.Label, false); // Parse the identifier var identifier = ""; try { identifier = LineParser.ParseToken(ref input, TokenType.Parameter, true); } catch { throw new ArgumentException("Missing identifier"); } return((CommandName)Enum.Parse(typeof(CommandName), identifier, true) switch { CommandName.PRINT => new Action(() => ls.BotData.Logger.Log(BlockBase.ReplaceValues(input, ls), LogColors.White)), CommandName.SET => SetParser.Parse(input, ls), CommandName.DELETE => DeleteParser.Parse(input, ls), // TODO: Readd this // CommandName.MOUSEACTION => MouseActionParser.Parse(input, ls), _ => throw new ArgumentException($"Invalid identifier '{identifier}'"), });
/// <summary> /// Scans for a given set of identifiers in the script and returns the line index value of the first found. /// </summary> /// <param name="lines">The lines of the script</param> /// <param name="current">The index of the current line</param> /// <param name="downwards">Whether to scan downwards or upwards</param> /// <param name="options">The target identifiers</param> /// <returns></returns> public static int ScanFor(string[] lines, int current, bool downwards, string[] options) { var i = downwards ? current + 1 : current - 1; var found = false; while (i >= 0 && i < lines.Length) { try { var token = LineParser.ParseToken(ref lines[i], TokenType.Parameter, false, false); if (options.Any(o => token.ToUpper() == o.ToUpper())) { found = true; break; } } catch { } if (downwards) { i++; } else { i--; } } if (found) { return(i); } else { throw new Exception("Not found"); } }
/// <summary> /// Gets the Action that needs to be executed. /// </summary> static internal Action Parse(string line, LSGlobals ls) { var data = ls.BotData; var input = line.Trim(); var field = LineParser.ParseToken(ref input, TokenType.Parameter, true).ToUpper(); return(new Action(() => { switch (field) { case "SOURCE": data.SOURCE = LineParser.ParseLiteral(ref input, "SOURCE", true, ls); break; case "STATUS": data.STATUS = LineParser.ParseToken(ref input, TokenType.Parameter, true); // E.g. user wrote SET STATUS CUSTOM "TEST" if (data.STATUS == "CUSTOM" && !string.IsNullOrEmpty(input)) { data.STATUS = LineParser.ParseLiteral(ref input, "CUSTOM STATUS"); } break; case "RESPONSECODE": data.RESPONSECODE = LineParser.ParseInt(ref input, "RESPONSECODE"); break; case "COOKIE": var name = LineParser.ParseLiteral(ref input, "NAME", true, ls); data.COOKIES.Add(name, LineParser.ParseLiteral(ref input, "VALUE", true, ls)); break; case "ADDRESS": data.ADDRESS = LineParser.ParseLiteral(ref input, "ADDRESS", true, ls); break; case "USEPROXY": var use = LineParser.ParseToken(ref input, TokenType.Parameter, true).ToUpper(); if (use == "TRUE") { data.UseProxy = true; } else if (use == "FALSE") { data.UseProxy = false; } break; case "PROXY": var prox = LineParser.ParseLiteral(ref input, "PROXY", true, ls); data.Proxy = Proxy.Parse(prox); break; case "PROXYTYPE": data.Proxy.Type = (ProxyType)LineParser.ParseEnum(ref input, "PROXYTYPE", typeof(ProxyType)); break; case "DATA": data.Line.Data = LineParser.ParseLiteral(ref input, "DATA", true, ls); break; case "VAR": var varName = LineParser.ParseLiteral(ref input, "NAME", true, ls); var varValue = LineParser.ParseLiteral(ref input, "VALUE", true, ls); BlockBase.GetVariables(data).Set(new StringVariable(varValue) { Name = varName }); break; case "CAP": var capName = LineParser.ParseLiteral(ref input, "NAME", true, ls); var capValue = LineParser.ParseLiteral(ref input, "VALUE", true, ls); BlockBase.GetVariables(data).Set(new StringVariable(capValue) { Name = capName }); data.MarkForCapture(capName); break; case "GVAR": try { var globalVarName = LineParser.ParseLiteral(ref input, "NAME", true, ls); var globalVarValue = LineParser.ParseLiteral(ref input, "VALUE", true, ls); ls.Globals.Set(new StringVariable(globalVarValue) { Name = globalVarName }); } catch { } break; case "NEWGVAR": try { var globalVarName = LineParser.ParseLiteral(ref input, "NAME", true, ls); var globalVarValue = LineParser.ParseLiteral(ref input, "VALUE", true, ls); ls.Globals.SetIfNew(new StringVariable(globalVarValue) { Name = globalVarName }); } catch { } break; case "GCOOKIES": ls.GlobalCookies.Clear(); foreach (var cookie in data.COOKIES) { ls.GlobalCookies.Add(cookie.Key, cookie.Value); } break; default: throw new ArgumentException($"Invalid identifier {field}"); } data.Logger.Log($"SET command executed on field {field}", LogColors.White); })); }
/// <summary> /// Executes a line of the script. /// </summary> /// <param name="data">The BotData needed for variable replacement</param> public async Task TakeStep(LSGlobals ls) { var data = ls.BotData; // 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 (!CanContinue(data)) { i = lines.Length; // 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.Length) { 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)) { BlockBase block = null; try { block = BlockParser.Parse(CurrentLine); CurrentBlock = block.Label; data.ExecutionInfo = $"Executing block: {block.Label}"; if (!block.Disabled) { await block.Process(ls); } } catch (Exception ex) { // We log the error message var errorMessage = data.Providers.GeneralSettings.VerboseMode ? ex.ToString() : ex.Message; data.Logger.Log("ERROR: " + errorMessage, LogColors.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 is BlockRequest) { data.STATUS = "ERROR"; throw new BlockProcessingException(ex.Message); } } } // If Command -> Process Command else if (CommandParser.IsCommand(CurrentLine)) { try { var action = CommandParser.Parse(CurrentLine, ls); action?.Invoke(); } catch (Exception ex) { var errorMessage = data.Providers.GeneralSettings.VerboseMode ? ex.ToString() : ex.Message; data.Logger.Log("ERROR: " + errorMessage, LogColors.Tomato); data.STATUS = "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, ls)) { i = ScanFor(lines, i, true, new string[] { "ENDIF", "ELSE" }); data.Logger.Log($"Jumping to line {i + 1}", LogColors.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.Logger.Log($"Jumping to line {i + 1}", LogColors.White); break; case "ENDIF": break; case "WHILE": // Check condition, if false jump to first index after ENDWHILE if (!ParseCheckCondition(ref cfLine, ls)) { i = ScanFor(lines, i, true, new string[] { "ENDWHILE" }); data.Logger.Log($"Jumping to line {i + 1}", LogColors.White); } break; case "ENDWHILE": // Jump back to the previous WHILE index i = ScanFor(lines, i, false, new string[] { "WHILE" }) - 1; data.Logger.Log($"Jumping to line {i + 1}", LogColors.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.Logger.Log($"Jumping to line {i + 2}", LogColors.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)); var 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.Logger.Log($"Jumping to line {i + 2}", LogColors.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) { var errorMessage = data.Providers.GeneralSettings.VerboseMode ? ex.ToString() : ex.Message; data.Logger.Log($"The script failed to be executed: {errorMessage}", LogColors.Tomato); } break; } break; default: break; } } } catch (BlockProcessingException) { // Rethrow the Block Processing Exception so the error can be displayed in the view above throw; } catch (Exception e) { // Catch inner and throw line exception throw new Exception($"Parsing Exception on line {i + 1}: {e.Message}"); } i += 1 + lookahead; }
/// <summary> /// Gets the Action that needs to be executed. /// </summary> static internal Action Parse(string line, LSGlobals ls) { var data = ls.BotData; var input = line.Trim(); var field = LineParser.ParseToken(ref input, TokenType.Parameter, true).ToUpper(); return(new Action(() => { var name = ""; var comparer = Comparer.EqualTo; switch (field) { case "COOKIE": if (LineParser.Lookahead(ref input) == TokenType.Parameter) { comparer = (Comparer)LineParser.ParseEnum(ref input, "TYPE", typeof(Comparer)); } name = LineParser.ParseLiteral(ref input, "NAME"); for (var i = 0; i < data.COOKIES.Count; i++) { var curr = data.COOKIES.ToList()[i].Key; if (Condition.ReplaceAndVerify(curr, comparer, name, ls)) { data.COOKIES.Remove(curr); } } break; case "VAR": if (LineParser.Lookahead(ref input) == TokenType.Parameter) { comparer = (Comparer)LineParser.ParseEnum(ref input, "TYPE", typeof(Comparer)); } name = LineParser.ParseLiteral(ref input, "NAME"); BlockBase.GetVariables(data).RemoveAll(comparer, name, ls); break; case "GVAR": if (LineParser.Lookahead(ref input) == TokenType.Parameter) { comparer = (Comparer)LineParser.ParseEnum(ref input, "TYPE", typeof(Comparer)); } name = LineParser.ParseLiteral(ref input, "NAME"); try { ls.Globals.RemoveAll(comparer, name, ls); } catch { } break; default: throw new ArgumentException($"Invalid identifier {field}"); } data.Logger.Log($"DELETE command executed on field {field}", LogColors.White); })); }