From 2a3017972a03740217e835c0f7d6282139b8799f Mon Sep 17 00:00:00 2001 From: Tzahi12345 Date: Tue, 3 Jan 2023 23:59:16 -0500 Subject: [PATCH] Added ability to generate RSS feeds from downloads --- Public API v1.yaml | 38 +++++++++++ backend/app.js | 80 ++++++++++++++++-------- backend/config.js | 1 + backend/consts.js | 4 ++ backend/db.js | 28 ++++++++- backend/package-lock.json | 21 +++++++ backend/package.json | 1 + src/app/settings/settings.component.html | 11 ++++ 8 files changed, 155 insertions(+), 29 deletions(-) diff --git a/Public API v1.yaml b/Public API v1.yaml index 3e84cee..0650019 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -111,6 +111,37 @@ paths: $ref: '#/components/schemas/GetAllFilesResponse' security: - Auth query parameter: [] + /api/rss: + get: + tags: + - files + summary: Generates an RSS feed + description: Generates an RSS feed for downloaded files + operationId: get-rss + parameters: + - in: query + name: params + schema: + allOf: + - $ref: '#/components/schemas/GetAllFilesRequest' + - type: object + properties: + uuid: + type: string + description: user uid + default: null + style: form + explode: true + responses: + '200': + description: OK + content: + text/plain: + schema: + type: string + description: RSS feed + security: + - Auth query parameter: [] /api/getFile: post: tags: @@ -1755,32 +1786,39 @@ components: description: Two elements allowed, start index and end index minItems: 2 maxItems: 2 + default: null text_search: type: string description: Filter files by title + default: null file_type_filter: $ref: '#/components/schemas/FileTypeFilter' favorite_filter: type: boolean description: If set to true, only gets favorites + default: false sub_id: type: string description: Include if you want to filter by subscription + default: null Sort: type: object properties: by: type: string description: Property to sort by + default: registered order: type: number description: 1 for ascending, -1 for descending + default: -1 FileTypeFilter: type: string enum: - audio_only - video_only - both + default: both GetAllFilesResponse: required: - files diff --git a/backend/app.js b/backend/app.js index f4c66ef..90c72ab 100644 --- a/backend/app.js +++ b/backend/app.js @@ -18,6 +18,7 @@ const URL = require('url').URL; const CONSTS = require('./consts') const read_last_lines = require('read-last-lines'); const ps = require('ps-node'); +const Feed = require('feed').Feed; // needed if bin/details somehow gets deleted if (!fs.existsSync(CONSTS.DETAILS_BIN_PATH)) fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, {"version":"2000.06.06","path":"node_modules\\youtube-dl\\bin\\youtube-dl.exe","exec":"youtube-dl.exe","downloader":"youtube-dl"}) @@ -700,7 +701,7 @@ app.use(function(req, res, next) { next(); } else if (req.query.apiKey && config_api.getConfigItem('ytdl_use_api_key') && req.query.apiKey === config_api.getConfigItem('ytdl_api_key')) { next(); - } else if (req.path.includes('/api/stream/') || req.path.includes('/api/thumbnail/')) { + } else if (req.path.includes('/api/stream/') || req.path.includes('/api/thumbnail/') || req.path.includes('/api/rss')) { next(); } else { logger.verbose(`Rejecting request - invalid API use for endpoint: ${req.path}. API key received: ${req.query.apiKey}`); @@ -923,7 +924,6 @@ app.post('/api/getFile', optionalJwt, async function (req, res) { app.post('/api/getAllFiles', optionalJwt, async function (req, res) { // these are returned - let files = null; const sort = req.body.sort; const range = req.body.range; const text_search = req.body.text_search; @@ -932,31 +932,7 @@ app.post('/api/getAllFiles', optionalJwt, async function (req, res) { const sub_id = req.body.sub_id; const uuid = req.isAuthenticated() ? req.user.uid : null; - const filter_obj = {user_uid: uuid}; - const regex = true; - if (text_search) { - if (regex) { - filter_obj['title'] = {$regex: `.*${text_search}.*`, $options: 'i'}; - } else { - filter_obj['$text'] = { $search: utils.createEdgeNGrams(text_search) }; - } - } - - if (favorite_filter) { - filter_obj['favorite'] = true; - } - - if (sub_id) { - filter_obj['sub_id'] = sub_id; - } - - if (file_type_filter === 'audio_only') filter_obj['isAudio'] = true; - else if (file_type_filter === 'video_only') filter_obj['isAudio'] = false; - - files = await db_api.getRecords('files', filter_obj, false, sort, range, text_search); - const file_count = await db_api.getRecords('files', filter_obj, true); - - files = JSON.parse(JSON.stringify(files)); + const {files, file_count} = await db_api.getAllFiles(sort, range, text_search, file_type_filter, favorite_filter, sub_id, uuid); res.send({ files: files, @@ -2043,6 +2019,56 @@ app.post('/api/deleteAllNotifications', optionalJwt, async (req, res) => { res.send({success: success}); }); +// rss feed + +app.get('/api/rss', async function (req, res) { + if (!config_api.getConfigItem('ytdl_enable_rss_feed')) { + logger.error('RSS feed is disabled! It must be enabled in the settings before it can be generated.'); + res.sendStatus(403); + return; + } + + // these are returned + const sort = req.query.sort; + const range = req.query.range; + const text_search = req.query.text_search; + const file_type_filter = req.query.file_type_filter; + const favorite_filter = req.query.favorite_filter; + const sub_id = req.query.sub_id; + const uuid = req.query.uuid; + + const {files} = await db_api.getAllFiles(sort, range, text_search, file_type_filter, favorite_filter, sub_id, uuid); + + const feed = new Feed({ + title: 'Downloads', + description: 'YoutubeDL-Material downloads', + id: utils.getBaseURL(), + link: utils.getBaseURL(), + image: 'https://github.com/Tzahi12345/YoutubeDL-Material/blob/master/src/assets/images/logo_128px.png', + favicon: 'https://raw.githubusercontent.com/Tzahi12345/YoutubeDL-Material/master/src/favicon.ico', + generator: 'YoutubeDL-Material' + }); + + files.forEach(file => { + feed.addItem({ + title: file.title, + link: `${utils.getBaseURL()}/#/player;uid=${file.uid}`, + description: file.description, + author: [ + { + name: file.uploader, + link: file.url + } + ], + contributor: [], + date: file.timestamp, + // https://stackoverflow.com/a/45415677/8088021 + image: file.thumbnailURL.replace('&', '&') + }); + }); + res.send(feed.rss2()); +}); + // web server app.use(function(req, res, next) { diff --git a/backend/config.js b/backend/config.js index 432b382..2d7dff1 100644 --- a/backend/config.js +++ b/backend/config.js @@ -202,6 +202,7 @@ const DEFAULT_CONFIG = { "enable_notifications": true, "enable_all_notifications": true, "allowed_notification_types": [], + "enable_rss_feed": false, }, "API": { "use_API_key": false, diff --git a/backend/consts.js b/backend/consts.js index fcb2e1c..e64f9b6 100644 --- a/backend/consts.js +++ b/backend/consts.js @@ -92,6 +92,10 @@ exports.CONFIG_ITEMS = { 'key': 'ytdl_allowed_notification_types', 'path': 'YoutubeDLMaterial.Extra.allowed_notification_types' }, + 'ytdl_enable_rss_feed': { + 'key': 'ytdl_enable_rss_feed', + 'path': 'YoutubeDLMaterial.Extra.enable_rss_feed' + }, // API 'ytdl_use_api_key': { diff --git a/backend/db.js b/backend/db.js index f3c1948..8249ece 100644 --- a/backend/db.js +++ b/backend/db.js @@ -538,8 +538,32 @@ exports.getVideo = async (file_uid) => { return await exports.getRecord('files', {uid: file_uid}); } -exports.getFiles = async (uuid = null) => { - return await exports.getRecords('files', {user_uid: uuid}); +exports.getAllFiles = async (sort, range, text_search, file_type_filter, favorite_filter, sub_id, uuid) => { + const filter_obj = {user_uid: uuid}; + const regex = true; + if (text_search) { + if (regex) { + filter_obj['title'] = {$regex: `.*${text_search}.*`, $options: 'i'}; + } else { + filter_obj['$text'] = { $search: utils.createEdgeNGrams(text_search) }; + } + } + + if (favorite_filter) { + filter_obj['favorite'] = true; + } + + if (sub_id) { + filter_obj['sub_id'] = sub_id; + } + + if (file_type_filter === 'audio_only') filter_obj['isAudio'] = true; + else if (file_type_filter === 'video_only') filter_obj['isAudio'] = false; + + const files = JSON.parse(JSON.stringify(await exports.getRecords('files', filter_obj, false, sort, range, text_search))); + const file_count = await exports.getRecords('files', filter_obj, true); + + return {files, file_count}; } exports.setVideoProperty = async (file_uid, assignment_obj) => { diff --git a/backend/package-lock.json b/backend/package-lock.json index 03bea2c..46d5761 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1024,6 +1024,14 @@ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" }, + "feed": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", + "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "requires": { + "xml-js": "^1.6.11" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2607,6 +2615,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -3112,6 +3125,14 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, "xmlbuilder2": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz", diff --git a/backend/package.json b/backend/package.json index 2ba0ef3..a1a3527 100644 --- a/backend/package.json +++ b/backend/package.json @@ -27,6 +27,7 @@ "compression": "^1.7.4", "config": "^3.2.3", "express": "^4.17.3", + "feed": "^4.2.2", "fluent-ffmpeg": "^2.1.2", "fs-extra": "^9.0.0", "gotify": "^1.1.0", diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index c34301b..a704bc7 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -297,6 +297,17 @@ +
+
+
+
RSS Feed
+ Enable RSS Feed +

Be careful enabling this with multi-user mode! User data may be exposed.

+

See documentation here.

+
+
+
+