diff --git a/.idea/.idea.KfChatDotNet/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.KfChatDotNet/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..1aaccf7
--- /dev/null
+++ b/.idea/.idea.KfChatDotNet/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/KfChatDotNetBot/ApplicationDbContext.cs b/KfChatDotNetBot/ApplicationDbContext.cs
index 05038b9..1da2ce4 100644
--- a/KfChatDotNetBot/ApplicationDbContext.cs
+++ b/KfChatDotNetBot/ApplicationDbContext.cs
@@ -14,4 +14,5 @@ public class ApplicationDbContext : DbContext
public DbSet Juicers { get; set; }
public DbSet Settings { get; set; }
public DbSet HowlggBets { get; set; }
+ public DbSet RainbetBets { get; set; }
}
\ No newline at end of file
diff --git a/KfChatDotNetBot/ChatBot.cs b/KfChatDotNetBot/ChatBot.cs
index 0c0c5b8..bfbfe6f 100644
--- a/KfChatDotNetBot/ChatBot.cs
+++ b/KfChatDotNetBot/ChatBot.cs
@@ -41,6 +41,7 @@ public class ChatBot
private bool _twitchDisabled = false;
private Task _websocketWatchdog;
private Jackpot _jackpot;
+ private Rainbet _rainbet;
public ChatBot()
{
@@ -121,6 +122,7 @@ public class ChatBot
BuildTwitchChat();
BuildHowlgg();
BuildJackpot();
+ BuildRainbet();
_logger.Info("Starting websocket watchdog");
_websocketWatchdog = WebsocketWatchdog();
@@ -185,6 +187,14 @@ public class ChatBot
_jackpot = null!;
BuildJackpot();
}
+
+ // if (!_rainbet.IsConnected())
+ // {
+ // _logger.Error("Rainbet died, recreating it");
+ // _rainbet.Dispose();
+ // _rainbet = null!;
+ // BuildRainbet();
+ // }
}
catch (Exception e)
{
@@ -194,6 +204,49 @@ public class ChatBot
}
}
+
+ private void BuildRainbet()
+ {
+ _rainbet = new Rainbet(_cancellationToken);
+ _rainbet.OnRainbetBet += OnRainbetBet;
+ _rainbet.StartGameHistoryTimer();
+ }
+
+ private void OnRainbetBet(object sender, List bets)
+ {
+ var settings = Helpers
+ .GetMultipleValues([
+ BuiltIn.Keys.RainbetBmjPublicId, BuiltIn.Keys.TwitchBossmanJackUsername,
+ BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor
+ ]).Result;
+ _logger.Trace("Rainbet bet has arrived");
+ using var db = new ApplicationDbContext();
+ foreach (var bet in bets.Where(b => b.User.PublicId == settings[BuiltIn.Keys.RainbetBmjPublicId].Value))
+ //foreach (var bet in bets)
+ {
+ if (db.RainbetBets.Any(b => b.BetId == bet.Id))
+ {
+ _logger.Trace($"Ignoring bet {bet.Id} as we've already logged it");
+ continue;
+ }
+
+ db.RainbetBets.Add(new RainbetBetsDbModel
+ {
+ PublicId = bet.User.PublicId,
+ RainbetUserId = bet.User.Id,
+ GameName = bet.Game.Name,
+ Value = bet.Value,
+ Payout = bet.Payout,
+ Multiplier = bet.Multiplier,
+ BetId = bet.Id,
+ UpdatedAt = bet.UpdatedAt,
+ BetSeenAt = DateTimeOffset.UtcNow
+ });
+ _logger.Info("Added a Bossman Rainbet bet to the database");
+ }
+
+ db.SaveChanges();
+ }
private void BuildJackpot()
{
diff --git a/KfChatDotNetBot/KfChatDotNetBot.csproj b/KfChatDotNetBot/KfChatDotNetBot.csproj
index 1725843..38a433a 100644
--- a/KfChatDotNetBot/KfChatDotNetBot.csproj
+++ b/KfChatDotNetBot/KfChatDotNetBot.csproj
@@ -8,6 +8,7 @@
+
diff --git a/KfChatDotNetBot/Migrations/20240802172200_Rainbet.Designer.cs b/KfChatDotNetBot/Migrations/20240802172200_Rainbet.Designer.cs
new file mode 100644
index 0000000..471191b
--- /dev/null
+++ b/KfChatDotNetBot/Migrations/20240802172200_Rainbet.Designer.cs
@@ -0,0 +1,187 @@
+//
+using System;
+using KfChatDotNetBot;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace KfChatDotNetBot.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20240802172200_Rainbet")]
+ partial class Rainbet
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
+
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.HowlggBetsDbModel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Bet")
+ .HasColumnType("INTEGER");
+
+ b.Property("BetId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Date")
+ .HasColumnType("TEXT");
+
+ b.Property("Game")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("GameId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Profit")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("HowlggBets");
+ });
+
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Amount")
+ .HasColumnType("REAL");
+
+ b.Property("JuicedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Juicers");
+ });
+
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.RainbetBetsDbModel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("BetId")
+ .HasColumnType("INTEGER");
+
+ b.Property("BetSeenAt")
+ .HasColumnType("TEXT");
+
+ b.Property("GameName")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Multiplier")
+ .HasColumnType("REAL");
+
+ b.Property("Payout")
+ .HasColumnType("REAL");
+
+ b.Property("PublicId")
+ .HasColumnType("TEXT");
+
+ b.Property("RainbetUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("REAL");
+
+ b.HasKey("Id");
+
+ b.ToTable("RainbetBets");
+ });
+
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.SettingDbModel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Default")
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("IsSecret")
+ .HasColumnType("INTEGER");
+
+ b.Property("Key")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Regex")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserDbModel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Ignored")
+ .HasColumnType("INTEGER");
+
+ b.Property("KfId")
+ .HasColumnType("INTEGER");
+
+ b.Property("KfUsername")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("UserRight")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b =>
+ {
+ b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/KfChatDotNetBot/Migrations/20240802172200_Rainbet.cs b/KfChatDotNetBot/Migrations/20240802172200_Rainbet.cs
new file mode 100644
index 0000000..adc42e6
--- /dev/null
+++ b/KfChatDotNetBot/Migrations/20240802172200_Rainbet.cs
@@ -0,0 +1,43 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace KfChatDotNetBot.Migrations
+{
+ ///
+ public partial class Rainbet : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "RainbetBets",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ PublicId = table.Column(type: "TEXT", nullable: true),
+ RainbetUserId = table.Column(type: "INTEGER", nullable: false),
+ GameName = table.Column(type: "TEXT", nullable: false),
+ Value = table.Column(type: "REAL", nullable: false),
+ Payout = table.Column(type: "REAL", nullable: false),
+ Multiplier = table.Column(type: "REAL", nullable: false),
+ BetId = table.Column(type: "INTEGER", nullable: false),
+ UpdatedAt = table.Column(type: "TEXT", nullable: false),
+ BetSeenAt = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_RainbetBets", x => x.Id);
+ });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "RainbetBets");
+ }
+ }
+}
diff --git a/KfChatDotNetBot/Migrations/ApplicationDbContextModelSnapshot.cs b/KfChatDotNetBot/Migrations/ApplicationDbContextModelSnapshot.cs
index c566c0b..a1f76f6 100644
--- a/KfChatDotNetBot/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/KfChatDotNetBot/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -17,7 +17,7 @@ namespace KfChatDotNetBot.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
- modelBuilder.Entity("KfChatDotNetKickBot.Models.DbModels.HowlggBetsDbModel", b =>
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.HowlggBetsDbModel", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd()
@@ -50,7 +50,7 @@ namespace KfChatDotNetBot.Migrations
b.ToTable("HowlggBets");
});
- modelBuilder.Entity("KfChatDotNetKickBot.Models.DbModels.JuicerDbModel", b =>
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd()
@@ -72,7 +72,46 @@ namespace KfChatDotNetBot.Migrations
b.ToTable("Juicers");
});
- modelBuilder.Entity("KfChatDotNetKickBot.Models.DbModels.SettingDbModel", b =>
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.RainbetBetsDbModel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("BetId")
+ .HasColumnType("INTEGER");
+
+ b.Property("BetSeenAt")
+ .HasColumnType("TEXT");
+
+ b.Property("GameName")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Multiplier")
+ .HasColumnType("REAL");
+
+ b.Property("Payout")
+ .HasColumnType("REAL");
+
+ b.Property("PublicId")
+ .HasColumnType("TEXT");
+
+ b.Property("RainbetUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("REAL");
+
+ b.HasKey("Id");
+
+ b.ToTable("RainbetBets");
+ });
+
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.SettingDbModel", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd()
@@ -105,7 +144,7 @@ namespace KfChatDotNetBot.Migrations
b.ToTable("Settings");
});
- modelBuilder.Entity("KfChatDotNetKickBot.Models.DbModels.UserDbModel", b =>
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserDbModel", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd()
@@ -129,9 +168,9 @@ namespace KfChatDotNetBot.Migrations
b.ToTable("Users");
});
- modelBuilder.Entity("KfChatDotNetKickBot.Models.DbModels.JuicerDbModel", b =>
+ modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b =>
{
- b.HasOne("KfChatDotNetKickBot.Models.DbModels.UserDbModel", "User")
+ b.HasOne("KfChatDotNetBot.Models.DbModels.UserDbModel", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
diff --git a/KfChatDotNetBot/Models/DbModels/RainbetBetsDbModel.cs b/KfChatDotNetBot/Models/DbModels/RainbetBetsDbModel.cs
new file mode 100644
index 0000000..eb0b7c4
--- /dev/null
+++ b/KfChatDotNetBot/Models/DbModels/RainbetBetsDbModel.cs
@@ -0,0 +1,18 @@
+namespace KfChatDotNetBot.Models.DbModels;
+
+public class RainbetBetsDbModel
+{
+ public int Id { get; set; }
+ // Weird gibberish identifier given to users, may be hidden on bet feeds for users who opt out of the social shit
+ // Null if the user has opted out
+ public string? PublicId { get; set; }
+ // This is always set. Rainbet never omits the user's ID even if they're anonymous
+ public required int RainbetUserId { get; set; }
+ public required string GameName { get; set; }
+ public required float Value { get; set; }
+ public required float Payout { get; set; }
+ public required float Multiplier { get; set; }
+ public required long BetId { get; set; }
+ public required DateTimeOffset UpdatedAt { get; set; }
+ public required DateTimeOffset BetSeenAt { get; set; }
+}
\ No newline at end of file
diff --git a/KfChatDotNetBot/Models/RainbetModels.cs b/KfChatDotNetBot/Models/RainbetModels.cs
new file mode 100644
index 0000000..f854124
--- /dev/null
+++ b/KfChatDotNetBot/Models/RainbetModels.cs
@@ -0,0 +1,64 @@
+using System.Text.Json.Serialization;
+
+namespace KfChatDotNetBot.Models;
+
+public class RainbetBetHistoryModel
+{
+ [JsonPropertyName("id")]
+ public required int Id { get; set; }
+ [JsonPropertyName("value")]
+ public required float Value { get; set; }
+ [JsonPropertyName("payout")]
+ public required float Payout { get; set; }
+ [JsonPropertyName("multiplier")]
+ public required float Multiplier { get; set; }
+ [JsonPropertyName("updated_at")]
+ public required DateTimeOffset UpdatedAt { get; set; }
+ [JsonPropertyName("user")]
+ public required RainbetBetHistoryUserModel User { get; set; }
+ [JsonPropertyName("game")]
+ public required RainbetBetHistoryGameModel Game { get; set; }
+}
+
+public class RainbetBetHistoryGameModel
+{
+ [JsonPropertyName("id")]
+ public required int Id { get; set; }
+ // It's actually a slug
+ [JsonPropertyName("url")]
+ public required string Url { get; set; }
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+}
+
+public class RainbetBetHistoryUserRankModel
+{
+ [JsonPropertyName("id")]
+ public required int Id { get; set; }
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+ [JsonPropertyName("level")]
+ public required int Level { get; set; }
+ [JsonPropertyName("threshold")]
+ public required int Threshold { get; set; }
+}
+
+public class RainbetBetHistoryUserModel
+{
+ // Can still uniquely identify users even if they're private. Bossman is ID 50
+ [JsonPropertyName("id")]
+ public required int Id { get; set; }
+ // Set to null on private-profiles
+ [JsonPropertyName("publicId")]
+ public string? PublicId { get; set; }
+ // Set to null on private profiles
+ [JsonPropertyName("username")]
+ public string? Username { get; set; }
+ [JsonPropertyName("wageredAmount")]
+ public required float WageredAmount { get; set; }
+ [JsonPropertyName("public_profile")]
+ public required int PublicProfile { get; set; }
+ // Null when they have no rank
+ [JsonPropertyName("rank")]
+ public RainbetBetHistoryUserRankModel? Rank { get; set; }
+}
\ No newline at end of file
diff --git a/KfChatDotNetBot/Services/Rainbet.cs b/KfChatDotNetBot/Services/Rainbet.cs
new file mode 100644
index 0000000..5a9e9ce
--- /dev/null
+++ b/KfChatDotNetBot/Services/Rainbet.cs
@@ -0,0 +1,120 @@
+using System.Net;
+using System.Net.Http.Json;
+using FlareSolverrSharp;
+using FlareSolverrSharp.Exceptions;
+using KfChatDotNetBot.Models;
+using KfChatDotNetBot.Settings;
+using NLog;
+
+namespace KfChatDotNetBot.Services;
+
+public class Rainbet : IDisposable
+{
+ private Logger _logger = LogManager.GetCurrentClassLogger();
+ public delegate void OnRainbetBetEventHandler(object sender, List bets);
+ public event OnRainbetBetEventHandler OnRainbetBet;
+ private CancellationToken _cancellationToken = CancellationToken.None;
+ private CancellationTokenSource _gameHistoryCts = new();
+ private Task? _gameHistoryTask;
+ private TimeSpan _gameHistoryInterval = TimeSpan.FromSeconds(60);
+
+ public Rainbet(CancellationToken? cancellationToken = null)
+ {
+ if (cancellationToken != null) _cancellationToken = cancellationToken.Value;
+ _logger.Info("Rainbet client created");
+ }
+
+ public void StartGameHistoryTimer()
+ {
+ _gameHistoryTask = GameHistoryTimer();
+ }
+
+ private async Task GameHistoryTimer()
+ {
+ using var timer = new PeriodicTimer(_gameHistoryInterval);
+ while (await timer.WaitForNextTickAsync(_gameHistoryCts.Token))
+ {
+ try
+ {
+ _logger.Info("Retrieving game history from Rainbet");
+ var bets = await GetGameHistory(1000);
+ OnRainbetBet?.Invoke(this, bets);
+ }
+ catch (FlareSolverrException e)
+ {
+ _logger.Error("Caught a FlareSolverrException, probably that retarded cookie bug that has been unfixed for 3+ years");
+ _logger.Error("Trying again immediately as it's pretty rare it happens twice in a row");
+ _logger.Error(e);
+ try
+ {
+ var bets = await GetGameHistory(1000);
+ OnRainbetBet?.Invoke(this, bets);
+ }
+ catch (Exception ee)
+ {
+ _logger.Error("Fuck my life, failed again. We'll just wait until the next tick");
+ _logger.Error(ee);
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error("Caught error when retrieving bets and invoking the event");
+ _logger.Error(e);
+ }
+ }
+ }
+
+ // FlareSolverr C# client does not support POSTing application/json so this method involves
+ // 1. Getting the home page (as you're unlikely to get a CF challenge checkbox. Probably due to some config to limit
+ // friction for degens on VPNs, which is like 99% of the traffic for these shit casinos)
+ // 2. Using the cookies from that request to do the actual POST
+ // Cookies and UA must match or the trannies at Cloudflare will reject your cookies
+ // take = 10 is the default, but it can go higher
+ public async Task> GetGameHistory(int take = 10)
+ {
+ var settings =
+ await Helpers.GetMultipleValues([BuiltIn.Keys.FlareSolverrApiUrl, BuiltIn.Keys.FlareSolverrProxy]);
+ var flareSolverrUrl = settings[BuiltIn.Keys.FlareSolverrApiUrl];
+ var flareSolverrProxy = settings[BuiltIn.Keys.FlareSolverrProxy];
+ var handler = new ClearanceHandler(flareSolverrUrl.Value)
+ {
+ // Generally takes <5 seconds
+ MaxTimeout = 30000,
+ };
+ _logger.Debug($"Configured clearance handler to use FlareSolverr endpoint: {flareSolverrUrl.Value}");
+ // I would suggest not using a proxy. It's pretty much a miracle this works at all.
+ if (flareSolverrProxy.Value != null)
+ {
+ handler.ProxyUrl = flareSolverrProxy.Value;
+ _logger.Debug($"Configured clearance handler to use {flareSolverrProxy.Value} for proxying the request");
+ }
+ var gameHistoryUrl = "https://sportsbook.rainbet.com/v1/game-history";
+ var client = new HttpClient(handler);
+ var jsonBody = new Dictionary { {"take", take} };
+ var postData = JsonContent.Create(jsonBody);
+ // You get CF checkbox'd if you go directly to sportsbook.rainbet.com but works ok for root
+ var getResponse = await client.GetAsync("https://rainbet.com/", _cancellationToken);
+ var postClientHandler = new HttpClientHandler();
+ if (flareSolverrProxy.Value != null)
+ {
+ postClientHandler.Proxy = new WebProxy(flareSolverrProxy.Value);
+ postClientHandler.UseProxy = true;
+ _logger.Debug($"Configured API request to use {flareSolverrProxy.Value}");
+ }
+ var postClient = new HttpClient(postClientHandler);
+ postClient.DefaultRequestHeaders.Add("Cookie", getResponse.Headers.GetValues("Set-Cookie"));
+ postClient.DefaultRequestHeaders.UserAgent.Clear();
+ postClient.DefaultRequestHeaders.UserAgent.ParseAdd(getResponse.RequestMessage.Headers.UserAgent.ToString());
+ var response = await postClient.PostAsync(gameHistoryUrl, postData, _cancellationToken);
+ var bets = await response.Content.ReadFromJsonAsync>(cancellationToken: _cancellationToken);
+ return bets;
+ }
+
+ public void Dispose()
+ {
+ _gameHistoryCts.Cancel();
+ _gameHistoryCts.Dispose();
+ _gameHistoryTask?.Dispose();
+ GC.SuppressFinalize(this);
+ }
+}
\ No newline at end of file
diff --git a/KfChatDotNetBot/Services/Shuffle.cs b/KfChatDotNetBot/Services/Shuffle.cs
index 25a8691..6eb323e 100644
--- a/KfChatDotNetBot/Services/Shuffle.cs
+++ b/KfChatDotNetBot/Services/Shuffle.cs
@@ -207,7 +207,16 @@ public class Shuffle : IDisposable
public void Dispose()
{
_wsClient.Dispose();
- _pingCts.Cancel();
+ // Rare bug but has happened at least once
+ try
+ {
+ _pingCts.Cancel();
+ }
+ catch (ObjectDisposedException e)
+ {
+ _logger.Error("Caught object disposed exception when trying to send a cancellation to the ping task");
+ _logger.Error(e);
+ }
_pingCts.Dispose();
_pingTask.Dispose();
GC.SuppressFinalize(this);
diff --git a/KfChatDotNetBot/Settings/BuiltIn.cs b/KfChatDotNetBot/Settings/BuiltIn.cs
index dcac89b..4b6a18a 100644
--- a/KfChatDotNetBot/Settings/BuiltIn.cs
+++ b/KfChatDotNetBot/Settings/BuiltIn.cs
@@ -352,6 +352,30 @@ public static class BuiltIn
Description = "Bossman's username on Jackpot",
Default = "TheBossmanJack",
IsSecret = false
+ },
+ new BuiltInSettingsModel
+ {
+ Key = Keys.RainbetBmjPublicId,
+ Regex = ".+",
+ Description = "Bossman's rainbet public ID",
+ Default = "Ir04170wLulcjtePCL7P6lmeOlepRaNp",
+ IsSecret = false
+ },
+ new BuiltInSettingsModel
+ {
+ Key = Keys.FlareSolverrApiUrl,
+ Regex = ".+",
+ Description = "URL for your FlareSolverr service API",
+ Default = "http://localhost:8191/",
+ IsSecret = false
+ },
+ new BuiltInSettingsModel
+ {
+ Key = Keys.FlareSolverrProxy,
+ Regex = ".+",
+ Description = "Proxy in use specifically for FlareSolverr",
+ Default = null,
+ IsSecret = false
}
];
@@ -387,5 +411,8 @@ public static class BuiltIn
public static string KiwiFarmsGreenColor = "KiwiFarms.GreenColor";
public static string KiwiFarmsRedColor = "KiwiFarms.RedColor";
public static string JackpotBmjUsername = "Jackpot.BmjUsername";
+ public static string RainbetBmjPublicId = "Rainbet.BmjPublicId";
+ public static string FlareSolverrApiUrl = "FlareSolverr.ApiUrl";
+ public static string FlareSolverrProxy = "FlareSolverr.Proxy";
}
}
\ No newline at end of file