private string GetImageLeaderboardEntry(ScoreboardSummaryEntry team, ScoreboardImageDetails image, int friendlyIndex, bool useAbbreviatedDivision = false, string prefix = "#") { string divisionFormatString = useAbbreviatedDivision ? "{0,6}" : " {0,-10}"; string vulnPenString = new string(' ', 10); if (ScoreFormattingOptions.EvaluateNumericDisplay(ScoreRetrieverMetadata.FormattingOptions.VulnerabilityDisplay, image.VulnerabilitiesFound, image.VulnerabilitiesRemaining)) { vulnPenString = $"{image.VulnerabilitiesFound,5}v {image.Penalties,2}p"; } return($"{prefix}{friendlyIndex,-5}{team.TeamId,-7}{team.Location,4}" + string.Format(divisionFormatString, AbbreviateDivision(team)) + $"{team.Tier,10}" + $"{ScoreRetrieverMetadata.FormattingOptions.FormatScoreForLeaderboard(image.Score),13}" + vulnPenString + $"{(ScoreFormattingOptions.EvaluateNumericDisplay(ScoreRetrieverMetadata.FormattingOptions.TimeDisplay, image.PlayTime) ? image.PlayTime.ToHoursMinutesSecondsString() : ""),7}" + $"{image.Warnings.ToConciseString(),4}"); }
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); }
public string CreateImageLeaderboardEmbed(IEnumerable <KeyValuePair <ScoreboardSummaryEntry, ScoreboardImageDetails> > completeImageData, string filterDescription, int teamCount = -1, int pageNumber = 1, int pageSize = 15) { if (pageSize <= 0) { throw new ArgumentOutOfRangeException(nameof(pageSize)); } if (teamCount == -1) { completeImageData = completeImageData.ToIList(); teamCount = completeImageData.Count(); } int pageCount = (int)Math.Ceiling(((double)teamCount) / pageSize); pageNumber--; if (pageNumber < 0 || pageNumber >= pageCount) { throw new ArgumentOutOfRangeException(nameof(pageNumber)); } IList <KeyValuePair <ScoreboardSummaryEntry, ScoreboardImageDetails> > thisPageImageData = completeImageData.Skip(pageNumber * pageSize).Take(pageSize).ToIList(); StringBuilder resultBuilder = new StringBuilder(); resultBuilder.Append("**CyberPatriot Image Scoreboard"); if (!string.IsNullOrWhiteSpace(filterDescription)) { resultBuilder.Append(", ").Append(filterDescription); } if (pageCount > 1) { resultBuilder.Append($" (Page {pageNumber + 1} of {pageCount})"); } resultBuilder.AppendLine("**"); ScoreboardImageDetails canonicalImage = thisPageImageData[0].Value; resultBuilder.AppendFormat("**`{0}`", canonicalImage.ImageName); int vulnCt = canonicalImage.VulnerabilitiesRemaining + canonicalImage.VulnerabilitiesFound; double pts = canonicalImage.PointsPossible; bool displayVulns = ScoreFormattingOptions.EvaluateNumericDisplay(ScoreRetrieverMetadata.FormattingOptions.VulnerabilityDisplay, vulnCt); bool displayPts = ScoreFormattingOptions.EvaluateNumericDisplay(ScoreRetrieverMetadata.FormattingOptions.VulnerabilityDisplay, pts); if (displayVulns || displayPts) { resultBuilder.Append(": "); if (displayVulns) { resultBuilder.Append(Utilities.Pluralize("vulnerability", vulnCt)); if (displayPts) { resultBuilder.Append(", "); } } if (displayPts) { resultBuilder.Append(ScoreRetrieverMetadata.FormattingOptions.FormatScore(pts)).Append(" points possible"); } } resultBuilder.AppendLine("**"); resultBuilder.AppendLine("```"); bool conciseDivision = !thisPageImageData.Any(x => x.Key.Category != null); for (int i = 0; i < thisPageImageData.Count; i++) { var teamScore = thisPageImageData[i]; int friendlyIndex = i + 1 + (pageNumber * pageSize); resultBuilder.AppendLine(GetImageLeaderboardEntry(teamScore.Key, teamScore.Value, friendlyIndex, useAbbreviatedDivision: conciseDivision)); } resultBuilder.AppendLine("```"); return(resultBuilder.ToString()); }