285 lines
10 KiB
C#
285 lines
10 KiB
C#
using BritishHorseRacingApi.Models;
|
|
using Newtonsoft.Json.Linq;
|
|
using SuccessfulCodeTools;
|
|
using SuccessfulCodeTools.LowLevelHttpClient;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Threading.Tasks;
|
|
using System.Web;
|
|
|
|
namespace BritishHorseRacingApi
|
|
{
|
|
public class ApiClient : BaseApiClient
|
|
{
|
|
private const int _waitTimeAfterFail = 20;
|
|
private const string _userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36";
|
|
|
|
public ApiClient(TimeSpan? delay = null) : base(delay) { }
|
|
|
|
public static class Endpoints
|
|
{
|
|
public const string Endpoint = "https://www.britishhorseracing.com/feeds";
|
|
private const string V1Endpoint = Endpoint + "/v1";
|
|
private const string V3Endpoint = Endpoint + "/v3";
|
|
public const string Fixtures = V3Endpoint + "/fixtures";
|
|
public const string Races = Fixtures + "/{0}/{1}/races";
|
|
public const string Runners = V3Endpoint + "/races/{0}/{1}/{2}/entries";
|
|
public const string RaceResults = V3Endpoint + "/races/{0}/{1}/{2}/results";
|
|
public const string Racecourses = V1Endpoint + "/racecourses";
|
|
public const string Jockeys = V3Endpoint + "/jockeys";
|
|
public const string Animals = V3Endpoint + "/racehorses/{0}";
|
|
}
|
|
|
|
public virtual string CurrentToken { get; set; }
|
|
|
|
public async Task<string> GetNewToken()
|
|
{
|
|
await DelayBetweenRequests();
|
|
var message = CreateTokenMessage();
|
|
var response = await _client.SendAsync(message);
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
throw new Exception("Token not retrieved");
|
|
}
|
|
|
|
var content = response.Content;
|
|
while (content.StartsWith("<"))
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(_waitTimeAfterFail));
|
|
_client.DefaultHeaders.Items.Clear();
|
|
message = CreateTokenMessage();
|
|
response = await _client.SendAsync(message);
|
|
content = response.Content;
|
|
}
|
|
|
|
var tokenResponse = content.DeserializeJson<TokenResponse>();
|
|
CurrentToken = tokenResponse.Token;
|
|
|
|
return tokenResponse.Token;
|
|
}
|
|
|
|
private static CustomHttpRequestMessage CreateTokenMessage()
|
|
{
|
|
var message = new CustomHttpRequestMessage(HttpMethod.Get, "http://www.britishhorseracing.com/feeds/v1/token/request");
|
|
message.Headers.Add("Accept", "application/json, text/plain, */*");
|
|
message.Headers.Add("Accept-Language", "en-US,en;q=0.9");
|
|
message.Headers.Add("User-Agent", _userAgent);
|
|
message.Headers.Add("Host", "www.britishhorseracing.com");
|
|
message.Headers.Add("cache-control", "no-cache");
|
|
message.Headers.Add("Connection", "close");
|
|
|
|
message.Headers.Add("accept-encoding", "gzip, deflate");
|
|
return message;
|
|
}
|
|
|
|
public async Task<List<Race>> GetAllRacesForDay(int year, int month, int day)
|
|
{
|
|
var fixtures = await GetAllFixturesForDay(year, month, day);
|
|
var races = new List<Race>();
|
|
foreach(var fixture in fixtures)
|
|
{
|
|
var page = await GetRaces(year, fixture.FixtureId);
|
|
races.AddRange(page.Data);
|
|
}
|
|
|
|
return races;
|
|
}
|
|
|
|
public async Task<List<RunnerResult>> GetAllRaceResultsForDay(int year, int month, int day)
|
|
{
|
|
var races = await GetAllRacesForDay(year, month, day);
|
|
var runnerResults = new List<RunnerResult>();
|
|
foreach (var race in races)
|
|
{
|
|
var page = await GetRaceResults(year, race.RaceId, race.DivisionSequence);
|
|
runnerResults.AddRange(page.Data);
|
|
}
|
|
return runnerResults;
|
|
}
|
|
|
|
public async Task<PagedResult<JockeySummary>> GetJockeys(int page = 1, int perPage = 2)
|
|
{
|
|
var uriBuilder = new UriBuilder(Endpoints.Jockeys)
|
|
{
|
|
Port = -1
|
|
};
|
|
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
|
|
query["page"] = page.ToString();
|
|
query["per_page"] = perPage.ToString();
|
|
uriBuilder.Query = query.ToString();
|
|
|
|
var results = await ExecuteRequestForPagedResults<JockeySummary>(uriBuilder.ToString());
|
|
return results;
|
|
}
|
|
|
|
public async Task<Jockey> GetJockey(int id)
|
|
{
|
|
var result = await ExecuteRequest<SingleResult<Jockey>>(Endpoints.Jockeys + $"/{id}", (token) => {
|
|
return !(token["data"] is JArray);
|
|
});
|
|
|
|
if(result == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return result.Data;
|
|
}
|
|
|
|
public async Task<PagedResult<RunnerResult>> GetRaceResults(int year, int raceId, int divisionSequence = 0)
|
|
{
|
|
var requestUrl = String.Format(Endpoints.RaceResults, year, raceId, divisionSequence);
|
|
return await ExecuteRequestForPagedResults<RunnerResult>(requestUrl);
|
|
}
|
|
|
|
public async Task<PagedResult<Runner>> GetRaceRunners(int year, int raceId, int divisionSequence = 0)
|
|
{
|
|
var requestUrl = String.Format(Endpoints.Runners, year, raceId, divisionSequence);
|
|
return await ExecuteRequestForPagedResults<Runner>(requestUrl);
|
|
}
|
|
|
|
public async Task<PagedResult<Racecourse>> GetRacecourses()
|
|
{
|
|
return await ExecuteRequestForPagedResults<Racecourse>(Endpoints.Racecourses);
|
|
}
|
|
|
|
public async Task<Racecourse> GetRacecourse(int id)
|
|
{
|
|
var result = await ExecuteRequest<SingleResult<Racecourse>>(Endpoints.Racecourses + "/" + id);
|
|
return result.Data;
|
|
}
|
|
|
|
public async Task<PagedResult<Race>> GetRaces(int year, int fixtureId)
|
|
{
|
|
var finalUrl = String.Format(Endpoints.Races, year, fixtureId);
|
|
return await ExecuteRequestForPagedResults<Race>(finalUrl);
|
|
}
|
|
|
|
public async Task<Animal> GetAnimal(int id)
|
|
{
|
|
var endpoint = String.Format(Endpoints.Animals, id);
|
|
var result = await ExecuteRequest<SingleResult<Animal>>(endpoint, (token) => !(token["data"] is JArray));
|
|
|
|
if (result == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return result.Data;
|
|
}
|
|
|
|
public async Task<List<Fixture>> GetAllFixturesForDay(int year, int month, int day)
|
|
{
|
|
var fixtures = new List<Fixture>();
|
|
var pages = 1;
|
|
|
|
for(var i = 1; i <= pages; i++)
|
|
{
|
|
var resultsPage = await GetFixtures(year, month, i);
|
|
|
|
foreach(var item in resultsPage.Data)
|
|
{
|
|
//Items returned day descending, skip until relevant day
|
|
if (item.FixtureDate.Day > day)
|
|
continue;
|
|
|
|
//If we hit another day dont bother getting more
|
|
if (item.FixtureDate.Day < day)
|
|
return fixtures;
|
|
|
|
fixtures.Add(item);
|
|
}
|
|
|
|
if (resultsPage.CurrentPage == resultsPage.LastPage)
|
|
return fixtures;
|
|
|
|
pages++;
|
|
}
|
|
|
|
return fixtures;
|
|
}
|
|
|
|
public async Task<PagedResult<Fixture>> GetFixtures(int? year = null, int? month = null, int page = 1, int perPage = 10, string order = "desc", bool? resultsAvailable = true)
|
|
{
|
|
var uriBuilder = new UriBuilder(Endpoints.Fixtures);
|
|
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
|
|
query["page"] = page.ToString();
|
|
query["per_page"] = perPage.ToString();
|
|
query["order"] = order;
|
|
if (year != null)
|
|
{
|
|
query["year"] = year.ToString();
|
|
}
|
|
|
|
if(month != null)
|
|
{
|
|
query["month"] = month.ToString();
|
|
}
|
|
|
|
if(resultsAvailable != null)
|
|
{
|
|
query["resultAvailable"] = resultsAvailable.Value.ToString();
|
|
}
|
|
|
|
uriBuilder.Query = query.ToString();
|
|
var finalUrl = uriBuilder.ToString();
|
|
return await ExecuteRequestForPagedResults<Fixture>(finalUrl);
|
|
}
|
|
|
|
private Task<PagedResult<T>> ExecuteRequestForPagedResults<T>(string endpoint)
|
|
{
|
|
return ExecuteRequest<PagedResult<T>>(endpoint);
|
|
}
|
|
|
|
private async Task<T> ExecuteRequest<T>(string endpoint, Func<JToken, bool> preDeserilizationValidation = null)
|
|
{
|
|
var response = await SendRequest(endpoint);
|
|
|
|
if(response.StatusCode == HttpStatusCode.Unauthorized)
|
|
{
|
|
await GetNewToken();
|
|
response = await SendRequest(endpoint);
|
|
}
|
|
|
|
var content = response.Content;
|
|
while (content.StartsWith("<"))
|
|
{
|
|
await Task.Delay(TimeSpan.FromMinutes(_waitTimeAfterFail));
|
|
//_client.OutgoingPort++;
|
|
await GetNewToken();
|
|
response = await SendRequest(endpoint);
|
|
content = response.Content;
|
|
}
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
throw new Exception($"Unexpected error: {content}" );
|
|
}
|
|
|
|
if(preDeserilizationValidation != null && !preDeserilizationValidation(JToken.Parse(content)))
|
|
{
|
|
return default(T);
|
|
}
|
|
|
|
var deserializedResponse = content.DeserializeJson<T>();
|
|
return deserializedResponse;
|
|
}
|
|
|
|
protected override async Task PresendRequestAlterations(CustomHttpRequestMessage message)
|
|
{
|
|
message.Headers.Add("User-Agent", _userAgent);
|
|
if (CurrentToken == null)
|
|
{
|
|
await GetNewToken();
|
|
}
|
|
//message.Headers.TryAddWithoutValidation("Authorization", CurrentToken);
|
|
message.Headers.Add("Authorization", CurrentToken);
|
|
message.Headers.Add("Accept", "application/json");
|
|
message.Headers.Add("accept-encoding", "gzip, deflate");
|
|
message.Headers.Add("Accept-Language", "en-US,en;q=0.9");
|
|
}
|
|
}
|
|
}
|