1 module ffeedd.feed;
2 
3 import std.datetime;
4 import ffeedd.author;
5 import ffeedd.item;
6 import vibe.textfilter.html;
7 import std.conv;
8 
9 /// Based on:
10 /// RSS:	https://www.rssboard.org/rss-specification
11 /// ATOM:	https://www.tutorialspoint.com/rss/feed.htm
12 class Feed
13 {
14 	/// Instances a new channel only with the required elements
15 	this(string feedTitle, string feedLink, string feedDescription)
16 	{
17 		title = feedTitle;
18 		link = feedLink;
19 		description = feedDescription;
20 	}
21 
22 	/// The name of the feed. It's how people refer to your service.
23 	/// e.g. "GoUpstate.com News Headlines"
24 	string title;
25 	/// The URL to the HTML website corresponding to the channel.
26 	/// eg. "http://www.goupstate.com/"
27 	string link;
28 	/// Phrase or sentence describing the channel.
29 	/// e.g. "The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site."
30 	string description;
31 	/// Items in this feed
32 	FeedItem[] items;
33 
34 	// Optional elements
35 	/// Time when the feed was last updated
36 	DateTime updated;
37 	/// This contains the time of the initial creation or the first availability of the entry.
38 	DateTime published;
39 	/// List of authors
40 	Author[] authors;
41 
42 	string createAtomFeed()
43 	{
44 		string s = "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n";
45 
46 		if (title)
47 			s ~= "\t<title>" ~ htmlAttribEscape(title) ~ "</title>\n";
48 
49 		if (link)
50 			s ~= "\t<id>" ~ htmlAttribEscape(link) ~ "</id>\n";
51 
52 		if (description)
53 			s ~= "\t<subtitle>" ~ htmlAttribEscape(link) ~ "</subtitle>\n";
54 
55 		if (updated != DateTime())
56 			s ~= "\t<updated>" ~ toRFC3339String(updated) ~ "</updated>\n";
57 
58 		// Stops w3.org checker from validating 
59 		// tutorialspoint.com probably made a mistake and included <published> in the feed
60 
61 		// if (published != DateTime())
62 		// 	s ~= "\t<published>" ~ toRFC3339String(updated) ~ "</published>\n";
63 
64 		foreach (author; authors)
65 		{
66 			s ~= "\t<author>\n";
67 
68 			if (author.name)
69 				s ~= "\t\t<name>" ~ htmlAttribEscape(author.name) ~ "</name>\n";
70 
71 			if (author.email)
72 				s ~= "\t\t<email>" ~ htmlAttribEscape(author.email) ~ "</email>\n";
73 
74 			s ~= "\t</author>\n";
75 		}
76 
77 		foreach (item; items)
78 		{
79 			s ~= "\t<entry>\n";
80 
81 			if (item.title)
82 				s ~= "\t\t<title>" ~ htmlAttribEscape(item.title) ~ "</title>\n";
83 
84 			if (item.link)
85 			{
86 				s ~= "\t\t<id>" ~ htmlAttribEscape(item.link) ~ "</id>\n";
87 				s ~= "\t\t<link rel=\"alternate\" href=\"" ~ htmlAttribEscape(item.link) ~ "\"/>\n";
88 			}
89 
90 			if (item.description)
91 				s ~= "\t\t<summary>" ~ htmlAttribEscape(item.description) ~ "</summary>\n";
92 
93 			foreach (author; item.authors)
94 			{
95 				s ~= "\t\t<author>\n";
96 
97 				if (author.name)
98 					s ~= "\t\t\t<name>" ~ htmlAttribEscape(author.name) ~ "</name>\n";
99 
100 				if (author.email)
101 					s ~= "\t\t\t<email>" ~ htmlAttribEscape(author.email) ~ "</email>\n";
102 
103 				s ~= "\t\t</author>\n";
104 			}
105 
106 			if (item.updated != DateTime())
107 				s ~= "\t\t<updated>" ~ toRFC3339String(item.updated) ~ "</updated>\n";
108 
109 			if (item.published != DateTime())
110 				s ~= "\t\t<published>" ~ toRFC3339String(item.published) ~ "</published>\n";
111 
112 
113 			s ~= "\t</entry>\n";
114 		}
115 
116 		s ~= "</feed>";
117 
118 		return s;
119 	}
120 
121 	string createRSSFeed()
122 	{
123 		string s = "<rss version=\"2.0\">
124 	<channel>\n";
125 
126 		if (title)
127 			s ~= "\t<title>" ~ htmlAttribEscape(title) ~ "</title>\n";
128 
129 		if (link)
130 			s ~= "\t<link>" ~ htmlAttribEscape(link) ~ "</link>\n";
131 
132 		if (description)
133 			s ~= "\t<description>" ~ htmlAttribEscape(link) ~ "</description>\n";
134 
135 		if (updated != DateTime())
136 			s ~= "\t<lastBuildDate>" ~ toRFC822String(updated) ~ "</lastBuildDate>\n";
137 
138 		if (published != DateTime())
139 			s ~= "\t<pubDate>" ~ toRFC822String(updated) ~ "</pubDate>\n";
140 
141 		// RSS 2.0 feed doesn't have author tags
142 		// but it doesn't break anything so I'm keeping it
143 		foreach (author; authors)
144 			s ~= "\t<author>" ~ htmlAttribEscape(author.email) ~ " (" ~ htmlAttribEscape(author.name) ~ ")</author>\n";
145 
146 		foreach (item; items)
147 		{
148 			s ~= "\t\t<item>\n";
149 
150 			if (item.title)
151 				s ~= "\t\t\t<title>" ~ htmlAttribEscape(item.title) ~ "</title>\n";
152 
153 			if (item.link)
154 		{
155 				s ~= "\t\t\t<link>" ~ htmlAttribEscape(item.link) ~ "</link>\n";
156 				s ~= "\t\t\t<guid isPermaLink=\"true\">" ~ htmlAttribEscape(item.link) ~ "</guid>\n";
157 		}
158 
159 			if (item.description)
160 				s ~= "\t\t\t<description>" ~ htmlAttribEscape(item.description) ~ "</description>\n";
161 
162 			foreach (author; authors)
163 			s ~= "\t\t\t<author>" ~ htmlAttribEscape(author.email) ~ " (" ~ htmlAttribEscape(author.name) ~ ")</author>\n";
164 
165 			if (item.published != DateTime())
166 				s ~= "\t\t\t<pubDate>" ~ toRFC822String(item.published) ~ "</pubDate>\n";
167 
168 
169 			s ~= "\t\t</item>\n";
170 		}
171 
172 		s ~= "\t</channel>\n</rss>";
173 
174 		return s;
175 	}
176 }
177 
178 /// Convert DateTime to RFC 3339 date-time string
179 /// e.g. 2002-10-02T10:00:00Z
180 string toRFC3339String(DateTime date)
181 {
182 	return date.toISOExtString() ~ "Z";
183 }
184 
185 /// Convert DateTime to RFC 822 date-time string
186 /// Sat, 07 Sep 2002 00:00:01 GMT
187 string toRFC822String(DateTime date)
188 {
189 	import std.range;
190 	import std.string;
191 	import std.format;
192 
193 	string day =  date.dayOfWeek.to!string();
194 	//day[0] = day[0].toUpper();
195 	
196 	string month = date.month.to!string();
197 	//month[0].toUpper()[0];
198 
199 	return day ~ ", " ~ date.day.to!string().padLeft('0', 2).text ~ " " ~ month ~ " " ~ date.year.to!string() ~ " " ~ 
200 			date.hour.to!string().padLeft('0', 2).text ~ ":" ~ 
201 			date.minute.to!string().padLeft('0', 2).text ~ ":" ~ 
202 			date.second.to!string().padLeft('0', 2).text ~ " +0000";
203 }