mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-04-30 03:22:04 -04:00
Added Rainbet bet history scraping. Uses FlareSolverr to grab Cloudflare cookies then retrieves from the bet feed. Not perfect but mostly works.
This commit is contained in:
6
.idea/.idea.KfChatDotNet/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/.idea.KfChatDotNet/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="AngularNgOptimizedImage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
@@ -14,4 +14,5 @@ public class ApplicationDbContext : DbContext
|
||||
public DbSet<JuicerDbModel> Juicers { get; set; }
|
||||
public DbSet<SettingDbModel> Settings { get; set; }
|
||||
public DbSet<HowlggBetsDbModel> HowlggBets { get; set; }
|
||||
public DbSet<RainbetBetsDbModel> RainbetBets { get; set; }
|
||||
}
|
||||
@@ -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<RainbetBetHistoryModel> 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()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FlareSolverrSharp" Version="3.0.7" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7">
|
||||
|
||||
187
KfChatDotNetBot/Migrations/20240802172200_Rainbet.Designer.cs
generated
Normal file
187
KfChatDotNetBot/Migrations/20240802172200_Rainbet.Designer.cs
generated
Normal file
@@ -0,0 +1,187 @@
|
||||
// <auto-generated />
|
||||
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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Bet")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BetId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Game")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("GameId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Profit")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("HowlggBets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float>("Amount")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<DateTimeOffset>("JuicedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Juicers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.RainbetBetsDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("BetId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("BetSeenAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("GameName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("Multiplier")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<float>("Payout")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("PublicId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("RainbetUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("Value")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RainbetBets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.SettingDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Default")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSecret")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Regex")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserDbModel", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Ignored")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("KfId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("KfUsername")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("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
|
||||
}
|
||||
}
|
||||
}
|
||||
43
KfChatDotNetBot/Migrations/20240802172200_Rainbet.cs
Normal file
43
KfChatDotNetBot/Migrations/20240802172200_Rainbet.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace KfChatDotNetBot.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Rainbet : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RainbetBets",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
PublicId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
RainbetUserId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
GameName = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Value = table.Column<float>(type: "REAL", nullable: false),
|
||||
Payout = table.Column<float>(type: "REAL", nullable: false),
|
||||
Multiplier = table.Column<float>(type: "REAL", nullable: false),
|
||||
BetId = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
UpdatedAt = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||
BetSeenAt = table.Column<DateTimeOffset>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RainbetBets", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "RainbetBets");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<int>("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<int>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("BetId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("BetSeenAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("GameName")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("Multiplier")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<float>("Payout")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<string>("PublicId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("RainbetUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("Value")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RainbetBets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.SettingDbModel", b =>
|
||||
{
|
||||
b.Property<int>("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<int>("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)
|
||||
|
||||
18
KfChatDotNetBot/Models/DbModels/RainbetBetsDbModel.cs
Normal file
18
KfChatDotNetBot/Models/DbModels/RainbetBetsDbModel.cs
Normal file
@@ -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; }
|
||||
}
|
||||
64
KfChatDotNetBot/Models/RainbetModels.cs
Normal file
64
KfChatDotNetBot/Models/RainbetModels.cs
Normal file
@@ -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; }
|
||||
}
|
||||
120
KfChatDotNetBot/Services/Rainbet.cs
Normal file
120
KfChatDotNetBot/Services/Rainbet.cs
Normal file
@@ -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<RainbetBetHistoryModel> 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<List<RainbetBetHistoryModel>> 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<string, int> { {"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<List<RainbetBetHistoryModel>>(cancellationToken: _cancellationToken);
|
||||
return bets;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gameHistoryCts.Cancel();
|
||||
_gameHistoryCts.Dispose();
|
||||
_gameHistoryTask?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user