Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support crumb and cookie to fetching data #49

Merged
merged 3 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/Helpers/CrumbHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Net.Http;

namespace OoplesFinance.YahooFinanceAPI.Helpers
{
internal sealed class CrumbHelper
{
/// <summary>
/// Crumb value for the Yahoo Finance API
/// </summary>
internal readonly string Crumb;

private static List<string> cookies = new List<string>();
private static CrumbHelper? _instance;
CrumbHelper()
{
Crumb = string.Empty;

HttpClient client = GetHttpClient();

var loginResponse = client.GetAsync("https://login.yahoo.com/").Result;

if (loginResponse.IsSuccessStatusCode)
{
var login = loginResponse.Content.ReadAsStringAsync().Result;
if (loginResponse.Headers.TryGetValues(name: "Set-Cookie", out IEnumerable<string>? setCookie))
{
cookies = setCookie.Where(c => c.ToLower().IndexOf("domain=.yahoo.com") != -1).ToList();
var crumbResponse = client.GetAsync("https://query1.finance.yahoo.com/v1/test/getcrumb").Result;

if (crumbResponse.IsSuccessStatusCode)
{
Crumb = crumbResponse.Content.ReadAsStringAsync().Result;
}
}
}
if (string.IsNullOrEmpty(Crumb))
{
throw new Exception("Failed to get crumb");
}
}

public HttpClient GetHttpClient()
{
HttpClientHandler handler = new HttpClientHandler();
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Cookie", cookies);
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3");
client.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
client.DefaultRequestHeaders.Add("Connection", "keep-alive");
client.DefaultRequestHeaders.Add("Pragma", "no-cache");
client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
return client;
}

/// <summary>
/// Single instance of the CrumbHelper
/// </summary>
public static CrumbHelper Instance
{
get
{
if (_instance == null)
{
_instance = new CrumbHelper();
}
return _instance;
}
}
}
}
5 changes: 2 additions & 3 deletions src/Helpers/DownloadHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,8 @@ private static async Task<string> DownloadRawDataAsync(string urlString)
}
else
{
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, urlString);
var response = await client.SendAsync(request);
using var client = CrumbHelper.Instance.GetHttpClient();
var response = await client.GetAsync(urlString);

