mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-04-30 03:22:04 -04:00
132 lines
5.8 KiB
C#
132 lines
5.8 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using FlareSolverrSharp;
|
|
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;
|
|
private CancellationTokenSource _gameHistoryCts = new();
|
|
private Task? _gameHistoryTask;
|
|
private TimeSpan _gameHistoryInterval = TimeSpan.FromSeconds(60);
|
|
private IEnumerable<string>? _cookies = null;
|
|
private string? _userAgent = null;
|
|
public DateTimeOffset LastSuccessfulRefresh = DateTimeOffset.MinValue;
|
|
|
|
public Rainbet(CancellationToken cancellationToken = default)
|
|
{
|
|
_cancellationToken = cancellationToken;
|
|
_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))
|
|
{
|
|
var enabled = await SettingsProvider.GetValueAsync(BuiltIn.Keys.RainbetEnabled);
|
|
if (!enabled.ToBoolean())
|
|
{
|
|
_logger.Debug("Rainbet is disabled");
|
|
continue;
|
|
};
|
|
try
|
|
{
|
|
_logger.Info($"Retrieving game history from Rainbet, last success: {LastSuccessfulRefresh:yyyy-MM-dd HH:mm:ss}");
|
|
var bets = await GetGameHistory(5000);
|
|
OnRainbetBet?.Invoke(this, bets);
|
|
LastSuccessfulRefresh = DateTimeOffset.UtcNow;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_logger.Error("Caught error when retrieving bets and invoking the event");
|
|
_logger.Error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task RefreshCookies()
|
|
{
|
|
var settings =
|
|
await SettingsProvider.GetMultipleValuesAsync([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 client = new HttpClient(handler);
|
|
// 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);
|
|
_cookies = getResponse.Headers.GetValues("Set-Cookie");
|
|
_userAgent = getResponse.RequestMessage!.Headers.UserAgent.ToString();
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
if (_cookies == null)
|
|
{
|
|
_logger.Info("Retrieving cookies for Rainbet as they have not been retrieved yet");
|
|
await RefreshCookies();
|
|
}
|
|
var flareSolverrProxy = await SettingsProvider.GetValueAsync(BuiltIn.Keys.FlareSolverrProxy);
|
|
var gameHistoryUrl = "https://services.rainbet.com/v1/game-history";
|
|
var jsonBody = new Dictionary<string, int> { {"take", take} };
|
|
var postData = JsonContent.Create(jsonBody);
|
|
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", _cookies!);
|
|
postClient.DefaultRequestHeaders.UserAgent.Clear();
|
|
postClient.DefaultRequestHeaders.UserAgent.ParseAdd(_userAgent);
|
|
var response = await postClient.PostAsync(gameHistoryUrl, postData, _cancellationToken);
|
|
if (response.Headers.Contains("cf-mitigated"))
|
|
{
|
|
_logger.Error("Hit a Cloudflare challenge, cookies expired? Marking cookies as null so it'll refresh next time we run");
|
|
_cookies = null;
|
|
throw new Exception("Cloudflare challenged");
|
|
}
|
|
var bets = await response.Content.ReadFromJsonAsync<List<RainbetBetHistoryModel>>(cancellationToken: _cancellationToken);
|
|
if (bets == null) throw new Exception("Caught a null when deserializing Rainbet bet history");
|
|
return bets;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_gameHistoryCts.Cancel();
|
|
_gameHistoryCts.Dispose();
|
|
_gameHistoryTask?.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
} |