Exemple #1
0
 double ICompetitionRoundLogicService.GetCiscoPointsPossible(CompetitionRound round, Division division, Tier?tier) => underlyingService.GetCiscoPointsPossible(round, division, tier);
        public async Task <ScoreboardDetails> GetDetailsAsync(TeamId team)
        {
            if (team == null)
            {
                throw new ArgumentNullException(nameof(team));
            }

            string detailsPage;
            Uri    detailsUri = BuildDetailsUri(team);

            await RateLimiter.GetWorkAuthorizationAsync().ConfigureAwait(false);

            Task <string> stringTask = Client.GetStringAsync(detailsUri);

            RateLimiter.AddPrerequisite(stringTask);
            try
            {
                detailsPage = await stringTask.ConfigureAwait(false);

                // hacky, cause they don't return a proper error page for nonexistant teams
                if (!detailsPage.Contains(@"<div id='chart_div' class='chart'>"))
                {
                    throw new ArgumentException("The given team does not exist.");
                }
            }
            catch (HttpRequestException e)
            {
                throw new InvalidOperationException("Error getting team details page, perhaps the scoreboard is offline?", e);
            }

            ScoreboardDetails retVal = new ScoreboardDetails();

            retVal.OriginUri = detailsUri;

            HtmlDocument doc = new HtmlDocument();

            doc.LoadHtml(detailsPage);
            var timestampHeader = doc.DocumentNode.SelectSingleNode("/html/body/div[2]/div/h2[2]")?.InnerText;

            retVal.SnapshotTimestamp = timestampHeader == null ? DateTimeOffset.UtcNow : DateTimeOffset.Parse(timestampHeader.Replace("Generated At: ", string.Empty).Replace("UTC", "+0:00"));
            var summaryHeaderRow     = doc.DocumentNode.SelectSingleNode("/html/body/div[2]/div/table[1]/tr[1]");
            var summaryHeaderRowData = summaryHeaderRow.ChildNodes.Select(x => x.InnerText).ToArray();
            var summaryRow           = doc.DocumentNode.SelectSingleNode("/html/body/div[2]/div/table[1]/tr[2]");
            var summaryRowData       = summaryRow.ChildNodes.Select(x => x.InnerText).ToArray();

            ParseDetailedSummaryEntry(retVal, summaryRowData);

            // summary parsed
            var imagesTable = doc.DocumentNode.SelectSingleNode("/html/body/div[2]/div/table[2]").ChildNodes.Where(n => n.Name != "#text").ToArray();

            for (int i = 1; i < imagesTable.Length; i++)
            {
                // skip team IDs to account for legacy scoreboards
                string[] dataEntries         = imagesTable[i].ChildNodes.Select(n => n.InnerText.Trim()).SkipWhile(s => TeamId.TryParse(s, out TeamId _)).ToArray();
                ScoreboardImageDetails image = new ScoreboardImageDetails();
                image.PointsPossible           = 100;
                image.ImageName                = dataEntries[0];
                image.PlayTime                 = Utilities.ParseHourMinuteSecondTimespan(dataEntries[1]);
                image.VulnerabilitiesFound     = int.Parse(dataEntries[2]);
                image.VulnerabilitiesRemaining = int.Parse(dataEntries[3]);
                image.Penalties                = int.Parse(dataEntries[4]);
                image.Score     = double.Parse(dataEntries[5]);
                image.Warnings |= dataEntries[6].Contains("T") ? ScoreWarnings.TimeOver : 0;
                image.Warnings |= dataEntries[6].Contains("M") ? ScoreWarnings.MultiImage : 0;
                retVal.Images.Add(image);
            }

            // reparse summary table (CCS+Cisco case)
            // pseudoimages: Cisco, administrative adjustment (usually penalty)
            int ciscoIndex   = summaryHeaderRowData.IndexOfWhere(x => x.ToLower().Contains("cisco"));
            int penaltyIndex = summaryHeaderRowData.IndexOfWhere(x => x.ToLower().Contains("adjust"));

            ScoreboardImageDetails CreatePseudoImage(string name, double score, double possible)
            {
                var image = new ScoreboardImageDetails();

                image.PointsPossible = possible;
                image.ImageName      = name;
                image.Score          = score;

                image.VulnerabilitiesFound     = 0;
                image.VulnerabilitiesRemaining = 0;
                image.Penalties = 0;
                image.Warnings  = 0;
                image.PlayTime  = TimeSpan.Zero;

                return(image);
            }

            if (ciscoIndex != -1)
            {
                // pseudoimage
                // FIXME shouldn't display vulns and penalties and time

                double ciscoDenom = -1;
                try
                {
                    ciscoDenom = _roundInferenceService.GetCiscoPointsPossible(Round, retVal.Summary.Division, retVal.Summary.Tier);
                }
                catch
                {
                    // probably because round 0; unknown total
                }

                retVal.Images.Add(CreatePseudoImage("Cisco (Total)", double.Parse(summaryRowData[ciscoIndex]), ciscoDenom));
            }

            if (penaltyIndex != -1)
            {
                retVal.Images.Add(CreatePseudoImage("Administrative Adjustment", double.Parse(summaryRowData[penaltyIndex]), 0));
            }

            // score graph
            try
            {
                var   teamScoreGraphHeader = new Regex(@"\['Time'(?:, '(\w+)')* *\]");
                var   teamScoreGraphEntry  = new Regex(@"\['(\d{2}/\d{2} \d{2}:\d{2})'(?:, (-?\d+|null))*\]");
                Match headerMatch          = teamScoreGraphHeader.Match(detailsPage);
                if (headerMatch?.Success ?? false)
                {
                    retVal.ImageScoresOverTime = new Dictionary <string, SortedDictionary <DateTimeOffset, int?> >();
                    string[] imageHeaders = headerMatch.Groups[1].Captures.Cast <Capture>().Select(c => c.Value).ToArray();
                    SortedDictionary <DateTimeOffset, int?>[] dictArr = new SortedDictionary <DateTimeOffset, int?> [imageHeaders.Length];
                    for (int i = 0; i < dictArr.Length; i++)
                    {
                        dictArr[i] = new SortedDictionary <DateTimeOffset, int?>();
                        retVal.ImageScoresOverTime[imageHeaders[i]] = dictArr[i];
                    }
                    foreach (var m in teamScoreGraphEntry.Matches(detailsPage).Cast <Match>().Where(g => g?.Success ?? false))
                    {
                        DateTimeOffset dto = default(DateTimeOffset);
                        try
                        {
                            // MM/dd hh:mm
                            string   dateStr           = m.Groups[1].Value;
                            string[] dateStrComponents = dateStr.Split(' ');
                            string[] dateComponents    = dateStrComponents[0].Split('/');
                            string[] timeComponents    = dateStrComponents[1].Split(':');
                            dto = new DateTimeOffset(DateTimeOffset.UtcNow.Year, int.Parse(dateComponents[0]), int.Parse(dateComponents[1]), int.Parse(timeComponents[0]), int.Parse(timeComponents[1]), 0, TimeSpan.Zero);
                        }
                        catch
                        {
                            continue;
                        }

                        var captures = m.Groups[2].Captures;

                        for (int i = 0; i < captures.Count; i++)
                        {
                            int?scoreVal = null;
                            if (int.TryParse(captures[i].Value, out int thingValTemp))
                            {
                                scoreVal = thingValTemp;
                            }
                            dictArr[i][dto] = scoreVal;
                        }
                    }
                }
            }
            catch
            {
                // TODO log
            }
            return(retVal);
        }