feed
Version:
Feed is a RSS, Atom and JSON feed generator for Node.js, making content syndication simple and intuitive!
423 lines (416 loc) • 13.7 kB
JavaScript
import * as convert$1 from "xml-js";
import * as convert from "xml-js";
//#region src/config.ts
const generator = "https://github.com/jpmonette/feed";
//#endregion
//#region src/utils.ts
function escapeAmp(value) {
return value.replace(/&/g, "&");
}
function sanitize(text) {
if (text === void 0) return void 0;
return escapeAmp(text);
}
function sanitizeUrl(url) {
if (url === void 0) return void 0;
const parsed = new URL(url);
return escapeAmp(parsed.toString());
}
//#endregion
//#region src/atom1.ts
var atom1_default = (ins) => {
const { options } = ins;
const base = {
_declaration: { _attributes: {
version: "1.0",
encoding: "utf-8"
} },
_instruction: {},
feed: {
_attributes: { xmlns: "http://www.w3.org/2005/Atom" },
id: options.id,
title: options.title,
updated: options.updated?.toISOString() ?? new Date().toISOString(),
...options.generator === false ? {} : { generator: sanitize(options.generator ?? generator) }
}
};
if (options.stylesheet) base._instruction = { "xml-stylesheet": { _attributes: {
href: options.stylesheet,
type: "text/xsl"
} } };
else delete base._instruction;
if (options.author) base.feed.author = formatAuthor(options.author);
base.feed.link = [];
if (options.link) base.feed.link.push({ _attributes: {
rel: "alternate",
href: sanitizeUrl(options.link)
} });
const atomLink = options.feed ?? options.feedLinks?.atom;
if (atomLink) base.feed.link.push({ _attributes: {
rel: "self",
href: sanitizeUrl(atomLink)
} });
if (options.hub) base.feed.link.push({ _attributes: {
rel: "hub",
href: sanitizeUrl(options.hub)
} });
if (options.description) base.feed.subtitle = options.description;
if (options.image) base.feed.logo = options.image;
if (options.favicon) base.feed.icon = options.favicon;
if (options.copyright) base.feed.rights = options.copyright;
base.feed.category = [];
ins.categories.forEach((category) => {
base.feed.category.push({ _attributes: { term: sanitize(category) } });
});
base.feed.contributor = [];
ins.contributors.forEach((contributor) => {
base.feed.contributor.push(formatAuthor(contributor));
});
base.feed.entry = [];
ins.items.forEach((item) => {
const entry = {
title: {
_attributes: { type: "html" },
_cdata: item.title
},
id: sanitizeUrl(item.id ?? item.link),
link: [{ _attributes: { href: sanitizeUrl(item.link) } }],
updated: item.date.toISOString()
};
if (item.description) entry.summary = {
_attributes: { type: "html" },
_cdata: item.description
};
if (item.content) entry.content = {
_attributes: { type: "html" },
_cdata: item.content
};
if (Array.isArray(item.author)) {
entry.author = [];
item.author.forEach((author) => {
entry.author.push(formatAuthor(author));
});
}
if (Array.isArray(item.category)) {
entry.category = [];
item.category.forEach((category) => {
entry.category.push(formatCategory$1(category));
});
}
if (item.enclosure) entry.link.push(formatEnclosure$1(item.enclosure));
if (item.image) entry.link.push(formatEnclosure$1(item.image, "image"));
if (item.audio) entry.link.push(formatEnclosure$1(item.audio, "audio"));
if (item.video) entry.link.push(formatEnclosure$1(item.video, "video"));
if (item.contributor && Array.isArray(item.contributor)) {
entry.contributor = [];
item.contributor.forEach((contributor) => {
entry.contributor.push(formatAuthor(contributor));
});
}
if (item.published) entry.published = item.published.toISOString();
if (item.copyright) entry.rights = item.copyright;
base.feed.entry.push(entry);
});
return convert$1.js2xml(base, {
compact: true,
ignoreComment: true,
spaces: 4
});
};
const formatAuthor = (author) => {
const { name, email, link } = author;
const out = { name };
if (email) out.email = email;
if (link) out.uri = sanitizeUrl(link);
return out;
};
const formatEnclosure$1 = (enclosure, mimeCategory = "image") => {
if (typeof enclosure === "string") {
const sanitizedUrl$1 = sanitizeUrl(enclosure);
const type$1 = new URL(sanitizedUrl$1).pathname.split(".").slice(-1)[0];
return { _attributes: {
rel: "enclosure",
href: sanitizedUrl$1,
type: `${mimeCategory}/${type$1}`
} };
}
const sanitizedUrl = sanitizeUrl(enclosure.url);
const type = new URL(sanitizedUrl).pathname.split(".").slice(-1)[0];
return { _attributes: {
rel: "enclosure",
href: sanitizedUrl,
title: enclosure.title,
type: `${mimeCategory}/${type}`,
length: enclosure.length
} };
};
const formatCategory$1 = (category) => {
const { name, scheme, term } = category;
return { _attributes: {
label: sanitize(name),
scheme: sanitizeUrl(scheme),
term: sanitize(term)
} };
};
//#endregion
//#region src/json.ts
var json_default = (ins) => {
const { options, items, extensions } = ins;
const feed = {
version: "https://jsonfeed.org/version/1",
title: options.title
};
if (options.link) feed.home_page_url = options.link;
if (options.feedLinks?.json) feed.feed_url = options.feedLinks.json;
if (options.description) feed.description = options.description;
if (options.image) feed.icon = options.image;
if (options.author) {
feed.author = {};
if (options.author.name) feed.author.name = options.author.name;
if (options.author.link) feed.author.url = options.author.link;
if (options.author.avatar) feed.author.avatar = options.author.avatar;
}
extensions.forEach((e) => {
feed[e.name] = e.objects;
});
feed.items = items.map((item) => {
const feedItem = {
id: item.id,
content_html: item.content ?? item.description
};
if (item.link) feedItem.url = item.link;
if (item.title) feedItem.title = item.title;
if (item.description && item.content) feedItem.summary = item.description;
if (item.image) feedItem.image = typeof item.image === "string" ? item.image : item.image.url;
if (item.date) feedItem.date_modified = item.date.toISOString();
if (item.published) feedItem.date_published = item.published.toISOString();
if (item.author) {
const author = Array.isArray(item.author) ? item.author[0] : item.author;
feedItem.author = {};
if (author.name) feedItem.author.name = author.name;
if (author.link) feedItem.author.url = author.link;
if (author.avatar) feedItem.author.avatar = author.avatar;
}
if (Array.isArray(item.category)) {
feedItem.tags = [];
item.category.forEach((category) => {
if (category.name) feedItem.tags.push(category.name);
});
}
if (item.extensions) item.extensions.forEach((e) => {
feedItem[e.name] = e.objects;
});
return feedItem;
});
return JSON.stringify(feed, null, 4);
};
//#endregion
//#region src/rss2.ts
var rss2_default = (ins) => {
const { options, extensions } = ins;
let needsAtomNamespace = false;
let needsContentNamespace = false;
const base = {
_declaration: { _attributes: {
version: "1.0",
encoding: "utf-8"
} },
_instruction: {},
rss: {
_attributes: { version: "2.0" },
channel: {
title: { _text: options.title },
link: { _text: sanitizeUrl(options.link) },
description: { _text: options.description ?? "" },
lastBuildDate: { _text: options.updated ? options.updated.toUTCString() : new Date().toUTCString() },
docs: { _text: options.docs ?? "https://validator.w3.org/feed/docs/rss2.html" },
...options.generator === false ? {} : { generator: { _text: options.generator ?? generator } }
}
}
};
if (options.stylesheet) base._instruction = { "xml-stylesheet": { _attributes: {
href: options.stylesheet,
type: "text/xsl"
} } };
else delete base._instruction;
if (options.language) base.rss.channel.language = { _text: options.language };
if (options.ttl) base.rss.channel.ttl = { _text: options.ttl };
if (options.image) base.rss.channel.image = {
title: { _text: options.title },
url: { _text: options.image },
link: { _text: sanitizeUrl(options.link) }
};
if (options.copyright) base.rss.channel.copyright = { _text: options.copyright };
ins.categories.forEach((category) => {
if (!base.rss.channel.category) base.rss.channel.category = [];
base.rss.channel.category.push({ _text: category });
});
const atomLink = options.feed ?? options.feedLinks?.rss;
if (atomLink) {
needsAtomNamespace = true;
if (!base.rss.channel["atom:link"]) base.rss.channel["atom:link"] = [];
base.rss.channel["atom:link"].push({ _attributes: {
href: sanitizeUrl(atomLink),
rel: "self",
type: "application/rss+xml"
} });
}
if (options.hub) {
needsAtomNamespace = true;
if (!base.rss.channel["atom:link"]) base.rss.channel["atom:link"] = [];
base.rss.channel["atom:link"].push({ _attributes: {
href: sanitizeUrl(options.hub),
rel: "hub"
} });
}
base.rss.channel.item = [];
ins.items.forEach((entry) => {
const item = {};
if (entry.title) item.title = { _cdata: entry.title };
if (entry.link) item.link = { _text: sanitizeUrl(entry.link) };
if (entry.guid) item.guid = {
_text: entry.guid,
_attributes: { isPermaLink: false }
};
else if (entry.id) item.guid = {
_text: entry.id,
_attributes: { isPermaLink: false }
};
else if (entry.link) item.guid = {
_text: sanitizeUrl(entry.link),
_attributes: { isPermaLink: true }
};
if (entry.date) item.pubDate = { _text: entry.date.toUTCString() };
if (entry.published) item.pubDate = { _text: entry.published.toUTCString() };
if (entry.description) item.description = { _cdata: entry.description };
if (entry.content) {
needsContentNamespace = true;
item["content:encoded"] = { _cdata: entry.content };
}
if (Array.isArray(entry.author)) {
item.author = [];
entry.author.forEach((author) => {
if (author.email && author.name) item.author.push({ _text: `${author.email} (${author.name})` });
else if (author.name) item.author.push({ _text: author.name });
});
}
if (Array.isArray(entry.category)) {
item.category = [];
entry.category.forEach((category) => {
item.category.push(formatCategory(category));
});
}
if (entry.enclosure) item.enclosure = formatEnclosure(entry.enclosure);
if (entry.image) item.enclosure = formatEnclosure(entry.image, "image");
if (entry.audio) {
const duration = options.podcast && typeof entry.audio !== "string" && entry.audio.duration ? entry.audio.duration : void 0;
if (duration) entry.audio.duration = void 0;
item.enclosure = formatEnclosure(entry.audio, "audio");
if (duration) item["itunes:duration"] = formatDuration(duration);
}
if (entry.video) item.enclosure = formatEnclosure(entry.video, "video");
if (entry.extensions) entry.extensions.forEach((extension) => {
item[extension.name] = extension.objects;
});
base.rss.channel.item.push(item);
});
if (needsContentNamespace) {
base.rss._attributes["xmlns:dc"] = "http://purl.org/dc/elements/1.1/";
base.rss._attributes["xmlns:content"] = "http://purl.org/rss/1.0/modules/content/";
}
if (extensions) extensions.forEach((e) => {
base.rss.channel[e.name] = e.objects;
});
if (needsAtomNamespace) base.rss._attributes["xmlns:atom"] = "http://www.w3.org/2005/Atom";
if (options.podcast) {
base.rss._attributes["xmlns:googleplay"] = "http://www.google.com/schemas/play-podcasts/1.0";
base.rss._attributes["xmlns:itunes"] = "http://www.itunes.com/dtds/podcast-1.0.dtd";
if (options.category) {
base.rss.channel["googleplay:category"] = options.category;
base.rss.channel["itunes:category"] = options.category;
}
if (options.author?.email) {
base.rss.channel["googleplay:owner"] = options.author.email;
base.rss.channel["itunes:owner"] = { "itunes:email": options.author.email };
}
if (options.author?.name) {
base.rss.channel["googleplay:author"] = options.author.name;
base.rss.channel["itunes:author"] = options.author.name;
}
if (options.image) base.rss.channel["googleplay:image"] = { _attributes: { href: sanitizeUrl(options.image) } };
}
return convert.js2xml(base, {
compact: true,
ignoreComment: true,
spaces: 4
});
};
const formatEnclosure = (enclosure, mimeCategory = "image") => {
if (typeof enclosure === "string") {
const sanitizedUrl$1 = sanitizeUrl(enclosure);
const type$1 = new URL(sanitizedUrl$1).pathname.split(".").slice(-1)[0];
return { _attributes: {
url: sanitizedUrl$1,
length: 0,
type: `${mimeCategory}/${type$1}`
} };
}
const sanitizedUrl = sanitizeUrl(enclosure.url);
const type = new URL(sanitizedUrl).pathname.split(".").slice(-1)[0];
return { _attributes: {
length: 0,
type: `${mimeCategory}/${type}`,
...enclosure,
url: sanitizedUrl
} };
};
const formatCategory = (category) => {
const { name, domain } = category;
return {
_text: name,
_attributes: { domain }
};
};
const formatDuration = (duration) => {
const seconds = duration % 60;
const totalMinutes = Math.floor(duration / 60);
const minutes = totalMinutes % 60;
const hours = Math.floor(totalMinutes / 60);
const notHours = `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
return hours > 0 ? `${hours}:${notHours}` : notHours;
};
//#endregion
//#region src/feed.ts
var Feed = class {
items = [];
categories = [];
contributors = [];
extensions = [];
constructor(options) {
this.options = options;
}
addItem(item) {
this.items.push(item);
}
addCategory(category) {
this.categories.push(category);
}
addContributor(contributor) {
this.contributors.push(contributor);
}
addExtension(extension) {
this.extensions.push(extension);
}
atom1() {
return atom1_default(this);
}
rss2() {
return rss2_default(this);
}
json1() {
return json_default(this);
}
};
//#endregion
export { Feed };
//# sourceMappingURL=feed.js.map