Files
BritishHorseRacingApi/BritishHorseRacingApi/ApiClient.cs
T
Martin 3e0b9690c8
SuccessfulCode/BritishHorseRacingApi/master This commit looks good
Change headers
2020-02-26 17:01:33 +00:00

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");
}
}
}