mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-04 02:05:06 +02:00
Update:Remove rss feed dependencies add node-xml lib
This commit is contained in:
parent
46fc89e247
commit
03bffb725a
9 changed files with 556 additions and 105 deletions
193
server/libs/rss/index.js
Normal file
193
server/libs/rss/index.js
Normal file
|
@ -0,0 +1,193 @@
|
|||
// node-rss
|
||||
// SOURCE: https://github.com/dylang/node-rss
|
||||
// LICENSE: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
'use strict';
|
||||
|
||||
var mime = require('mime-types');
|
||||
var xml = require('../xml');
|
||||
var fs = require('fs');
|
||||
|
||||
|
||||
function ifTruePush(bool, array, data) {
|
||||
if (bool) {
|
||||
array.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
function ifTruePushArray(bool, array, dataArray) {
|
||||
if (!bool) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataArray.forEach(function (item) {
|
||||
ifTruePush(item, array, item);
|
||||
});
|
||||
}
|
||||
|
||||
function getSize(filename) {
|
||||
if (typeof fs === 'undefined') {
|
||||
return 0;
|
||||
}
|
||||
return fs.statSync(filename).size;
|
||||
}
|
||||
|
||||
function generateXML(data) {
|
||||
|
||||
var channel = [];
|
||||
channel.push({ title: { _cdata: data.title } });
|
||||
channel.push({ description: { _cdata: data.description || data.title } });
|
||||
channel.push({ link: data.site_url || 'http://github.com/dylang/node-rss' });
|
||||
// image_url set?
|
||||
if (data.image_url) {
|
||||
channel.push({ image: [{ url: data.image_url }, { title: data.title }, { link: data.site_url }] });
|
||||
}
|
||||
channel.push({ generator: data.generator });
|
||||
channel.push({ lastBuildDate: new Date().toUTCString() });
|
||||
|
||||
ifTruePush(data.feed_url, channel, { 'atom:link': { _attr: { href: data.feed_url, rel: 'self', type: 'application/rss+xml' } } });
|
||||
ifTruePush(data.author, channel, { 'author': { _cdata: data.author } });
|
||||
ifTruePush(data.pubDate, channel, { 'pubDate': new Date(data.pubDate).toGMTString() });
|
||||
ifTruePush(data.copyright, channel, { 'copyright': { _cdata: data.copyright } });
|
||||
ifTruePush(data.language, channel, { 'language': { _cdata: data.language } });
|
||||
ifTruePush(data.managingEditor, channel, { 'managingEditor': { _cdata: data.managingEditor } });
|
||||
ifTruePush(data.webMaster, channel, { 'webMaster': { _cdata: data.webMaster } });
|
||||
ifTruePush(data.docs, channel, { 'docs': data.docs });
|
||||
ifTruePush(data.ttl, channel, { 'ttl': data.ttl });
|
||||
ifTruePush(data.hub, channel, { 'atom:link': { _attr: { href: data.hub, rel: 'hub' } } });
|
||||
|
||||
if (data.categories) {
|
||||
data.categories.forEach(function (category) {
|
||||
ifTruePush(category, channel, { category: { _cdata: category } });
|
||||
});
|
||||
}
|
||||
|
||||
ifTruePushArray(data.custom_elements, channel, data.custom_elements);
|
||||
|
||||
data.items.forEach(function (item) {
|
||||
var item_values = [
|
||||
{ title: { _cdata: item.title } }
|
||||
];
|
||||
ifTruePush(item.description, item_values, { description: { _cdata: item.description } });
|
||||
ifTruePush(item.url, item_values, { link: item.url });
|
||||
ifTruePush(item.link || item.guid || item.title, item_values, { guid: [{ _attr: { isPermaLink: !item.guid && !!item.url } }, item.guid || item.url || item.title] });
|
||||
|
||||
item.categories.forEach(function (category) {
|
||||
ifTruePush(category, item_values, { category: { _cdata: category } });
|
||||
});
|
||||
|
||||
ifTruePush(item.author || data.author, item_values, { 'dc:creator': { _cdata: item.author || data.author } });
|
||||
ifTruePush(item.date, item_values, { pubDate: new Date(item.date).toGMTString() });
|
||||
|
||||
//Set GeoRSS to true if lat and long are set
|
||||
data.geoRSS = data.geoRSS || (item.lat && item.long);
|
||||
ifTruePush(item.lat, item_values, { 'geo:lat': item.lat });
|
||||
ifTruePush(item.long, item_values, { 'geo:long': item.long });
|
||||
|
||||
if (item.enclosure && item.enclosure.url) {
|
||||
if (item.enclosure.file) {
|
||||
item_values.push({
|
||||
enclosure: {
|
||||
_attr: {
|
||||
url: item.enclosure.url,
|
||||
length: item.enclosure.size || getSize(item.enclosure.file),
|
||||
type: item.enclosure.type || mime.lookup(item.enclosure.file)
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
item_values.push({
|
||||
enclosure: {
|
||||
_attr: {
|
||||
url: item.enclosure.url,
|
||||
length: item.enclosure.size || 0,
|
||||
type: item.enclosure.type || mime.lookup(item.enclosure.url)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ifTruePushArray(item.custom_elements, item_values, item.custom_elements);
|
||||
|
||||
channel.push({ item: item_values });
|
||||
|
||||
});
|
||||
|
||||
//set up the attributes for the RSS feed.
|
||||
var _attr = {
|
||||
'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
|
||||
'xmlns:content': 'http://purl.org/rss/1.0/modules/content/',
|
||||
'xmlns:atom': 'http://www.w3.org/2005/Atom',
|
||||
version: '2.0'
|
||||
};
|
||||
|
||||
Object.keys(data.custom_namespaces).forEach(function (name) {
|
||||
_attr['xmlns:' + name] = data.custom_namespaces[name];
|
||||
});
|
||||
|
||||
//only add namespace if GeoRSS is true
|
||||
if (data.geoRSS) {
|
||||
_attr['xmlns:geo'] = 'http://www.w3.org/2003/01/geo/wgs84_pos#';
|
||||
}
|
||||
|
||||
return {
|
||||
rss: [
|
||||
{ _attr: _attr },
|
||||
{ channel: channel }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function RSS(options, items) {
|
||||
options = options || {};
|
||||
|
||||
this.title = options.title || 'Untitled RSS Feed';
|
||||
this.description = options.description || '';
|
||||
this.generator = options.generator || 'RSS for Node';
|
||||
this.feed_url = options.feed_url;
|
||||
this.site_url = options.site_url;
|
||||
this.image_url = options.image_url;
|
||||
this.author = options.author;
|
||||
this.categories = options.categories;
|
||||
this.pubDate = options.pubDate;
|
||||
this.hub = options.hub;
|
||||
this.docs = options.docs;
|
||||
this.copyright = options.copyright;
|
||||
this.language = options.language;
|
||||
this.managingEditor = options.managingEditor;
|
||||
this.webMaster = options.webMaster;
|
||||
this.ttl = options.ttl;
|
||||
//option to return feed as GeoRSS is set automatically if feed.lat/long is used
|
||||
this.geoRSS = options.geoRSS || false;
|
||||
this.custom_namespaces = options.custom_namespaces || {};
|
||||
this.custom_elements = options.custom_elements || [];
|
||||
this.items = items || [];
|
||||
|
||||
this.item = function (options) {
|
||||
options = options || {};
|
||||
var item = {
|
||||
title: options.title || 'No title',
|
||||
description: options.description || '',
|
||||
url: options.url,
|
||||
guid: options.guid,
|
||||
categories: options.categories || [],
|
||||
author: options.author,
|
||||
date: options.date,
|
||||
lat: options.lat,
|
||||
long: options.long,
|
||||
enclosure: options.enclosure || false,
|
||||
custom_elements: options.custom_elements || []
|
||||
};
|
||||
|
||||
this.items.push(item);
|
||||
return this;
|
||||
};
|
||||
|
||||
this.xml = function (indent) {
|
||||
return '<?xml version="1.0" encoding="UTF-8"?>' +
|
||||
xml(generateXML(this), indent);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = RSS;
|
17
server/libs/xml/escapeForXML.js
Normal file
17
server/libs/xml/escapeForXML.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
var XML_CHARACTER_MAP = {
|
||||
'&': '&',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'<': '<',
|
||||
'>': '>'
|
||||
};
|
||||
|
||||
function escapeForXML(string) {
|
||||
return string && string.replace
|
||||
? string.replace(/([&"<>'])/g, function (str, item) {
|
||||
return XML_CHARACTER_MAP[item];
|
||||
})
|
||||
: string;
|
||||
}
|
||||
|
||||
module.exports = escapeForXML;
|
286
server/libs/xml/index.js
Normal file
286
server/libs/xml/index.js
Normal file
|
@ -0,0 +1,286 @@
|
|||
// node-xml
|
||||
// SOURCE: https://github.com/dylang/node-xml
|
||||
// LICENSE: https://github.com/dylang/node-xml/blob/master/LICENSE
|
||||
|
||||
var escapeForXML = require('./escapeForXML');
|
||||
var Stream = require('stream').Stream;
|
||||
|
||||
var DEFAULT_INDENT = ' ';
|
||||
|
||||
function xml(input, options) {
|
||||
|
||||
if (typeof options !== 'object') {
|
||||
options = {
|
||||
indent: options
|
||||
};
|
||||
}
|
||||
|
||||
var stream = options.stream ? new Stream() : null,
|
||||
output = "",
|
||||
interrupted = false,
|
||||
indent = !options.indent ? ''
|
||||
: options.indent === true ? DEFAULT_INDENT
|
||||
: options.indent,
|
||||
instant = true;
|
||||
|
||||
|
||||
function delay(func) {
|
||||
if (!instant) {
|
||||
func();
|
||||
} else {
|
||||
process.nextTick(func);
|
||||
}
|
||||
}
|
||||
|
||||
function append(interrupt, out) {
|
||||
if (out !== undefined) {
|
||||
output += out;
|
||||
}
|
||||
if (interrupt && !interrupted) {
|
||||
stream = stream || new Stream();
|
||||
interrupted = true;
|
||||
}
|
||||
if (interrupt && interrupted) {
|
||||
var data = output;
|
||||
delay(function () { stream.emit('data', data) });
|
||||
output = "";
|
||||
}
|
||||
}
|
||||
|
||||
function add(value, last) {
|
||||
format(append, resolve(value, indent, indent ? 1 : 0), last);
|
||||
}
|
||||
|
||||
function end() {
|
||||
if (stream) {
|
||||
var data = output;
|
||||
delay(function () {
|
||||
stream.emit('data', data);
|
||||
stream.emit('end');
|
||||
stream.readable = false;
|
||||
stream.emit('close');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addXmlDeclaration(declaration) {
|
||||
var encoding = declaration.encoding || 'UTF-8',
|
||||
attr = { version: '1.0', encoding: encoding };
|
||||
|
||||
if (declaration.standalone) {
|
||||
attr.standalone = declaration.standalone
|
||||
}
|
||||
|
||||
add({ '?xml': { _attr: attr } });
|
||||
output = output.replace('/>', '?>');
|
||||
}
|
||||
|
||||
// disable delay delayed
|
||||
delay(function () { instant = false });
|
||||
|
||||
if (options.declaration) {
|
||||
addXmlDeclaration(options.declaration);
|
||||
}
|
||||
|
||||
if (input && input.forEach) {
|
||||
input.forEach(function (value, i) {
|
||||
var last;
|
||||
if (i + 1 === input.length)
|
||||
last = end;
|
||||
add(value, last);
|
||||
});
|
||||
} else {
|
||||
add(input, end);
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
stream.readable = true;
|
||||
return stream;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function element(/*input, …*/) {
|
||||
var input = Array.prototype.slice.call(arguments),
|
||||
self = {
|
||||
_elem: resolve(input)
|
||||
};
|
||||
|
||||
self.push = function (input) {
|
||||
if (!this.append) {
|
||||
throw new Error("not assigned to a parent!");
|
||||
}
|
||||
var that = this;
|
||||
var indent = this._elem.indent;
|
||||
format(this.append, resolve(
|
||||
input, indent, this._elem.icount + (indent ? 1 : 0)),
|
||||
function () { that.append(true) });
|
||||
};
|
||||
|
||||
self.close = function (input) {
|
||||
if (input !== undefined) {
|
||||
this.push(input);
|
||||
}
|
||||
if (this.end) {
|
||||
this.end();
|
||||
}
|
||||
};
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function create_indent(character, count) {
|
||||
return (new Array(count || 0).join(character || ''))
|
||||
}
|
||||
|
||||
function resolve(data, indent, indent_count) {
|
||||
indent_count = indent_count || 0;
|
||||
var indent_spaces = create_indent(indent, indent_count);
|
||||
var name;
|
||||
var values = data;
|
||||
var interrupt = false;
|
||||
|
||||
if (typeof data === 'object') {
|
||||
var keys = Object.keys(data);
|
||||
name = keys[0];
|
||||
values = data[name];
|
||||
|
||||
if (values && values._elem) {
|
||||
values._elem.name = name;
|
||||
values._elem.icount = indent_count;
|
||||
values._elem.indent = indent;
|
||||
values._elem.indents = indent_spaces;
|
||||
values._elem.interrupt = values;
|
||||
return values._elem;
|
||||
}
|
||||
}
|
||||
|
||||
var attributes = [],
|
||||
content = [];
|
||||
|
||||
var isStringContent;
|
||||
|
||||
function get_attributes(obj) {
|
||||
var keys = Object.keys(obj);
|
||||
keys.forEach(function (key) {
|
||||
attributes.push(attribute(key, obj[key]));
|
||||
});
|
||||
}
|
||||
|
||||
switch (typeof values) {
|
||||
case 'object':
|
||||
if (values === null) break;
|
||||
|
||||
if (values._attr) {
|
||||
get_attributes(values._attr);
|
||||
}
|
||||
|
||||
if (values._cdata) {
|
||||
content.push(
|
||||
('<![CDATA[' + values._cdata).replace(/\]\]>/g, ']]]]><![CDATA[>') + ']]>'
|
||||
);
|
||||
}
|
||||
|
||||
if (values.forEach) {
|
||||
isStringContent = false;
|
||||
content.push('');
|
||||
values.forEach(function (value) {
|
||||
if (typeof value == 'object') {
|
||||
var _name = Object.keys(value)[0];
|
||||
|
||||
if (_name == '_attr') {
|
||||
get_attributes(value._attr);
|
||||
} else {
|
||||
content.push(resolve(
|
||||
value, indent, indent_count + 1));
|
||||
}
|
||||
} else {
|
||||
//string
|
||||
content.pop();
|
||||
isStringContent = true;
|
||||
content.push(escapeForXML(value));
|
||||
}
|
||||
|
||||
});
|
||||
if (!isStringContent) {
|
||||
content.push('');
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
//string
|
||||
content.push(escapeForXML(values));
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
name: name,
|
||||
interrupt: interrupt,
|
||||
attributes: attributes,
|
||||
content: content,
|
||||
icount: indent_count,
|
||||
indents: indent_spaces,
|
||||
indent: indent
|
||||
};
|
||||
}
|
||||
|
||||
function format(append, elem, end) {
|
||||
|
||||
if (typeof elem != 'object') {
|
||||
return append(false, elem);
|
||||
}
|
||||
|
||||
var len = elem.interrupt ? 1 : elem.content.length;
|
||||
|
||||
function proceed() {
|
||||
while (elem.content.length) {
|
||||
var value = elem.content.shift();
|
||||
|
||||
if (value === undefined) continue;
|
||||
if (interrupt(value)) return;
|
||||
|
||||
format(append, value);
|
||||
}
|
||||
|
||||
append(false, (len > 1 ? elem.indents : '')
|
||||
+ (elem.name ? '</' + elem.name + '>' : '')
|
||||
+ (elem.indent && !end ? '\n' : ''));
|
||||
|
||||
if (end) {
|
||||
end();
|
||||
}
|
||||
}
|
||||
|
||||
function interrupt(value) {
|
||||
if (value.interrupt) {
|
||||
value.interrupt.append = append;
|
||||
value.interrupt.end = proceed;
|
||||
value.interrupt = false;
|
||||
append(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
append(false, elem.indents
|
||||
+ (elem.name ? '<' + elem.name : '')
|
||||
+ (elem.attributes.length ? ' ' + elem.attributes.join(' ') : '')
|
||||
+ (len ? (elem.name ? '>' : '') : (elem.name ? '/>' : ''))
|
||||
+ (elem.indent && len > 1 ? '\n' : ''));
|
||||
|
||||
if (!len) {
|
||||
return append(false, elem.indent ? '\n' : '');
|
||||
}
|
||||
|
||||
if (!interrupt(elem)) {
|
||||
proceed();
|
||||
}
|
||||
}
|
||||
|
||||
function attribute(key, value) {
|
||||
return key + '=' + '"' + escapeForXML(value) + '"';
|
||||
}
|
||||
|
||||
module.exports = xml;
|
||||
module.exports.element = module.exports.Element = element;
|
Loading…
Add table
Add a link
Reference in a new issue