using AngleSharp.Dom; using Sony.Filtr.Core.SpotifyCharts.Data; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; using static Sony.Filtr.Functional.FunctionalProgramming; namespace Sony.Filtr.Core.SpotifyCharts.Extensions { public abstract class ChartReader { public ChartReader() { } public abstract IEnumerable ToChartItems(); } public abstract class ChartReader : ChartReader { private readonly Func> GetRows; private readonly Func GetTrackLink; private readonly Func GetStreams; private readonly Func GetPosition; private readonly Func GetTrackNameAndArtist; public ChartReader( Func> getRows, Func getTrackLink, Func getStreams, Func getPosition, Func getTrackNameAndArtist) { this.GetRows = getRows; this.GetTrackLink = getTrackLink; this.GetStreams = getStreams; this.GetPosition = getPosition; this.GetTrackNameAndArtist = getTrackNameAndArtist; } protected IEnumerable ToChartItems(TMain container) { var items = new List(); foreach (TRow row in this.GetRows(container)) { ChartItemCsv item = this.ToChartItem(row); if (item != null) { items.Add(item); } } return items; } private ChartItemCsv ToChartItem(TRow row) { var item = new ChartItemCsv(); try { AddLinkAndId(this.GetTrackLink(row), item); AddStreams(this.GetStreams(row), item); AddTrackPosition(this.GetPosition(row), item); AddTrackNameAndArtist(this.GetTrackNameAndArtist(row), item); } catch (Exception ex) { } return ReturnItemIfValidOrNULL(item); } private static ChartItemCsv ReturnItemIfValidOrNULL(ChartItemCsv item) { if (!String.IsNullOrWhiteSpace(item.ArtistName) && !String.IsNullOrWhiteSpace(item.TrackName) && !String.IsNullOrWhiteSpace(item.TrackUrl) && !String.IsNullOrWhiteSpace(item.TrackId)) { return item; } return null; } private ChartItemCsv AddLinkAndId(string trackLink, ChartItemCsv item) { if (!String.IsNullOrWhiteSpace(trackLink)) { item.TrackUrl = trackLink; var uri = new Uri(trackLink); if (uri.Segments != null && uri.Segments.Length == 3 && uri.Segments[1].ToLower() == "track/") { item.TrackId = uri.Segments[2]; } } return item; } private static Regex NonDigitRegex = new Regex(@"[^\d]"); private ChartItemCsv AddStreams(string streams, ChartItemCsv item) { if (!String.IsNullOrWhiteSpace(streams)) { if (Int32.TryParse(NonDigitRegex.Replace(streams, ""), out int i)) { item.NumberOfStreams = i; } } return item; } private ChartItemCsv AddTrackPosition(string position, ChartItemCsv item) { if (!String.IsNullOrWhiteSpace(position)) { if (Int32.TryParse(position, out int i)) { item.Position = i; } } return item; } private ChartItemCsv AddTrackNameAndArtist((string TrackName, string Artist) trackNameAndArtist, ChartItemCsv item) { item.TrackName = System.Net.WebUtility.HtmlDecode(trackNameAndArtist.TrackName); item.ArtistName = System.Net.WebUtility.HtmlDecode(trackNameAndArtist.Artist); return item; } } public class DocumentChartReader : ChartReader { private readonly IDocument Document; private static Regex ArtistNameRegex = new Regex("^by (.+)$", RegexOptions.IgnoreCase); public DocumentChartReader(IDocument document) : base(d => d.QuerySelectorAll("table.chart-table tr"), row => row.QuerySelector("td.chart-table-image a")?.GetAttribute("href"), row => row.QuerySelector("td.chart-table-streams")?.InnerHtml, row => row.QuerySelector("td.chart-table-position")?.InnerHtml, row => { var trackTd = row.QuerySelector("td.chart-table-track"); var trackName = trackTd?.QuerySelector("strong")?.InnerHtml; var artist = trackTd?.QuerySelector("span")?.InnerHtml; if (!String.IsNullOrWhiteSpace(artist)) { var match = ArtistNameRegex.Match(artist); if (match.Success) { artist = match.Groups[1].Value.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries)[0]; } } return (trackName, artist); }) { this.Document = document; } public override IEnumerable ToChartItems() { return base.ToChartItems(this.Document); } } public class StreamLinesChartReader : ChartReader { private enum Segments { Position, TrackName, Artist, Streams, TrackLink } private readonly Stream Stream; private readonly ChartData Chart; private static readonly string[] LineDelimiter = new string[] { "\",\"" }; //"Position","Track Name","Artist","URL" - VIRAL private static Dictionary> viralMap = new Dictionary> { {Segments.Position, PartialFirst(GetLineSegment, 0) }, {Segments.TrackName, PartialFirst(GetLineSegment, 1) }, {Segments.Artist, PartialFirst(GetLineSegment, 2) }, {Segments.TrackLink, PartialFirst(GetLineSegment, 3) }, {Segments.Streams, PartialFirst(GetEmptyStreams, 0) }, }; //"Position","Track Name","Artist","Streams","URL" - REGIONAL private static Dictionary> regionalMap = new Dictionary> { {Segments.Position, PartialFirst(GetLineSegment, 0) }, {Segments.TrackName, PartialFirst(GetLineSegment, 1) }, {Segments.Artist, PartialFirst(GetLineSegment, 2) }, {Segments.TrackLink, PartialFirst(GetLineSegment, 4) }, {Segments.Streams, PartialFirst(GetLineSegment, 3) }, }; public StreamLinesChartReader(Stream stream, ChartData chart) : base(PartialFirst>(ReadLinesFromStream, chart.ListType == SpotifyChartsListType.Viral ? 1 : 2), line => GetLineSegment(line, Segments.TrackLink, chart), line => GetLineSegment(line, Segments.Streams, chart), line => GetLineSegment(line, Segments.Position, chart), line => (GetLineSegment(line, Segments.TrackName, chart), GetLineSegment(line, Segments.Artist, chart))) { this.Stream = stream; this.Chart = chart; } public override IEnumerable ToChartItems() { return base.ToChartItems(this.Stream); } private static string GetEmptyStreams(int segment, string line) { return "0"; } private static string GetLineSegment(string line, Segments segment, ChartData chart) { var map = chart.ListType == SpotifyChartsListType.Viral ? viralMap : regionalMap; return map[segment](line); } private static string GetLineSegment(int segment, string line) { var split = line.Split(LineDelimiter, StringSplitOptions.RemoveEmptyEntries); //"1","MONTERO (Call Me By Your Name)","Lil Nas X","10869","https://open.spotify.com/track/67BtfxlNbhBmCDR2L2l8qd" return split[segment].Trim('"').Trim('\\'); } private static IEnumerable ReadLinesFromStream(int linesToSkip, Stream stream) { using (stream) { using (var reader = new StreamReader(stream, Encoding.UTF8)) { string line = null; for (int i = 0; i < linesToSkip; ++i) { reader.ReadLine(); } while ((line = reader.ReadLine()) != null) { yield return line; } } } } } }