mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-05-31 20:25:34 -04:00
Update:Remove rss feed dependencies add node-xml lib
This commit is contained in:
parent
46fc89e247
commit
03bffb725a
83
package-lock.json
generated
83
package-lock.json
generated
@ -5,7 +5,7 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"version": "2.0.17",
|
"version": "2.0.20",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "^5.3.0",
|
"archiver": "^5.3.0",
|
||||||
@ -26,7 +26,6 @@
|
|||||||
"node-cron": "^3.0.0",
|
"node-cron": "^3.0.0",
|
||||||
"node-ffprobe": "^3.0.0",
|
"node-ffprobe": "^3.0.0",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
"podcast": "^2.0.0",
|
|
||||||
"proper-lockfile": "^4.1.2",
|
"proper-lockfile": "^4.1.2",
|
||||||
"read-chunk": "^3.1.0",
|
"read-chunk": "^3.1.0",
|
||||||
"recursive-readdir-async": "^1.1.8",
|
"recursive-readdir-async": "^1.1.8",
|
||||||
@ -1560,14 +1559,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/podcast": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/podcast/-/podcast-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-1NZe7cVabfkcMe39yOOhBMJrXC6OROLOIBkfEPayiJL59ncyJmOeO5bxolSCSGroho8jQ0zURMczyICI1U/xbw==",
|
|
||||||
"dependencies": {
|
|
||||||
"rss": "^1.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/process-nextick-args": {
|
"node_modules/process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
@ -1713,34 +1704,6 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rss": {
|
|
||||||
"version": "1.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/rss/-/rss-1.2.2.tgz",
|
|
||||||
"integrity": "sha1-UKFpiHYTgTOnT5oF0r3I240nqSE=",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-types": "2.1.13",
|
|
||||||
"xml": "1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rss/node_modules/mime-db": {
|
|
||||||
"version": "1.25.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz",
|
|
||||||
"integrity": "sha1-wY29fHOl2/b0SgJNwNFloeexw5I=",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rss/node_modules/mime-types": {
|
|
||||||
"version": "2.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz",
|
|
||||||
"integrity": "sha1-4HqqnGxrmnyjASxpADrSWjnpKog=",
|
|
||||||
"dependencies": {
|
|
||||||
"mime-db": "~1.25.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@ -2072,11 +2035,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/xml": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU="
|
|
||||||
},
|
|
||||||
"node_modules/xml2js": {
|
"node_modules/xml2js": {
|
||||||
"version": "0.4.23",
|
"version": "0.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||||
@ -3271,14 +3229,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
|
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
|
||||||
},
|
},
|
||||||
"podcast": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/podcast/-/podcast-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-1NZe7cVabfkcMe39yOOhBMJrXC6OROLOIBkfEPayiJL59ncyJmOeO5bxolSCSGroho8jQ0zURMczyICI1U/xbw==",
|
|
||||||
"requires": {
|
|
||||||
"rss": "^1.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"process-nextick-args": {
|
"process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
@ -3391,30 +3341,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||||
"integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
|
"integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
|
||||||
},
|
},
|
||||||
"rss": {
|
|
||||||
"version": "1.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/rss/-/rss-1.2.2.tgz",
|
|
||||||
"integrity": "sha1-UKFpiHYTgTOnT5oF0r3I240nqSE=",
|
|
||||||
"requires": {
|
|
||||||
"mime-types": "2.1.13",
|
|
||||||
"xml": "1.0.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"mime-db": {
|
|
||||||
"version": "1.25.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz",
|
|
||||||
"integrity": "sha1-wY29fHOl2/b0SgJNwNFloeexw5I="
|
|
||||||
},
|
|
||||||
"mime-types": {
|
|
||||||
"version": "2.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz",
|
|
||||||
"integrity": "sha1-4HqqnGxrmnyjASxpADrSWjnpKog=",
|
|
||||||
"requires": {
|
|
||||||
"mime-db": "~1.25.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
@ -3654,11 +3580,6 @@
|
|||||||
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"xml": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU="
|
|
||||||
},
|
|
||||||
"xml2js": {
|
"xml2js": {
|
||||||
"version": "0.4.23",
|
"version": "0.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||||
@ -3684,4 +3605,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,11 +45,10 @@
|
|||||||
"node-cron": "^3.0.0",
|
"node-cron": "^3.0.0",
|
||||||
"node-ffprobe": "^3.0.0",
|
"node-ffprobe": "^3.0.0",
|
||||||
"node-stream-zip": "^1.15.0",
|
"node-stream-zip": "^1.15.0",
|
||||||
"podcast": "^2.0.0",
|
|
||||||
"proper-lockfile": "^4.1.2",
|
"proper-lockfile": "^4.1.2",
|
||||||
"read-chunk": "^3.1.0",
|
"read-chunk": "^3.1.0",
|
||||||
"recursive-readdir-async": "^1.1.8",
|
"recursive-readdir-async": "^1.1.8",
|
||||||
"socket.io": "^4.4.1",
|
"socket.io": "^4.4.1",
|
||||||
"xml2js": "^0.4.23"
|
"xml2js": "^0.4.23"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
@ -1,7 +1,5 @@
|
|||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const date = require('date-and-time')
|
|
||||||
const { Podcast } = require('podcast')
|
|
||||||
const Feed = require('../objects/Feed')
|
const Feed = require('../objects/Feed')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
|
|
||||||
@ -35,7 +33,7 @@ class RssFeedManager {
|
|||||||
res.sendStatus(404)
|
res.sendStatus(404)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// var xml = feedData.feed.buildXml()
|
|
||||||
var xml = feed.buildXml()
|
var xml = feed.buildXml()
|
||||||
res.set('Content-Type', 'text/xml')
|
res.set('Content-Type', 'text/xml')
|
||||||
res.send(xml)
|
res.send(xml)
|
||||||
@ -55,9 +53,6 @@ class RssFeedManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.sendFile(episodePath)
|
res.sendFile(episodePath)
|
||||||
// var remainingPath = req.params['0']
|
|
||||||
// var fullPath = Path.join(feedData.libraryItemPath, remainingPath)
|
|
||||||
// res.sendFile(fullPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getFeedCover(req, res) {
|
getFeedCover(req, res) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const FeedMeta = require('./FeedMeta')
|
const FeedMeta = require('./FeedMeta')
|
||||||
const FeedEpisode = require('./FeedEpisode')
|
const FeedEpisode = require('./FeedEpisode')
|
||||||
const { Podcast } = require('podcast')
|
const RSS = require('../libs/rss')
|
||||||
|
|
||||||
class Feed {
|
class Feed {
|
||||||
constructor(feed) {
|
constructor(feed) {
|
||||||
@ -90,6 +90,7 @@ class Feed {
|
|||||||
this.meta.imageUrl = media.coverPath ? `${serverAddress}/feed/${slug}/cover` : `${serverAddress}/Logo.png`
|
this.meta.imageUrl = media.coverPath ? `${serverAddress}/feed/${slug}/cover` : `${serverAddress}/Logo.png`
|
||||||
this.meta.feedUrl = feedUrl
|
this.meta.feedUrl = feedUrl
|
||||||
this.meta.link = `${serverAddress}/items/${libraryItem.id}`
|
this.meta.link = `${serverAddress}/items/${libraryItem.id}`
|
||||||
|
this.meta.explicit = !!mediaMetadata.explicit
|
||||||
|
|
||||||
this.episodes = []
|
this.episodes = []
|
||||||
if (isPodcast) { // PODCAST EPISODES
|
if (isPodcast) { // PODCAST EPISODES
|
||||||
@ -113,11 +114,11 @@ class Feed {
|
|||||||
buildXml() {
|
buildXml() {
|
||||||
if (this.xml) return this.xml
|
if (this.xml) return this.xml
|
||||||
|
|
||||||
const pod = new Podcast(this.meta.getPodcastMeta())
|
var rssfeed = new RSS(this.meta.getRSSData())
|
||||||
this.episodes.forEach((ep) => {
|
this.episodes.forEach((ep) => {
|
||||||
pod.addItem(ep.getPodcastEpisode())
|
rssfeed.item(ep.getRSSData())
|
||||||
})
|
})
|
||||||
this.xml = pod.buildXml()
|
this.xml = rssfeed.xml()
|
||||||
return this.xml
|
return this.xml
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const date = require('date-and-time')
|
const date = require('date-and-time')
|
||||||
|
const { secondsToTimestamp } = require('../utils/index')
|
||||||
|
|
||||||
class FeedEpisode {
|
class FeedEpisode {
|
||||||
constructor(episode) {
|
constructor(episode) {
|
||||||
@ -116,14 +117,23 @@ class FeedEpisode {
|
|||||||
this.fullPath = audioTrack.metadata.path
|
this.fullPath = audioTrack.metadata.path
|
||||||
}
|
}
|
||||||
|
|
||||||
getPodcastEpisode() {
|
getRSSData() {
|
||||||
return {
|
return {
|
||||||
title: this.title,
|
title: this.title,
|
||||||
description: this.description || '',
|
description: this.description || '',
|
||||||
enclosure: this.enclosure,
|
|
||||||
date: this.pubDate || '',
|
|
||||||
url: this.link,
|
url: this.link,
|
||||||
author: this.author
|
guid: this.enclosure.url,
|
||||||
|
author: this.author,
|
||||||
|
date: this.pubDate,
|
||||||
|
enclosure: this.enclosure,
|
||||||
|
custom_elements: [
|
||||||
|
{ 'itunes:author': this.author },
|
||||||
|
{ 'itunes:duration': secondsToTimestamp(this.duration) },
|
||||||
|
{ 'itunes:summary': this.description || '' },
|
||||||
|
{
|
||||||
|
"itunes:explicit": !!this.explicit
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ class FeedMeta {
|
|||||||
this.imageUrl = null
|
this.imageUrl = null
|
||||||
this.feedUrl = null
|
this.feedUrl = null
|
||||||
this.link = null
|
this.link = null
|
||||||
|
this.explicit = null
|
||||||
|
|
||||||
if (meta) {
|
if (meta) {
|
||||||
this.construct(meta)
|
this.construct(meta)
|
||||||
@ -19,6 +20,7 @@ class FeedMeta {
|
|||||||
this.imageUrl = meta.imageUrl
|
this.imageUrl = meta.imageUrl
|
||||||
this.feedUrl = meta.feedUrl
|
this.feedUrl = meta.feedUrl
|
||||||
this.link = meta.link
|
this.link = meta.link
|
||||||
|
this.explicit = meta.explicit
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
@ -28,19 +30,46 @@ class FeedMeta {
|
|||||||
author: this.author,
|
author: this.author,
|
||||||
imageUrl: this.imageUrl,
|
imageUrl: this.imageUrl,
|
||||||
feedUrl: this.feedUrl,
|
feedUrl: this.feedUrl,
|
||||||
link: this.link
|
link: this.link,
|
||||||
|
explicit: this.explicit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPodcastMeta() {
|
getRSSData() {
|
||||||
return {
|
return {
|
||||||
title: this.title,
|
title: this.title,
|
||||||
description: this.description,
|
description: this.description || '',
|
||||||
feedUrl: this.feedUrl,
|
generator: 'Audiobookshelf',
|
||||||
siteUrl: this.link,
|
feed_url: this.feedUrl,
|
||||||
imageUrl: this.imageUrl,
|
site_url: this.link,
|
||||||
author: this.author || 'advplyr',
|
image_url: this.imageUrl,
|
||||||
language: 'en'
|
language: 'en',
|
||||||
|
custom_namespaces: {
|
||||||
|
'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd',
|
||||||
|
'psc': 'http://podlove.org/simple-chapters',
|
||||||
|
'podcast': 'https://podcastindex.org/namespace/1.0'
|
||||||
|
},
|
||||||
|
custom_elements: [
|
||||||
|
{ 'author': this.author || 'advplyr' },
|
||||||
|
{ 'itunes:author': this.author || 'advplyr' },
|
||||||
|
{ 'itunes:summary': this.description || '' },
|
||||||
|
{
|
||||||
|
'itunes:image': {
|
||||||
|
_attr: {
|
||||||
|
href: this.imageUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'itunes:owner': [
|
||||||
|
{ 'itunes:name': this.author || '' },
|
||||||
|
{ 'itunes:email': '' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itunes:explicit": !!this.explicit
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user