mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-04-22 05:23:21 +03:00
Replaced /audio and /video APIs with /stream that now requires a type parameter to simplify future code changes
getSubscription can now accept a subscription name instead of just an ID Added API call to delete a category Categories can now have a custom path Minor code cleanup
This commit is contained in:
127
backend/app.js
127
backend/app.js
@@ -83,16 +83,6 @@ db_api.initialize(db, users_db, logger);
|
|||||||
subscriptions_api.initialize(db, users_db, logger, db_api);
|
subscriptions_api.initialize(db, users_db, logger, db_api);
|
||||||
categories_api.initialize(db, users_db, logger, db_api);
|
categories_api.initialize(db, users_db, logger, db_api);
|
||||||
|
|
||||||
|
|
||||||
async function test() {
|
|
||||||
const test_cat = await categories_api.categorize(fs.readJSONSync('video/Claire Lost Her First Tooth!.info.json'));
|
|
||||||
console.log(test_cat);
|
|
||||||
}
|
|
||||||
|
|
||||||
test();
|
|
||||||
|
|
||||||
// var GithubContent = require('github-content');
|
|
||||||
|
|
||||||
// Set some defaults
|
// Set some defaults
|
||||||
db.defaults(
|
db.defaults(
|
||||||
{
|
{
|
||||||
@@ -184,7 +174,6 @@ const subscription_timeouts = {};
|
|||||||
// don't overwrite config if it already happened.. NOT
|
// don't overwrite config if it already happened.. NOT
|
||||||
// let alreadyWritten = db.get('configWriteFlag').value();
|
// let alreadyWritten = db.get('configWriteFlag').value();
|
||||||
let writeConfigMode = process.env.write_ytdl_config;
|
let writeConfigMode = process.env.write_ytdl_config;
|
||||||
var config = null;
|
|
||||||
|
|
||||||
// checks if config exists, if not, a config is auto generated
|
// checks if config exists, if not, a config is auto generated
|
||||||
config_api.configExistsCheck();
|
config_api.configExistsCheck();
|
||||||
@@ -1221,7 +1210,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
|
|||||||
} catch(e) {
|
} catch(e) {
|
||||||
output_json = null;
|
output_json = null;
|
||||||
}
|
}
|
||||||
var modified_file_name = output_json ? output_json['title'] : null;
|
|
||||||
if (!output_json) {
|
if (!output_json) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1250,8 +1239,11 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
|
|||||||
if (!success) logger.error('Failed to apply ID3 tag to audio file ' + output_json['_filename']);
|
if (!success) logger.error('Failed to apply ID3 tag to audio file ' + output_json['_filename']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const file_path = options.noRelativePath ? path.basename(full_file_path) : full_file_path.substring(fileFolderPath.length, full_file_path.length);
|
||||||
|
const customPath = options.noRelativePath ? path.dirname(full_file_path).split(path.sep).pop() : null;
|
||||||
|
|
||||||
// registers file in DB
|
// registers file in DB
|
||||||
file_uid = db_api.registerFileDB(full_file_path.substring(fileFolderPath.length, full_file_path.length), type, multiUserMode);
|
file_uid = db_api.registerFileDB(file_path, type, multiUserMode, null, customPath);
|
||||||
|
|
||||||
if (file_name) file_names.push(file_name);
|
if (file_name) file_names.push(file_name);
|
||||||
}
|
}
|
||||||
@@ -1795,7 +1787,7 @@ app.use(function(req, res, next) {
|
|||||||
next();
|
next();
|
||||||
} else if (req.query.apiKey && config_api.getConfigItem('ytdl_use_api_key') && req.query.apiKey === config_api.getConfigItem('ytdl_api_key')) {
|
} else if (req.query.apiKey && config_api.getConfigItem('ytdl_use_api_key') && req.query.apiKey === config_api.getConfigItem('ytdl_api_key')) {
|
||||||
next();
|
next();
|
||||||
} else if (req.path.includes('/api/video/') || req.path.includes('/api/audio/')) {
|
} else if (req.path.includes('/api/stream/')) {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
logger.verbose(`Rejecting request - invalid API use for endpoint: ${req.path}. API key received: ${req.query.apiKey}`);
|
logger.verbose(`Rejecting request - invalid API use for endpoint: ${req.path}. API key received: ${req.query.apiKey}`);
|
||||||
@@ -1808,15 +1800,14 @@ app.use(compression());
|
|||||||
const optionalJwt = function (req, res, next) {
|
const optionalJwt = function (req, res, next) {
|
||||||
const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode');
|
const multiUserMode = config_api.getConfigItem('ytdl_multi_user_mode');
|
||||||
if (multiUserMode && ((req.body && req.body.uuid) || (req.query && req.query.uuid)) && (req.path.includes('/api/getFile') ||
|
if (multiUserMode && ((req.body && req.body.uuid) || (req.query && req.query.uuid)) && (req.path.includes('/api/getFile') ||
|
||||||
req.path.includes('/api/audio') ||
|
req.path.includes('/api/stream') ||
|
||||||
req.path.includes('/api/video') ||
|
|
||||||
req.path.includes('/api/downloadFile'))) {
|
req.path.includes('/api/downloadFile'))) {
|
||||||
// check if shared video
|
// check if shared video
|
||||||
const using_body = req.body && req.body.uuid;
|
const using_body = req.body && req.body.uuid;
|
||||||
const uuid = using_body ? req.body.uuid : req.query.uuid;
|
const uuid = using_body ? req.body.uuid : req.query.uuid;
|
||||||
const uid = using_body ? req.body.uid : req.query.uid;
|
const uid = using_body ? req.body.uid : req.query.uid;
|
||||||
const type = using_body ? req.body.type : req.query.type;
|
const type = using_body ? req.body.type : req.query.type;
|
||||||
const file = !req.query.id ? auth_api.getUserVideo(uuid, uid, type, true, req.body) : auth_api.getUserPlaylist(uuid, req.query.id, null, true);
|
const file = !req.query.id ? auth_api.getUserVideo(uuid, uid, type, true, !!req.body) : auth_api.getUserPlaylist(uuid, req.query.id, null, true);
|
||||||
const is_shared = file ? file['sharingEnabled'] : false;
|
const is_shared = file ? file['sharingEnabled'] : false;
|
||||||
if (is_shared) {
|
if (is_shared) {
|
||||||
req.can_watch = true;
|
req.can_watch = true;
|
||||||
@@ -2184,6 +2175,16 @@ app.post('/api/createCategory', optionalJwt, async (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/deleteCategory', optionalJwt, async (req, res) => {
|
||||||
|
const category_uid = req.body.category_uid;
|
||||||
|
|
||||||
|
db.get('categories').remove({uid: category_uid}).write();
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
success: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/api/updateCategory', optionalJwt, async (req, res) => {
|
app.post('/api/updateCategory', optionalJwt, async (req, res) => {
|
||||||
const category = req.body.category;
|
const category = req.body.category;
|
||||||
db.get('categories').find({uid: category.uid}).assign(category).write();
|
db.get('categories').find({uid: category.uid}).assign(category).write();
|
||||||
@@ -2282,10 +2283,17 @@ app.post('/api/deleteSubscriptionFile', optionalJwt, async (req, res) => {
|
|||||||
|
|
||||||
app.post('/api/getSubscription', optionalJwt, async (req, res) => {
|
app.post('/api/getSubscription', optionalJwt, async (req, res) => {
|
||||||
let subID = req.body.id;
|
let subID = req.body.id;
|
||||||
|
let subName = req.body.name; // if included, subID is optional
|
||||||
|
|
||||||
let user_uid = req.isAuthenticated() ? req.user.uid : null;
|
let user_uid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
|
||||||
// get sub from db
|
// get sub from db
|
||||||
let subscription = subscriptions_api.getSubscription(subID, user_uid);
|
let subscription = null;
|
||||||
|
if (subID) {
|
||||||
|
subscription = subscriptions_api.getSubscription(subID, user_uid)
|
||||||
|
} else if (subName) {
|
||||||
|
subscription = subscriptions_api.getSubscriptionByName(subName, user_uid)
|
||||||
|
}
|
||||||
|
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
// failed to get subscription from db, send 400 error
|
// failed to get subscription from db, send 400 error
|
||||||
@@ -2708,25 +2716,33 @@ app.post('/api/generateNewAPIKey', function (req, res) {
|
|||||||
|
|
||||||
// Streaming API calls
|
// Streaming API calls
|
||||||
|
|
||||||
app.get('/api/video/:id', optionalJwt, function(req , res){
|
app.get('/api/stream/:id', optionalJwt, (req, res) => {
|
||||||
|
const type = req.query.type;
|
||||||
|
const ext = type === 'audio' ? '.mp3' : '.mp4';
|
||||||
|
const mimetype = type === 'audio' ? 'audio/mp3' : 'video/mp4';
|
||||||
var head;
|
var head;
|
||||||
let optionalParams = url_api.parse(req.url,true).query;
|
let optionalParams = url_api.parse(req.url,true).query;
|
||||||
let id = decodeURIComponent(req.params.id);
|
let id = decodeURIComponent(req.params.id);
|
||||||
let file_path = videoFolderPath + id + '.mp4';
|
let file_path = req.query.file_path ? decodeURIComponent(req.query.file_path) : null;
|
||||||
if (req.isAuthenticated() || req.can_watch) {
|
if (!file_path && (req.isAuthenticated() || req.can_watch)) {
|
||||||
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
||||||
if (optionalParams['subName']) {
|
if (optionalParams['subName']) {
|
||||||
const isPlaylist = optionalParams['subPlaylist'];
|
const isPlaylist = optionalParams['subPlaylist'];
|
||||||
file_path = path.join(usersFileFolder, req.user.uid, 'subscriptions', (isPlaylist === 'true' ? 'playlists/' : 'channels/'),optionalParams['subName'], id + '.mp4')
|
file_path = path.join(usersFileFolder, req.user.uid, 'subscriptions', (isPlaylist === 'true' ? 'playlists/' : 'channels/'),optionalParams['subName'], id + ext)
|
||||||
} else {
|
} else {
|
||||||
file_path = path.join(usersFileFolder, req.query.uuid ? req.query.uuid : req.user.uid, 'video', id + '.mp4');
|
file_path = path.join(usersFileFolder, req.query.uuid ? req.query.uuid : req.user.uid, type, id + ext);
|
||||||
}
|
}
|
||||||
} else if (optionalParams['subName']) {
|
} else if (!file_path && optionalParams['subName']) {
|
||||||
let basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
let basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
||||||
const isPlaylist = optionalParams['subPlaylist'];
|
const isPlaylist = optionalParams['subPlaylist'];
|
||||||
basePath += (isPlaylist === 'true' ? 'playlists/' : 'channels/');
|
basePath += (isPlaylist === 'true' ? 'playlists/' : 'channels/');
|
||||||
file_path = basePath + optionalParams['subName'] + '/' + id + '.mp4';
|
file_path = basePath + optionalParams['subName'] + '/' + id + ext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!file_path) {
|
||||||
|
file_path = path.join(videoFolderPath, id + ext);
|
||||||
|
}
|
||||||
|
|
||||||
const stat = fs.statSync(file_path)
|
const stat = fs.statSync(file_path)
|
||||||
const fileSize = stat.size
|
const fileSize = stat.size
|
||||||
const range = req.headers.range
|
const range = req.headers.range
|
||||||
@@ -2749,77 +2765,20 @@ app.get('/api/video/:id', optionalJwt, function(req , res){
|
|||||||
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
|
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
|
||||||
'Accept-Ranges': 'bytes',
|
'Accept-Ranges': 'bytes',
|
||||||
'Content-Length': chunksize,
|
'Content-Length': chunksize,
|
||||||
'Content-Type': 'video/mp4',
|
'Content-Type': mimetype,
|
||||||
}
|
}
|
||||||
res.writeHead(206, head);
|
res.writeHead(206, head);
|
||||||
file.pipe(res);
|
file.pipe(res);
|
||||||
} else {
|
} else {
|
||||||
head = {
|
head = {
|
||||||
'Content-Length': fileSize,
|
'Content-Length': fileSize,
|
||||||
'Content-Type': 'video/mp4',
|
'Content-Type': mimetype,
|
||||||
}
|
}
|
||||||
res.writeHead(200, head)
|
res.writeHead(200, head)
|
||||||
fs.createReadStream(file_path).pipe(res)
|
fs.createReadStream(file_path).pipe(res)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/audio/:id', optionalJwt, function(req , res){
|
|
||||||
var head;
|
|
||||||
let id = decodeURIComponent(req.params.id);
|
|
||||||
let file_path = "audio/" + id + '.mp3';
|
|
||||||
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
|
||||||
let optionalParams = url_api.parse(req.url,true).query;
|
|
||||||
if (req.isAuthenticated()) {
|
|
||||||
if (optionalParams['subName']) {
|
|
||||||
const isPlaylist = optionalParams['subPlaylist'];
|
|
||||||
file_path = path.join(usersFileFolder, req.user.uid, 'subscriptions', (isPlaylist === 'true' ? 'playlists/' : 'channels/'),optionalParams['subName'], id + '.mp3')
|
|
||||||
} else {
|
|
||||||
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
|
|
||||||
file_path = path.join(usersFileFolder, req.user.uid, 'audio', id + '.mp3');
|
|
||||||
}
|
|
||||||
} else if (optionalParams['subName']) {
|
|
||||||
let basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
|
|
||||||
const isPlaylist = optionalParams['subPlaylist'];
|
|
||||||
basePath += (isPlaylist === 'true' ? 'playlists/' : 'channels/');
|
|
||||||
file_path = basePath + optionalParams['subName'] + '/' + id + '.mp3';
|
|
||||||
}
|
|
||||||
file_path = file_path.replace(/\"/g, '\'');
|
|
||||||
const stat = fs.statSync(file_path)
|
|
||||||
const fileSize = stat.size
|
|
||||||
const range = req.headers.range
|
|
||||||
if (range) {
|
|
||||||
const parts = range.replace(/bytes=/, "").split("-")
|
|
||||||
const start = parseInt(parts[0], 10)
|
|
||||||
const end = parts[1]
|
|
||||||
? parseInt(parts[1], 10)
|
|
||||||
: fileSize-1
|
|
||||||
const chunksize = (end-start)+1
|
|
||||||
const file = fs.createReadStream(file_path, {start, end});
|
|
||||||
if (config_api.descriptors[id]) config_api.descriptors[id].push(file);
|
|
||||||
else config_api.descriptors[id] = [file];
|
|
||||||
file.on('close', function() {
|
|
||||||
let index = config_api.descriptors[id].indexOf(file);
|
|
||||||
config_api.descriptors[id].splice(index, 1);
|
|
||||||
logger.debug('Successfully closed stream and removed file reference.');
|
|
||||||
});
|
|
||||||
head = {
|
|
||||||
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
|
|
||||||
'Accept-Ranges': 'bytes',
|
|
||||||
'Content-Length': chunksize,
|
|
||||||
'Content-Type': 'audio/mp3',
|
|
||||||
}
|
|
||||||
res.writeHead(206, head);
|
|
||||||
file.pipe(res);
|
|
||||||
} else {
|
|
||||||
head = {
|
|
||||||
'Content-Length': fileSize,
|
|
||||||
'Content-Type': 'audio/mp3',
|
|
||||||
}
|
|
||||||
res.writeHead(200, head)
|
|
||||||
fs.createReadStream(file_path).pipe(res)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Downloads management
|
// Downloads management
|
||||||
|
|
||||||
app.get('/api/downloads', async (req, res) => {
|
app.get('/api/downloads', async (req, res) => {
|
||||||
|
|||||||
@@ -34,30 +34,27 @@ Rules:
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
async function categorize(file_json) {
|
async function categorize(file_json) {
|
||||||
return new Promise(resolve => {
|
let selected_category = null;
|
||||||
let selected_category = null;
|
const categories = getCategories();
|
||||||
const categories = getCategories();
|
if (!categories) {
|
||||||
if (!categories) {
|
logger.warn('Categories could not be found. Initializing categories...');
|
||||||
logger.warn('Categories could not be found. Initializing categories...');
|
db.assign({categories: []}).write();
|
||||||
db.assign({categories: []}).write();
|
return null;
|
||||||
resolve(null);
|
return;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < categories.length; i++) {
|
||||||
|
const category = categories[i];
|
||||||
|
const rules = category['rules'];
|
||||||
|
|
||||||
|
// if rules for current category apply, then that is the selected category
|
||||||
|
if (applyCategoryRules(file_json, rules, category['name'])) {
|
||||||
|
selected_category = category;
|
||||||
|
logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`);
|
||||||
|
return selected_category;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (let i = 0; i < categories.length; i++) {
|
return selected_category;
|
||||||
const category = categories[i];
|
|
||||||
const rules = category['rules'];
|
|
||||||
|
|
||||||
// if rules for current category apply, then that is the selected category
|
|
||||||
if (applyCategoryRules(file_json, rules, category['name'])) {
|
|
||||||
selected_category = category;
|
|
||||||
logger.verbose(`Selected category ${category['name']} for ${file_json['webpage_url']}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(selected_category);
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCategories() {
|
function getCategories() {
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ function initialize(input_db, input_users_db, input_logger) {
|
|||||||
setLogger(input_logger);
|
setLogger(input_logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerFileDB(file_path, type, multiUserMode = null, sub = null) {
|
function registerFileDB(file_path, type, multiUserMode = null, sub = null, customPath = null) {
|
||||||
let db_path = null;
|
let db_path = null;
|
||||||
const file_id = file_path.substring(0, file_path.length-4);
|
const file_id = file_path.substring(0, file_path.length-4);
|
||||||
const file_object = generateFileObject(file_id, type, multiUserMode && multiUserMode.file_path, sub);
|
const file_object = generateFileObject(file_id, type, customPath || multiUserMode && multiUserMode.file_path, sub);
|
||||||
if (!file_object) {
|
if (!file_object) {
|
||||||
logger.error(`Could not find associated JSON file for ${type} file ${file_id}`);
|
logger.error(`Could not find associated JSON file for ${type} file ${file_id}`);
|
||||||
return false;
|
return false;
|
||||||
@@ -27,7 +27,7 @@ function registerFileDB(file_path, type, multiUserMode = null, sub = null) {
|
|||||||
utils.fixVideoMetadataPerms(file_id, type, multiUserMode && multiUserMode.file_path);
|
utils.fixVideoMetadataPerms(file_id, type, multiUserMode && multiUserMode.file_path);
|
||||||
|
|
||||||
// add thumbnail path
|
// add thumbnail path
|
||||||
file_object['thumbnailPath'] = utils.getDownloadedThumbnail(file_id, type, multiUserMode && multiUserMode.file_path);
|
file_object['thumbnailPath'] = utils.getDownloadedThumbnail(file_id, type, customPath || multiUserMode && multiUserMode.file_path);
|
||||||
|
|
||||||
if (!sub) {
|
if (!sub) {
|
||||||
if (multiUserMode) {
|
if (multiUserMode) {
|
||||||
|
|||||||
@@ -433,6 +433,13 @@ function getSubscription(subID, user_uid = null) {
|
|||||||
return db.get('subscriptions').find({id: subID}).value();
|
return db.get('subscriptions').find({id: subID}).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSubscriptionByName(subName, user_uid = null) {
|
||||||
|
if (user_uid)
|
||||||
|
return users_db.get('users').find({uid: user_uid}).get('subscriptions').find({name: subName}).value();
|
||||||
|
else
|
||||||
|
return db.get('subscriptions').find({name: subName}).value();
|
||||||
|
}
|
||||||
|
|
||||||
function updateSubscription(sub, user_uid = null) {
|
function updateSubscription(sub, user_uid = null) {
|
||||||
if (user_uid) {
|
if (user_uid) {
|
||||||
users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign(sub).write();
|
users_db.get('users').find({uid: user_uid}).get('subscriptions').find({id: sub.id}).assign(sub).write();
|
||||||
@@ -500,6 +507,7 @@ function removeIDFromArchive(archive_path, id) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getSubscription : getSubscription,
|
getSubscription : getSubscription,
|
||||||
|
getSubscriptionByName : getSubscriptionByName,
|
||||||
getAllSubscriptions : getAllSubscriptions,
|
getAllSubscriptions : getAllSubscriptions,
|
||||||
updateSubscription : updateSubscription,
|
updateSubscription : updateSubscription,
|
||||||
subscribe : subscribe,
|
subscribe : subscribe,
|
||||||
|
|||||||
Reference in New Issue
Block a user