1 | import * as convert from "xml-js";
|
2 | import { generator } from "./config";
|
3 | import { Feed } from "./feed";
|
4 | import { Author, Category, Enclosure, Item } from "./typings";
|
5 | import { sanitize } from "./utils";
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | export 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 |
|
32 |
|
33 |
|
34 | if (options.language) {
|
35 | base.rss.channel.language = { _text: options.language };
|
36 | }
|
37 |
|
38 | |
39 |
|
40 |
|
41 |
|
42 | if (options.ttl) {
|
43 | base.rss.channel.ttl = { _text: options.ttl };
|
44 | }
|
45 |
|
46 | |
47 |
|
48 |
|
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 |
|
60 |
|
61 |
|
62 | if (options.copyright) {
|
63 | base.rss.channel.copyright = { _text: options.copyright };
|
64 | }
|
65 |
|
66 | |
67 |
|
68 |
|
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 |
|
79 |
|
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 |
|
97 |
|
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 |
|
114 |
|
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 |
|
155 |
|
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 |
|
167 |
|
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 |
|
178 |
|
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 |
|
212 |
|
213 |
|
214 |
|
215 | const 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 |
|
227 |
|
228 |
|
229 | const formatCategory = (category: Category) => {
|
230 | const { name, domain } = category;
|
231 | return {
|
232 | _text: name,
|
233 | _attributes: {
|
234 | domain,
|
235 | },
|
236 | };
|
237 | };
|