if (response.IsSuccessStatusCode)
{
Expand Down
9 changes: 4 additions & 5 deletions src/Helpers/UrlHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal static class UrlHelper
/// <param name="includeAdjClose"></param>
/// <returns></returns>
internal static string BuildYahooCsvUrl(string symbol, DataType dataType, DataFrequency dataFrequency, DateTime startDate, DateTime? endDate, bool includeAdjClose) =>
string.Format(CultureInfo.InvariantCulture, $"https://query1.finance.yahoo.com/v7/finance/download/{symbol}?period1={startDate.ToUnixTimestamp()}" +
string.Format(CultureInfo.InvariantCulture, $"https://query2.finance.yahoo.com/v7/finance/download/{symbol}?period1={startDate.ToUnixTimestamp()}" +
$"&period2={(endDate ?? DateTime.Now).ToUnixTimestamp()}&interval={GetFrequencyString(dataFrequency)}&events={GetEventsString(dataType)}" +
$"&includeAdjustedClose={includeAdjClose}");

Expand Down Expand Up @@ -103,8 +103,7 @@ internal static string BuildYahooSparkChartUrl(IEnumerable<string> symbols, Time
/// <param name="module"></param>
/// <returns></returns>
internal static string BuildYahooStatsUrl(string symbol, Country country, Language language, YahooModule module) =>
string.Format(CultureInfo.InvariantCulture, $"https://query1.finance.yahoo.com/v11/finance/quoteSummary/{symbol}?lang={GetLanguageString(language)}" +
$"&region={GetCountryString(country)}&modules={GetModuleString(module)}");
string.Format(CultureInfo.InvariantCulture, $"https://query2.finance.yahoo.com/v10/finance/quoteSummary/{symbol}?crumb={CrumbHelper.Instance.Crumb}&lang={GetLanguageString(language)}&region={GetCountryString(country)}&modules={GetModuleString(module)}");

/// <summary>
/// Creates a url that will be used to get real-time quotes for multiple symbols
Expand All @@ -114,8 +113,8 @@ internal static string BuildYahooStatsUrl(string symbol, Country country, Langua
/// <param name="language"></param>
/// <returns></returns>
internal static string BuildYahooRealTimeQuoteUrl(IEnumerable<string> symbols, Country country, Language language) =>
string.Format(CultureInfo.InvariantCulture, $"https://query1.finance.yahoo.com/v6/finance/quote?region=" +
$"{GetCountryString(country)}&lang={GetLanguageString(language)}&symbols={GetSymbolsString(symbols)}");
string.Format(CultureInfo.InvariantCulture, $"https://query2.finance.yahoo.com/v7/finance/quote?region=" +
$"{GetCountryString(country)}&lang={GetLanguageString(language)}&symbols={GetSymbolsString(symbols)}&crumb={CrumbHelper.Instance.Crumb}");

/// <summary>
/// Returns a custom string for the symbols option
Expand Down
2 changes: 1 addition & 1 deletion src/Models/TrendingStocksData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public record TrendingStocksQuote(
[property: JsonProperty("epsForward", NullValueHandling = NullValueHandling.Ignore)] double? EpsForward,
[property: JsonProperty("epsCurrentYear", NullValueHandling = NullValueHandling.Ignore)] double? EpsCurrentYear,
[property: JsonProperty("priceEpsCurrentYear", NullValueHandling = NullValueHandling.Ignore)] double? PriceEpsCurrentYear,
[property: JsonProperty("sharesOutstanding", NullValueHandling = NullValueHandling.Ignore)] int? SharesOutstanding,
[property: JsonProperty("sharesOutstanding", NullValueHandling = NullValueHandling.Ignore)] long? SharesOutstanding,
[property: JsonProperty("bookValue", NullValueHandling = NullValueHandling.Ignore)] double? BookValue,
[property: JsonProperty("fiftyDayAverage", NullValueHandling = NullValueHandling.Ignore)] double? FiftyDayAverage,
[property: JsonProperty("fiftyDayAverageChange", NullValueHandling = NullValueHandling.Ignore)] double? FiftyDayAverageChange,
Expand Down
4 changes: 2 additions & 2 deletions src/YahooClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,9 +569,9 @@ public async Task<IEnumerable<SparkInfo>> GetSparkChartInfoAsync(IEnumerable<str
/// </summary>
/// <param name="symbol"></param>
/// <returns></returns>
public async Task<RealTimeQuoteResult> GetRealTimeQuotesAsync(string symbol)
public async Task<RealTimeQuoteResult?> GetRealTimeQuotesAsync(string symbol)
{
return new RealTimeQuoteHelper().ParseYahooJsonData<RealTimeQuoteResult>(await DownloadRealTimeQuoteDataAsync(symbol, Country, Language)).First();
return new RealTimeQuoteHelper().ParseYahooJsonData<RealTimeQuoteResult>(await DownloadRealTimeQuoteDataAsync(symbol, Country, Language)).FirstOrDefault();
}

/// <summary>
Expand Down
15 changes: 8 additions & 7 deletions tests/UnitTests/YahooClientTests.cs
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are good changes but you would need to add some unit tests for your crumb helper class with at least one positive and negative test case. Negative test case could be to assert that the exception is thrown when crumb is null or empty. Positive case could be to assert that crumb is not null or empty when everything is valid

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public sealed class YahooClientTests
private readonly YahooClient _sut;
private const string BadSymbol = "OOPLES";
private const string GoodSymbol = "MSFT";
private const string GoodFundSymbol = "VTSAX";
private const int ValidCount = 10;
private const int InvalidCount = -1;
private readonly DateTime _startDate;
Expand All @@ -15,10 +16,10 @@ public sealed class YahooClientTests
public YahooClientTests()
{
_sut = new YahooClient();
_startDate = DateTime.Now.AddYears(-1);
_startDate = DateTime.Now.AddMonths(-1);
_emptySymbols = Enumerable.Empty<string>();
_tooManySymbols = Enumerable.Repeat(GoodSymbol, 255);
_goodSymbols = Enumerable.Repeat(GoodSymbol, 50);
_goodSymbols = Enumerable.Repeat(GoodSymbol, 20);
}

[Fact]
Expand Down Expand Up @@ -183,10 +184,10 @@ public async Task GetTopTrendingStocks_ReturnsData_WhenCountIsValid()
// Arrange

// Act
var result = async () => await _sut.GetTopTrendingStocksAsync(Country.UnitedStates, ValidCount);
var result = await _sut.GetTopTrendingStocksAsync(Country.UnitedStates, ValidCount);

// Assert
await result.Should().ThrowAsync<ArgumentException>().WithMessage("Count Must Be At Least 1 To Return Any Data");
result.Should().NotBeNull();
}

[Fact]
Expand Down Expand Up @@ -903,7 +904,7 @@ public async Task GetFundProfile_ReturnsData_WhenValidSymbolIsUsed()
// Arrange

// Act
var result = await _sut.GetFundProfileAsync(GoodSymbol);
var result = await _sut.GetFundProfileAsync(GoodFundSymbol);

// Assert
result.Should().NotBeNull();
Expand Down Expand Up @@ -1455,10 +1456,10 @@ public async Task GetRealTimeQuotes_ThrowsException_WhenNoSymbolIsFound()
// Arrange

// Act
var result = async () => await _sut.GetRealTimeQuotesAsync(BadSymbol);
var result = await _sut.GetRealTimeQuotesAsync(BadSymbol);

// Assert
await result.Should().ThrowAsync<InvalidOperationException>().WithMessage("Requested Information Not Available On Yahoo Finance");
result.Should().BeNull();
}

[Fact]
Expand Down