diff --git a/lottery-co-uk-scraper/EuroMillions/DrawBalls.cs b/lottery-co-uk-scraper/EuroMillions/DrawBalls.cs new file mode 100644 index 0000000..f94502f --- /dev/null +++ b/lottery-co-uk-scraper/EuroMillions/DrawBalls.cs @@ -0,0 +1,242 @@ +using HtmlAgilityPack; +using lottery_co_uk_scraper.core.Models; +using lottery_co_uk_scraper.Utilities; +using Microsoft.Extensions.Logging; +using static lottery_co_uk_scraper.NationalLottery.Lotto; + +namespace lottery_co_uk_scraper.EuroMillions +{ + internal class DrawBalls + { + private static readonly ILogger _logger; + + private static HtmlNode GetNodeById(HtmlNode containerNode, string nodeId) + { + try + { + return containerNode.DescendantsAndSelf() + .FirstOrDefault(x => x.Attributes["id"] != null && x.Attributes["id"].Value == nodeId) + ?? throw new InvalidOperationException($"Node with ID '{nodeId}' not found."); + } + catch + { + throw new InvalidOperationException($"Node with ID '{nodeId}' not found."); + } + } + + public static int ProcessBallSetUsed(HtmlDocument doc, EurosResult eurosResult) + { + try + { + var ballSetUsedString = doc.DocumentNode.Descendants("td") + .Where(x => x.InnerHtml.Contains("Ball Set Used:")) + .Select(x => x.InnerText.Split(':')[1].Trim()) + .FirstOrDefault(); + + if (int.TryParse(ballSetUsedString, out int ballSetUsed)) + { + AssignBallSetToModelProperty(LotteryTableRow.BallSetUsed, ballSetUsed, eurosResult); + + return ballSetUsed; + } + else + { + _logger.LogInformation("Failed to parse Euro Millions Ball Set Used."); + + throw new Exception("Failed to parse Euro Millions Ball Set Used."); + } + } + catch + { + throw new Exception("Failed to parse Euro Millions Ball Set Used."); + } + } + + public static void AssignBallSetToModelProperty(string containerNode, int ballSetUsed, EurosResult eurosResult) + { + if (containerNode == LotteryTableRow.BallSetUsed) + { + PropertyManager.SetProperty(nameof(eurosResult.BallSetUsed), eurosResult, ballSetUsed); + } + } + + public static void ProcessMainBalls(HtmlDocument doc, EurosResult eurosResult) + { + try + { + var ballsDrawn = GetNodeById(doc.DocumentNode, LotteryTableRow.BallsDrawn); + + List? euroBalls = ballsDrawn != null + ? ExtractBalls(ballsDrawn, "euromillions-ball") + : null; + + if (euroBalls != null) + { + AssignDrawBallsToModelProperty(LotteryTableRow.BallsDrawn, euroBalls, eurosResult); + } + else + { + _logger.LogError("EuroMillions Draw Balls were null!"); + + throw new Exception("EuroMillions Draw Balls were null!"); + } + } + catch (Exception ex) + { + _logger.LogError("There was a problem processing the EuroMillions Draw Balls. {Message}", ex.Message); + + throw new Exception($"There was a problem processing the EuroMillions Draw Balls. {ex}"); + } + } + + private static List ExtractBalls(HtmlNode ballsNode, string className) + { + try + { + return ballsNode.Descendants("span") + .Where(x => x.Attributes["class"] != null && x.Attributes["class"].Value.Contains(className)) + .Select(x => + { + if (int.TryParse(x.InnerText, out int ball)) + { + return ball; + } + + _logger.LogError("Failed to parse {className} value: {x.InnerText}", className, x.InnerText); + + throw new Exception($"Failed to parse {className} value: {x.InnerText}"); + }) + .ToList(); + } + catch + { + throw new Exception($"Failed to parse {className}"); + } + } + + public static void AssignDrawBallsToModelProperty(string containerNode, object ballInformation, EurosResult eurosResult) + { + try + { + if (containerNode == LotteryTableRow.BallsDrawn) + { + if (ballInformation is List balls && balls.Count >= 5) + { + for (int i = 0; i < 5; i++) + { + string propertyName = $"DrawnBall{i + 1}"; + PropertyManager.SetProperty(propertyName, eurosResult, balls[i]); + } + } + else + { + _logger.LogError("Invalid drawn balls information."); + + throw new Exception("Invalid drawn balls information."); + } + } + else + { + _logger.LogError("Unhandled containerNode: {containerNode}", containerNode); + + throw new Exception($"Unhandled containerNode: {containerNode}"); + } + } + catch + { + // ToDo: + } + } + + public static void ProcessLuckyStars(HtmlDocument doc, EurosResult eurosResult) + { + try + { + var ballsDrawn = GetNodeById(doc.DocumentNode, LotteryTableRow.BallsDrawn); + + List? luckyStars = ballsDrawn != null + ? ExtractLuckyStar(ballsDrawn, "euromillions-lucky-star") + : null; + + if (luckyStars != null) + { + AssignLuckyStarToModelProperty(LotteryTableRow.BallsDrawn, luckyStars, eurosResult); + } + else + { + _logger.LogError("Lucky Stars appear to be null."); + + throw new Exception("Lucky Stars appear to be null."); + } + } + catch (Exception ex) + { + _logger.LogError("There was a problem processing the EuroMillions Lucky Stars. {Message}", ex.Message); + + throw new Exception($"There was a problem processing the EuroMillions Lucky Stars. {ex}"); + } + } + + private static ListExtractLuckyStar(HtmlNode ballsNode, string ballClass) + { + try + { + return ballsNode.Descendants("span") + .Where(x => x.HasClass(ballClass)) + .Select(x => + { + if (int.TryParse(x.InnerText, out int ball)) + { + return ball; + } + + _logger.LogError("Failed to parse {className} value: {x.InnerText}", ballClass, x.InnerText); + + throw new Exception($"Failed to parse {ballClass} value: {x.InnerText}"); + }) + .ToList(); + } + catch + { + _logger.LogError("There was a problem processing the EuroMillions Lucky Stars."); + + throw new Exception($"There was a problem processing the EuroMillions Lucky Stars."); + } + + } + + public static void AssignLuckyStarToModelProperty(string containerNode, object ballInformation, EurosResult eurosResult) + { + try + { + if (containerNode == LotteryTableRow.BallsDrawn) + { + if (ballInformation is List balls && balls.Count >= 2) + { + for (int i = 0; i < 2; i++) + { + string propertyName = $"LuckyStar{i + 1}"; + PropertyManager.SetProperty(propertyName, eurosResult, balls[i]); + } + } + else + { + _logger.LogError("Invalid Lucky Star balls information."); + + throw new Exception("Invalid Lucky Star balls information."); + } + } + else + { + _logger.LogError("Unhandled containerNode: {containerNode}", containerNode); + + throw new Exception($"Unhandled containerNode: {containerNode}"); + } + } + catch + { + // ToDo: + } + } + } +} \ No newline at end of file