aboutsummaryrefslogtreecommitdiff
path: root/back-end/src/FeedGenerator.cs
diff options
context:
space:
mode:
Diffstat (limited to 'back-end/src/FeedGenerator.cs')
-rw-r--r--back-end/src/FeedGenerator.cs210
1 files changed, 210 insertions, 0 deletions
diff --git a/back-end/src/FeedGenerator.cs b/back-end/src/FeedGenerator.cs
new file mode 100644
index 0000000..de4a1e2
--- /dev/null
+++ b/back-end/src/FeedGenerator.cs
@@ -0,0 +1,210 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: CMNext
+* Package: Content.Publishing.Blog.Admin
+* File: FeedGenerator.cs
+*
+* CMNext is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* CMNext is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Xml;
+using System.Linq;
+using System.Text;
+using System.Collections.Generic;
+
+using VNLib.Utils.IO;
+using VNLib.Plugins;
+
+using Content.Publishing.Blog.Admin.Model;
+
+namespace Content.Publishing.Blog.Admin
+{
+ internal sealed class FeedGenerator : IRssFeedGenerator
+ {
+ const int defaultMaxItems = 20;
+ const string ITUNES_XML_ATTR = "http://www.itunes.com/dtds/podcast-1.0.dtd";
+ const string CONTENT_XML_ATTR = "http://purl.org/rss/1.0/modules/content/";
+
+ public FeedGenerator(PluginBase pbase)
+ { }
+
+ public void BuildFeed(IChannelContext context, IEnumerable<PostMeta> posts, VnMemoryStream output)
+ {
+ _ = context.Feed ?? throw new ArgumentNullException(nameof(context.Feed));
+
+ //Build the feed
+ using XmlWriter writer = XmlWriter.Create(output, new XmlWriterSettings
+ {
+ Encoding = Encoding.UTF8,
+ Indent = true,
+ IndentChars = " ",
+ NewLineChars = "\n",
+ NewLineHandling = NewLineHandling.Entitize,
+ NewLineOnAttributes = false,
+ CloseOutput = false,
+ NamespaceHandling = NamespaceHandling.OmitDuplicates,
+ });
+
+ string currentTime = DateTime.UtcNow.ToString("R");
+
+ //Write the feed
+ writer.WriteStartDocument();
+ writer.WriteStartElement("rss");
+ writer.WriteAttributeString("version", "2.0");
+ writer.WriteAttributeString("xmlns", "itunes", null, ITUNES_XML_ATTR);
+ writer.WriteAttributeString("xmlns", "content", null, CONTENT_XML_ATTR);
+
+
+ //Channel element
+ writer.WriteStartElement("channel");
+
+ writer.WriteElementString("title", context.BlogName);
+
+ writer.WriteElementString("link", context.Feed.PublihUrl);
+
+ //Description/summary
+ writer.WriteElementString("description", context.Feed.Description);
+ writer.WriteElementString("itunes", "summary", null, context.Feed.Description);
+
+ writer.WriteElementString("itunes", "author", null, context.Feed.Author);
+
+ //Itunes owner tag
+ writer.WriteStartElement("itunes", "owner", null);
+ writer.WriteElementString("itunes", "email", null, context.Feed.WebMaster);
+ writer.WriteElementString("itunes", "name", null, context.Feed.Author);
+ writer.WriteEndElement();
+
+ //Write extended properties
+ if (context.Feed.ExtendedProperties != null)
+ {
+ foreach (ExtendedProperty prop in context.Feed.ExtendedProperties)
+ {
+ PrintExtendedProps(prop, writer);
+ }
+ }
+
+ //Author
+ writer.WriteElementString("itunes", "author", null, context.Feed.Author);
+
+ //Itunes image url
+ if (context.Feed.ImageUrl != null)
+ {
+ WriteImageTag(writer, context.Feed.ImageUrl);
+ }
+
+ writer.WriteElementString("language", "en-us");
+
+ writer.WriteElementString("pubDate", currentTime);
+ writer.WriteElementString("lastBuildDate", currentTime);
+
+ int maxItems = context.Feed.MaxItems ?? defaultMaxItems;
+ //Take only the latest max items
+ posts = posts.OrderByDescending(static p => p.Date).Take(maxItems);
+
+ //Write the posts as items but sort in order of their pub date
+ foreach (PostMeta post in posts)
+ {
+ writer.WriteStartElement("item");
+
+ writer.WriteElementString("title", post.Title);
+ writer.WriteElementString("itunes","title", null, post.Title);
+
+ writer.WriteElementString("link", $"{context.Feed.PublihUrl}/{post.Id}");
+
+ writer.WriteElementString("itunes", "author", null, post.Author);
+
+ //Description is just the post summary
+ writer.WriteElementString("description", post.Summary);
+ writer.WriteElementString("itunes", "summary", null, post.Summary);
+
+ //Time as iso string from unix seconds timestamp
+ string pubDate = DateTimeOffset.FromUnixTimeSeconds(post.Date).ToString("R");
+
+ writer.WriteElementString("pubDate", pubDate);
+ writer.WriteElementString("published", pubDate);
+
+ if (post.Image != null)
+ {
+ WriteImageTag(writer, post.Image);
+ }
+
+ //Add extended properties as itunes tags
+ if (post.ExtendedProperties != null)
+ {
+ //Recursivley add extended properties
+ foreach (ExtendedProperty prop in post.ExtendedProperties)
+ {
+ PrintExtendedProps(prop, writer);
+ }
+ }
+
+ //Set post id as the guid
+ writer.WriteElementString("guid", post.Id);
+
+ writer.WriteEndElement();
+ }
+
+ //End the feed
+ writer.WriteEndElement();
+ writer.WriteEndElement();
+ writer.WriteEndDocument();
+ }
+
+ private static void PrintExtendedProps(ExtendedProperty? prop, XmlWriter writer)
+ {
+ if (prop?.Name == null)
+ {
+ return;
+ }
+
+ //Open the element
+ writer.WriteStartElement(prop.NameSpace, prop.Name, null);
+
+ //Write the attributes
+ if (prop.Attributes != null)
+ {
+ foreach (KeyValuePair<string, string> attr in prop.Attributes)
+ {
+ writer.WriteAttributeString(attr.Key, attr.Value);
+ }
+ }
+
+ //nested child elements before closing
+ if (prop.Children != null)
+ {
+ foreach (ExtendedProperty child in prop.Children)
+ {
+ PrintExtendedProps(child, writer);
+ }
+ }
+ else
+ {
+ //Write the value
+ writer.WriteString(prop.Value);
+ }
+
+ //Close the element
+ writer.WriteEndElement();
+ }
+
+ private static void WriteImageTag(XmlWriter writer, string imageUrl)
+ {
+ writer.WriteStartElement("itunes", "image",null);
+ writer.WriteAttributeString("href", imageUrl);
+ writer.WriteEndElement();
+ }
+ }
+}