using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Argotic.Common; using Argotic.Extensions.Core; using Argotic.Syndication; using Serilog; using SharpRss.Models; namespace SharpRss { public struct SyndicationContainer { public GenericSyndicationFeed SyndicationFeed { get; set; } public CategoryModel Category { get; set; } public SyndicationModel SyndicationModel { get; set; } public HashSet SyndicationItems { get; set; } public bool Fetched; } public static class SyndicationManager { public static SyndicationContainer CreateSyndication(string feedUrl) { GenericSyndicationFeed? syndicationFeed = null; Uri feedUri = new Uri(feedUrl); try { Log.Information("Fetching syndication: {SyndicationUri}", feedUri.ToString()); syndicationFeed = GenericSyndicationFeed.Create(feedUri); } catch (Exception e) { Log.Error(e,"Could not get syndication: {SyndicationUrl}", feedUrl); } return ConstructSyndicationContainer(syndicationFeed, feedUrl); } public static Stream SyndicationToStream(GenericSyndicationFeed syndicationFeed) { MemoryStream memStream = new MemoryStream(); syndicationFeed.Resource.Save(memStream); if (memStream.Length <= 0) Log.Warning("Failed to serialize {SyndicationType} feed: {SyndicationUri}", syndicationFeed.Format.ToString(), syndicationFeed.Title); memStream.Position = 0; return memStream; } private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed, string syndicationUrl) { SyndicationContainer container = new SyndicationContainer(); if (syndicationFeed == null) { Log.Error("Could not construct syndication container!"); return container; } container.SyndicationFeed = syndicationFeed; container.SyndicationModel = new SyndicationModel(); container.SyndicationItems = new HashSet(); switch (syndicationFeed.Resource.Format) { case SyndicationContentFormat.Rss: RssFeed rssFeed = (RssFeed)container.SyndicationFeed.Resource; if (rssFeed.Channel == null) break; container.SyndicationModel.EncodedUrl = EncodeUrlToBase64(syndicationUrl); container.SyndicationModel.Title = rssFeed.Channel.Title ?? string.Empty; container.SyndicationModel.SyndicationType = rssFeed.Format.ToString() ?? string.Empty; container.SyndicationModel.Version = rssFeed.Version?.ToString() ?? string.Empty; container.SyndicationModel.Description = rssFeed.Channel.Description ?? string.Empty; container.SyndicationModel.Language = rssFeed.Channel.Language?.ToString() ?? string.Empty; container.SyndicationModel.Copyright = rssFeed.Channel.Copyright ?? string.Empty; container.SyndicationModel.PublicationDate = rssFeed.Channel.PublicationDate is { Ticks: > 0 } ? new DateTimeOffset(rssFeed.Channel.PublicationDate) : DateTimeOffset.MinValue; container.SyndicationModel.Categories = rssFeed.Channel.Categories?.Select(x => x.Value).ToArray() ?? Array.Empty(); container.SyndicationModel.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty; foreach (var rssItem in rssFeed.Channel.Items) { SyndicationItemModel itemModel = new SyndicationItemModel() { Link = rssItem.Link?.ToString() ?? string.Empty, EncodedSyndicationUrl = container.SyndicationModel.EncodedUrl ?? string.Empty, Type = container.SyndicationModel.SyndicationType ?? string.Empty, Title = rssItem.Title ?? string.Empty, Description = rssItem.Description ?? string.Empty, PublishingDate = rssItem.PublicationDate is not { Ticks: <= 0 } ? new DateTimeOffset(rssItem.PublicationDate) : DateTimeOffset.MinValue, Authors = rssItem.Author != null ? new []{ rssItem.Author } : Array.Empty(), Categories = rssItem.Categories?.Select(x => x.Value).ToArray() ?? Array.Empty(), Content = rssItem.Extensions?.Where(x => x is SiteSummaryContentSyndicationExtension).Select(x => (x as SiteSummaryContentSyndicationExtension)?.Context.Encoded).FirstOrDefault() ?? string.Empty, CommentsUrl = rssItem.Extensions?.Where(x => x is WellFormedWebCommentsSyndicationExtension).Select(x => (x as WellFormedWebCommentsSyndicationExtension)?.Context.CommentsFeed.ToString()).FirstOrDefault() ?? string.Empty }; container.SyndicationItems.Add(itemModel); } break; case SyndicationContentFormat.Atom: AtomFeed atomFeed = (AtomFeed)container.SyndicationFeed.Resource; container.SyndicationModel.EncodedUrl = EncodeUrlToBase64(syndicationUrl); container.SyndicationModel.Title = atomFeed.Title?.Content ?? string.Empty; container.SyndicationModel.SyndicationType = atomFeed.Format.ToString() ?? string.Empty; container.SyndicationModel.Version = atomFeed.Version?.ToString() ?? string.Empty; container.SyndicationModel.Description = atomFeed.Subtitle?.Content ?? string.Empty; container.SyndicationModel.Language = atomFeed.Language?.ToString() ?? string.Empty; container.SyndicationModel.Copyright = atomFeed.Rights?.Content ?? string.Empty; container.SyndicationModel.PublicationDate = atomFeed.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(atomFeed.UpdatedOn) : DateTimeOffset.MinValue; container.SyndicationModel.Categories = atomFeed.Categories?.Select(x => x.Label).ToArray() ?? Array.Empty(); container.SyndicationModel.ImageUrl = atomFeed.Icon?.Uri.ToString() ?? string.Empty; foreach (var entry in atomFeed.Entries) { SyndicationItemModel itemModel = new SyndicationItemModel() { Link = entry.Id?.Uri.ToString() ?? string.Empty, EncodedSyndicationUrl = container.SyndicationModel?.EncodedUrl ?? string.Empty, Type = container.SyndicationModel?.SyndicationType ?? string.Empty, Title = entry.Title?.Content ?? string.Empty, Description = entry.Summary?.Content ?? string.Empty, ItemUpdatedDate = entry.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(entry.UpdatedOn) : DateTimeOffset.Now, PublishingDate = entry.PublishedOn is { Ticks: > 0 } ? new DateTimeOffset(entry.PublishedOn) : DateTimeOffset.MinValue, Authors = entry.Authors?.Select(auth => auth.Name).ToArray() ?? Array.Empty(), Categories = entry.Categories?.Select(cat => cat.Label).ToArray() ?? Array.Empty(), Content = entry.Content?.Content ?? string.Empty }; container.SyndicationItems.Add(itemModel); } break; default: Log.Warning("Syndication implementation missing!"); break; } container.Fetched = true; return container; } public static string EncodeUrlToBase64(string url) => Convert.ToBase64String(Encoding.UTF8.GetBytes(url)); public static string DecodeUrlFromBase64(string base64) { byte[] bytes = Convert.FromBase64String(base64); return Encoding.Default.GetString(bytes); } public static string FormatStringArrayToString(string[] sArr) => string.Join('|', sArr); public static string[] StringToStringArray(string stringFormat) => stringFormat.Split('|'); public static void Init() { DbAccess.Initialize(); } } }