mirror of
https://github.com/openbullet/openbullet.git
synced 2023-10-21 07:33:42 -05:00
1335 lines
57 KiB
C#
1335 lines
57 KiB
C#
using Extreme.Net;
|
|
using Newtonsoft.Json;
|
|
using RuriLib.Interfaces;
|
|
using RuriLib.LS;
|
|
using RuriLib.Models;
|
|
using RuriLib.Models.Stats;
|
|
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
|
|
{
|
|
/// <summary>
|
|
/// Whether to use proxies or not for the current session.
|
|
/// </summary>
|
|
public enum ProxyMode
|
|
{
|
|
/// <summary>Use the default setting in the config.</summary>
|
|
Default,
|
|
/// <summary>Always use proxies.</summary>
|
|
On,
|
|
/// <summary>Never use proxies.</summary>
|
|
Off
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main class that handles all the multi-threaded checking of a Wordlist given a Config.
|
|
/// </summary>
|
|
// TODO: Split this into partial classes
|
|
public class RunnerViewModel : ViewModelBase, IRunnerMessaging, IRunner
|
|
{
|
|
#region Constructor
|
|
/// <summary>
|
|
/// Constructs the RunnerViewModel instance.
|
|
/// </summary>
|
|
/// <param name="environment">The environment settings</param>
|
|
/// <param name="settings">The RuriLib settings</param>
|
|
/// <param name="random">A reference to the global random generator</param>
|
|
public RunnerViewModel(EnvironmentSettings environment, RLSettingsViewModel settings, Random random = null)
|
|
{
|
|
Env = environment;
|
|
Settings = settings;
|
|
this.random = random != null ? random : new Random();
|
|
OnPropertyChanged("Busy");
|
|
OnPropertyChanged("ControlsEnabled");
|
|
}
|
|
#endregion
|
|
|
|
#region Settings
|
|
private RLSettingsViewModel Settings { get; set; }
|
|
private EnvironmentSettings Env { get; set; }
|
|
private Random random;
|
|
private object randomLocker = new object();
|
|
#endregion
|
|
|
|
#region Workers
|
|
/// <summary>The Master Worker that manages all the other Workers and updates the observable properties.</summary>
|
|
public AbortableBackgroundWorker Master { get; set; } = new AbortableBackgroundWorker();
|
|
/// <summary>The status of the Master Worker.</summary>
|
|
public WorkerStatus WorkerStatus { get { return Master.Status; } }
|
|
/// <summary>The managed workers that run single data checks.</summary>
|
|
public ObservableCollection<RunnerBotViewModel> Bots { get; } = new ObservableCollection<RunnerBotViewModel>();
|
|
#endregion
|
|
|
|
#region Visible Properties and Components
|
|
/// <summary>Whether the Master Worker is busy or idle.</summary>
|
|
public bool Busy => Master.Status != WorkerStatus.Idle;
|
|
|
|
/// <summary>Whether the user can set the properties (a.k.a. whether the Master Worker is idle).</summary>
|
|
public bool ControlsEnabled => !Busy;
|
|
|
|
private int botsAmount = 1;
|
|
/// <summary>The amount of bots to run simultaneously for multi-threaded checking.</summary>
|
|
public int BotsAmount { get => botsAmount; set { botsAmount = value; OnPropertyChanged(); } }
|
|
|
|
private int startingPoint = 1;
|
|
/// <summary>How many data lines to skip before starting the checking process.</summary>
|
|
public int StartingPoint { get { return startingPoint; } set { startingPoint = value; OnPropertyChanged(); OnPropertyChanged("Progress"); } }
|
|
|
|
/// <summary>The amount of data lines checked, including the starting point.</summary>
|
|
public int ProgressCount { get { return TestedCount + StartingPoint - 1; } }
|
|
|
|
/// <summary>The rounded percentage of checked data lines (0 to 100).</summary>
|
|
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;
|
|
/// <summary>The checks per minute.</summary>
|
|
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;
|
|
/// <summary>The remaining balance in the captcha solver account.</summary>
|
|
public double Balance { get { return _balance; } set { _balance = value; OnPropertyChanged("BalanceString"); } }
|
|
|
|
/// <summary>The remaining balance in the captcha solver account preceeded by a $ sign.</summary>
|
|
public string BalanceString { get { return $"${_balance}"; } }
|
|
|
|
private ProxyMode proxyMode = ProxyMode.Default;
|
|
/// <summary>The Proxy Mode.</summary>
|
|
public ProxyMode ProxyMode { get { return proxyMode; } set { proxyMode = value; OnPropertyChanged(); } }
|
|
|
|
/// <summary>Whether proxies can be used for the current session given the Proxy Mode and the Config.</summary>
|
|
public bool UseProxies
|
|
{
|
|
get
|
|
{
|
|
if (Config != null)
|
|
{
|
|
return (Config.Settings.NeedsProxies && ProxyMode == ProxyMode.Default) || ProxyMode == ProxyMode.On;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>The loaded Config to use for the check.</summary>
|
|
public Config Config { get; private set; }
|
|
|
|
/// <summary>The name of the loaded Config.</summary>
|
|
public string ConfigName { get { return Config == null ? "None" : Config.Settings.Name; } }
|
|
|
|
/// <summary>The loaded Wordlist to use for the check.</summary>
|
|
public Wordlist Wordlist { get; private set; }
|
|
|
|
/// <summary>The name of the loaded Wordlist.</summary>
|
|
public string WordlistName { get { return Wordlist == null ? "None" : Wordlist.Name; } }
|
|
|
|
/// <summary>The size of the loaded Wordlist.</summary>
|
|
public int WordlistSize { get { return Wordlist == null ? 0 : Wordlist.Total; } }
|
|
|
|
/// <summary>The pool from which bots can get a proxy upon request.</summary>
|
|
public ProxyPool ProxyPool { get; set; } = new ProxyPool(new List<CProxy>());
|
|
|
|
/// <summary>The total amount of proxies loaded.</summary>
|
|
public int TotalProxiesCount { get { return ProxyPool.Proxies.Count; } }
|
|
|
|
/// <summary>The amount of proxies loaded that are alive.</summary>
|
|
public int AliveProxiesCount { get { return ProxyPool.Alive.Count; } }
|
|
|
|
/// <summary>The amount of proxies loaded that are available.</summary>
|
|
public int AvailableProxiesCount { get { return ProxyPool.Available.Count; } }
|
|
|
|
/// <summary>The amount of proxies loaded that are banned.</summary>
|
|
public int BannedProxiesCount { get { return ProxyPool.Banned.Count; } }
|
|
|
|
/// <summary>The amount of proxies loaded that are bad.</summary>
|
|
public int BadProxiesCount { get { return ProxyPool.Bad.Count; } }
|
|
|
|
/// <summary>The pool from which the Master worker draws data to assign to bots for the checks.</summary>
|
|
public DataPool DataPool { get; set; }
|
|
|
|
/// <summary>The size of the DataPool.</summary>
|
|
public int DataSize
|
|
{
|
|
get
|
|
{
|
|
if (DataPool != null)
|
|
{
|
|
return DataPool.Size;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Bot-Shared Fields
|
|
/// <summary>The pairs of (variable name, value) set by the user.</summary>
|
|
public List<KeyValuePair<string, string>> CustomInputs { get; set; } = new List<KeyValuePair<string, string>>();
|
|
|
|
/// <summary>The Global Variables list that are set in all bots when they start.</summary>
|
|
public VariableList GlobalVariables { get; set; } = new VariableList();
|
|
|
|
/// <summary>The Global Cookies list that are set in all bots when they start.</summary>
|
|
public CookieDictionary GlobalCookies { get; set; } = new CookieDictionary();
|
|
|
|
/// <summary>If not null, bots will perform this action instead of the default behaviour.</summary>
|
|
public Action<BotData> CustomAction { get; set; }
|
|
#endregion
|
|
|
|
#region Results
|
|
/// <summary>The list of data lines checked with a FAIL outcome.</summary>
|
|
public List<ValidData> FailedList { get; set; } = new List<ValidData>();
|
|
|
|
/// <summary>The list of data lines checked with a SUCCESS outcome.</summary>
|
|
public ObservableCollection<ValidData> HitsList { get; set; } = new ObservableCollection<ValidData>();
|
|
|
|
/// <summary>The list of data lines checked with a CUSTOM outcome.</summary>
|
|
public ObservableCollection<ValidData> CustomList { get; set; } = new ObservableCollection<ValidData>();
|
|
|
|
/// <summary>The list of data lines checked with a NONE outcome.</summary>
|
|
public ObservableCollection<ValidData> ToCheckList { get; set; } = new ObservableCollection<ValidData>();
|
|
|
|
/// <summary>Auxiliary empty list.</summary>
|
|
public ObservableCollection<ValidData> EmptyList { get; set; } = new ObservableCollection<ValidData>();
|
|
|
|
/// <summary>The collection of data that was checked with a positive outcome.</summary>
|
|
public IEnumerable<ValidData> Checked => (new IEnumerable<ValidData>[] { HitsList, CustomList, ToCheckList }).SelectMany(h => h).OrderBy(h => h.Time);
|
|
|
|
/// <summary>Filter based on the Bot Status.</summary>
|
|
public BotStatus ResultsFilter { get; set; } = BotStatus.SUCCESS;
|
|
|
|
/// <summary>Amount of data lines checked with a FAIL outcome.</summary>
|
|
public int FailCount { get { return FailedList.Count; } }
|
|
|
|
/// <summary>Amount of data lines checked with a SUCCESS outcome.</summary>
|
|
public int HitCount { get { return HitsList.Count; } }
|
|
|
|
/// <summary>Amount of data lines checked with a CUSTOM outcome.</summary>
|
|
public int CustomCount { get { return CustomList.Count; } }
|
|
|
|
/// <summary>Amount of data lines checked with a NONE outcome.</summary>
|
|
public int ToCheckCount { get { return ToCheckList.Count; } }
|
|
|
|
private int retryCount = 0;
|
|
/// <summary>Amount of data lines retried due to a BAN, RETRY or ERROR outcome.</summary>
|
|
public int RetryCount { get { return retryCount; } set { retryCount = value; OnPropertyChanged(); } }
|
|
|
|
/// <summary>Total amount of successfully tested data lines.</summary>
|
|
public int TestedCount { get { return FailCount + HitCount + CustomCount + ToCheckCount; } }
|
|
#endregion
|
|
|
|
#region Stats
|
|
/// <summary>
|
|
/// Statistics of the checking process.
|
|
/// </summary>
|
|
public RunnerStats Stats => new RunnerStats(
|
|
new RunnerStatsData(TestedCount, HitCount, CustomCount, FailCount, RetryCount, ToCheckCount),
|
|
new RunnerStatsProxies(TotalProxiesCount, AliveProxiesCount, BannedProxiesCount, BadProxiesCount),
|
|
CPM, (decimal)Balance);
|
|
#endregion
|
|
|
|
#region Locks
|
|
/// <summary>Whether the workers are waiting for proxies to be reloaded.</summary>
|
|
private bool IsReloadingProxies { get; set; } = false;
|
|
|
|
/// <summary>Whether the CPM is already being calculated.</summary>
|
|
private bool IsCPMLocked { get; set; } = false;
|
|
|
|
/// <summary>Whether the warning about no proxy availability has already been issued.</summary>
|
|
private bool NoProxyWarningSent { get; set; } = false;
|
|
|
|
/// <summary>Whether the Custom Inputs have already been initialized.</summary>
|
|
public bool CustomInputsInitialized { get; set; } = false;
|
|
#endregion
|
|
|
|
#region Timing
|
|
private Stopwatch Timer { get; set; } = new Stopwatch();
|
|
|
|
/// <summary>Days elapsed since the runner was started.</summary>
|
|
public string TimerDays { get { return Timer.Elapsed.Days.ToString(); } }
|
|
|
|
/// <summary>Hours elapsed since the runner was started.</summary>
|
|
public string TimerHours { get { return Timer.Elapsed.Hours.ToString("D2"); } }
|
|
|
|
/// <summary>Minutes elapsed since the runner was started.</summary>
|
|
public string TimerMinutes { get { return Timer.Elapsed.Minutes.ToString("D2"); ; } }
|
|
|
|
/// <summary>Seconds elapsed since the runner was started.</summary>
|
|
public string TimerSeconds { get { return Timer.Elapsed.Seconds.ToString("D2"); ; } }
|
|
|
|
/// <summary>Representation of the expected time left to check the remaining data lines.</summary>
|
|
public string TimeLeft
|
|
{
|
|
get
|
|
{
|
|
if (Wordlist == null) return "Unknown time left";
|
|
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
|
|
/// <summary>
|
|
/// Sets a Config in the Runner instance.
|
|
/// </summary>
|
|
/// <param name="config">The Config to be used for the check</param>
|
|
/// <param name="setRecommended">Whether to automatically set the recommended amount of bots from the Config settings.</param>
|
|
public void SetConfig(Config config, bool setRecommended)
|
|
{
|
|
Config = config;
|
|
if (setRecommended) BotsAmount = Clamp(config.Settings.SuggestedBots, 1, 200);
|
|
OnPropertyChanged("ConfigName");
|
|
RaiseConfigChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a Wordlist in the Runner instance.
|
|
/// </summary>
|
|
/// <param name="wordlist">The Wordlist to be used for the check</param>
|
|
public void SetWordlist(Wordlist wordlist)
|
|
{
|
|
Wordlist = wordlist;
|
|
OnPropertyChanged("WordlistName");
|
|
OnPropertyChanged("WordlistSize");
|
|
RaiseWordlistChanged();
|
|
}
|
|
#endregion
|
|
|
|
#region Worker Control Methods
|
|
/// <summary>
|
|
/// Starts the Master Worker.
|
|
/// </summary>
|
|
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); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the Master Worker.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forcefully aborts the Bots and stops the Master Worker.
|
|
/// </summary>
|
|
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 a custom action was defined, set a default config to avoid Null Pointer Exceptions
|
|
if (CustomAction != null) Config = new Config(new ConfigSettings(), "");
|
|
|
|
if (Config == null) throw new Exception("No Config loaded!");
|
|
if (Wordlist == null) throw new Exception("No Wordlist loaded!");
|
|
|
|
if (!Wordlist.Temporary) 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 (CustomAction == null && 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 <= BotsAmount; 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 (BotsAmount != Bots.Count)
|
|
{
|
|
RaiseMessageArrived(LogLevel.Info, $"Bots Number was changed from {Bots.Count} to {BotsAmount}", false);
|
|
|
|
// If it was increased
|
|
if (BotsAmount > Bots.Count)
|
|
{
|
|
// Create the missing bots
|
|
for (int b = Bots.Count + 1; b <= BotsAmount; 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 >= BotsAmount; 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;
|
|
}
|
|
|
|
// Periodic actions
|
|
if (Timer.IsRunning && (int)Timer.Elapsed.TotalSeconds != 0)
|
|
{
|
|
// 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 ((int)Timer.Elapsed.TotalSeconds % 120 == 0)
|
|
{
|
|
RaiseSaveProgress();
|
|
}
|
|
|
|
// Reload proxies if a reload interval was set
|
|
if (Settings.Proxies.ReloadInterval > 0 && (int)Timer.Elapsed.TotalSeconds % (Settings.Proxies.ReloadInterval * 60) == 0)
|
|
{
|
|
LoadProxies();
|
|
}
|
|
}
|
|
|
|
// 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 (Settings.General.SendToCheckOnAbort)
|
|
{
|
|
foreach (var bot in Bots.Where(b => b.Worker.IsBusy))
|
|
{
|
|
ValidData validData = new ValidData(bot.Data, bot.Proxy, ProxyType.Http, BotStatus.NONE, "NONE", "", "", new List<LogEntry>());
|
|
ToCheckList.Add(validData);
|
|
UpdateStats();
|
|
var hit = new Hit(bot.Data, new VariableList(), bot.Proxy, "NONE", ConfigName, WordlistName);
|
|
RaiseFoundHit(hit);
|
|
}
|
|
}
|
|
|
|
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, 2);
|
|
NoProxyWarningSent = true;
|
|
}
|
|
|
|
Master.CancelAsync();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assign the proxy to the bot's displayed fields
|
|
bot.Proxy = currentProxy.Proxy;
|
|
}
|
|
var proxyUsedText = currentProxy == null ? "NONE" : $"{currentProxy.Proxy} ({currentProxy.Type})";
|
|
|
|
// Initialize the Bot Data
|
|
BotData botData = null;
|
|
lock (randomLocker)
|
|
{
|
|
botData = new BotData(Settings, Config.Settings, currentData, currentProxy, UseProxies, random, bot.Id, false);
|
|
}
|
|
botData.Driver = bot.Driver;
|
|
botData.BrowserOpen = bot.IsDriverOpen;
|
|
List<LogEntry> BotLog = new List<LogEntry>();
|
|
|
|
// 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 { }
|
|
|
|
// Set the cloudflare cookies (to be used in normal requests) if we already have clearance and we don't have to get it each time
|
|
if (botData.UseProxies && botData.Proxy != null && botData.Proxy.Clearance != string.Empty && !Settings.Proxies.AlwaysGetClearance)
|
|
{
|
|
botData.Cookies["cf_clearance"] = botData.Proxy.Clearance;
|
|
botData.Cookies["__cfduid"] = botData.Proxy.Cfduid;
|
|
}
|
|
|
|
// Print the start message
|
|
BotLog.Add(new LogEntry($"===== LOG FOR BOT #{bot.Id} WITH DATA {botData.Data.Data} AND PROXY {proxyUsedText} ====={Environment.NewLine}", Colors.White));
|
|
|
|
if (CustomAction != null)
|
|
{
|
|
RaiseMessageArrived(LogLevel.Info, $"[{bot.Id}] Executing custom action", false);
|
|
try
|
|
{
|
|
CustomAction.Invoke(botData);
|
|
}
|
|
catch (Exception ex) { RaiseMessageArrived(LogLevel.Info, $"[{bot.Id}] CUSTOM ACTION EXCEPTION: {ex.ToString()}", false); throw; }
|
|
goto FINISH;
|
|
}
|
|
|
|
/* =================
|
|
* SCRIPT PROCESSING *
|
|
================= */
|
|
|
|
// Initialize the LoliScript
|
|
LoliScript loli = new LoliScript(Config.Script);
|
|
loli.Reset();
|
|
|
|
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
|
|
|
|
FINISH:
|
|
|
|
// 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 || (Config.Settings.QuitOnBanRetry && (botData.Status == BotStatus.BAN || botData.Status == BotStatus.RETRY)))
|
|
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 the NeverBan option is true or we don't use a proxy, the BAN gets treated as a RETRY
|
|
if (UseProxies && !Settings.Proxies.NeverBan)
|
|
{
|
|
currentProxy.Status = Status.BANNED;
|
|
currentProxy.LastUsed = DateTime.Now;
|
|
RetryCount++;
|
|
}
|
|
|
|
if (ShouldTriggerEvasion(currentData.Retries))
|
|
{
|
|
currentData.Retries++;
|
|
goto GETPROXY;
|
|
}
|
|
else
|
|
{
|
|
RaiseMessageArrived(LogLevel.Warning, $"[{bot.Id}][{bot.Data}] Maximum retries exceeded");
|
|
botData.Status = BotStatus.NONE;
|
|
hitType = botData.Status.ToString();
|
|
goto TOCHECK;
|
|
}
|
|
|
|
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:
|
|
TOCHECK:
|
|
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(botData.Data.Data, capturedData, currentProxy == null ? "" : currentProxy.Proxy, hitType, ConfigName, WordlistName);
|
|
RaiseFoundHit(hit);
|
|
}
|
|
|
|
// Call the webhook
|
|
if (Settings.General.WebhookEnabled && (botData.Status == BotStatus.SUCCESS || botData.Status == BotStatus.CUSTOM))
|
|
{
|
|
HttpRequest request = new HttpRequest();
|
|
try
|
|
{
|
|
var toSend = new WebhookFormat(data, hitType, capturedData.ToCaptureString(), DateTime.Now, Config.Settings.Name, Config.Settings.Author, Settings.General.WebhookUser);
|
|
var json = JsonConvert.SerializeObject(toSend);
|
|
request.PostAsync(Settings.General.WebhookURL, json, "application/json");
|
|
}
|
|
catch
|
|
{
|
|
RaiseMessageArrived(LogLevel.Error, $"Could not register the hit to webhook {Settings.General.WebhookURL}");
|
|
}
|
|
}
|
|
|
|
// 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); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the Master Worker is running and has not been cancelled.
|
|
/// </summary>
|
|
/// <returns>True if the Master Worker is stopping or has already stopped</returns>
|
|
private bool ShouldStop()
|
|
{
|
|
return Master.CancellationPending || Master.Status != WorkerStatus.Running;
|
|
}
|
|
#endregion
|
|
|
|
#region Update Methods
|
|
|
|
/// <summary>
|
|
/// Update the observable properties for the Timer.
|
|
/// </summary>
|
|
public void UpdateTimer()
|
|
{
|
|
OnPropertyChanged("TimerDays");
|
|
OnPropertyChanged("TimerHours");
|
|
OnPropertyChanged("TimerMinutes");
|
|
OnPropertyChanged("TimerSeconds");
|
|
OnPropertyChanged("TimeLeft");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the observable properties for the statistics.
|
|
/// </summary>
|
|
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");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the observable CPM property.
|
|
/// </summary>
|
|
public void UpdateCPM()
|
|
{
|
|
OnPropertyChanged("CPM");
|
|
}
|
|
#endregion
|
|
|
|
#region Events
|
|
/// <summary>Fired when a new message needs to be logged.</summary>
|
|
public event Action<IRunnerMessaging, LogLevel, string, bool, int> MessageArrived;
|
|
|
|
/// <summary>Fired when the Master Worker status changed.</summary>
|
|
public event Action<IRunnerMessaging> WorkerStatusChanged;
|
|
|
|
/// <summary>Fired when a Hit was found.</summary>
|
|
public event Action<IRunnerMessaging, Hit> FoundHit;
|
|
|
|
/// <summary>Fired when proxies need to be reloaded.</summary>
|
|
public event Action<IRunnerMessaging> ReloadProxies;
|
|
|
|
/// <summary>/// Fired when an Action could change the UI and needs to be dispatched to another thread (usually it's handled by the UI thread).</summary>
|
|
public event Action<IRunnerMessaging, Action> DispatchAction;
|
|
|
|
/// <summary>Fired when the progress record needs to be saved to the Database.</summary>
|
|
public event Action<IRunnerMessaging> SaveProgress;
|
|
|
|
/// <summary>Fired when custom inputs from the user are required.</summary>
|
|
public event Action<IRunnerMessaging> AskCustomInputs;
|
|
|
|
/// <summary>Fired when the currently selected Config changed.</summary>
|
|
public event Action<IRunnerMessaging> ConfigChanged;
|
|
|
|
/// <summary>Fired when the currently selected Wordlist changed.</summary>
|
|
public event Action<IRunnerMessaging> WordlistChanged;
|
|
|
|
private void RaiseMessageArrived(LogLevel level, string message, bool prompt = false, int timeout = 0)
|
|
{
|
|
MessageArrived?.Invoke(this, level, message, prompt, timeout);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
private void RaiseConfigChanged()
|
|
{
|
|
ConfigChanged?.Invoke(this);
|
|
}
|
|
|
|
private void RaiseWordlistChanged()
|
|
{
|
|
WordlistChanged?.Invoke(this);
|
|
}
|
|
#endregion
|
|
|
|
#region Proxy Management Methods
|
|
|
|
/// <summary>
|
|
/// Loads the proxies from the specified source.
|
|
/// </summary>
|
|
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<CProxy> proxies = new List<CProxy>();
|
|
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 {s.Url} for {s.Type} proxies - {ex.Message}", true, 5); }
|
|
});
|
|
ProxyPool = new ProxyPool(proxies, Settings.Proxies.ShuffleOnStart);
|
|
// ProxyPool.RemoveDuplicates();
|
|
break;
|
|
|
|
case ProxyReloadSource.File:
|
|
try
|
|
{
|
|
ProxyPool = new ProxyPool(
|
|
GetProxiesFromFile(Settings.Proxies.ReloadPath,
|
|
Settings.Proxies.ReloadType), Settings.Proxies.ShuffleOnStart);
|
|
// ProxyPool.RemoveDuplicates();
|
|
}
|
|
catch (Exception ex) { RaiseMessageArrived(LogLevel.Error, $"Could not read the proxies from file {Settings.Proxies.ReloadPath} - {ex.Message}", true); }
|
|
break;
|
|
}
|
|
|
|
RaiseMessageArrived(LogLevel.Info, $"Loaded {TotalProxiesCount} proxies", false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a list of proxies from a remote source.
|
|
/// </summary>
|
|
/// <param name="url">The URL of the remote source</param>
|
|
/// <param name="type">The type of the proxies</param>
|
|
/// <param name="pattern">The Regex pattern to be used for parsing the proxies</param>
|
|
/// <param name="output">The output format of the groups matched by the regex</param>
|
|
/// <returns>The list of CProxy objects loaded from the API</returns>
|
|
public static List<CProxy> GetProxiesFromRemoteSource(string url, ProxyType type, string pattern, string output)
|
|
{
|
|
var proxies = new List<CProxy>();
|
|
|
|
HttpRequest req = new HttpRequest();
|
|
req.ConnectTimeout = 5000;
|
|
req.ReadWriteTimeout = 5000;
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a list of proxies from a remote source asynchronously.
|
|
/// </summary>
|
|
/// <param name="url">The URL of the remote source</param>
|
|
/// <param name="type">The type of the proxies</param>
|
|
/// <param name="pattern">The Regex pattern to be used for parsing the proxies</param>
|
|
/// <param name="output">The output format of the groups matched by the regex</param>
|
|
/// <returns>The list of CProxy objects loaded from the API</returns>
|
|
public async static Task<RemoteProxySourceResult> GetProxiesFromRemoteSourceAsync(string url, ProxyType type, string pattern, string output)
|
|
{
|
|
var proxies = new List<CProxy>();
|
|
|
|
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<CProxy>()
|
|
};
|
|
}
|
|
|
|
return new RemoteProxySourceResult()
|
|
{
|
|
Successful = true,
|
|
Error = "",
|
|
Url = url,
|
|
Proxies = proxies
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a list of proxies from a file.
|
|
/// </summary>
|
|
/// <param name="fileName">The file containing the proxies, one per line</param>
|
|
/// <param name="type">The type of the proxies</param>
|
|
/// <returns>The list of CProxy objects loaded from the file</returns>
|
|
public static List<CProxy> GetProxiesFromFile(string fileName, ProxyType type)
|
|
{
|
|
var lines = File.ReadAllLines(fileName);
|
|
var proxies = new List<CProxy>();
|
|
|
|
proxies.AddRange(lines.Select(l => new CProxy(l, type)));
|
|
|
|
return proxies;
|
|
}
|
|
|
|
private bool ShouldTriggerEvasion(int retries)
|
|
{
|
|
var evasionValue = Config.Settings.BanLoopEvasionOverride == -1
|
|
? Settings.Proxies.BanLoopEvasion
|
|
: Config.Settings.BanLoopEvasionOverride;
|
|
|
|
return retries < evasionValue || evasionValue == 0;
|
|
}
|
|
#endregion
|
|
|
|
#region Bot Management Methods
|
|
|
|
/// <summary>
|
|
/// Cancels the async job on all bots and quits all the drivers.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Quits all the open selenium drivers.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Clamps an integer to an interval of values.
|
|
/// </summary>
|
|
/// <param name="a">The integer to clamp</param>
|
|
/// <param name="min">The minimum value of the interval</param>
|
|
/// <param name="max">The maximum value of the interval</param>
|
|
/// <returns>The clamped integer</returns>
|
|
public int Clamp(int a, int min, int max)
|
|
{
|
|
if (a < min) return min;
|
|
else if (a > max) return max;
|
|
else return a;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|