mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-05-02 04: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<JuicerDbModel> Juicers { get; set; }
|
||||||
public DbSet<SettingDbModel> Settings { get; set; }
|
public DbSet<SettingDbModel> Settings { get; set; }
|
||||||
public DbSet<HowlggBetsDbModel> HowlggBets { 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 bool _twitchDisabled = false;
|
||||||
private Task _websocketWatchdog;
|
private Task _websocketWatchdog;
|
||||||
private Jackpot _jackpot;
|
private Jackpot _jackpot;
|
||||||
|
private Rainbet _rainbet;
|
||||||
|
|
||||||
public ChatBot()
|
public ChatBot()
|
||||||
{
|
{
|
||||||
@@ -121,6 +122,7 @@ public class ChatBot
|
|||||||
BuildTwitchChat();
|
BuildTwitchChat();
|
||||||
BuildHowlgg();
|
BuildHowlgg();
|
||||||
BuildJackpot();
|
BuildJackpot();
|
||||||
|
BuildRainbet();
|
||||||
|
|
||||||
_logger.Info("Starting websocket watchdog");
|
_logger.Info("Starting websocket watchdog");
|
||||||
_websocketWatchdog = WebsocketWatchdog();
|
_websocketWatchdog = WebsocketWatchdog();
|
||||||
@@ -185,6 +187,14 @@ public class ChatBot
|
|||||||
_jackpot = null!;
|
_jackpot = null!;
|
||||||
BuildJackpot();
|
BuildJackpot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (!_rainbet.IsConnected())
|
||||||
|
// {
|
||||||
|
// _logger.Error("Rainbet died, recreating it");
|
||||||
|
// _rainbet.Dispose();
|
||||||
|
// _rainbet = null!;
|
||||||
|
// BuildRainbet();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -195,6 +205,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()
|
private void BuildJackpot()
|
||||||
{
|
{
|
||||||
var proxy = Helpers.GetValue(BuiltIn.Keys.Proxy).Result.Value;
|
var proxy = Helpers.GetValue(BuiltIn.Keys.Proxy).Result.Value;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FlareSolverrSharp" Version="3.0.7" />
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.7" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" 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
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.7");
|
||||||
|
|
||||||
modelBuilder.Entity("KfChatDotNetKickBot.Models.DbModels.HowlggBetsDbModel", b =>
|
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.HowlggBetsDbModel", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -50,7 +50,7 @@ namespace KfChatDotNetBot.Migrations
|
|||||||
b.ToTable("HowlggBets");
|
b.ToTable("HowlggBets");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("KfChatDotNetKickBot.Models.DbModels.JuicerDbModel", b =>
|
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.JuicerDbModel", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -72,7 +72,46 @@ namespace KfChatDotNetBot.Migrations
|
|||||||
b.ToTable("Juicers");
|
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")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -105,7 +144,7 @@ namespace KfChatDotNetBot.Migrations
|
|||||||
b.ToTable("Settings");
|
b.ToTable("Settings");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("KfChatDotNetKickBot.Models.DbModels.UserDbModel", b =>
|
modelBuilder.Entity("KfChatDotNetBot.Models.DbModels.UserDbModel", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -129,9 +168,9 @@ namespace KfChatDotNetBot.Migrations
|
|||||||
b.ToTable("Users");
|
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()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_wsClient.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();
|
_pingCts.Dispose();
|
||||||
_pingTask.Dispose();
|
_pingTask.Dispose();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
|||||||
@@ -352,6 +352,30 @@ public static class BuiltIn
|
|||||||
Description = "Bossman's username on Jackpot",
|
Description = "Bossman's username on Jackpot",
|
||||||
Default = "TheBossmanJack",
|
Default = "TheBossmanJack",
|
||||||
IsSecret = false
|
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 KiwiFarmsGreenColor = "KiwiFarms.GreenColor";
|
||||||
public static string KiwiFarmsRedColor = "KiwiFarms.RedColor";
|
public static string KiwiFarmsRedColor = "KiwiFarms.RedColor";
|
||||||
public static string JackpotBmjUsername = "Jackpot.BmjUsername";
|
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