2021-03
Mastercart
A Supreme automation tool
2021-03
A Supreme automation tool
Mastercart is software that automates the process of checking out on webstores, in this case specifically Supreme. Purchasing items with limited stock has become rather competitive the last few years, whether it be for personal use or to sell at marked up prices. Due to high demand for products, automation but also anti-bot measures are very unforgiving and changing a lot.
Mastercart is built within ASP.NET Core 3.1 using the Electron.NET framework. This provides me with a known environment (C#) that allows multithreading, great performance but also the visual flexibility and features from Electron such as auto updater and packages from npm. Each task is essentially a state machine with each state being a step in the progress of making a successful checkout. This makes it easy to repeat steps or restart at certain points, for example whenever the correct id's have already been scraped and you want to retry checking out.
Initially this project started out as a personal interest. Rather quickly, however, it drew the attention of a small group of people who have beta tested the software for multiple weeks. Dominik Beens has mainly been responsible for the implementation of the frontend. Other than the application itself, I run a NodeJS server that is both used for the Discord implementation and serves as a licensing server linked to a database. Updates are released through Amazon S3 and users get prompted in the app to update without any reinstallation.
Some of the features within Mastercart are:
I chose the CaptchaService example since it shows a couple of sides of object-oriented programming: services, interfaces and abstract classes. Mastercart makes use of multiple different services varying from the captcha harvester up to the way you want to save your data.
using System;
namespace Mastercart.Utilities {
public abstract class Service {
public Service() {
}
}
public abstract class Service<T> : Service where T : IService {
private static T _implementation;
protected static T implementation {
get {
if(_implementation == null) {
Console.WriteLine("Implementation for " + typeof(T).ToString() + " not set yet!");
}
return _implementation;
}
private set {
if(_implementation != null) {
Console.WriteLine("Implementation for " + typeof(T).ToString() + " was set, again!");
}
_implementation = value;
}
}
public Service(T implementation) : base() {
Service<T>.implementation = implementation;
}
protected static void Override(T implemenation) {
_implementation = implemenation;
}
}
}using System;
using System.Threading;
using Mastercart.Utilities;
using Promise = System.Threading.Tasks.Task<Mastercart.Response>;
namespace Mastercart {
public interface ICaptchaService : IService {
bool RequiresUserInteraction { get; }
Promise GetCaptchaToken(string hostUrl, string hostSitekey, CancellationToken cancellationToken);
}
}using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Mastercart.Utilities;
using Promise = System.Threading.Tasks.Task<Mastercart.Response>;
using Microsoft.AspNetCore.Mvc.Rendering;
using Newtonsoft.Json;
using Mastercart.Data;
namespace Mastercart {
public enum CaptchaImplementation {
captchasolver = 0,
anticaptcha = 1,
twocaptcha = 2
}
public class CaptchaService : Service<ICaptchaService> {
public static readonly List<SelectListItem> Implementations = new List<SelectListItem>() {
new SelectListItem("Captcha Solver", CaptchaImplementation.captchasolver.ToString(), true),
new SelectListItem("Anti-Captcha", CaptchaImplementation.anticaptcha.ToString()),
new SelectListItem("2Captcha", CaptchaImplementation.twocaptcha.ToString())
};
public const string SUPREME_CAPTCHA_URL = "http://www.supremenewyork.com/";
public const string SUPREME_CAPTCHA_SITEKEY = "6LeWwRkUAAAAAOBsau7KpuC9AV-6J8mhw4AjC3Xz";
public static ICaptchaService Implementation => implementation;
public CaptchaService() : base(GetCaptchaImplemenation()) {
}
public static void Reinitialize() {
Override(GetCaptchaImplemenation());
}
private static ICaptchaService GetCaptchaImplemenation() {
if(Enum.TryParse(Preferences.Instance.CaptchaImplementation, out CaptchaImplementation captchaImplementation)) {
switch(captchaImplementation) {
default:
case CaptchaImplementation.captchasolver:
return new CaptchaWindowService();
case CaptchaImplementation.twocaptcha:
return new TwoCaptchaService();
case CaptchaImplementation.anticaptcha:
return new AntiCaptchaService();
}
} else {
return new CaptchaWindowService();
}
}
public static async Promise GetCaptchaToken(string hostUrl, string hostSitekey, CancellationToken cancellationToken) {
return await implementation.GetCaptchaToken(hostUrl, hostSitekey, cancellationToken);
}
}
}using System;
using System.Threading;
using Mastercart.Data;
using Newtonsoft.Json.Linq;
using Promise = System.Threading.Tasks.Task<Mastercart.Response>;
namespace Mastercart {
public class AntiCaptchaService : ICaptchaService {
public const string ANTICAPTCHA_URL_CREATETASK = "https://api.anti-captcha.com/createTask";
public const string ANTICAPTCHA_URL_GETTASKRESULT = "https://api.anti-captcha.com/getTaskResult";
public bool RequiresUserInteraction => false;
private string apiKey;
public AntiCaptchaService() {
apiKey = Preferences.Instance.AntiCaptchaApiKey;
}
public async Promise GetCaptchaToken(string hostUrl, string hostSitekey, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
if(string.IsNullOrEmpty(apiKey)) {
return Response.Empty();
}
return await PostCaptchaTask(hostUrl, hostSitekey, cancellationToken);
}
private async Promise PostCaptchaTask(string hostUrl, string hostSitekey, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
JObject captchaTask = new JObject {
{"type", "NoCaptchaTaskProxyless"},
{"websiteURL", hostUrl},
{"websiteKey", hostSitekey},
};
JObject taskPostData = new JObject() {
{ "clientKey", apiKey},
{ "task", captchaTask }
};
Response response = await ConnectionService.POST(ANTICAPTCHA_URL_CREATETASK, cancellationToken, data: taskPostData);
if(response.Success) {
JObject taskResponse = JObject.Parse(response.Data);
if(taskResponse.TryGetValue("errorId", out JToken errorId)) {
if(errorId.ToString() == "0") {
JObject taskGetData = new JObject() {
{ "clientKey", apiKey},
{ "taskId", taskResponse.GetValue("taskId")}
};
return await OnGetCaptchaTaskSuccess(taskGetData, cancellationToken);
}
}
}
return OnGetCaptchaFailed(cancellationToken);
}
private async Promise OnGetCaptchaTaskSuccess(JObject taskGetData, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
Response response = await ConnectionService.POST(ANTICAPTCHA_URL_GETTASKRESULT, cancellationToken, data: taskGetData);
if(response.Success) {
JObject taskResponse = JObject.Parse(response.Data);
if(taskResponse.TryGetValue("errorId", out JToken errorId)) {
if(errorId.ToString() == "0") {
string status = taskResponse.GetValue("status").ToString();
if(status == "ready") {
JToken solution = taskResponse.GetValue("solution");
string token = solution["gRecaptchaResponse"].ToString();
return new Response(true, token);
} else if(status == "processing") {
return await WaitForTaskCompletion(taskGetData, cancellationToken);
}
}
}
}
return OnGetCaptchaFailed(cancellationToken);
}
private async Promise WaitForTaskCompletion(JObject taskPostData, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
await AsyncTaskHelper.Delay(100, cancellationToken);
return await OnGetCaptchaTaskSuccess(taskPostData, cancellationToken);
}
private Response OnGetCaptchaFailed(CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();
return Response.Empty();
}
}
}The StateMachine and State classes are set up in such a way where a state machine is a state in itself. This makes it easy to step out or step over large portions of states. States are also responsible for their own logs.
using Mastercart.Data;
using System;
using System.Threading.Tasks;
using AsyncTask = System.Threading.Tasks.Task;
namespace Mastercart.Utilities.StateMachine {
public class StateMachine : State {
public State CurrentState { get; protected set; }
public StateMachine() {
_ = Enter();
}
public async AsyncTask GoToState<T>(params object[] parameters) where T : State, new() {
T newState = new T();
await GoToNewState(newState, parameters);
}
protected async override AsyncTask OnEnter(params object[] parameters) {
await AsyncTask.CompletedTask;
}
protected async override AsyncTask OnExit() {
if(CurrentState != null) {
await CurrentState.Exit();
FinishStateLog();
}
}
protected void ClearLog() {
StateLog = "";
}
private async AsyncTask GoToNewState<T>(T newState, params object[] parameters) where T : State {
if(CurrentState != null) {
await CurrentState.Exit();
if(CurrentState.LogCheckpoint || StagingEnvironment.LogAll) {
FinishStateLog();
}
}
CurrentState = newState;
await CurrentState.Enter(this, parameters);
}
private void FinishStateLog() {
StateLog += CurrentState.StateLog;
}
}
}using System;
using System.Threading;
using System.Threading.Tasks;
using AsyncTask = System.Threading.Tasks.Task;
namespace Mastercart.Utilities.StateMachine {
public abstract class State {
public string StateLog { get; protected set; } = "";
public virtual bool LogCheckpoint => true;
protected StateMachine stateMachine { get; private set; }
protected DateTime enterTime { get; private set; }
public async virtual AsyncTask Enter(StateMachine stateMachine, params object[] parameters) {
this.stateMachine = stateMachine;
await Enter(parameters);
}
public async AsyncTask Enter(params object[] parameters) {
enterTime = DateTime.UtcNow;
await OnEnter(parameters);
}
public async AsyncTask Exit() {
await OnExit();
}
protected abstract AsyncTask OnEnter(params object[] parameters);
protected abstract AsyncTask OnExit();
public void Log(string log, bool timestamp = true) {
string time = timestamp ? $"{DateTime.Now.ToString("HH:mm:ss.FFF")} " : "";
StateLog += $"{time}{log}\n";
}
public void ClearStateLog() {
StateLog = "";
}
public void EnvironmentLog(string log, bool timestamp = true) {
StagingEnvironment.Log(Log, log, timestamp);
}
public void AppendLog(string log) {
StateLog += log;
}
}
}