using Extreme.Net; using RuriLib.LS; using RuriLib.Models; using RuriLib.ViewModels; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows.Media; namespace RuriLib.Runner { /// /// Whether to use proxies or not for the current session. /// public enum ProxyMode { /// Use the default setting in the config. Default, /// Always use proxies. On, /// Never use proxies. Off } /// /// Main class that handles all the multi-threaded checking of a Wordlist given a Config. /// public class RunnerViewModel : ViewModelBase, IRunnerMessaging { #region Constructor /// /// Constructs the RunnerViewModel instance. /// /// The environment settings /// The RuriLib settings public RunnerViewModel(EnvironmentSettings environment, RLSettingsViewModel settings) { Env = environment; Settings = settings; OnPropertyChanged("Busy"); OnPropertyChanged("ControlsEnabled"); } #endregion #region Settings private RLSettingsViewModel Settings { get; set; } private EnvironmentSettings Env { get; set; } #endregion #region Workers /// The Master Worker that manages all the other Workers and updates the observable properties. public AbortableBackgroundWorker Master { get; set; } = new AbortableBackgroundWorker(); /// The status of the Master Worker. public WorkerStatus WorkerStatus { get { return Master.Status; } } /// The managed workers that run single data checks. public ObservableCollection Bots { get; } = new ObservableCollection(); #endregion #region Visible Properties and Components /// Whether the Master Worker is busy or idle. public bool Busy { get { return Master.Status != WorkerStatus.Idle; } } /// Whether the user can set the properties (a.k.a. whether the Master Worker is idle). public bool ControlsEnabled { get { return !Busy; } } private int botsNumber = 1; /// The amount of bots to run simultaneously for multi-threaded checking. public int BotsNumber { get { return botsNumber; } set { botsNumber = value; OnPropertyChanged(); } } private int startingPoint = 1; /// How many data lines to skip before starting the checking process. public int StartingPoint { get { return startingPoint; } set { startingPoint = value; OnPropertyChanged(); OnPropertyChanged("Progress"); } } /// The amount of data lines checked, including the starting point. public int ProgressCount { get { return TestedCount + StartingPoint - 1; } } /// The rounded percentage of checked data lines (0 to 100). public int Progress { get { int ret = 0; try { double curr = TestedCount + StartingPoint - 1; double tot = DataPool.Size; double ratio = curr / tot; double percent = ratio * 100; ret = (int)percent; } catch { } return Clamp(ret, 0, 100); } } private int cpm = 0; /// The checks per minute. public int CPM { get { if (TestedCount == 0) { cpm = 0; return cpm; } if (IsCPMLocked) { return cpm; } try { var temp = 0; IsCPMLocked = true; for (int i = FailedList.Count() - 1; i >= 0; i--) { if ((DateTime.Now - FailedList[i].Time).TotalSeconds > 60) break; temp++; } for (int i = HitsList.Count() - 1; i >= 0; i--) { if ((DateTime.Now - HitsList[i].Time).TotalSeconds > 60) break; temp++; } for (int i = CustomList.Count() - 1; i >= 0; i--) { if ((DateTime.Now - CustomList[i].Time).TotalSeconds > 60) break; temp++; } for (int i = ToCheckList.Count() - 1; i >= 0; i--) { if ((DateTime.Now - ToCheckList[i].Time).TotalSeconds > 60) break; temp++; } cpm = temp; IsCPMLocked = false; } catch { } finally { IsCPMLocked = false; } return cpm; } } private double _balance = 0; /// The remaining balance in the captcha solver account. public double Balance { get { return _balance; } set { _balance = value; OnPropertyChanged("BalanceString"); } } /// The remaining balance in the captcha solver account preceeded by a $ sign. public string BalanceString { get { return $"${_balance}"; } } private ProxyMode proxyMode = ProxyMode.Default; /// The Proxy Mode. public ProxyMode ProxyMode { get { return proxyMode; } set { proxyMode = value; OnPropertyChanged(); } } /// Whether proxies can be used for the current session given the Proxy Mode and the Config. public bool UseProxies { get { return (Config.Settings.NeedsProxies && ProxyMode == ProxyMode.Default) || ProxyMode == ProxyMode.On; } } /// The loaded Config to use for the check. public Config Config { get; private set; } /// The name of the loaded Config. public string ConfigName { get { return Config == null ? "None" : Config.Settings.Name; } } /// The loaded Wordlist to use for the check. public Wordlist Wordlist { get; private set; } /// The name of the loaded Wordlist. public string WordlistName { get { return Wordlist == null ? "None" : Wordlist.Name; } } /// The size of the loaded Wordlist. public int WordlistSize { get { return Wordlist == null ? 0 : Wordlist.Total; } } /// The pool from which bots can get a proxy upon request. public ProxyPool ProxyPool { get; set; } = new ProxyPool(new List()); /// The total amount of proxies loaded. public int TotalProxiesCount { get { return ProxyPool.Proxies.Count; } } /// The amount of proxies loaded that are alive. public int AliveProxiesCount { get { return ProxyPool.Alive.Count; } } /// The amount of proxies loaded that are available. public int AvailableProxiesCount { get { return ProxyPool.Available.Count; } } /// The amount of proxies loaded that are banned. public int BannedProxiesCount { get { return ProxyPool.Banned.Count; } } /// The amount of proxies loaded that are bad. public int BadProxiesCount { get { return ProxyPool.Bad.Count; } } /// The pool from which the Master worker draws data to assign to bots for the checks. public DataPool DataPool { get; set; } /// The size of the DataPool. public int DataSize { get { return DataPool.Size; } } #endregion #region Bot-Shared Fields /// The pairs of (variable name, value) set by the user. public List> CustomInputs { get; set; } = new List>(); /// The Global Variables list that are set in all bots when they start. public VariableList GlobalVariables { get; set; } = new VariableList(); /// The Global Cookies list that are set in all bots when they start. public CookieDictionary GlobalCookies { get; set; } = new CookieDictionary(); #endregion #region Results /// The list of data lines checked with a FAIL outcome. public List FailedList { get; set; } = new List(); /// The list of data lines checked with a SUCCESS outcome. public ObservableCollection HitsList { get; set; } = new ObservableCollection(); /// The list of data lines checked with a CUSTOM outcome. public ObservableCollection CustomList { get; set; } = new ObservableCollection(); /// The list of data lines checked with a NONE outcome. public ObservableCollection ToCheckList { get; set; } = new ObservableCollection(); /// Auxiliary empty list. public ObservableCollection EmptyList { get; set; } = new ObservableCollection(); /// Filter based on the Bot Status. public BotStatus ResultsFilter { get; set; } = BotStatus.SUCCESS; /// Amount of data lines checked with a FAIL outcome. public int FailCount { get { return FailedList.Count; } } /// Amount of data lines checked with a SUCCESS outcome. public int HitCount { get { return HitsList.Count; } } /// Amount of data lines checked with a CUSTOM outcome. public int CustomCount { get { return CustomList.Count; } } /// Amount of data lines checked with a NONE outcome. public int ToCheckCount { get { return ToCheckList.Count; } } private int retryCount = 0; /// Amount of data lines retried due to a BAN, RETRY or ERROR outcome. public int RetryCount { get { return retryCount; } set { retryCount = value; OnPropertyChanged(); } } /// Total amount of successfully tested data lines. public int TestedCount { get { return FailCount + HitCount + CustomCount + ToCheckCount; } } #endregion #region Locks /// Whether the workers are waiting for proxies to be reloaded. private bool IsReloadingProxies { get; set; } = false; /// Whether the CPM is already being calculated. private bool IsCPMLocked { get; set; } = false; /// Whether the warning about no proxy availability has already been issued. private bool NoProxyWarningSent { get; set; } = false; /// Whether the Custom Inputs have already been initialized. public bool CustomInputsInitialized { get; set; } = false; #endregion #region Timing private Stopwatch Timer { get; set; } = new Stopwatch(); /// Days elapsed since the runner was started. public string TimerDays { get { return Timer.Elapsed.Days.ToString(); } } /// Hours elapsed since the runner was started. public string TimerHours { get { return Timer.Elapsed.Hours.ToString("D2"); } } /// Minutes elapsed since the runner was started. public string TimerMinutes { get { return Timer.Elapsed.Minutes.ToString("D2"); ; } } /// Seconds elapsed since the runner was started. public string TimerSeconds { get { return Timer.Elapsed.Seconds.ToString("D2"); ; } } /// Representation of the expected time left to check the remaining data lines. public string TimeLeft { get { var dataLeft = Wordlist.Total - StartingPoint - TestedCount; if (CPM == 0) return "+inf"; int amountLeft = (dataLeft / CPM) * 60; // in seconds var unitOfTime = "seconds"; if (amountLeft > 60) { amountLeft /= 60; unitOfTime = "minutes"; } // More than 60s -> convert to minutes if (amountLeft > 60) { amountLeft /= 60; unitOfTime = "hours"; } // More than 60m -> convert to hours if (amountLeft > 24) { amountLeft /= 24; unitOfTime = "days"; } // More than 24h -> convert to days return $"{amountLeft} {unitOfTime} left"; } } #endregion #region Setters /// /// Sets a Config in the Runner instance. /// /// The Config to be used for the check /// Whether to automatically set the recommended amount of bots from the Config settings. public void SetConfig(Config config, bool setRecommended) { Config = config; if (setRecommended) BotsNumber = Clamp(config.Settings.SuggestedBots, 1, 200); OnPropertyChanged("ConfigName"); } /// /// Sets a Wordlist in the Runner instance. /// /// The Wordlist to be used for the check public void SetWordlist(Wordlist wordlist) { Wordlist = wordlist; OnPropertyChanged("WordlistName"); OnPropertyChanged("WordlistSize"); } #endregion #region Worker Control Methods /// /// Starts the Master Worker. /// public void Start() { RaiseMessageArrived(LogLevel.Info, "Setting up the background worker", false); Master = new AbortableBackgroundWorker(); Master.DoWork += new DoWorkEventHandler(Run); Master.RunWorkerCompleted += new RunWorkerCompletedEventHandler(RunCompleted); Master.WorkerSupportsCancellation = true; Timer.Reset(); Timer.Start(); if (!Master.IsBusy) { Master.Status = WorkerStatus.Running; OnPropertyChanged("Busy"); OnPropertyChanged("ControlsEnabled"); RaiseWorkerStatusChanged(); Master.RunWorkerAsync(); RaiseMessageArrived(LogLevel.Info, "Started the Master Worker", false); } else { RaiseMessageArrived(LogLevel.Error, "Cannot start the Background Worker (busy)", false); } } /// /// Stops the Master Worker. /// public void Stop() { if (Master.IsBusy) Master.CancelAsync(); RaiseMessageArrived(LogLevel.Info, "Sent cancellation request to Master Worker", false); Master.Status = WorkerStatus.Stopping; OnPropertyChanged("Busy"); OnPropertyChanged("ControlsEnabled"); RaiseWorkerStatusChanged(); } /// /// Forcefully aborts the Bots and stops the Master Worker. /// public void ForceStop() { AbortAllBots(); Master.Abort(); RaiseMessageArrived(LogLevel.Info, "Hard Aborted the Master Worker", false); Timer.Stop(); Master.Status = WorkerStatus.Idle; OnPropertyChanged("Busy"); OnPropertyChanged("ControlsEnabled"); RaiseWorkerStatusChanged(); StartingPoint += TestedCount; } #endregion #region Master Run Logic // Main job of the Master Worker private void Run(object sender, DoWorkEventArgs e) { if (Config == null) throw new Exception("No Config loaded!"); if (Wordlist == null) throw new Exception("No Wordlist loaded!"); DataPool = new DataPool(File.ReadLines(Wordlist.Path)); RaiseMessageArrived(LogLevel.Info, $"Loaded {DataPool.Size} lines", false); RaiseMessageArrived(LogLevel.Info, $"Using Proxies: {UseProxies}", false); if (DataPool.Size == 0) throw new Exception("No data to process!"); if (StartingPoint > DataPool.Size) throw new Exception("Illegal Starting Point!"); if (Config.BlocksAmount == 0) throw new Exception("The Config has zero blocks!"); // Reset the stats NoProxyWarningSent = false; CustomInputsInitialized = false; RetryCount = 0; FailedList.Clear(); GlobalVariables = new VariableList(); GlobalCookies = new CookieDictionary(); // We need to dispatch this to the main thread because these change the observable collections, hence changing the UI RaiseDispatchAction(new Action(() => { HitsList.Clear(); CustomList.Clear(); ToCheckList.Clear(); })); // Load the Proxies if (UseProxies) { LoadProxies(); if (TotalProxiesCount == 0) throw new Exception("Zero proxies available!"); } // Ask for the inputs if (Config.Settings.CustomInputs.Count > 0) { RaiseAskCustomInputs(); while (!CustomInputsInitialized) Thread.Sleep(100); } // Empty the bots list (dispatch it to the main thread because it's observable) RaiseDispatchAction(new Action(() => Bots.Clear())); // Create the given amount of bots and assign them their DoWork function for (int i = 1; i <= BotsNumber; i++) { RaiseMessageArrived(LogLevel.Info, $"Creating bot {i}", false); var bot = new RunnerBotViewModel(i); if (Settings.General.BotsDisplayMode == BotsDisplayMode.None) bot.Status = "Bots Display is Disabled in Settings"; bot.Worker.DoWork += new DoWorkEventHandler(RunBot); RaiseDispatchAction(new Action(() => Bots.Add(bot))); } // Checking Process foreach (var data in DataPool.List.Skip(StartingPoint - 1)) { // Check if there is a cancellation request if (Master.CancellationPending) { AbortAllBots(); return; } // Create an instance of CData basing on the current data line CData c = new CData(data, Env.GetWordlistType(Wordlist.Type)); // Check if it's valid if (!c.IsValid || !c.RespectsRules(Config.Settings.DataRules.ToList())) { FailedList.Add(new ValidData(data, "", ProxyType.Http, BotStatus.FAIL, "", "", "", null)); continue; } // Update the observable properties UpdateTimer(); UpdateStats(); UpdateCPM(); // If the amount of Bots was changed if (BotsNumber != Bots.Count) { RaiseMessageArrived(LogLevel.Info, $"Bots Number was changed from {Bots.Count} to {BotsNumber}", false); // If it was increased if (BotsNumber > Bots.Count) { // Create the missing bots for (int b = Bots.Count + 1; b <= BotsNumber; b++) { RaiseMessageArrived(LogLevel.Info, $"Creating bot {b}", false); try { var bot = new RunnerBotViewModel(b); if (Settings.General.BotsDisplayMode == BotsDisplayMode.None) bot.Status = "Bots Display is Disabled in Settings"; bot.Worker.DoWork += new DoWorkEventHandler(RunBot); RaiseDispatchAction(new Action(() => Bots.Add(bot))); } catch (Exception ex) { RaiseMessageArrived(LogLevel.Error, $"Error while creating bot {b}: " + ex.Message, false); } } } // If it was decreased else { // Terminate the unnecessary bots for (int b = Bots.Count - 1; b >= BotsNumber; b--) { RaiseMessageArrived(LogLevel.Info, $"Removing bot {b}", false); try { var bot = Bots[b]; if (bot.IsDriverOpen) bot.Driver.Quit(); bot.Worker.CancelAsync(); bot.Worker.Abort(); RaiseDispatchAction(new Action(() => Bots.RemoveAt(b))); } catch (Exception ex) { RaiseMessageArrived(LogLevel.Error, $"Error while creating bot {b}: " + ex.Message, false); } } } } RaiseMessageArrived(LogLevel.Info, $"Trying to assign data " + data, false); bool assigned = false; // Assignment of a data line to a bot while (!assigned) { // If we reached the max number of hits, abort if (Settings.General.MaxHits != 0 && HitCount >= Settings.General.MaxHits) { AbortAllBots(); return; } // Check if there is a cancellation request if (Master.CancellationPending) { AbortAllBots(); return; } // Write progress to DB every 2 minutes // Useful if we cannot save it upon work completion (e.g. for a crash or power outage) if (Timer.IsRunning && (int)Timer.Elapsed.TotalSeconds != 0 && (int)Timer.Elapsed.TotalSeconds % 120 == 0) { RaiseSaveProgress(); } // If we are above the CPM limit, go to the wait (use cpm NOT CPM so it doesn't calculate it uselessly when it checks the IF conditions) if (Config.Settings.MaxCPM > 0 && cpm >= Config.Settings.MaxCPM) { // Update the CPM and go directly to the wait (which will update the other stats too) UpdateCPM(); } else { // Search for the first available bot, assign the data to it and start its job foreach (var bot in Bots) { if (!bot.Worker.IsBusy) { RaiseMessageArrived(LogLevel.Info, "Assigned data " + data + " to bot " + bot.Id, false); bot.Worker.RunWorkerAsync(data); assigned = true; break; } } } //If all bots are busy, sleep a while before checking again if (!assigned) { UpdateTimer(); UpdateStats(); // Do not update CPM because it would be very CPU demanding Thread.Sleep(200); } } } // Wait until all threads have finished their last job and keep updating the observable properties while (Bots.Select(b => b.Worker).Any(w => w.IsBusy) && !Master.CancellationPending) { RaiseMessageArrived(LogLevel.Info, "All data assigned, waiting for completion", false); UpdateStats(); UpdateCPM(); Thread.Sleep(200); } } // Executed when the Master Worker has finished its job private void RunCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Error != null) RaiseMessageArrived(LogLevel.Error, "The Master Worker has encountered an error: " + e.Error.Message, true); Master.Status = WorkerStatus.Idle; OnPropertyChanged("Busy"); OnPropertyChanged("ControlsEnabled"); RaiseWorkerStatusChanged(); Timer.Stop(); AbortAllBots(); StartingPoint += TestedCount; } #endregion #region Bot Run Logic // Bot job logic private void RunBot(object sender, DoWorkEventArgs e) { // Get a reference to the Worker that is executing the job and the corresponding Bot var senderABW = (AbortableBackgroundWorker)sender; var bot = Bots.First(b => b.Id == (senderABW.Id)); bot.Status = "INITIALIZING..."; // The data line that needs to be processed var data = (string)e.Argument; bot.Data = data; CData currentData = new CData(data, Env.GetWordlistType(Wordlist.Type)); RaiseMessageArrived(LogLevel.Info, $"[{bot.Id}] started with data " + bot.Data, false); try { GETPROXY: // Check if the job was cancelled or if the Master Worker is not running if (senderABW.CancellationPending || ShouldStop()) { RaiseMessageArrived(LogLevel.Info, $"[{bot.Id}] Cancellation pending, aborting", false); return; } CProxy currentProxy = null; // If the config requires proxies or we enforced proxy use if (UseProxies) { // Try to get a proxy from the pool currentProxy = ProxyPool.GetProxy(Settings.Proxies.ConcurrentUse, Config.Settings.MaxProxyUses, Settings.Proxies.NeverBan); // If there are no more proxies available if (currentProxy == null) { // If we can reload them from the proxy source if (Settings.Proxies.Reload) { // If no one else is already reloading the proxies, reload them if (!IsReloadingProxies) { IsReloadingProxies = true; LoadProxies(); IsReloadingProxies = false; } // If there is already someone else reloading the proxies, try again until they finished else { Thread.Sleep(100); goto GETPROXY; } } // If we cannot reload else { // If the Master Worker is busy if (!ShouldStop()) { // If no one else issued the no more proxies warning message, issue it if (!NoProxyWarningSent) { RaiseMessageArrived(LogLevel.Error, "No more proxies and no Unban All option selected OR the config has a max proxy use! Aborting", true); NoProxyWarningSent = true; } Master.CancelAsync(); return; } } } // Assign the proxy to the bot's displayed fields bot.Proxy = currentProxy.Proxy; } // Initialize the Bot Data BotData botData = new BotData(Settings, Config.Settings, currentData, currentProxy, UseProxies, bot.Id, false); botData.Driver = bot.Driver; botData.BrowserOpen = bot.IsDriverOpen; List BotLog = new List(); // Set the variables from the Custom Inputs foreach (var pair in CustomInputs) botData.Variables.Set(new CVar(pair.Key, pair.Value)); // Set the global variables (pass by reference so every bot acts on the same list) botData.GlobalVariables = GlobalVariables; // Set the global cookies try { foreach (var cookie in GlobalCookies) botData.Cookies.Add(cookie.Key, cookie.Value); } catch { } // Initialize the LoliScript LoliScript loli = new LoliScript(Config.Script); loli.Reset(); /* ================= * SCRIPT PROCESSING * ================= */ // Print the start message var proxyUsedText = botData.Proxy == null ? "NONE" : $"{botData.Proxy.Proxy} ({botData.Proxy.Type})"; BotLog.Add(new LogEntry($"===== LOG FOR BOT #{bot.Id} WITH DATA {botData.Data.Data} AND PROXY {proxyUsedText} ====={Environment.NewLine}", Colors.White)); if (!Settings.General.EnableBotLog) BotLog.Add(new LogEntry("The Bot Logging is disabled in General Settings", Colors.Tomato)); // Open browser if Always Open if (Config.Settings.AlwaysOpen) SBlockBrowserAction.OpenBrowser(botData); do { // Check if the job was cancelled or if the Master Worker is not running if (senderABW.CancellationPending || ShouldStop()) { RaiseMessageArrived(LogLevel.Info, $"[{bot.Id}] Cancellation pending, aborting", false); return; } // Output the label of the current block being processed if (Settings.General.BotsDisplayMode == BotsDisplayMode.Everything) bot.Status = "<<< PROCESSING BLOCK: " + loli.NextBlock + " >>>"; // Try to take a step in the LoliScript and output any errors try { loli.TakeStep(botData); } catch (BlockProcessingException ex) { if (Settings.General.BotsDisplayMode == BotsDisplayMode.Everything) bot.Status = $"<<< ERROR IN BLOCK: {loli.NextBlock} >>>"; RaiseMessageArrived(LogLevel.Error, $"[{bot.Id}][{bot.Data}][{proxyUsedText}] ERROR in block {loli.NextBlock} | Exception: {ex.Message}", false); Thread.Sleep(1000); } catch (Exception ex) { if (Settings.General.BotsDisplayMode == BotsDisplayMode.Everything) bot.Status = "<<< SCRIPT ERROR >>>"; RaiseMessageArrived(LogLevel.Error, $"[{bot.Id}][{bot.Data}][{proxyUsedText}] ERROR in the script | Exception: {ex.Message}", false); Thread.Sleep(1000); } // Save the contents of the LogBuffer (that gets cleaned with every TakeStep() call) to the BotLog if (botData.LogBuffer.Count > 0) { BotLog.AddRange(botData.LogBuffer); BotLog.Add(new LogEntry("", Colors.White)); } } while (loli.CanProceed); // Do this while the LoliScript has stuff to process // Print the end message BotLog.Add(new LogEntry($"===== BOT TERMINATED WITH RESULT: {botData.StatusString} =====", Colors.White)); if (Settings.General.BotsDisplayMode != BotsDisplayMode.None) bot.Status = $"<<< FINISHED WITH RESULT: {botData.StatusString} >>>"; RaiseMessageArrived(LogLevel.Info, $"[{bot.Id}][{bot.Data}][{proxyUsedText}] Ended with result {botData.StatusString}", false); // Quit Browser if Always Quit if (Config.Settings.AlwaysQuit) try { botData.Driver.Quit(); botData.BrowserOpen = false; } catch { } // Save Browser Status bot.Driver = botData.Driver; bot.IsDriverOpen = botData.BrowserOpen; // Put the proxy status back to available and increment the uses if (UseProxies) { currentProxy.Status = Status.AVAILABLE; currentProxy.Uses++; currentProxy.Hooked--; } // Update the captcha service balance Balance = botData.Balance; // Push the Global Cookies to the shared Dictionary try { GlobalCookies = new CookieDictionary(); foreach (var cookie in botData.GlobalCookies) GlobalCookies.Add(cookie.Key, cookie.Value); } catch { } // Analyze the Result of the Check ValidData validData = null; string hitType = botData.Status.ToString(); // Build the Captured Data VariableList capturedData = new VariableList(botData.Variables.All.Where(v => v.IsCapture && !v.Hidden).ToList()); // Detect the result of the check and act accordingly switch (botData.Status) { case BotStatus.SUCCESS: validData = new ValidData(botData.Data.Data, botData.Proxy == null ? "" : botData.Proxy.Proxy, botData.Proxy == null ? ProxyType.Http : botData.Proxy.Type, botData.Status, "HIT", capturedData.ToCaptureString(), Settings.General.SaveLastSource ? botData.ResponseSource : "", BotLog); RaiseDispatchAction(new Action(() => HitsList.Add(validData))); if (UseProxies && !Settings.Proxies.NeverBan && Config.Settings.BanProxyAfterGoodStatus) { currentProxy.Status = Status.BANNED; currentProxy.LastUsed = DateTime.Now; RetryCount++; } break; case BotStatus.FAIL: FailedList.Add(new ValidData("", "", ProxyType.Http, botData.Status, "", "", "", null)); // Only needed for CPM calculation so we can set blank data to avoid filling RAM break; case BotStatus.CUSTOM: hitType = botData.CustomStatus; validData = new ValidData(botData.Data.Data, botData.Proxy == null ? "" : botData.Proxy.Proxy, botData.Proxy == null ? ProxyType.Http : botData.Proxy.Type, botData.Status, hitType, capturedData.ToCaptureString(), Settings.General.SaveLastSource ? botData.ResponseSource : "", BotLog); RaiseDispatchAction(new Action(() => CustomList.Add(validData))); if (UseProxies && !Settings.Proxies.NeverBan && Config.Settings.BanProxyAfterGoodStatus) { currentProxy.Status = Status.BANNED; currentProxy.LastUsed = DateTime.Now; RetryCount++; } break; case BotStatus.BAN: if (UseProxies && !Settings.Proxies.NeverBan) { currentProxy.Status = Status.BANNED; currentProxy.LastUsed = DateTime.Now; RetryCount++; } // If the NeverBan option is true or we don't use a proxy, the BAN gets treated as a RETRY goto GETPROXY; case BotStatus.ERROR: // We assume it's a proxy error and that the Config is working correctly, so we mark the proxy as bad RetryCount++; if (UseProxies) currentProxy.Status = Status.BAD; goto GETPROXY; case BotStatus.RETRY: RetryCount++; goto GETPROXY; case BotStatus.NONE: validData = new ValidData(botData.Data.Data, botData.Proxy == null ? "" : botData.Proxy.Proxy, botData.Proxy == null ? ProxyType.Http : botData.Proxy.Type, botData.Status, "TOCHK", capturedData.ToCaptureString(), Settings.General.SaveLastSource ? botData.ResponseSource : "", BotLog); RaiseDispatchAction(new Action(() => ToCheckList.Add(validData))); if (UseProxies && !Settings.Proxies.NeverBan && Config.Settings.BanProxyAfterGoodStatus) { currentProxy.Status = Status.BANNED; currentProxy.LastUsed = DateTime.Now; RetryCount++; } break; } // Add it to the List and to the Database as well if (validData != null) { var hit = new Hit(data, capturedData, currentProxy == null ? "" : currentProxy.Proxy, hitType, ConfigName, WordlistName); RaiseFoundHit(hit); } // Wait time if (Settings.General.WaitTime > 0) Thread.Sleep(Settings.General.WaitTime); } catch (Exception ex) { RaiseMessageArrived(LogLevel.Error, $"[{bot.Id}] Check exception on data " + data + $" - {ex.Message}", false); } } /// /// Checks if the Master Worker is running and has not been cancelled. /// /// True if the Master Worker is stopping or has already stopped private bool ShouldStop() { return Master.CancellationPending || Master.Status != WorkerStatus.Running; } #endregion #region Update Methods /// /// Update the observable properties for the Timer. /// public void UpdateTimer() { OnPropertyChanged("TimerDays"); OnPropertyChanged("TimerHours"); OnPropertyChanged("TimerMinutes"); OnPropertyChanged("TimerSeconds"); OnPropertyChanged("TimeLeft"); } /// /// Update the observable properties for the statistics. /// public void UpdateStats() { OnPropertyChanged("HitsList"); OnPropertyChanged("FreeList"); OnPropertyChanged("ToCheckList"); OnPropertyChanged("TestedCount"); OnPropertyChanged("ProgressCount"); OnPropertyChanged("FailCount"); OnPropertyChanged("HitCount"); OnPropertyChanged("CustomCount"); OnPropertyChanged("ToCheckCount"); OnPropertyChanged("Balance"); OnPropertyChanged("Progress"); OnPropertyChanged("TotalProxiesCount"); OnPropertyChanged("AvailableProxiesCount"); OnPropertyChanged("AliveProxiesCount"); OnPropertyChanged("BannedProxiesCount"); OnPropertyChanged("BadProxiesCount"); } /// /// Update the observable CPM property. /// public void UpdateCPM() { OnPropertyChanged("CPM"); } #endregion #region Interface Calls /// /// Fired when a new message needs to be logged. /// public event Action MessageArrived; /// /// Fired when the Master Worker status changed. /// public event Action WorkerStatusChanged; /// /// Fired when a Hit was found. /// public event Action FoundHit; /// /// Fired when proxies need to be reloaded. /// public event Action ReloadProxies; /// /// Fired when an Action could change the UI and needs to be dispatched to another thread (usually it's handled by the UI thread). /// public event Action DispatchAction; /// /// Fired when the progress record needs to be saved to the Database. /// public event Action SaveProgress; /// /// Fired when custom inputs from the user are required. /// public event Action AskCustomInputs; private void RaiseMessageArrived(LogLevel level, string message, bool prompt) { MessageArrived?.Invoke(this, level, message, prompt); } private void RaiseWorkerStatusChanged() { OnPropertyChanged("WorkerStatus"); WorkerStatusChanged?.Invoke(this); } private void RaiseFoundHit(Hit hit) { FoundHit?.Invoke(this, hit); } private void RaiseReloadingProxies() { ReloadProxies?.Invoke(this); } private void RaiseDispatchAction(Action action) { DispatchAction?.Invoke(this, action); } private void RaiseSaveProgress() { SaveProgress?.Invoke(this); } private void RaiseAskCustomInputs() { AskCustomInputs?.Invoke(this); } #endregion #region Proxy Management Methods /// /// Loads the proxies from the specified source. /// private void LoadProxies() { RaiseMessageArrived(LogLevel.Info, $"Loading proxies from {Settings.Proxies.ReloadSource}", false); switch (Settings.Proxies.ReloadSource) { case ProxyReloadSource.Manager: // Raise the event and wait a bit so the proxies are loaded from the manager. RaiseReloadingProxies(); Thread.Sleep(100); break; case ProxyReloadSource.Remote: List proxies = new List(); Parallel.ForEach(Settings.Proxies.RemoteProxySources.Where(s => s.Active), s => { try { proxies.AddRange(GetProxiesFromRemoteSource(s.Url, s.Type, s.Pattern, s.Output)); } catch (Exception ex) { RaiseMessageArrived(LogLevel.Error, $"Could not contact the reload API - {ex.Message}", true); } }); ProxyPool = new ProxyPool(proxies, Settings.Proxies.ShuffleOnStart); break; case ProxyReloadSource.File: try { ProxyPool = new ProxyPool( GetProxiesFromFile(Settings.Proxies.ReloadPath, Settings.Proxies.ReloadType), Settings.Proxies.ShuffleOnStart); } catch (Exception ex) { RaiseMessageArrived(LogLevel.Error, $"Could not read the proxies from file - {ex.Message}", true); } break; } RaiseMessageArrived(LogLevel.Info, $"Loaded {TotalProxiesCount} proxies", false); } /// /// Loads a list of proxies from a remote source. /// /// The URL of the remote source /// The type of the proxies /// The Regex pattern to be used for parsing the proxies /// The output format of the groups matched by the regex /// The list of CProxy objects loaded from the API public static List GetProxiesFromRemoteSource(string url, ProxyType type, string pattern, string output) { var proxies = new List(); HttpRequest req = new HttpRequest(); req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 OPR/52.0.2871.64"; var resp = req.Get(url).ToString(); try { var matches = Regex.Matches(resp, pattern); foreach (Match match in matches) { var result = output; for (var i = 0; i < match.Groups.Count; i++) result = result.Replace("[" + i + "]", match.Groups[i].Value); proxies.Add(new CProxy(result, type)); } } catch { } return proxies; } /// /// Loads a list of proxies from a remote source asynchronously. /// /// The URL of the remote source /// The type of the proxies /// The Regex pattern to be used for parsing the proxies /// The output format of the groups matched by the regex /// The list of CProxy objects loaded from the API public async static Task GetProxiesFromRemoteSourceAsync(string url, ProxyType type, string pattern, string output) { var proxies = new List(); try { HttpRequest req = new HttpRequest(); req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 OPR/52.0.2871.64"; var resp = (await req.GetAsync(url)).ToString(); var matches = Regex.Matches(resp, pattern); foreach (Match match in matches) { var result = output; for (var i = 0; i < match.Groups.Count; i++) result = result.Replace("[" + i + "]", match.Groups[i].Value); proxies.Add(new CProxy(result, type)); } } catch (Exception ex) { return new RemoteProxySourceResult() { Successful = false, Error = ex.Message, Url = url, Proxies = new List() }; } return new RemoteProxySourceResult() { Successful = true, Error = "", Url = url, Proxies = proxies }; } /// /// Loads a list of proxies from a file. /// /// The file containing the proxies, one per line /// The type of the proxies /// The list of CProxy objects loaded from the file public static List GetProxiesFromFile(string fileName, ProxyType type) { var lines = File.ReadAllLines(fileName); var proxies = new List(); proxies.AddRange(lines.Select(l => new CProxy(l, type))); return proxies; } #endregion #region Bot Management Methods /// /// Cancels the async job on all bots and quits all the drivers. /// private void AbortAllBots() { foreach (var bot in Bots) { try { bot.Worker.CancelAsync(); } catch (Exception ex) { RaiseMessageArrived(LogLevel.Error, $"Error while deleting bot {bot.Id}: " + ex.Message, false); } } QuitAllDrivers(); } /// /// Quits all the open selenium drivers. /// private void QuitAllDrivers() { foreach (var bot in Bots) { try { if (bot.IsDriverOpen) bot.Driver.Quit(); } catch (Exception ex) { RaiseMessageArrived(LogLevel.Error, $"Error while quitting driver {bot.Id}: " + ex.Message, false); } } } #endregion #region Utility Methods /// /// Clamps an integer to an interval of values. /// /// The integer to clamp /// The minimum value of the interval /// The maximum value of the interval /// The clamped integer public int Clamp(int a, int min, int max) { if (a < min) return min; else if (a > max) return max; else return a; } #endregion } }