OpenBullet2 Core Selected Detailed Documentation
OpenBullet2 Core Selected Detailed Documentation
cs
■using Microsoft.EntityFrameworkCore;
using OpenBullet2.Core.Entities;
namespace OpenBullet2.Core;
/// <summary>
/// The <see cref="DbContext"/> for the OpenBullet 2 core domain.
/// </summary>
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions options)
: base(options)
{
modelBuilder.Entity<ProxyGroupEntity>()
.HasOne(g => g.Owner)
.WithMany(u => u.ProxyGroups)
.OnDelete(DeleteBehavior.SetNull);
base.OnModelCreating(modelBuilder);
}
}
File: ApplicationDbContextFactory.cs
■using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace OpenBullet2.Core;
public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var dbContextBuilder = new DbContextOptionsBuilder();
dbContextBuilder.EnableSensitiveDataLogging(sensitiveLogging);
dbContextBuilder.UseSqlite(connectionString);
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Core" Version="1.6.0"/>
<PackageReference Include="MaxMind.GeoIP2" Version="5.2.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.6"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6"/>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0"/>
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RuriLib\RuriLib.csproj"/>
</ItemGroup>
</Project>
File: Entities/Entity.cs
■namespace OpenBullet2.Core.Entities;
/// <summary>
/// This is the base class for an entity that is saved to a database.
/// </summary>
public class Entity
{
public int Id { get; set; }
}
File: Entities/GuestEntity.cs
■using System;
using System.Collections.Generic;
namespace OpenBullet2.Core.Entities;
/// <summary>
/// This entity stores a guest user of OpenBullet 2.
/// </summary>
public class GuestEntity : Entity
{
/// <summary>
/// The username that the guest uses to log in.
/// </summary>
public string Username { get; set; }
/// <summary>
/// The bcrypt hash of the password of the guest.
/// </summary>
public string PasswordHash { get; set; }
/// <summary>
/// The time when access will expire for this guest.
/// </summary>
public DateTime AccessExpiration { get; set; }
/// <summary>
/// A comma-separated list of IPv4 or IPv6 addresses that the guest
/// is allowed to use when connecting to the remote instance of OpenBullet 2.
/// These can include masked IP ranges and static DNS.
/// </summary>
public string AllowedAddresses { get; set; }
/// <summary>
/// The proxy groups that the guest owns.
/// </summary>
public ICollection<ProxyGroupEntity> ProxyGroups { get; set; }
}
File: Entities/HitEntity.cs
■using System;
namespace OpenBullet2.Core.Entities;
/// <summary>
/// This entity stores a hit from a job in the database.
/// </summary>
public class HitEntity : Entity
{
/// <summary>
/// The data that was provided to the bot to get the hit.
/// </summary>
public string Data { get; set; } = string.Empty;
/// <summary>
/// The variables captured by the bot.
/// </summary>
public string CapturedData { get; set; } = string.Empty;
/// <summary>
/// The string representation of the proxy that was used to get the hit (blank if none).
/// </summary>
public string Proxy { get; set; } = string.Empty;
/// <summary>
/// The exact date when the hit was found.
/// </summary>
public DateTime Date { get; set; }
/// <summary>
/// The type of hit, for example SUCCESS, NONE, CUSTOM etc.
/// </summary>
public string Type { get; set; }
/// <summary>
/// The ID of the owner of this hit (0 if admin).
/// </summary>
public int OwnerId { get; set; } = 0;
/// <summary>
/// The ID of the config that was used to get the hit.
/// </summary>
public string ConfigId { get; set; } = null;
/// <summary>
/// The name of the config that was used to get the hit.
/// Needed to identify the name even if the config was deleted.
/// </summary>
public string ConfigName { get; set; } = string.Empty;
/// <summary>
/// The category of the config that was used to get the hit.
/// Needed to identify the category even if the config was deleted.
/// </summary>
public string ConfigCategory { get; set; } = string.Empty;
/// <summary>
/// The ID of the wordlist that was used to get the hit, -1 if no wordlist was used, < -1 for other data p
/// </summary>
public int WordlistId { get; set; } = -1;
/// <summary>
/// The name of the wordlist that was used to get the hit, blank if no wordlist was used.
/// Needed to identify the name even if the wordlist was deleted. If <see cref="WordlistId"/> is less th
/// this field contains information about the data pool that was used.
/// </summary>
public string WordlistName { get; set; } = string.Empty;
/// <summary>
/// Gets a unique hash of the hit.
/// </summary>
/// <param name="ignoreWordlistName">Whether the wordlist name should affect the generated ha
public int GetHashCode(bool ignoreWordlistName = true)
{
var id = ignoreWordlistName
? Data + ConfigName
: Data + ConfigName + WordlistName;
return id.GetHashCode();
}
}
File: Entities/JobEntity.cs
■using OpenBullet2.Core.Models.Jobs;
using System;
namespace OpenBullet2.Core.Entities;
/// <summary>
/// This entity stores a job of the OpenBullet 2 instance.
/// </summary>
public class JobEntity : Entity
{
/// <summary>
/// The creation date and time of the job.
/// </summary>
public DateTime CreationDate { get; set; }
/// <summary>
/// The type of job, used to know how to deserialize the options.
/// </summary>
public JobType JobType { get; set; }
/// <summary>
/// The job options as a json string.
/// </summary>
public string JobOptions { get; set; }
/// <summary>
/// The owner of this job. Null if admin.
/// </summary>
public GuestEntity Owner { get; set; }
}
File: Entities/ProxyEntity.cs
■using RuriLib.Models.Proxies;
using System;
using System.Text;
namespace OpenBullet2.Core.Entities;
/// <summary>
/// This entity stores a proxy that belongs to a proxy group.
/// </summary>
public class ProxyEntity : Entity
{
/// <summary>
/// The host on which the proxy server is running.
/// </summary>
public string Host { get; set; }
/// <summary>
/// The port on which the proxy server is listening.
/// </summary>
public int Port { get; set; }
/// <summary>
/// The protocol used by the proxy server to open a proxy tunnel.
/// </summary>
public ProxyType Type { get; set; }
/// <summary>
/// The username, if required by the proxy server.
/// </summary>
public string Username { get; set; }
/// <summary>
/// The password, if required by the proxy server.
/// </summary>
public string Password { get; set; }
/// <summary>
/// The country of the proxy, detected after checking it with a geolocalization service.
/// </summary>
public string Country { get; set; }
/// <summary>
/// The working status of the proxy.
/// </summary>
public ProxyWorkingStatus Status { get; set; }
/// <summary>
/// The ping of the proxy in milliseconds.
/// </summary>
public int Ping { get; set; }
/// <summary>
/// The last time the proxy was checked.
/// </summary>
public DateTime LastChecked { get; set; }
/// <summary>
/// The proxy group to which the proxy belongs to.
/// </summary>
public ProxyGroupEntity Group { get; set; }
/// <summary>
/// Returns a string representation of the proxy.
/// For example <code>(Socks5)192.168.1.1:8080:username:password</code>
/// </summary>
public override string ToString()
{
var sb = new StringBuilder();
if (Type != ProxyType.Http)
{
sb.Append($"({Type})");
}
sb.Append($"{Host}:{Port}");
if (!string.IsNullOrWhiteSpace(Username))
{
sb.Append($":{Username}:{Password}");
}
return sb.ToString();
}
}
File: Entities/ProxyGroupEntity.cs
■using System.Collections.Generic;
namespace OpenBullet2.Core.Entities;
/// <summary>
/// This entity stores a group that identifies a collection of proxies.
/// </summary>
public class ProxyGroupEntity : Entity
{
/// <summary>
/// The name of the group.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The owner of this group (null if admin).
/// </summary>
public GuestEntity Owner { get; set; }
/// <summary>
/// The proxies in this group.
/// </summary>
public ICollection<ProxyEntity> Proxies { get; set; }
}
File: Entities/RecordEntity.cs
■namespace OpenBullet2.Core.Entities;
/// <summary>
/// This entity stores a record that matches a given config ID and wordlist ID
/// to a checkpoint in the checking process, identified by the amount of data
/// lines processed up to the point when it was last saved.
/// </summary>
public class RecordEntity : Entity
{
/// <summary>
/// The ID of the config that was running.
/// </summary>
public string ConfigId { get; set; }
/// <summary>
/// The ID of the wordlist that was being used.
/// </summary>
public int WordlistId { get; set; }
/// <summary>
/// The amount of data lines processed until the last save.
/// </summary>
public int Checkpoint { get; set; }
}
File: Entities/WordlistEntity.cs
■namespace OpenBullet2.Core.Entities;
/// <summary>
/// This entity stores the metadata of a wordlist in OpenBullet 2.
/// </summary>
public class WordlistEntity : Entity
{
/// <summary>
/// The name of the wordlist.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The path to the file on disk that contains the lines of the wordlist.
/// </summary>
public string FileName { get; set; }
/// <summary>
/// The purpose of the wordlist.
/// </summary>
public string Purpose { get; set; }
/// <summary>
/// The total amount of lines of the wordlist, usually calculated during import.
/// </summary>
public int Total { get; set; }
/// <summary>
/// The Wordlist Type.
/// </summary>
public string Type { get; set; }
/// <summary>
/// The owner of the wordlist (null if admin).
/// </summary>
public GuestEntity Owner { get; set; }
}
File: Exceptions/EntityNotFoundException.cs
using System;
namespace OpenBullet2.Core.Exceptions;
/// <summary>
/// Represents an exception thrown when an entity is not found.
/// </summary>
public class EntityNotFoundException : Exception
{
/// <summary>
/// Creates a new <see cref="EntityNotFoundException"/> with a message.
/// </summary>
public EntityNotFoundException(string message) : base(message) { }
}
File: Exceptions/MissingUserAgentsException.cs
using System;
namespace OpenBullet2.Core.Exceptions;
/// <summary>
/// Thrown when no valid user agents are found in the user-agents.json file.
/// </summary>
public class MissingUserAgentsException : Exception
{
/// <summary>
/// Creates a new MissingUserAgentsException.
/// </summary>
public MissingUserAgentsException(string message) : base(message) { }
}
File: Exceptions/UnsupportedFileTypeException.cs
using System;
namespace OpenBullet2.Core.Exceptions;
/// <summary>
/// The exception that is thrown when a file type is not supported.
/// </summary>
public class UnsupportedFileTypeException : Exception
{
/// <summary>
/// Creates a new <see cref="UnsupportedFileTypeException"/> with a message.
/// </summary>
public UnsupportedFileTypeException(string message) : base(message) { }
}
File: Extensions/DbContextExtensions.cs
■using Microsoft.EntityFrameworkCore;
using OpenBullet2.Core.Entities;
using System.Linq;
namespace OpenBullet2.Core.Extensions;
namespace OpenBullet2.Core.Extensions;
if (ipAdressBytes.Length != subnetMaskBytes.Length)
{
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
}
if (ipAdressBytes.Length != subnetMaskBytes.Length)
{
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
}
public static bool IsInSameSubnet(this IPAddress address2, IPAddress address, IPAddress subnetMask)
{
var network1 = address.GetNetworkAddress(subnetMask);
var network2 = address2.GetNetworkAddress(subnetMask);
return network1.Equals(network2);
}
}
File: Extensions/StringExtensions.cs
■using System.Globalization;
using System.Text;
namespace OpenBullet2.Core.Extensions;
return CultureInfo.CurrentCulture.TextInfo
.ToTitleCase(sb.ToString().ToLower());
}
}
File: Extensions/TimespanExtensions.cs
■using System;
namespace OpenBullet2.Core.Extensions;
if (formatted.EndsWith(", "))
{
formatted = formatted.Substring(0, formatted.Length - 2);
}
if (string.IsNullOrEmpty(formatted))
{
formatted = "0 seconds";
}
return formatted;
}
}
File: Helpers/Firewall.cs
■using OpenBullet2.Core.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Helpers;
continue;
}
if (ip.IsInSameSubnet(toCompare, mask))
{
return true;
}
continue;
}
}
}
return false;
}
}
File: Helpers/ImageEditor.cs
■using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Processing;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace OpenBullet2.Core.Helpers;
return bytes;
}
image.Mutate(x => x
.Resize(width, height));
File: Helpers/Mapper.cs
■using OpenBullet2.Core.Entities;
using RuriLib.Models.Proxies;
namespace OpenBullet2.Core.Helpers;
// TODO: Refactor these methods, make it so that you can call Map<TInput,TOutput>
public static class Mapper
{
/// <summary>
/// Maps a <see cref="Proxy"/> to a <see cref="ProxyEntity"/>.
/// </summary>
public static ProxyEntity MapProxyToProxyEntity(Proxy proxy) => new()
{
Country = proxy.Country,
Host = proxy.Host,
Port = proxy.Port,
LastChecked = proxy.LastChecked,
Username = proxy.Username,
Password = proxy.Password,
Ping = proxy.Ping,
Status = proxy.WorkingStatus,
Type = proxy.Type
};
}
File: Helpers/RootUtils.cs
■namespace OpenBullet2.Core.Helpers;
This is due to the fact that configs can contain C# code that is not picked up by your antivirus.
This can lead to information leaks, malware, system takeover and more.
Please consider creating a user with limited privileges and running it from there.
";
}
File: Helpers/SubnetMask.cs
■using System;
using System.Net;
namespace OpenBullet2.Core.Helpers;
if (netPartLength < 2)
{
throw new ArgumentException("Number of hosts is too large for IPv4");
}
namespace OpenBullet2.Logging;
/// <summary>
/// An in-memory logger for job operations.
/// </summary>
public class MemoryJobLogger
{
private readonly Dictionary<int, List<JobLogEntry>> logs = new();
private readonly object locker = new();
private readonly OpenBulletSettings settings;
public event EventHandler<int> NewLog; // The integer is the id of the job for which a new log came
public void Log(int jobId, string message, LogKind kind = LogKind.Custom, string color = "white")
{
if (!settings.GeneralSettings.EnableJobLogging)
{
return;
}
lock (locker)
{
if (!logs.ContainsKey(jobId))
{
logs[jobId] = new List<JobLogEntry> { entry };
}
else
{
lock (logs[jobId])
{
logs[jobId].Add(entry);
NewLog?.Invoke(this, jobId);
}
public void LogInfo(int jobId, string message) => Log(jobId, message, LogKind.Info, "var(--fg-primary
public void LogSuccess(int jobId, string message) => Log(jobId, message, LogKind.Success, "var(--
public void LogWarning(int jobId, string message) => Log(jobId, message, LogKind.Warning, "var(--
public void LogError(int jobId, string message) => Log(jobId, message, LogKind.Error, "var(--fg-fail)"
logs[jobId].Clear();
}
}
File: Migrations/20201228145119_Initial.Designer.cs
■// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using OpenBullet2.Core;
namespace OpenBullet2.Core.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20201228145119_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.8");
modelBuilder.Entity("OpenBullet2.Core.Entities.GuestEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AccessExpiration")
.HasColumnType("TEXT");
b.Property<string>("AllowedAddresses")
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("Username")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Guests");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.HitEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CapturedData")
.HasColumnType("TEXT");
b.Property<string>("ConfigCategory")
.HasColumnType("TEXT");
b.Property<string>("ConfigId")
.HasColumnType("TEXT");
b.Property<string>("ConfigName")
.HasColumnType("TEXT");
b.Property<string>("Data")
.HasColumnType("TEXT");
b.Property<DateTime>("Date")
.HasColumnType("TEXT");
b.Property<int>("OwnerId")
.HasColumnType("INTEGER");
b.Property<string>("Proxy")
.HasColumnType("TEXT");
b.Property<string>("Type")
.HasColumnType("TEXT");
b.Property<int>("WordlistId")
.HasColumnType("INTEGER");
b.Property<string>("WordlistName")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Hits");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.JobEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreationDate")
.HasColumnType("TEXT");
b.Property<string>("JobOptions")
.HasColumnType("TEXT");
b.Property<int>("JobType")
.HasColumnType("INTEGER");
b.Property<int?>("OwnerId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Jobs");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Country")
.HasColumnType("TEXT");
b.Property<int?>("GroupId")
.HasColumnType("INTEGER");
b.Property<string>("Host")
.HasColumnType("TEXT");
b.Property<DateTime>("LastChecked")
.HasColumnType("TEXT");
b.Property<string>("Password")
.HasColumnType("TEXT");
b.Property<int>("Ping")
.HasColumnType("INTEGER");
b.Property<int>("Port")
.HasColumnType("INTEGER");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<string>("Username")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GroupId");
b.ToTable("Proxies");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyGroupEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int?>("OwnerId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("ProxyGroups");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.RecordEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Checkpoint")
.HasColumnType("INTEGER");
b.Property<string>("ConfigId")
.HasColumnType("TEXT");
b.Property<int>("WordlistId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Records");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.WordlistEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("FileName")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int?>("OwnerId")
.HasColumnType("INTEGER");
b.Property<string>("Purpose")
.HasColumnType("TEXT");
b.Property<int>("Total")
.HasColumnType("INTEGER");
b.Property<string>("Type")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Wordlists");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.JobEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.GuestEntity", "Owner")
.WithMany()
.HasForeignKey("OwnerId");
b.Navigation("Owner");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.ProxyGroupEntity", "Group")
.WithMany()
.HasForeignKey("GroupId");
b.Navigation("Group");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyGroupEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.GuestEntity", "Owner")
.WithMany()
.HasForeignKey("OwnerId");
b.Navigation("Owner");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.WordlistEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.GuestEntity", "Owner")
.WithMany()
.HasForeignKey("OwnerId");
b.Navigation("Owner");
});
#pragma warning restore 612, 618
}
}
}
File: Migrations/20201228145119_Initial.cs
■using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace OpenBullet2.Core.Migrations;
migrationBuilder.CreateTable(
name: "Hits",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Data = table.Column<string>(type: "TEXT", nullable: true),
CapturedData = table.Column<string>(type: "TEXT", nullable: true),
Proxy = table.Column<string>(type: "TEXT", nullable: true),
Date = table.Column<DateTime>(type: "TEXT", nullable: false),
Type = table.Column<string>(type: "TEXT", nullable: true),
OwnerId = table.Column<int>(type: "INTEGER", nullable: false),
ConfigId = table.Column<string>(type: "TEXT", nullable: true),
ConfigName = table.Column<string>(type: "TEXT", nullable: true),
ConfigCategory = table.Column<string>(type: "TEXT", nullable: true),
WordlistId = table.Column<int>(type: "INTEGER", nullable: false),
WordlistName = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Hits", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Records",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ConfigId = table.Column<string>(type: "TEXT", nullable: true),
WordlistId = table.Column<int>(type: "INTEGER", nullable: false),
Checkpoint = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Records", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Jobs",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
CreationDate = table.Column<DateTime>(type: "TEXT", nullable: false),
JobType = table.Column<int>(type: "INTEGER", nullable: false),
JobOptions = table.Column<string>(type: "TEXT", nullable: true),
OwnerId = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Jobs", x => x.Id);
table.ForeignKey(
name: "FK_Jobs_Guests_OwnerId",
column: x => x.OwnerId,
principalTable: "Guests",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "ProxyGroups",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: true),
OwnerId = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ProxyGroups", x => x.Id);
table.ForeignKey(
name: "FK_ProxyGroups_Guests_OwnerId",
column: x => x.OwnerId,
principalTable: "Guests",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Wordlists",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: true),
FileName = table.Column<string>(type: "TEXT", nullable: true),
Purpose = table.Column<string>(type: "TEXT", nullable: true),
Total = table.Column<int>(type: "INTEGER", nullable: false),
Type = table.Column<string>(type: "TEXT", nullable: true),
OwnerId = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Wordlists", x => x.Id);
table.ForeignKey(
name: "FK_Wordlists_Guests_OwnerId",
column: x => x.OwnerId,
principalTable: "Guests",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "Proxies",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Host = table.Column<string>(type: "TEXT", nullable: true),
Port = table.Column<int>(type: "INTEGER", nullable: false),
Type = table.Column<int>(type: "INTEGER", nullable: false),
Username = table.Column<string>(type: "TEXT", nullable: true),
Password = table.Column<string>(type: "TEXT", nullable: true),
Country = table.Column<string>(type: "TEXT", nullable: true),
Status = table.Column<int>(type: "INTEGER", nullable: false),
Ping = table.Column<int>(type: "INTEGER", nullable: false),
LastChecked = table.Column<DateTime>(type: "TEXT", nullable: false),
GroupId = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Proxies", x => x.Id);
table.ForeignKey(
name: "FK_Proxies_ProxyGroups_GroupId",
column: x => x.GroupId,
principalTable: "ProxyGroups",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Jobs_OwnerId",
table: "Jobs",
column: "OwnerId");
migrationBuilder.CreateIndex(
name: "IX_Proxies_GroupId",
table: "Proxies",
column: "GroupId");
migrationBuilder.CreateIndex(
name: "IX_ProxyGroups_OwnerId",
table: "ProxyGroups",
column: "OwnerId");
migrationBuilder.CreateIndex(
name: "IX_Wordlists_OwnerId",
table: "Wordlists",
column: "OwnerId");
}
migrationBuilder.DropTable(
name: "Jobs");
migrationBuilder.DropTable(
name: "Proxies");
migrationBuilder.DropTable(
name: "Records");
migrationBuilder.DropTable(
name: "Wordlists");
migrationBuilder.DropTable(
name: "ProxyGroups");
migrationBuilder.DropTable(
name: "Guests");
}
}
File: Migrations/20240220175839_ProxyGroupDeleteRelationships.Designer.cs
■// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using OpenBullet2.Core;
#nullable disable
namespace OpenBullet2.Core.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240220175839_ProxyGroupDeleteRelationships")]
partial class ProxyGroupDeleteRelationships
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
modelBuilder.Entity("OpenBullet2.Core.Entities.GuestEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AccessExpiration")
.HasColumnType("TEXT");
b.Property<string>("AllowedAddresses")
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("Username")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Guests");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.HitEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CapturedData")
.HasColumnType("TEXT");
b.Property<string>("ConfigCategory")
.HasColumnType("TEXT");
b.Property<string>("ConfigId")
.HasColumnType("TEXT");
b.Property<string>("ConfigName")
.HasColumnType("TEXT");
b.Property<string>("Data")
.HasColumnType("TEXT");
b.Property<DateTime>("Date")
.HasColumnType("TEXT");
b.Property<int>("OwnerId")
.HasColumnType("INTEGER");
b.Property<string>("Proxy")
.HasColumnType("TEXT");
b.Property<string>("Type")
.HasColumnType("TEXT");
b.Property<int>("WordlistId")
.HasColumnType("INTEGER");
b.Property<string>("WordlistName")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Hits");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.JobEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreationDate")
.HasColumnType("TEXT");
b.Property<string>("JobOptions")
.HasColumnType("TEXT");
b.Property<int>("JobType")
.HasColumnType("INTEGER");
b.Property<int?>("OwnerId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Jobs");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Country")
.HasColumnType("TEXT");
b.Property<int?>("GroupId")
.HasColumnType("INTEGER");
b.Property<string>("Host")
.HasColumnType("TEXT");
b.Property<DateTime>("LastChecked")
.HasColumnType("TEXT");
b.Property<string>("Password")
.HasColumnType("TEXT");
b.Property<int>("Ping")
.HasColumnType("INTEGER");
b.Property<int>("Port")
.HasColumnType("INTEGER");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<string>("Username")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GroupId");
b.ToTable("Proxies");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyGroupEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int?>("OwnerId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("ProxyGroups");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.RecordEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Checkpoint")
.HasColumnType("INTEGER");
b.Property<string>("ConfigId")
.HasColumnType("TEXT");
b.Property<int>("WordlistId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Records");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.WordlistEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("FileName")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int?>("OwnerId")
.HasColumnType("INTEGER");
b.Property<string>("Purpose")
.HasColumnType("TEXT");
b.Property<int>("Total")
.HasColumnType("INTEGER");
b.Property<string>("Type")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Wordlists");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.JobEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.GuestEntity", "Owner")
.WithMany()
.HasForeignKey("OwnerId");
b.Navigation("Owner");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.ProxyGroupEntity", "Group")
.WithMany("Proxies")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Group");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyGroupEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.GuestEntity", "Owner")
.WithMany("ProxyGroups")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Owner");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.WordlistEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.GuestEntity", "Owner")
.WithMany()
.HasForeignKey("OwnerId");
b.Navigation("Owner");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.GuestEntity", b =>
{
b.Navigation("ProxyGroups");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyGroupEntity", b =>
{
b.Navigation("Proxies");
});
#pragma warning restore 612, 618
}
}
}
File: Migrations/20240220175839_ProxyGroupDeleteRelationships.cs
■using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace OpenBullet2.Core.Migrations
{
public partial class ProxyGroupDeleteRelationships : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Proxies_ProxyGroups_GroupId",
table: "Proxies");
migrationBuilder.DropForeignKey(
name: "FK_ProxyGroups_Guests_OwnerId",
table: "ProxyGroups");
migrationBuilder.AddForeignKey(
name: "FK_Proxies_ProxyGroups_GroupId",
table: "Proxies",
column: "GroupId",
principalTable: "ProxyGroups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_ProxyGroups_Guests_OwnerId",
table: "ProxyGroups",
column: "OwnerId",
principalTable: "Guests",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
migrationBuilder.DropForeignKey(
name: "FK_ProxyGroups_Guests_OwnerId",
table: "ProxyGroups");
migrationBuilder.AddForeignKey(
name: "FK_Proxies_ProxyGroups_GroupId",
table: "Proxies",
column: "GroupId",
principalTable: "ProxyGroups",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_ProxyGroups_Guests_OwnerId",
table: "ProxyGroups",
column: "OwnerId",
principalTable: "Guests",
principalColumn: "Id");
}
}
}
File: Migrations/ApplicationDbContextModelSnapshot.cs
■// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using OpenBullet2.Core;
#nullable disable
namespace OpenBullet2.Core.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
modelBuilder.Entity("OpenBullet2.Core.Entities.GuestEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AccessExpiration")
.HasColumnType("TEXT");
b.Property<string>("AllowedAddresses")
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("Username")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Guests");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.HitEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CapturedData")
.HasColumnType("TEXT");
b.Property<string>("ConfigCategory")
.HasColumnType("TEXT");
b.Property<string>("ConfigId")
.HasColumnType("TEXT");
b.Property<string>("ConfigName")
.HasColumnType("TEXT");
b.Property<string>("Data")
.HasColumnType("TEXT");
b.Property<DateTime>("Date")
.HasColumnType("TEXT");
b.Property<int>("OwnerId")
.HasColumnType("INTEGER");
b.Property<string>("Proxy")
.HasColumnType("TEXT");
b.Property<string>("Type")
.HasColumnType("TEXT");
b.Property<int>("WordlistId")
.HasColumnType("INTEGER");
b.Property<string>("WordlistName")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Hits");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.JobEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreationDate")
.HasColumnType("TEXT");
b.Property<string>("JobOptions")
.HasColumnType("TEXT");
b.Property<int>("JobType")
.HasColumnType("INTEGER");
b.Property<int?>("OwnerId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Jobs");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Country")
.HasColumnType("TEXT");
b.Property<int?>("GroupId")
.HasColumnType("INTEGER");
b.Property<string>("Host")
.HasColumnType("TEXT");
b.Property<DateTime>("LastChecked")
.HasColumnType("TEXT");
b.Property<string>("Password")
.HasColumnType("TEXT");
b.Property<int>("Ping")
.HasColumnType("INTEGER");
b.Property<int>("Port")
.HasColumnType("INTEGER");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<string>("Username")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GroupId");
b.ToTable("Proxies");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyGroupEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int?>("OwnerId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("ProxyGroups");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.RecordEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Checkpoint")
.HasColumnType("INTEGER");
b.Property<string>("ConfigId")
.HasColumnType("TEXT");
b.Property<int>("WordlistId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Records");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.WordlistEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("FileName")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int?>("OwnerId")
.HasColumnType("INTEGER");
b.Property<string>("Purpose")
.HasColumnType("TEXT");
b.Property<int>("Total")
.HasColumnType("INTEGER");
b.Property<string>("Type")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Wordlists");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.JobEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.GuestEntity", "Owner")
.WithMany()
.HasForeignKey("OwnerId");
b.Navigation("Owner");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.ProxyGroupEntity", "Group")
.WithMany("Proxies")
.HasForeignKey("GroupId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Group");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyGroupEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.GuestEntity", "Owner")
.WithMany("ProxyGroups")
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Owner");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.WordlistEntity", b =>
{
b.HasOne("OpenBullet2.Core.Entities.GuestEntity", "Owner")
.WithMany()
.HasForeignKey("OwnerId");
b.Navigation("Owner");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.GuestEntity", b =>
{
b.Navigation("ProxyGroups");
});
modelBuilder.Entity("OpenBullet2.Core.Entities.ProxyGroupEntity", b =>
{
b.Navigation("Proxies");
});
#pragma warning restore 612, 618
}
}
}
File: Models/Data/CombinationsDataPoolOptions.cs
■using RuriLib.Models.Data.DataPools;
namespace OpenBullet2.Core.Models.Data;
/// <summary>
/// Options for a <see cref="CombinationsDataPool"/>.
/// </summary>
public class CombinationsDataPoolOptions : DataPoolOptions
{
/// <summary>
/// The possible characters that can be in a combination, one after the other without separators.
/// </summary>
public string CharSet { get; set; } = "0123456789";
/// <summary>
/// The length of the combinations to generate.
/// </summary>
public int Length { get; set; } = 4;
/// <summary>
/// The Wordlist Type.
/// </summary>
public string WordlistType { get; set; } = "Default";
}
File: Models/Data/DataPoolOptions.cs
■using RuriLib.Models.Data;
namespace OpenBullet2.Core.Models.Data;
/// <summary>
/// Base class for options of a <see cref="DataPool"/>.
/// </summary>
public abstract class DataPoolOptions
{
File: Models/Data/FileDataPoolOptions.cs
■using RuriLib.Models.Data.DataPools;
using System.Runtime.InteropServices;
namespace OpenBullet2.Core.Models.Data;
/// <summary>
/// Options for a <see cref="FileDataPool"/>.
/// </summary>
public class FileDataPoolOptions : DataPoolOptions
{
private string fileName = null;
/// <summary>
/// The path to the file on disk.
/// </summary>
public string FileName
{
get => fileName;
set
{
// Double quotes in file names are not allowed in Windows, but they are included
// at the start and end of the file path if you copy/paste it from some programs,
// so we need to remove them, otherwise it will not find the file.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Remove the double quotes from the file
fileName = value.Replace("\"", "");
}
else
{
fileName = value;
}
}
}
/// <summary>
/// The Wordlist Type.
/// </summary>
public string WordlistType { get; set; } = "Default";
}
File: Models/Data/InfiniteDataPoolOptions.cs
■using RuriLib.Models.Data.DataPools;
namespace OpenBullet2.Core.Models.Data;
/// <summary>
/// Options for an <see cref="InfiniteDataPool"/>.
/// </summary>
public class InfiniteDataPoolOptions : DataPoolOptions
{
/// <summary>
/// The Wordlist Type.
/// </summary>
public string WordlistType { get; set; } = "Default";
}
File: Models/Data/RangeDataPoolOptions.cs
■using RuriLib.Models.Data.DataPools;
namespace OpenBullet2.Core.Models.Data;
/// <summary>
/// Options for a <see cref="RangeDataPool"/>.
/// </summary>
public class RangeDataPoolOptions : DataPoolOptions
{
/// <summary>
/// The start of the range.
/// </summary>
public long Start { get; set; } = 0;
/// <summary>
/// The length of the range.
/// </summary>
public int Amount { get; set; } = 100;
/// <summary>
/// The entity of the interval between elements.
/// </summary>
public int Step { get; set; } = 1;
/// <summary>
/// Whether to pad numbers with zeroes basing on the number
/// of digits of the biggest number to generate.
/// </summary>
public bool Pad { get; set; } = false;
/// <summary>
/// The Wordlist Type.
/// </summary>
public string WordlistType { get; set; } = "Default";
}
File: Models/Data/WordlistDataPoolOptions.cs
■using RuriLib.Models.Data.DataPools;
namespace OpenBullet2.Core.Models.Data;
/// <summary>
/// Options for a <see cref="WordlistDataPool"/>.
/// </summary>
public class WordlistDataPoolOptions : DataPoolOptions
{
/// <summary>
/// The ID of the Wordlist in the repository.
/// </summary>
public int WordlistId { get; set; } = -1;
}
File: Models/Data/WordlistFactory.cs
■using OpenBullet2.Core.Entities;
using RuriLib.Exceptions;
using RuriLib.Models.Data;
using RuriLib.Services;
using System.Linq;
namespace OpenBullet2.Core.Models.Data;
/// <summary>
/// A factory that creates a <see cref="Wordlist"/> from a <see cref="WordlistEntity"/>.
/// </summary>
public class WordlistFactory
{
private readonly RuriLibSettingsService ruriLibSettings;
/// <summary>
/// Creates a <see cref="Wordlist"/> from a <see cref="WordlistEntity"/>.
/// </summary>
public Wordlist FromEntity(WordlistEntity entity)
{
var wordlistType = ruriLibSettings.Environment.WordlistTypes
.FirstOrDefault(w => w.Name == entity.Type);
if (wordlistType == null)
{
throw new InvalidWordlistTypeException(entity.Type);
}
return wordlist;
}
}
File: Models/Hits/CustomWebhookHitOutputOptions.cs
■using RuriLib.Models.Hits.HitOutputs;
namespace OpenBullet2.Core.Models.Hits;
/// <summary>
/// Options for a <see cref="CustomWebhookHitOutput"/>.
/// </summary>
public class CustomWebhookHitOutputOptions : HitOutputOptions
{
/// <summary>
/// The URL of the remote webhook.
/// </summary>
public string Url { get; set; } = "http://mycustomwebhook.com";
/// <summary>
/// The username to send inside the body of the data, to identify who
/// sent the data to the webhook.
/// </summary>
public string User { get; set; } = "Anonymous";
/// <summary>
/// Whether to only send proper hits (SUCCESS status) to the webhook.
/// </summary>
public bool OnlyHits { get; set; } = true;
}
File: Models/Hits/DatabaseHitOutput.cs
■using OpenBullet2.Core.Services;
using RuriLib.Models.Hits;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Models.Hits;
/// <summary>
/// A hit output that stores hits to a database.
/// </summary>
public class DatabaseHitOutput : IHitOutput
{
private readonly HitStorageService hitStorage;
/// <inheritdoc/>
public Task Store(Hit hit)
=> hitStorage.StoreAsync(hit);
}
File: Models/Hits/DatabaseHitOutputOptions.cs
■namespace OpenBullet2.Core.Models.Hits;
/// <summary>
/// Options for a <see cref="DatabaseHitOutput"/>.
/// </summary>
public class DatabaseHitOutputOptions : HitOutputOptions
{
File: Models/Hits/DiscordWebhookHitOutputOptions.cs
■using RuriLib.Models.Hits.HitOutputs;
namespace OpenBullet2.Core.Models.Hits;
/// <summary>
/// Options for a <see cref="DiscordWebhookHitOutput"/>.
/// </summary>
public class DiscordWebhookHitOutputOptions : HitOutputOptions
{
/// <summary>
/// The URL of the webhook.
/// </summary>
public string Webhook { get; set; } = string.Empty;
/// <summary>
/// The username to use when sending the message.
/// </summary>
public string Username { get; set; } = string.Empty;
/// <summary>
/// The URL of the avatar picture to use when sending the message.
/// </summary>
public string AvatarUrl { get; set; } = string.Empty;
/// <summary>
/// Whether to only send proper hits (SUCCESS status) to the webhook.
/// </summary>
public bool OnlyHits { get; set; } = true;
}
File: Models/Hits/FileSystemHitOutputOptions.cs
■using RuriLib.Models.Hits.HitOutputs;
namespace OpenBullet2.Core.Models.Hits;
/// <summary>
/// Options for a <see cref="FileSystemHitOutput"/>.
/// </summary>
public class FileSystemHitOutputOptions : HitOutputOptions
{
/// <summary>
/// The parent directory inside which the text files will be created.
/// </summary>
public string BaseDir { get; set; } = "Hits";
}
File: Models/Hits/HitOutputFactory.cs
■using OpenBullet2.Core.Services;
using RuriLib.Models.Hits;
using RuriLib.Models.Hits.HitOutputs;
using System;
namespace OpenBullet2.Core.Models.Hits;
/// <summary>
/// A factory that creates an <see cref="IHitOutput"/> from <see cref="HitOutputOptions"/>.
/// </summary>
public class HitOutputFactory
{
private readonly HitStorageService hitStorage;
/// <summary>
/// Creates an <see cref="IHitOutput"/> from <see cref="HitOutputOptions"/>.
/// </summary>
public IHitOutput FromOptions(HitOutputOptions options)
{
IHitOutput output = options switch
{
DatabaseHitOutputOptions _ => new DatabaseHitOutput(hitStorage),
FileSystemHitOutputOptions x => new FileSystemHitOutput(x.BaseDir),
DiscordWebhookHitOutputOptions x => new DiscordWebhookHitOutput(x.Webhook, x.Username, x.AvatarUrl),
TelegramBotHitOutputOptions x => new TelegramBotHitOutput(x.Token, x.ChatId),
CustomWebhookHitOutputOptions x => new CustomWebhookHitOutput(x.Url, x.User),
_ => throw new NotImplementedException()
};
return output;
}
}
File: Models/Hits/HitOutputOptions.cs
■using RuriLib.Models.Hits;
namespace OpenBullet2.Core.Models.Hits;
/// <summary>
/// Base class for options of an <see cref="IHitOutput"/>.
/// </summary>
public abstract class HitOutputOptions
{
}
File: Models/Hits/TelegramBotHitOutputOptions.cs
■using RuriLib.Models.Hits.HitOutputs;
namespace OpenBullet2.Core.Models.Hits;
/// <summary>
/// Options for a <see cref="TelegramBotHitOutput"/>.
/// </summary>
public class TelegramBotHitOutputOptions : HitOutputOptions
{
/// <summary>
/// The authentication token.
/// </summary>
public string Token { get; set; } = string.Empty;
/// <summary>
/// The ID of the telegram chat.
/// </summary>
public long ChatId { get; set; } = 0;
/// <summary>
/// Whether to only send proper hits (SUCCESS status) to the webhook.
/// </summary>
public bool OnlyHits { get; set; } = true;
}
File: Models/Jobs/JobOptions.cs
■using RuriLib.Models.Jobs;
using RuriLib.Models.Jobs.StartConditions;
namespace OpenBullet2.Core.Models.Jobs;
/// <summary>
/// Base class for options of a <see cref="Job"/>.
/// </summary>
public abstract class JobOptions
{
/// <summary>
/// The condition that needs to be verified in order to start the job.
/// </summary>
public StartCondition StartCondition { get; set; } = new RelativeTimeStartCondition();
/// <summary>
/// The name of the job.
/// </summary>
public string Name { get; set; } = string.Empty;
}
File: Models/Jobs/JobOptionsFactory.cs
■using OpenBullet2.Core.Models.Hits;
using OpenBullet2.Core.Models.Proxies;
using RuriLib.Helpers;
using RuriLib.Models.Jobs.StartConditions;
using System;
using System.Collections.Generic;
namespace OpenBullet2.Core.Models.Jobs;
/// <summary>
/// A factory that creates a <see cref="JobOptions"/> object with default values.
/// </summary>
public class JobOptionsFactory
{
/// <summary>
/// Creates a new <see cref="JobOptions"/> object according to the provided <paramref name="type"/>.
/// </summary>
public static JobOptions CreateNew(JobType type)
{
JobOptions options = type switch
{
JobType.MultiRun => MakeMultiRun(),
JobType.ProxyCheck => MakeProxyCheck(),
_ => throw new NotImplementedException()
};
/// <summary>
/// A wrapper around <see cref="JobOptions"/> for json serialization
/// when saving it to the database.
/// </summary>
public class JobOptionsWrapper
{
public JobOptions Options { get; set; }
}
File: Models/Jobs/JobType.cs
■namespace OpenBullet2.Core.Models.Jobs;
/// <summary>
/// The available job types.
/// </summary>
public enum JobType
{
/// <summary>
/// Used to run a config using multiple bots.
/// </summary>
MultiRun,
/// <summary>
/// Used to check proxies.
/// </summary>
ProxyCheck,
Spider,
Ripper,
PuppeteerUnitTest
}
File: Models/Jobs/MultiRunJobOptions.cs
■using OpenBullet2.Core.Models.Data;
using OpenBullet2.Core.Models.Hits;
using OpenBullet2.Core.Models.Proxies;
using RuriLib.Models.Jobs;
using RuriLib.Models.Proxies;
using System.Collections.Generic;
namespace OpenBullet2.Core.Models.Jobs;
/// <summary>
/// Options for a <see cref="MultiRunJob"/>.
/// </summary>
public class MultiRunJobOptions : JobOptions
{
/// <summary>
/// The ID of the config to use.
/// </summary>
public string ConfigId { get; set; }
/// <summary>
/// The amount of bots that will process the data lines concurrently.
/// </summary>
public int Bots { get; set; } = 1;
/// <summary>
/// The amount of data lines to skip from the start of the data pool.
/// </summary>
public int Skip { get; set; } = 0;
/// <summary>
/// The proxy mode.
/// </summary>
public JobProxyMode ProxyMode { get; set; } = JobProxyMode.Default;
/// <summary>
/// Whether to shuffle the proxies in the pool before starting the job.
/// </summary>
public bool ShuffleProxies { get; set; } = true;
/// <summary>
/// The behaviour that should be applied when no more valid proxies are present in the pool.
/// </summary>
public NoValidProxyBehaviour NoValidProxyBehaviour { get; set; } = NoValidProxyBehaviour.Reloa
/// <summary>
/// How long should proxies be banned for. ONLY use this when <see cref="NoValidProxyBehaviour
/// is set to <see cref="NoValidProxyBehaviour.Unban"/>.
/// </summary>
public int ProxyBanTimeSeconds { get; set; } = 0;
/// <summary>
/// Whether to mark the data lines that are currently being processed as To Check when the job
/// is aborted, in order to know which items weren't properly checked.
/// </summary>
public bool MarkAsToCheckOnAbort { get; set; } = false;
/// <summary>
/// Whether to never ban proxies in any case. Use this for rotating proxy services.
/// </summary>
public bool NeverBanProxies { get; set; } = false;
/// <summary>
/// Whether to allow multiple bots to use the same proxy. Use this for rotating proxy services.
/// </summary>
public bool ConcurrentProxyMode { get; set; } = false;
/// <summary>
/// The amount of seconds that the pool will wait before reloading all proxies from the sources (perio
/// Set it to 0 to disable this behaviour and only allow the pool to reload proxies when all are banned
/// to the value of <see cref="NoValidProxyBehaviour"/>.
/// </summary>
public int PeriodicReloadIntervalSeconds { get; set; } = 0;
/// <summary>
/// The options for the data pool that provides data lines to the job.
/// </summary>
public DataPoolOptions DataPool { get; set; } = new WordlistDataPoolOptions();
/// <summary>
/// The options for the proxy sources that will be used to fill the proxy pool whenever it requests a rel
/// </summary>
public List<ProxySourceOptions> ProxySources { get; set; } = new List<ProxySourceOptions>();
/// <summary>
/// The options for the outputs where hits will be stored.
/// </summary>
public List<HitOutputOptions> HitOutputs { get; set; } = new List<HitOutputOptions>();
}
File: Models/Jobs/ProxyCheckJobOptions.cs
■using OpenBullet2.Core.Models.Proxies;
using OpenBullet2.Core.Models.Settings;
using RuriLib.Models.Jobs;
namespace OpenBullet2.Core.Models.Jobs;
/// <summary>
/// Options for a <see cref="ProxyCheckJob"/>.
/// </summary>
public class ProxyCheckJobOptions : JobOptions
{
/// <summary>
/// The amount of bots that will check the proxies concurrently.
/// </summary>
public int Bots { get; set; } = 1;
/// <summary>
/// The ID of the proxy group to check.
/// </summary>
public int GroupId { get; set; } = -1;
/// <summary>
/// Whether to only check the proxies that were never been tested.
/// </summary>
public bool CheckOnlyUntested { get; set; } = true;
/// <summary>
/// The target site against which proxies should be checked.
/// </summary>
public ProxyCheckTarget Target { get; set; } = null;
/// <summary>
/// The maximum timeout that a valid proxy should have, in milliseconds.
/// </summary>
public int TimeoutMilliseconds { get; set; } = 10000;
/// <summary>
/// The options for the output of a proxy check.
/// </summary>
public ProxyCheckOutputOptions CheckOutput { get; set; }
}
File: Models/Proxies/DBIPProxyGeolocationProvider.cs
■using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using MaxMind.GeoIP2;
using RuriLib.Models.Proxies;
namespace OpenBullet2.Core.Models.Proxies;
/// <summary>
/// A provider that uses the free database from https://www.maxmind.com/ to geolocate proxies by IP.
/// </summary>
public class DBIPProxyGeolocationProvider : IProxyGeolocationProvider, IDisposable
{
private readonly DatabaseReader reader;
/// <inheritdoc/>
public async Task<string> GeolocateAsync(string host)
{
if (!IPAddress.TryParse(host, out var _))
{
var addresses = await Dns.GetHostAddressesAsync(host);
if (addresses.Length > 0)
{
host = addresses.First().MapToIPv4().ToString();
}
}
return reader.Country(host).Country.Name;
}
namespace OpenBullet2.Core.Models.Proxies;
/// <summary>
/// A proxy check output that writes proxies to an <see cref="IProxyRepository"/>.
/// </summary>
public class DatabaseProxyCheckOutput : IProxyCheckOutput, IDisposable
{
private readonly IServiceScope _scope;
private readonly IProxyRepository _proxyRepo;
private readonly SemaphoreSlim _semaphore;
/// <inheritdoc/>
public async Task StoreAsync(Proxy proxy)
{
try
{
var entity = await _proxyRepo.GetAsync(proxy.Id);
entity.Country = proxy.Country;
entity.LastChecked = proxy.LastChecked;
entity.Ping = proxy.Ping;
entity.Status = proxy.WorkingStatus;
try
{
await _proxyRepo.UpdateAsync(entity);
}
finally
{
_semaphore.Release();
}
}
catch (Exception ex)
{
/*
* If we are here it means a few possible things
* - we deleted the job but the parallelizer was still running
* - the original proxy was deleted (e.g. from the proxy tab)
* - the scope was disposed for some reason
*
* In any case we don't want to save anything to the database.
*/
/// <summary>
/// Options for a <see cref="DatabaseProxyCheckOutput"/>.
/// </summary>
public class DatabaseProxyCheckOutputOptions : ProxyCheckOutputOptions
{
File: Models/Proxies/FileProxySourceOptions.cs
■using RuriLib.Models.Proxies;
using RuriLib.Models.Proxies.ProxySources;
namespace OpenBullet2.Core.Models.Proxies;
/// <summary>
/// Options for a <see cref="FileProxySource"/>
/// </summary>
public class FileProxySourceOptions : ProxySourceOptions
{
/// <summary>
/// The path to the file where proxies are stored in a UTF-8 text format, one per line,
/// in a format that is supported by OB2.
/// </summary>
public string FileName { get; set; } = string.Empty;
/// <summary>
/// The default proxy type when not specified by the format of the proxy.
/// </summary>
public ProxyType DefaultType { get; set; } = ProxyType.Http;
}
File: Models/Proxies/GroupProxySourceOptions.cs
■using OpenBullet2.Core.Models.Proxies.Sources;
using OpenBullet2.Core.Repositories;
namespace OpenBullet2.Core.Models.Proxies;
/// <summary>
/// Options for a <see cref="GroupProxySource"/>
/// </summary>
public class GroupProxySourceOptions : ProxySourceOptions
{
/// <summary>
/// The ID of the proxy group, as stored in the <see cref="IProxyGroupRepository"/>.
/// </summary>
public int GroupId { get; set; } = -1;
}
File: Models/Proxies/ProxyCheckOutputFactory.cs
■using RuriLib.Models.Proxies;
using System;
using Microsoft.Extensions.DependencyInjection;
namespace OpenBullet2.Core.Models.Proxies;
/// <summary>
/// Factory that creates a <see cref="IProxyCheckOutput"/> from the <see cref="ProxyCheckOutputOptions"/>.
/// </summary>
public class ProxyCheckOutputFactory
{
private readonly IServiceScopeFactory _scopeFactory;
/// <summary></summary>
public ProxyCheckOutputFactory(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
/// <summary>
/// Creates a <see cref="IProxyCheckOutput"/> from the <see cref="ProxyCheckOutputOptions"/>.
/// </summary>
public IProxyCheckOutput FromOptions(ProxyCheckOutputOptions options)
{
IProxyCheckOutput output = options switch
{
DatabaseProxyCheckOutputOptions _ => new DatabaseProxyCheckOutput(_scopeFactory),
_ => throw new NotImplementedException()
};
return output;
}
}
File: Models/Proxies/ProxyCheckOutputOptions.cs
■using RuriLib.Models.Proxies;
namespace OpenBullet2.Core.Models.Proxies;
/// <summary>
/// Base class for options of an <see cref="IProxyCheckOutput"/>.
/// </summary>
public abstract class ProxyCheckOutputOptions
{
}
File: Models/Proxies/ProxyFactory.cs
■using OpenBullet2.Core.Entities;
using RuriLib.Models.Proxies;
namespace OpenBullet2.Core.Models.Proxies;
/// <summary>
/// Factory that creates a <see cref="Proxy"/> from a <see cref="ProxyEntity"/>.
/// </summary>
public class ProxyFactory
{
/// <summary>
/// Creates a <see cref="Proxy"/> from a <see cref="ProxyEntity"/>.
/// </summary>
public static Proxy FromEntity(ProxyEntity entity)
=> new(entity.Host, entity.Port, entity.Type, entity.Username, entity.Password)
{
Id = entity.Id,
Country = entity.Country,
WorkingStatus = entity.Status,
LastChecked = entity.LastChecked,
Ping = entity.Ping
};
}
File: Models/Proxies/ProxySourceOptions.cs
■using RuriLib.Models.Proxies;
namespace OpenBullet2.Core.Models.Proxies;
/// <summary>
/// Base class for the options of a <see cref="ProxySource"/>
/// </summary>
public abstract class ProxySourceOptions
{
File: Models/Proxies/RemoteProxySourceOptions.cs
■using RuriLib.Models.Proxies;
using RuriLib.Models.Proxies.ProxySources;
namespace OpenBullet2.Core.Models.Proxies;
/// <summary>
/// Options for a <see cref="RemoteProxySource"/>
/// </summary>
public class RemoteProxySourceOptions : ProxySourceOptions
{
/// <summary>
/// The URL to query in order to retrieve the proxies.
/// The API should return a text-based response with one proxy per line, in a format supported by OB
/// </summary>
public string Url { get; set; } = string.Empty;
/// <summary>
/// The default proxy type when not specified by the format of the proxy.
/// </summary>
public ProxyType DefaultType { get; set; } = ProxyType.Http;
}
File: Models/Proxies/Sources/GroupProxySource.cs
■using OpenBullet2.Core.Repositories;
using OpenBullet2.Core.Services;
using RuriLib.Models.Proxies;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Models.Proxies.Sources;
/// <summary>
/// A proxy source that gets proxies from a group of a <see cref="IProxyGroupRepository"/>.
/// </summary>
public class GroupProxySource : ProxySource, IDisposable
{
private readonly ProxyReloadService reloadService;
/// <summary>
/// The ID of the group in the <see cref="IProxyGroupRepository"/>.
/// </summary>
public int GroupId { get; set; }
/// <inheritdoc/>
public async override Task<IEnumerable<Proxy>> GetAllAsync(CancellationToken cancellationToken = default)
=> await reloadService.ReloadAsync(GroupId, UserId, cancellationToken).ConfigureAwait(false);
File: Models/Settings/CustomizationSettings.cs
■namespace OpenBullet2.Core.Models.Settings;
/// <summary>
/// Settings related to the appearance of the OpenBullet2 GUI.
/// </summary>
public class CustomizationSettings
{
/// <summary>
/// The theme to use. Themes are included in separate files and identified
/// by their name. Web UI only.
/// </summary>
public string Theme { get; set; } = "Default";
/// <summary>
/// The theme to use for the Monaco editor. Web UI only.
/// </summary>
public string MonacoTheme { get; set; } = "vs-dark";
/// <summary>
/// Whether to wrap words at viewport width.
/// </summary>
public bool WordWrap { get; set; } = false;
/// <summary>
/// The main background color. Native UI only.
/// </summary>
public string BackgroundMain { get; set; } = "#222";
/// <summary>
/// The background color for inputs. Native UI only.
/// </summary>
public string BackgroundInput { get; set; } = "#282828";
/// <summary>
/// The secondary background color. Native UI only.
/// </summary>
public string BackgroundSecondary { get; set; } = "#111";
/// <summary>
/// The main foreground color. Native UI only.
/// </summary>
public string ForegroundMain { get; set; } = "#DCDCDC";
/// <summary>
/// The foreground color for inputs. Native UI only.
/// </summary>
public string ForegroundInput { get; set; } = "#DCDCDC";
/// <summary>
/// The foreground color for hits. Native UI only.
/// </summary>
public string ForegroundGood { get; set; } = "#ADFF2F";
/// <summary>
/// The foreground color for fails. Native UI only.
/// </summary>
public string ForegroundBad { get; set; } = "#FF6347";
/// <summary>
/// The foreground color for custom hits. Native UI only.
/// </summary>
public string ForegroundCustom { get; set; } = "#FF8C00";
/// <summary>
/// The foreground color for retries. Native UI only.
/// </summary>
public string ForegroundRetry { get; set; } = "#FFFF00";
/// <summary>
/// The foreground color for bans. Native UI only.
/// </summary>
public string ForegroundBanned { get; set; } = "#DDA0DD";
/// <summary>
/// The foreground color for hits to check. Native UI only.
/// </summary>
public string ForegroundToCheck { get; set; } = "#7FFFD4";
/// <summary>
/// The foreground color for selected menu items. Native UI only.
/// </summary>
public string ForegroundMenuSelected { get; set; } = "#1E90FF";
/// <summary>
/// The color of success buttons. Native UI only.
/// </summary>
public string SuccessButton { get; set; } = "#2f5738";
/// <summary>
/// The color of primary buttons. Native UI only.
/// </summary>
public string PrimaryButton { get; set; } = "#3b3a63";
/// <summary>
/// The color of warning buttons. Native UI only.
/// </summary>
public string WarningButton { get; set; } = "#7a552a";
/// <summary>
/// The color of danger buttons. Native UI only.
/// </summary>
public string DangerButton { get; set; } = "#693838";
/// <summary>
/// The foreground color of buttons. Native UI only.
/// </summary>
public string ForegroundButton { get; set; } = "#DCDCDC";
/// <summary>
/// The background color of buttons. Native UI only.
/// </summary>
public string BackgroundButton { get; set; } = "#282828";
/// <summary>
/// The path to the background image. Native UI only.
/// </summary>
public string BackgroundImagePath { get; set; } = "";
/// <summary>
/// The opacity of the background image (from 0 to 100). Native UI only.
/// </summary>
public double BackgroundOpacity { get; set; } = 100;
/// <summary>
/// Whether to play a sound when a hit is found.
/// </summary>
public bool PlaySoundOnHit { get; set; } = false;
}
File: Models/Settings/GeneralSettings.cs
■using System.Collections.Generic;
namespace OpenBullet2.Core.Models.Settings;
/// <summary>
/// The available sections in which every part of a config can be edited.
/// </summary>
public enum ConfigSection
{
Metadata,
Readme,
Stacker,
LoliCode,
Settings,
CSharpCode,
LoliScript
}
/// <summary>
/// The level of detail when displaying information about a job.
/// </summary>
public enum JobDisplayMode
{
Standard = 0,
Detailed = 1
}
/// <summary>
/// A target to be used as proxy check.
/// </summary>
public class ProxyCheckTarget
{
/// <summary>
/// The URL of the website that the proxy will send a GET query to.
/// </summary>
public string Url { get; set; }
/// <summary>
/// A keyword that must be present in the HTTP response body in order
/// to mark the proxy as working. Case sensitive.
/// </summary>
public string SuccessKey { get; set; }
/// <summary>
/// A custom LoliCode snippet for editor autocompletion.
/// </summary>
public class CustomSnippet
{
/// <summary>
/// The name of the snippet which will need to be typed (at least partially) to get the suggestion.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The body of the snippet which will be inserted by the editor.
/// </summary>
public string Body { get; set; } = "The body of your snippet";
/// <summary>
/// The description of what the snippet does.
/// </summary>
public string Description { get; set; }
}
/// <summary>
/// General settings of OpenBullet 2.
/// </summary>
public class GeneralSettings
{
/// <summary>
/// Which page to navigate to on config load.
/// </summary>
public ConfigSection ConfigSectionOnLoad { get; set; } = ConfigSection.Stacker;
/// <summary>
/// Whether to automatically set the recommended amount of bots specified by a config
/// when selecting a config in a job.
/// </summary>
public bool AutoSetRecommendedBots { get; set; } = true;
/// <summary>
/// Whether to output a warning upon quitting or loading a new config when
/// the previous one was edited but not saved.
/// </summary>
public bool WarnConfigNotSaved { get; set; } = true;
/// <summary>
/// The default author to use when creating new configs.
/// </summary>
public string DefaultAuthor { get; set; } = "Anonymous";
/// <summary>
/// Whether to display the job log in the interface.
/// </summary>
public bool EnableJobLogging { get; set; } = false;
/// <summary>
/// The maximum amount of log entries that are saved in memory for each job.
/// </summary>
public int LogBufferSize { get; set; } = 30;
/// <summary>
/// Whether to ignore the wordlist name when removing duplicate hits (so that similar hits
/// obtained using different wordlists are treated as duplicate).
/// </summary>
public bool IgnoreWordlistNameOnHitsDedupe { get; set; } = false;
/// <summary>
/// The available targets that can be used to check proxies.
/// </summary>
public List<ProxyCheckTarget> ProxyCheckTargets { get; set; }
/// <summary>
/// The default display mode for job information.
/// </summary>
public JobDisplayMode DefaultJobDisplayMode { get; set; } = JobDisplayMode.Standard;
/// <summary>
/// The refresh interval for periodically displaying a job's progress and information
/// (in milliseconds).
/// </summary>
public int JobUpdateInterval { get; set; } = 1000;
/// <summary>
/// The refresh interval for periodically displaying all jobs' progress and information
/// in the job manager page (in milliseconds).
/// </summary>
public int JobManagerUpdateInterval { get; set; } = 1000;
/// <summary>
/// Whether to group captured variables together in the variables log of the debugger.
/// </summary>
public bool GroupCapturesInDebugger { get; set; } = false;
/// <summary>
/// The localization culture for the UI.
/// </summary>
public string Culture { get; set; } = "en";
/// <summary>
/// Custom user-defined snippets for editor autocompletion.
/// </summary>
public List<CustomSnippet> CustomSnippets { get; set; } = new();
}
File: Models/Settings/OpenBulletSettings.cs
■namespace OpenBullet2.Core.Models.Settings;
/// <summary>
/// Settings for the OpenBullet 2 application.
/// </summary>
public class OpenBulletSettings
{
/// <summary>
/// General settings.
/// </summary>
public GeneralSettings GeneralSettings { get; set; } = new();
/// <summary>
/// Settings related to remote repositories.
/// </summary>
public RemoteSettings RemoteSettings { get; set; } = new();
/// <summary>
/// Settings related to security.
/// </summary>
public SecuritySettings SecuritySettings { get; set; } = new();
/// <summary>
/// Settings related to the appearance of the UI.
/// </summary>
public CustomizationSettings CustomizationSettings { get; set; } = new();
}
File: Models/Settings/RemoteSettings.cs
■using System.Collections.Generic;
namespace OpenBullet2.Core.Models.Settings;
/// <summary>
/// A remote endpoint that hosts configs.
/// </summary>
public class RemoteConfigsEndpoint
{
/// <summary>
/// The URL of the endpoint.
/// </summary>
public string Url { get; set; } = "http://x.x.x.x:5000/api/shared/configs/ENDPOINT_NAME";
/// <summary>
/// The API key to use to access the endpoint.
/// </summary>
public string ApiKey { get; set; } = "MY_API_KEY";
}
/// <summary>
/// Settings related to remote endpoints.
/// </summary>
public class RemoteSettings
{
/// <summary>
/// Remote endpoints from which configs will be fetched by the config manager
/// upon reload.
/// </summary>
public List<RemoteConfigsEndpoint> ConfigsEndpoints { get; set; } = new();
}
File: Models/Settings/SecuritySettings.cs
■using System.Security.Cryptography;
namespace OpenBullet2.Core.Models.Settings;
/// <summary>
/// Settings related to security.
/// </summary>
public class SecuritySettings
{
/// <summary>
/// Whether to allow OpenBullet2 (mainly blocks and file system viewer) to access
/// the whole system or only the UserData folder and its subfolders.
/// </summary>
public bool AllowSystemWideFileAccess { get; set; } = false;
/// <summary>
/// Whether to require admin login when accessing the UI. Use this when exposing
/// an OpenBullet 2 instance on the unprotected internet.
/// </summary>
public bool RequireAdminLogin { get; set; } = false;
/// <summary>
/// The username for the admin user.
/// </summary>
public string AdminUsername { get; set; } = "admin";
/// <summary>
/// The bcrypt hash of the admin user's password.
/// </summary>
public string AdminPasswordHash { get; set; }
/// <summary>
/// The API key that the admin can use to authenticate to the API.
/// If empty, the admin will not be able to use the API.
/// </summary>
public string AdminApiKey { get; set; } = string.Empty;
/// <summary>
/// The JWT key that this application will use when issuing authentication tokens.
/// For security reasons this should be randomly generated via the <see cref="GenerateJwtKey"/> method.
/// </summary>
public byte[] JwtKey { get; set; }
/// <summary>
/// The number of hours that the admin session will last before requiring another login.
/// </summary>
public int AdminSessionLifetimeHours { get; set; } = 24;
/// <summary>
/// The number of hours that the guest session will last before requiring another login.
/// </summary>
public int GuestSessionLifetimeHours { get; set; } = 24;
/// <summary>
/// Whether to use HTTPS redirection when the application is accessed via HTTP.
/// </summary>
public bool HttpsRedirect { get; set; } = false;
/// <summary>
/// Generates a random JWT key to use in order to sign JWT tokens issued by the application
/// to both the admin and guests.
/// </summary>
public SecuritySettings GenerateJwtKey()
{
JwtKey = RandomNumberGenerator.GetBytes(64);
return this;
}
/// <summary>
/// Sets a new admin password.
/// </summary>
public SecuritySettings SetupAdminPassword(string password)
{
AdminPasswordHash = BCrypt.Net.BCrypt.HashPassword(password);
return this;
}
}
File: Models/Sharing/Endpoint.cs
■using System.Collections.Generic;
namespace OpenBullet2.Core.Models.Sharing;
/// <summary>
/// A sharing endpoint that will be used to share configs with other
/// OpenBullet 2 instances.
/// </summary>
public class Endpoint
{
/// <summary>
/// The route for the endpoint.
/// </summary>
public string Route { get; set; } = "configs";
/// <summary>
/// The API keys that are allowed to access the endpoint. When requesting configs
/// from this endpoint, users will send their API key inside the HTTP request.
/// </summary>
public List<string> ApiKeys { get; set; } = new();
/// <summary>
/// The IDs of the configs that will be delivered by the server to the clients.
/// </summary>
public List<string> ConfigIds { get; set; } = new();
}
File: Repositories/DbGuestRepository.cs
■using OpenBullet2.Core.Entities;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores guests to a database.
/// </summary>
public class DbGuestRepository : DbRepository<GuestEntity>, IGuestRepository
{
public DbGuestRepository(ApplicationDbContext context)
: base(context)
{
}
}
File: Repositories/DbHitRepository.cs
■using Microsoft.EntityFrameworkCore;
using OpenBullet2.Core.Entities;
using OpenBullet2.Core.Extensions;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores hits to a database.
/// </summary>
public class DbHitRepository : DbRepository<HitEntity>, IHitRepository
{
public DbHitRepository(ApplicationDbContext context)
: base(context)
{
/// <inheritdoc/>
public async Task PurgeAsync() => await context.Database
.ExecuteSqlRawAsync($"DELETE FROM {nameof(ApplicationDbContext.Hits)}");
/// <inheritdoc/>
public async Task<long> CountAsync() => await context.Hits.CountAsync();
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores jobs to a database.
/// </summary>
public class DbJobRepository : DbRepository<JobEntity>, IJobRepository
{
public DbJobRepository(ApplicationDbContext context)
: base(context)
{
/// <inheritdoc/>
public void Purge() => context.Database.ExecuteSqlRaw($"DELETE FROM {nameof(ApplicationDbContext.Jobs)}");
}
File: Repositories/DbProxyGroupRepository.cs
■using Microsoft.EntityFrameworkCore;
using OpenBullet2.Core.Entities;
using System.Threading;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores proxy groups to a database.
/// </summary>
public class DbProxyGroupRepository : DbRepository<ProxyGroupEntity>, IProxyGroupRepository
{
public DbProxyGroupRepository(ApplicationDbContext context)
: base(context)
{
/// <inheritdoc/>
public async override Task<ProxyGroupEntity> GetAsync(int id, CancellationToken cancellationToken = default)
=> await GetAll().Include(w => w.Owner)
.FirstOrDefaultAsync(e => e.Id == id, cancellationToken)
.ConfigureAwait(false);
}
File: Repositories/DbProxyRepository.cs
■using Microsoft.EntityFrameworkCore;
using OpenBullet2.Core.Entities;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores proxies to a database.
/// </summary>
public class DbProxyRepository : DbRepository<ProxyEntity>, IProxyRepository
{
public DbProxyRepository(ApplicationDbContext context)
: base(context)
{
/// <inheritdoc/>
public async Task<int> RemoveDuplicatesAsync(int groupId)
{
var proxies = await GetAll()
.Where(p => p.Group.Id == groupId)
.ToListAsync();
await DeleteAsync(duplicates);
return duplicates.Count;
}
}
File: Repositories/DbRecordRepository.cs
■using OpenBullet2.Core.Entities;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores records to a database.
/// </summary>
public class DbRecordRepository : DbRepository<RecordEntity>, IRecordRepository
{
public DbRecordRepository(ApplicationDbContext context)
: base(context)
{
}
}
File: Repositories/DbRepository.cs
■using Microsoft.EntityFrameworkCore;
using OpenBullet2.Core.Entities;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores data to a database.
/// </summary>
/// <typeparam name="T">The type of data to store</typeparam>
public class DbRepository<T> : IRepository<T> where T : Entity
{
protected readonly ApplicationDbContext context;
private readonly SemaphoreSlim _semaphore = new(1, 1);
/// <inheritdoc/>
public async virtual Task AddAsync(T entity, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
context.Add(entity);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
/// <inheritdoc/>
public async virtual Task AddAsync(IEnumerable<T> entities, CancellationToken cancellationToken
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
context.AddRange(entities);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
/// <inheritdoc/>
public async virtual Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
context.Remove(entity);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
/// <inheritdoc/>
public async virtual Task DeleteAsync(IEnumerable<T> entities, CancellationToken cancellationTok
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
context.RemoveRange(entities);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
/// <inheritdoc/>
public async virtual Task<T> GetAsync(int id, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
return await GetAll()
.FirstOrDefaultAsync(e => e.Id == id, cancellationToken).ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
/// <inheritdoc/>
public virtual IQueryable<T> GetAll()
=> context.Set<T>();
/// <inheritdoc/>
public async virtual Task UpdateAsync(T entity, CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
context.Update(entity);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
/// <inheritdoc/>
public async virtual Task UpdateAsync(IEnumerable<T> entities, CancellationToken cancellationTo
{
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
context.UpdateRange(entities);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
/// <inheritdoc/>
public void Attach<TEntity>(TEntity entity) where TEntity : Entity => context.Attach(entity);
}
File: Repositories/DiskConfigRepository.cs
■using RuriLib.Models.Configs;
using RuriLib.Helpers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System;
using RuriLib.Helpers.Transpilers;
using RuriLib.Services;
using RuriLib.Legacy.Configs;
using System.Text;
using OpenBullet2.Core.Exceptions;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores configs on disk.
/// </summary>
public class DiskConfigRepository : IConfigRepository
{
private readonly RuriLibSettingsService _rlSettings;
/// <inheritdoc/>
public async Task<IEnumerable<Config>> GetAllAsync()
{
// Try to convert legacy configs automatically before loading
foreach (var file in Directory.GetFiles(BaseFolder).Where(file => file.EndsWith(".loli")))
{
try
{
var id = Path.GetFileNameWithoutExtension(file);
var converted = ConfigConverter.Convert(File.ReadAllText(file), id);
await SaveAsync(converted);
File.Delete(file);
Console.WriteLine($"Converted legacy .loli config ({file}) to the new .opk format");
}
catch
{
Console.WriteLine($"Could not convert legacy .loli config ({file}) to the new .opk format");
}
}
/// <inheritdoc/>
public async Task<Config> GetAsync(string id)
{
var file = GetFileName(id);
if (File.Exists(file))
{
using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
/// <inheritdoc/>
public async Task<byte[]> GetBytesAsync(string id)
{
var file = GetFileName(id);
if (File.Exists(file))
{
using FileStream fileStream = new(file, FileMode.Open, FileAccess.Read);
using var ms = new MemoryStream();
await fileStream.CopyToAsync(ms);
return ms.ToArray();
}
/// <inheritdoc/>
public async Task<Config> CreateAsync(string id = null)
{
var config = new Config { Id = id ?? Guid.NewGuid().ToString() };
config.Settings.DataSettings.AllowedWordlistTypes = [
_rlSettings.Environment.WordlistTypes.First().Name
];
await SaveAsync(config);
return config;
}
/// <inheritdoc/>
public async Task UploadAsync(Stream stream, string fileName)
{
var extension = Path.GetExtension(fileName);
/// <inheritdoc/>
public async Task SaveAsync(Config config)
{
// Update the last modified date
config.Metadata.LastModified = DateTime.Now;
/// <inheritdoc/>
public void Delete(Config config)
{
var file = GetFileName(config);
if (File.Exists(file))
File.Delete(file);
}
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores wordlists to the disk and the database. Files are stored on disk while
/// metadata is stored in a database.
/// </summary>
public class HybridWordlistRepository : IWordlistRepository
{
private readonly string baseFolder;
private readonly ApplicationDbContext context;
/// <inheritdoc/>
public async Task AddAsync(WordlistEntity entity, CancellationToken cancellationToken = default)
{
// Save it to the DB
context.Add(entity);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
public async Task AddAsync(WordlistEntity entity, MemoryStream stream,
CancellationToken cancellationToken = default)
{
// Generate a unique filename
var path = Path.Combine(baseFolder, $"{Guid.NewGuid()}.txt");
entity.FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? path.Replace('/', '\\')
: path.Replace('\\', '/');
await AddAsync(entity);
}
/// <inheritdoc/>
public IQueryable<WordlistEntity> GetAll()
=> context.Wordlists;
/// <inheritdoc/>
public async Task<WordlistEntity> GetAsync(
int id, CancellationToken cancellationToken = default)
=> await GetAll().Include(w => w.Owner)
.FirstOrDefaultAsync(e => e.Id == id, cancellationToken: cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public async Task UpdateAsync(WordlistEntity entity, CancellationToken cancellationToken = defau
{
context.Entry(entity).State = EntityState.Modified;
context.Update(entity);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public async Task DeleteAsync(WordlistEntity entity, bool deleteFile = false,
CancellationToken cancellationToken = default)
{
if (deleteFile && File.Exists(entity.FileName))
File.Delete(entity.FileName);
context.Remove(entity);
await context.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
public void Purge() => _ = context.Database.ExecuteSqlRaw($"DELETE FROM {nameof(Application
/// <inheritdoc/>
public void Dispose()
{
GC.SuppressFinalize(this);
context?.Dispose();
}
}
File: Repositories/IConfigRepository.cs
■using RuriLib.Models.Configs;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores configs.
/// </summary>
public interface IConfigRepository
{
/// <summary>
/// Creates a new config with a given <paramref name="id"/>.
/// If <paramref name="id"/> is null, a random one will be generated.
/// </summary>
Task<Config> CreateAsync(string id = null);
/// <summary>
/// Deletes a config from the repository.
/// </summary>
void Delete(Config config);
/// <summary>
/// Retrieves and unpacks a config by ID.
/// </summary>
Task<Config> GetAsync(string id);
/// <summary>
/// Retrieves and unpacks all configs from the repository.
/// </summary>
/// <returns></returns>
Task<IEnumerable<Config>> GetAllAsync();
/// <summary>
/// Retrieves the raw bytes of the OPK config from the repository.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<byte[]> GetBytesAsync(string id);
/// <summary>
/// Packs and saves a config to the repository.
/// </summary>
Task SaveAsync(Config config);
/// <summary>
/// Saves a packed config (as a raw bytes stream) to the repository.
/// </summary>
Task UploadAsync(Stream stream, string fileName);
}
File: Repositories/IGuestRepository.cs
■using OpenBullet2.Core.Entities;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores guests.
/// </summary>
public interface IGuestRepository : IRepository<GuestEntity>
{
}
File: Repositories/IHitRepository.cs
■using OpenBullet2.Core.Entities;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores hits.
/// </summary>
public interface IHitRepository : IRepository<HitEntity>
{
/// <summary>
/// Deletes all hits from the repository.
/// </summary>
Task PurgeAsync();
/// <summary>
/// Count the number of hits.
/// </summary>
Task<long> CountAsync();
}
File: Repositories/IJobRepository.cs
■using OpenBullet2.Core.Entities;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores jobs.
/// </summary>
public interface IJobRepository : IRepository<JobEntity>
{
/// <summary>
/// Deletes all jobs from the repository.
/// </summary>
void Purge();
}
File: Repositories/IProxyGroupRepository.cs
■using OpenBullet2.Core.Entities;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores proxy groups.
/// </summary>
public interface IProxyGroupRepository : IRepository<ProxyGroupEntity>
{
}
File: Repositories/IProxyRepository.cs
■using OpenBullet2.Core.Entities;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores proxies.
/// </summary>
public interface IProxyRepository : IRepository<ProxyEntity>
{
/// <summary>
/// Removes duplicate proxies that belong to the group with a given <paramref name="groupId"/> from the Proxies table.
/// Duplication is checked on type, host, port, username and password.
/// Returns the number of removed entries.
/// </summary>
Task<int> RemoveDuplicatesAsync(int groupId);
}
File: Repositories/IRecordRepository.cs
■using OpenBullet2.Core.Entities;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores records.
/// </summary>
public interface IRecordRepository : IRepository<RecordEntity>
{
}
File: Repositories/IRepository.cs
■using OpenBullet2.Core.Entities;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores data.
/// </summary>
/// <typeparam name="T">The type of data to store</typeparam>
public interface IRepository<T> where T : Entity
{
// ------
// CREATE
// ------
/// <summary>
/// Adds an <paramref name="entity"/> to the repository.
/// </summary>
Task AddAsync(T entity, CancellationToken cancellationToken = default);
/// <summary>
/// Adds multiple <paramref name="entities"/> to the repository.
/// </summary>
Task AddAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default);
// ----
// READ
// ----
/// <summary>
/// Gets an entity by <paramref name="id"/>. Returns null if not found.
/// </summary>
Task<T> GetAsync(int id, CancellationToken cancellationToken = default);
/// <summary>
/// Gets an <see cref="IQueryable{T}"/> of all entities in the repository for further filtering.
/// </summary>
IQueryable<T> GetAll();
// ------
// UPDATE
// ------
/// <summary>
/// Updates an <paramref name="entity"/> in the repository.
/// </summary>
Task UpdateAsync(T entity, CancellationToken cancellationToken = default);
/// <summary>
/// Updates multiple <paramref name="entities"/> in the repository.
/// </summary>
Task UpdateAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default);
// ------
// DELETE
// ------
/// <summary>
/// Deletes an <paramref name="entity"/> from the repository.
/// </summary>
Task DeleteAsync(T entity, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes multiple <paramref name="entities"/> from the repository.
/// </summary>
Task DeleteAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default);
/// <summary>
/// Attaches to a given entity so that EF doesn't try to create a new one
/// in a one to many relationship.
/// </summary>
public void Attach<TEntity>(TEntity entity) where TEntity : Entity;
}
File: Repositories/IWordlistRepository.cs
■using OpenBullet2.Core.Entities;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Repositories;
/// <summary>
/// Stores wordlists.
/// </summary>
public interface IWordlistRepository : IDisposable
{
/// <summary>
/// Adds an <paramref name="entity"/> to the repository.
/// </summary>
Task AddAsync(WordlistEntity entity, CancellationToken cancellationToken = default);
/// <summary>
/// Adds an <paramref name="entity"/> to the repository and creates the file as well
/// by reading it from a raw <paramref name="stream"/>.
/// </summary>
Task AddAsync(WordlistEntity entity, MemoryStream stream, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes an <paramref name="entity"/> from the repository.
/// </summary>
/// <param name="deleteFile">Whether to delete the file as well</param>
Task DeleteAsync(WordlistEntity entity, bool deleteFile = false, CancellationToken cancellationToken = default);
/// <summary>
/// Gets an entity from the repository by <paramref name="id"/>.
/// </summary>
Task<WordlistEntity> GetAsync(int id, CancellationToken cancellationToken = default);
/// <summary>
/// Gets an <see cref="IQueryable"/> of all entities for further filtering.
/// </summary>
/// <returns></returns>
IQueryable<WordlistEntity> GetAll();
/// <summary>
/// Updates an <paramref name="entity"/> in the repository.
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
Task UpdateAsync(WordlistEntity entity, CancellationToken cancellationToken = default);
/// <summary>
/// Deletes all wordlists from the repository.
/// </summary>
void Purge();
}
File: Services/ConfigService.cs
■using Microsoft.Scripting.Utils;
using OpenBullet2.Core.Models.Settings;
using OpenBullet2.Core.Repositories;
using RuriLib.Models.Configs;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.IO.Compression;
using RuriLib.Helpers;
using System.IO;
using RuriLib.Functions.Conversion;
namespace OpenBullet2.Core.Services;
// TODO: The config service should also be in charge of calling methods of the IConfigRepository
/// <summary>
/// Manages the list of available configs.
/// </summary>
public class ConfigService
{
/// <summary>
/// The list of available configs.
/// </summary>
public List<Config> Configs { get; set; } = new();
/// <summary>
/// Called when a new config is selected.
/// </summary>
public event EventHandler<Config> OnConfigSelected;
/// <summary>
/// Called when all configs from configured remote endpoints are loaded.
/// </summary>
public event EventHandler OnRemotesLoaded;
/// <summary>
/// The currently selected config.
/// </summary>
public Config SelectedConfig
{
get => selectedConfig;
set
{
selectedConfig = value;
OnConfigSelected?.Invoke(this, selectedConfig);
}
}
public ConfigService(IConfigRepository configRepo, OpenBulletSettingsService openBulletSettingsS
{
this.configRepo = configRepo;
this.openBulletSettingsService = openBulletSettingsService;
}
/// <summary>
/// Reloads all configs from the <see cref="IConfigRepository"/> and remote endpoints.
/// </summary>
public async Task ReloadConfigsAsync()
{
// Load from the main repository
Configs = (await configRepo.GetAllAsync()).ToList();
SelectedConfig = null;
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
throw new UnauthorizedAccessException();
}
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
throw new FileNotFoundException();
}
try
{
using var entryStream = entry.Open();
var config = await ConfigPacker.UnpackAsync(entryStream);
// Calculate the hash of the metadata of the remote config to use as id.
// This is done to have a consistent id through successive pulls of configs
// from remotes, so that jobs can reference the id and retrieve the correct one
config.Id = HexConverter.ToHexString(config.Metadata.GetUniqueHash());
config.IsRemote = true;
// If a config with the same hash is not already present (e.g. same exact config
// from another source) add it to the list
if (!remoteConfigs.Any(c => c.Id == config.Id))
{
remoteConfigs.Add(config);
}
}
catch
{
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[{endpoint.Url}] Failed to pull configs from endpoint: {ex.Message}");
}
});
await Task.WhenAll(tasks).ConfigureAwait(false);
lock (Configs)
{
Configs.AddRange(remoteConfigs);
}
OnRemotesLoaded?.Invoke(this, EventArgs.Empty);
}
}
File: Services/DataPoolFactoryService.cs
■using OpenBullet2.Core.Models.Data;
using OpenBullet2.Core.Repositories;
using RuriLib.Models.Data;
using RuriLib.Models.Data.DataPools;
using RuriLib.Services;
using System;
using System.IO;
using System.Threading.Tasks;
using OpenBullet2.Core.Exceptions;
namespace OpenBullet2.Core.Services;
/// <summary>
/// Factory that creates a <see cref="DataPool"/> from <see cref="DataPoolOptions"/>.
/// </summary>
public class DataPoolFactoryService
{
private readonly IWordlistRepository _wordlistRepo;
private readonly RuriLibSettingsService _ruriLibSettings;
/// <summary>
/// Creates a <see cref="DataPool"/> from <see cref="DataPoolOptions"/>.
/// </summary>
public async Task<DataPool> FromOptionsAsync(DataPoolOptions options)
{
try
{
return options switch
{
InfiniteDataPoolOptions x => new InfiniteDataPool(x.WordlistType),
CombinationsDataPoolOptions x => new CombinationsDataPool(x.CharSet, x.Length, x.WordlistType),
RangeDataPoolOptions x => new RangeDataPool(x.Start, x.Amount, x.Step, x.Pad, x.WordlistType),
FileDataPoolOptions x => new FileDataPool(x.FileName, x.WordlistType),
WordlistDataPoolOptions x => await MakeWordlistDataPoolAsync(x),
_ => throw new NotImplementedException()
};
}
catch (Exception ex)
{
Console.WriteLine($"Exception while loading data pool. {ex.Message}");
return new InfiniteDataPool();
}
}
if (!File.Exists(entity.FileName))
{
throw new EntityNotFoundException($"Wordlist file not found: {entity.FileName}");
}
namespace OpenBullet2.Core.Services;
/// <summary>
/// Stores hits to an <see cref="IHitRepository"/> in a thread-safe manner.
/// </summary>
public class HitStorageService : IDisposable
{
private readonly SemaphoreSlim _semaphore;
private readonly IServiceScopeFactory _scopeFactory;
/// <summary>
/// Stores a hit in a thread-safe manner.
/// </summary>
public async Task StoreAsync(Hit hit)
{
using var scope = _scopeFactory.CreateScope();
switch (hit.DataPool)
{
case WordlistDataPool wordlistDataPool:
entity.WordlistId = wordlistDataPool.Wordlist.Id;
entity.WordlistName = wordlistDataPool.Wordlist.Name;
break;
// The following are not actual wordlists but it can help identify which pool was used
case FileDataPool fileDataPool:
entity.WordlistId = fileDataPool.POOL_CODE;
entity.WordlistName = fileDataPool.FileName;
break;
try
{
await hitRepo.AddAsync(entity);
}
finally
{
_semaphore.Release();
}
}
namespace OpenBullet2.Core.Services;
/// <summary>
/// Random UA provider that uses the User-Agents collected by intoli.com
/// </summary>
public class IntoliRandomUAProvider : IRandomUAProvider
{
private readonly Dictionary<UAPlatform, UserAgent[]> distributions = new Dictionary<UAPlatform, UserAgent[]>();
private readonly Random rand;
/// <inheritdoc/>
public int Total => distributions[UAPlatform.All].Length;
if (agents.Count == 0)
{
throw new MissingUserAgentsException("No valid user agents found in user-agents.json");
}
/// <inheritdoc/>
public string Generate() => Generate(UAPlatform.All);
/// <inheritdoc/>
public string Generate(UAPlatform platform)
{
// Take the correct precomputed cumulative distribution
var distribution = distributions[platform];
// Return the first user agent with cumulative greater or equal to the random one
return distribution.First(u => u.cumulative >= random).userAgentString;
}
return distribution.ToArray();
}
private static bool BelongsToPlatform(UAPlatform current, UAPlatform required) => required switch
{
UAPlatform.All => true,
UAPlatform.Desktop => current == UAPlatform.Linux || current == UAPlatform.Mac || current == U
UAPlatform.Mobile => current == UAPlatform.iPhone || current == UAPlatform.iPad || current == U
_ => current == required
};
}
File: Services/JobFactoryService.cs
■using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using OpenBullet2.Core.Models.Hits;
using OpenBullet2.Core.Models.Jobs;
using OpenBullet2.Core.Models.Proxies;
using RuriLib.Logging;
using RuriLib.Models.Bots;
using RuriLib.Models.Jobs;
using RuriLib.Models.Proxies;
using RuriLib.Providers.RandomNumbers;
using RuriLib.Providers.UserAgents;
using RuriLib.Services;
using System;
using System.Linq;
namespace OpenBullet2.Core.Services;
/// <summary>
/// Factory that creates a <see cref="Job"/> from <see cref="JobOptions"/>.
/// </summary>
public class JobFactoryService
{
private readonly ConfigService _configService;
private readonly RuriLibSettingsService _settingsService;
private readonly HitStorageService _hitStorage;
private readonly IServiceScopeFactory _scopeFactory;
private readonly ProxyCheckOutputFactory _proxyCheckOutputFactory;
private readonly ProxyReloadService _proxyReloadService;
private readonly IRandomUAProvider _randomUaProvider;
private readonly IRNGProvider _rngProvider;
private readonly IJobLogger _logger;
private readonly PluginRepository _pluginRepo;
/// <summary>
/// The maximum amount of bots that a job can use.
/// </summary>
public int BotLimit { get; init; } = 200;
/// <summary>
/// Creates a <see cref="Job"/> with the provided <paramref name="id"/> and <paramref name="own
/// from <see cref="JobOptions"/>.
/// </summary>
/// <param name="id">The ID of the newly created job, must be unique</param>
/// <param name="ownerId">The ID of the user who owns the job. 0 for admin</param>
/// <param name="options">The options to create the job from</param>
public Job FromOptions(int id, int ownerId, JobOptions options)
{
Job job = options switch
{
MultiRunJobOptions x => MakeMultiRunJob(x),
ProxyCheckJobOptions x => MakeProxyCheckJob(x),
_ => throw new NotImplementedException()
};
job.Id = id;
job.OwnerId = ownerId;
return job;
}
return job;
}
return job;
}
}
File: Services/JobManagerService.cs
■using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using OpenBullet2.Core.Entities;
using OpenBullet2.Core.Models.Data;
using OpenBullet2.Core.Models.Jobs;
using OpenBullet2.Core.Repositories;
using RuriLib.Models.Data.DataPools;
using RuriLib.Models.Jobs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Services;
/// <summary>
/// Manages multiple jobs.
/// </summary>
public class JobManagerService : IDisposable
{
/// <summary>
/// The list of all created jobs.
/// </summary>
public IEnumerable<Job> Jobs => _jobs;
private readonly List<Job> _jobs = new();
jobRepo.UpdateAsync(entity).Wait();
}
var options = JsonConvert.DeserializeObject<JobOptionsWrapper>(entity.JobOptions, jsonSet
var job = jobFactory.FromOptions(entity.Id, entity.Owner == null ? 0 : entity.Owner.Id, options);
AddJob(job);
}
_scopeFactory = scopeFactory;
}
}
}
}
await _recordSemaphore.WaitAsync();
try
{
var record = await recordRepo.GetAll()
.FirstOrDefaultAsync(r => r.ConfigId == job.Config.Id && r.WordlistId == pool.Wordlist.Id);
if (record == null)
{
await recordRepo.AddAsync(new RecordEntity
{
ConfigId = job.Config.Id,
WordlistId = pool.Wordlist.Id,
Checkpoint = checkpoint
});
}
else
{
record.Checkpoint = checkpoint;
await recordRepo.UpdateAsync(record);
}
}
catch
{
}
finally
{
_recordSemaphore.Release();
}
}
await SaveMultiRunJobOptionsAsync(job);
}
await _jobSemaphore.WaitAsync();
try
{
var entity = await jobRepo.GetAsync(job.Id);
// Update the skip (if not idle, also add the currently tested ones) and the bots
options.Skip = job.Status == JobStatus.Idle
? job.Skip
: job.Skip + job.DataTested;
options.Bots = job.Bots;
}
finally
{
_jobSemaphore.Release();
}
}
private void UnbindAllEvents()
{
foreach (var job in _jobs)
{
if (job is MultiRunJob mrj)
{
try
{
mrj.OnCompleted -= SaveRecord;
mrj.OnTimerTick -= SaveRecord;
mrj.OnCompleted -= SaveMultiRunJobOptionsAsync;
mrj.OnTimerTick -= SaveMultiRunJobOptionsAsync;
mrj.OnBotsChanged -= SaveMultiRunJobOptionsAsync;
}
catch
{
}
}
}
}
namespace OpenBullet2.Core.Services;
/// <summary>
/// Monitors jobs, checks defined triggers every second and executes the corresponding actions.
/// </summary>
public class JobMonitorService : IDisposable
{
/// <summary>
/// The list of triggered actions that can be executed by the job monitor.
/// </summary>
public List<TriggeredAction> TriggeredActions { get; set; } = new List<TriggeredAction>();
if (autoSave)
{
saveTimer = new Timer(new TimerCallback(_ => SaveStateIfChanged()), null, 5000, 5000);
}
}
try
{
var json = File.ReadAllText(fileName);
TriggeredActions = JsonConvert.DeserializeObject<TriggeredAction[]>(json, jsonSettings).ToL
}
catch
{
Console.WriteLine("Failed to deserialize triggered actions from json, recreating them");
}
}
if (hash != lastSavedHash)
{
try
{
File.WriteAllText(fileName, json);
lastSavedHash = hash;
}
catch
{
// File probably in use
}
}
}
namespace OpenBullet2.Core.Services;
/// <summary>
/// Provides interaction with settings of the OpenBullet 2 application.
/// </summary>
public class OpenBulletSettingsService
{
private string BaseFolder { get; }
private readonly JsonSerializerSettings jsonSettings;
/// <summary>
/// The path of the file where settings are saved.
/// </summary>
public string FileName => Path.Combine(BaseFolder, "OpenBulletSettings.json");
/// <summary>
/// The actual settings. After modifying them, call the <see cref="SaveAsync"/> method to persist them.
/// </summary>
public OpenBulletSettings Settings { get; private set; }
if (File.Exists(FileName))
{
Settings = JsonConvert.DeserializeObject<OpenBulletSettings>(File.ReadAllText(FileName), jsonSettings);
}
else
{
Recreate();
SaveAsync().Wait();
}
}
/// <summary>
/// Saves the <see cref="Settings"/> to disk.
/// </summary>
public async Task SaveAsync() => await File.WriteAllTextAsync(FileName, JsonConvert.SerializeObject(Settings, jsonS
/// <summary>
/// Restores the default <see cref="Settings"/> (does not save to disk).
/// </summary>
public void Recreate() => Settings = new OpenBulletSettings
{
GeneralSettings = new GeneralSettings { ProxyCheckTargets = new List<ProxyCheckTarget> { n
RemoteSettings = new RemoteSettings(),
SecuritySettings = new SecuritySettings().GenerateJwtKey().SetupAdminPassword("admin"),
CustomizationSettings = new CustomizationSettings()
};
}
File: Services/ProxyReloadService.cs
■using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using OpenBullet2.Core.Entities;
using OpenBullet2.Core.Models.Proxies;
using OpenBullet2.Core.Repositories;
using RuriLib.Models.Proxies;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace OpenBullet2.Core.Services;
/// <summary>
/// A reload service that will reload proxies from an <see cref="IProxyGroupRepository"/>.
/// </summary>
public class ProxyReloadService : IDisposable
{
private readonly SemaphoreSlim _semaphore;
private readonly IServiceScopeFactory _scopeFactory;
/// <summary>
/// Reloads proxies from a group with a given <paramref name="groupId"/> of a user with a given
/// <paramref name="userId"/>.
/// </summary>
public async Task<IEnumerable<Proxy>> ReloadAsync(int groupId, int userId, CancellationToken cancellationToken = d
{
using var scope = _scopeFactory.CreateScope();
var proxyGroupsRepo = scope.ServiceProvider.GetRequiredService<IProxyGroupRepository>();
var proxyRepo = scope.ServiceProvider.GetRequiredService<IProxyRepository>();
List<ProxyEntity> entities;
try
{
// If the groupId is -1 reload all proxies
if (groupId == -1)
{
entities = userId == 0
? await proxyRepo.GetAll().ToListAsync(cancellationToken).ConfigureAwait(false)
: await proxyRepo.GetAll().Include(p => p.Group).ThenInclude(g => g.Owner)
.Where(p => p.Group.Owner.Id == userId).ToListAsync(cancellationToken).ConfigureAwait(false);
}
else
{
var group = await proxyGroupsRepo.GetAsync(groupId, cancellationToken).ConfigureAwait(
entities = await proxyRepo.GetAll()
.Where(p => p.Group.Id == groupId)
.ToListAsync(cancellationToken).ConfigureAwait(false);
}
}
finally
{
_semaphore.Release();
}
namespace OpenBullet2.Core.Services;
/// <summary>
/// Factory that creates a <see cref="ProxySource"/> from a <see cref="ProxySourceOptions"/> object.
/// </summary>
public class ProxySourceFactoryService
{
private readonly ProxyReloadService _reloadService;
/// <summary>
/// Creates a <see cref="ProxySource"/> from a <see cref="ProxySourceOptions"/> object.
/// </summary>
public Task<ProxySource> FromOptions(ProxySourceOptions options)
{
ProxySource source = options switch
{
RemoteProxySourceOptions x => new RemoteProxySource(x.Url) { DefaultType = x.DefaultType },
FileProxySourceOptions x => new FileProxySource(x.FileName) { DefaultType = x.DefaultType },
GroupProxySourceOptions x => new GroupProxySource(x.GroupId, _reloadService),
_ => throw new NotImplementedException()
};
return Task.FromResult(source);
}
}