UNPKG

6.2 kBPlain TextView Raw
1import * as convert from "xml-js";
2import { generator } from "./config";
3import { Feed } from "./feed";
4import { Author, Category, Enclosure, Item } from "./typings";
5import { sanitize } from "./utils";
6
7/**
8 * Returns a RSS 2.0 feed
9 */
10export default (ins: Feed) => {
11 const { options } = ins;
12 let isAtom = false;
13 let isContent = false;
14
15 const base: any = {
16 _declaration: { _attributes: { version: "1.0", encoding: "utf-8" } },
17 rss: {
18 _attributes: { version: "2.0" },
19 channel: {
20 title: { _text: options.title },
21 link: { _text: sanitize(options.link) },
22 description: { _text: options.description },
23 lastBuildDate: { _text: options.updated ? options.updated.toUTCString() : new Date().toUTCString() },
24 docs: { _text: options.docs ? options.docs : "https://validator.w3.org/feed/docs/rss2.html" },
25 generator: { _text: options.generator || generator },
26 },
27 },
28 };
29
30 /**
31 * Channel language
32 * https://validator.w3.org/feed/docs/rss2.html#ltlanguagegtSubelementOfLtchannelgt
33 */
34 if (options.language) {
35 base.rss.channel.language = { _text: options.language };
36 }
37
38 /**
39 * Channel ttl
40 * https://validator.w3.org/feed/docs/rss2.html#ltttlgtSubelementOfLtchannelgt
41 */
42 if (options.ttl) {
43 base.rss.channel.ttl = { _text: options.ttl };
44 }
45
46 /**
47 * Channel Image
48 * https://validator.w3.org/feed/docs/rss2.html#ltimagegtSubelementOfLtchannelgt
49 */
50 if (options.image) {
51 base.rss.channel.image = {
52 title: { _text: options.title },
53 url: { _text: options.image },
54 link: { _text: sanitize(options.link) }
55 };
56 }
57
58 /**
59 * Channel Copyright
60 * https://validator.w3.org/feed/docs/rss2.html#optionalChannelElements
61 */
62 if (options.copyright) {
63 base.rss.channel.copyright = { _text: options.copyright };
64 }
65
66 /**
67 * Channel Categories
68 * https://validator.w3.org/feed/docs/rss2.html#comments
69 */
70 ins.categories.map((category) => {
71 if (!base.rss.channel.category) {
72 base.rss.channel.category = [];
73 }
74 base.rss.channel.category.push({ _text: category });
75 });
76
77 /**
78 * Feed URL
79 * http://validator.w3.org/feed/docs/warning/MissingAtomSelfLink.html
80 */
81 const atomLink = options.feed || (options.feedLinks && options.feedLinks.rss);
82 if (atomLink) {
83 isAtom = true;
84 base.rss.channel["atom:link"] = [
85 {
86 _attributes: {
87 href: sanitize(atomLink),
88 rel: "self",
89 type: "application/rss+xml",
90 },
91 },
92 ];
93 }
94
95 /**
96 * Hub for PubSubHubbub
97 * https://code.google.com/p/pubsubhubbub/
98 */
99 if (options.hub) {
100 isAtom = true;
101 if (!base.rss.channel["atom:link"]) {
102 base.rss.channel["atom:link"] = [];
103 }
104 base.rss.channel["atom:link"] = {
105 _attributes: {
106 href: sanitize(options.hub),
107 rel: "hub"
108 }
109 };
110 }
111
112 /**
113 * Channel Categories
114 * https://validator.w3.org/feed/docs/rss2.html#hrelementsOfLtitemgt
115 */
116 base.rss.channel.item = [];
117
118 ins.items.map((entry: Item) => {
119 let item: any = {};
120
121 if (entry.title) {
122 item.title = { _cdata: entry.title };
123 }
124
125 if (entry.link) {
126 item.link = { _text: sanitize(entry.link) };
127 }
128
129 if (entry.guid) {
130 item.guid = { _text: entry.guid };
131 } else if (entry.id) {
132 item.guid = { _text: entry.id };
133 } else if (entry.link) {
134 item.guid = { _text: sanitize(entry.link) };
135 }
136
137 if (entry.date) {
138 item.pubDate = { _text: entry.date.toUTCString() };
139 }
140
141 if (entry.published) {
142 item.pubDate = { _text: entry.published.toUTCString() };
143 }
144
145 if (entry.description) {
146 item.description = { _cdata: entry.description };
147 }
148
149 if (entry.content) {
150 isContent = true;
151 item["content:encoded"] = { _cdata: entry.content };
152 }
153 /**
154 * Item Author
155 * https://validator.w3.org/feed/docs/rss2.html#ltauthorgtSubelementOfLtitemgt
156 */
157 if (Array.isArray(entry.author)) {
158 item.author = [];
159 entry.author.map((author: Author) => {
160 if (author.email && author.name) {
161 item.author.push({ _text: author.email + " (" + author.name + ")" });
162 }
163 });
164 }
165 /**
166 * Item Category
167 * https://validator.w3.org/feed/docs/rss2.html#ltcategorygtSubelementOfLtitemgt
168 */
169 if (Array.isArray(entry.category)) {
170 item.category = [];
171 entry.category.map((category: Category) => {
172 item.category.push(formatCategory(category));
173 });
174 }
175
176 /**
177 * Item Enclosure
178 * https://validator.w3.org/feed/docs/rss2.html#ltenclosuregtSubelementOfLtitemgt
179 */
180 if (entry.enclosure) {
181 item.enclosure = formatEnclosure(entry.enclosure);
182 }
183
184 if (entry.image) {
185 item.enclosure = formatEnclosure(entry.image, "image");
186 }
187
188 if (entry.audio) {
189 item.enclosure = formatEnclosure(entry.audio, "audio");
190 }
191
192 if (entry.video) {
193 item.enclosure = formatEnclosure(entry.video, "video");
194 }
195
196 base.rss.channel.item.push(item);
197 });
198
199 if (isContent) {
200 base.rss._attributes["xmlns:dc"] = "http://purl.org/dc/elements/1.1/";
201 base.rss._attributes["xmlns:content"] = "http://purl.org/rss/1.0/modules/content/";
202 }
203
204 if (isAtom) {
205 base.rss._attributes["xmlns:atom"] = "http://www.w3.org/2005/Atom";
206 }
207 return convert.js2xml(base, { compact: true, ignoreComment: true, spaces: 4 });
208};
209
210/**
211 * Returns a formated enclosure
212 * @param enclosure
213 * @param mimeCategory
214 */
215const formatEnclosure = (enclosure: string | Enclosure, mimeCategory = "image") => {
216 if (typeof enclosure === "string") {
217 const type = new URL(enclosure).pathname.split(".").slice(-1)[0];
218 return { _attributes: { url: enclosure, length: 0, type: `${mimeCategory}/${type}` } };
219 }
220
221 const type = new URL(enclosure.url).pathname.split(".").slice(-1)[0];
222 return { _attributes: { length: 0, type: `${mimeCategory}/${type}`, ...enclosure } };
223};
224
225/**
226 * Returns a formated category
227 * @param category
228 */
229const formatCategory = (category: Category) => {
230 const { name, domain } = category;
231 return {
232 _text: name,
233 _attributes: {
234 domain,
235 },
236 };
237};