Compare commits

..

3 Commits

Author SHA1 Message Date
Isaac Abadi
c789ba9553 Server autocloses on crash
Thumbnails are now retrieved using file UID
2021-07-31 15:51:16 -06:00
Isaac Abadi
b8e1117ff6 Removed all __dirname references in backend to allow for electron to boot 2021-07-28 21:14:32 -06:00
Isaac Abadi
b64a001ae1 Electron almost boots, but errors presumably due to a filesystem issue (missing folder?) 2021-07-28 19:17:08 -06:00
29 changed files with 1805 additions and 223 deletions

View File

@@ -28,4 +28,4 @@ If applicable, add screenshots to help explain your problem.
- Docker tag: <tag> (optional)
**Additional context**
Add any other context about the problem here. For example, a YouTube link.
Add any other context about the problem here.

View File

@@ -18,18 +18,32 @@ var mergeFiles = require('merge-files');
const low = require('lowdb')
var ProgressBar = require('progress');
const NodeID3 = require('node-id3')
const downloader = require('youtube-dl/lib/downloader')
const fetch = require('node-fetch');
var URL = require('url').URL;
const shortid = require('shortid')
const url_api = require('url');
const CONSTS = require('./consts')
const { spawn } = require('child_process')
const read_last_lines = require('read-last-lines');
var ps = require('ps-node');
// 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"})
const using_electron = process.versions['electron'];
// youtube-dl consts
const YOUTUBE_DL_PATH = using_electron ? 'youtube-dl' : 'node_modules/youtube-dl/bin';
const DETAILS_BIN_PATH = path.join(YOUTUBE_DL_PATH, 'details');
const DEFAULT_BIN_JSON = {"version":"2000.06.06","path": path.join(YOUTUBE_DL_PATH, "youtube-dl.exe"),"exec":"youtube-dl.exe","downloader":"youtube-dl"};
if (!fs.existsSync(DETAILS_BIN_PATH)) fs.writeJSONSync(DETAILS_BIN_PATH, DEFAULT_BIN_JSON);
var youtubedl = require('youtube-dl');
if (using_electron) {
youtubedl.setYtdlBinary(YOUTUBE_DL_PATH);
}
// local APIs
var config_api = require('./config.js');
var subscriptions_api = require('./subscriptions')
var categories_api = require('./categories');
@@ -132,7 +146,7 @@ var downloadOnlyMode = null;
var useDefaultDownloadingAgent = null;
var customDownloadingAgent = null;
var allowSubscriptions = null;
var archivePath = path.join(__dirname, 'appdata', 'archives');
var archivePath = path.join('appdata', 'archives');
// other needed values
var url_domain = null;
@@ -313,9 +327,14 @@ async function startServer() {
await setPortItemFromENV();
}
app.listen(backendPort,function(){
const server = app.listen(backendPort,function(){
logger.info(`YoutubeDL-Material ${CONSTS['CURRENT_VERSION']} started on PORT ${backendPort}`);
});
process.on('uncaughtException', err => {
logger.error('Server has crashed!');
logger.error(err);
server.close();
})
}
async function restartServer(is_update = false) {
@@ -385,8 +404,8 @@ async function downloadReleaseFiles(tag) {
await downloadReleaseZip(tag);
// deletes contents of public dir
fs.removeSync(path.join(__dirname, 'public'));
fs.mkdirSync(path.join(__dirname, 'public'));
fs.removeSync(path.join('public'));
fs.mkdirSync(path.join('public'));
let replace_ignore_list = ['youtubedl-material/appdata/default.json',
'youtubedl-material/appdata/db.json',
@@ -395,7 +414,7 @@ async function downloadReleaseFiles(tag) {
logger.info(`Installing update ${tag}...`)
// downloads new package.json and adds new public dir files from the downloaded zip
fs.createReadStream(path.join(__dirname, `youtubedl-material-release-${tag}.zip`)).pipe(unzipper.Parse())
fs.createReadStream(path.join(`youtubedl-material-release-${tag}.zip`)).pipe(unzipper.Parse())
.on('entry', function (entry) {
var fileName = entry.path;
var type = entry.type; // 'Directory' or 'File'
@@ -405,8 +424,8 @@ async function downloadReleaseFiles(tag) {
// get public folder files
var actualFileName = fileName.replace('youtubedl-material/public/', '');
if (actualFileName.length !== 0 && actualFileName.substring(actualFileName.length-1, actualFileName.length) !== '/') {
fs.ensureDirSync(path.join(__dirname, 'public', path.dirname(actualFileName)));
entry.pipe(fs.createWriteStream(path.join(__dirname, 'public', actualFileName)));
fs.ensureDirSync(path.join('public', path.dirname(actualFileName)));
entry.pipe(fs.createWriteStream(path.join('public', actualFileName)));
} else {
entry.autodrain();
}
@@ -414,7 +433,7 @@ async function downloadReleaseFiles(tag) {
// get package.json
var actualFileName = fileName.replace('youtubedl-material/', '');
logger.verbose('Downloading file ' + actualFileName);
entry.pipe(fs.createWriteStream(path.join(__dirname, actualFileName)));
entry.pipe(fs.createWriteStream(path.join(actualFileName)));
} else {
entry.autodrain();
}
@@ -460,7 +479,7 @@ async function downloadReleaseZip(tag) {
const tag_without_v = tag.substring(1, tag.length);
const zip_file_name = `youtubedl-material-${tag_without_v}.zip`
const latest_zip_link = latest_release_link + zip_file_name;
let output_path = path.join(__dirname, `youtubedl-material-release-${tag}.zip`);
let output_path = path.join(`youtubedl-material-release-${tag}.zip`);
// download zip from release
await fetchFile(latest_zip_link, output_path, 'update ' + tag);
@@ -478,10 +497,10 @@ async function installDependencies() {
}
async function backupServerLite() {
await fs.ensureDir(path.join(__dirname, 'appdata', 'backups'));
await fs.ensureDir(path.join('appdata', 'backups'));
let output_path = path.join('appdata', 'backups', `backup-${Date.now()}.zip`);
logger.info(`Backing up your non-video/audio files to ${output_path}. This may take up to a few seconds/minutes.`);
let output = fs.createWriteStream(path.join(__dirname, output_path));
let output = fs.createWriteStream(path.join(output_path));
await new Promise(resolve => {
var archive = archiver('zip', {
@@ -750,6 +769,67 @@ function generateEnvVarConfigItem(key) {
return {key: key, value: process['env'][key]};
}
function getFileSizeMp3(name)
{
var jsonPath = audioFolderPath+name+".mp3.info.json";
if (fs.existsSync(jsonPath))
var obj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
else
var obj = 0;
return obj.filesize;
}
function getFileSizeMp4(name)
{
var jsonPath = videoFolderPath+name+".info.json";
var filesize = 0;
if (fs.existsSync(jsonPath))
{
var obj = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
var format = obj.format.substring(0,3);
for (i = 0; i < obj.formats.length; i++)
{
if (obj.formats[i].format_id == format)
{
filesize = obj.formats[i].filesize;
}
}
}
return filesize;
}
function getAmountDownloadedMp3(name)
{
var partPath = audioFolderPath+name+".mp3.part";
if (fs.existsSync(partPath))
{
const stats = fs.statSync(partPath);
const fileSizeInBytes = stats.size;
return fileSizeInBytes;
}
else
return 0;
}
function getAmountDownloadedMp4(name)
{
var format = getVideoFormatID(name);
var partPath = videoFolderPath+name+".f"+format+".mp4.part";
if (fs.existsSync(partPath))
{
const stats = fs.statSync(partPath);
const fileSizeInBytes = stats.size;
return fileSizeInBytes;
}
else
return 0;
}
function getVideoFormatID(name)
{
var jsonPath = videoFolderPath+name+".info.json";
@@ -1061,7 +1141,7 @@ async function generateArgs(url, type, options) {
}
if (useCookies) {
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
if (await fs.pathExists(path.join('appdata', 'cookies.txt'))) {
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
} else {
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
@@ -1115,7 +1195,7 @@ async function generateArgs(url, type, options) {
downloadConfig = downloadConfig.concat(globalArgs.split(',,'));
}
const default_downloader = utils.getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
const default_downloader = getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
if (default_downloader === 'yt-dlp') {
downloadConfig.push('--no-clean-infojson');
}
@@ -1219,6 +1299,17 @@ async function cropFile(file_path, start, end, ext) {
});
}
// archive helper functions
async function writeToBlacklist(type, line) {
let blacklistPath = path.join(archivePath, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt');
// adds newline to the beginning of the line
line.replace('\n', '');
line.replace('\r', '');
line = '\n' + line;
await fs.appendFile(blacklistPath, line);
}
// download management functions
async function updateDownloads() {
@@ -1285,19 +1376,19 @@ async function autoUpdateYoutubeDL() {
const default_downloader = config_api.getConfigItem('ytdl_default_downloader');
const tags_url = download_sources[default_downloader]['tags_url'];
// get current version
let current_app_details_exists = fs.existsSync(CONSTS.DETAILS_BIN_PATH);
let current_app_details_exists = fs.existsSync(DETAILS_BIN_PATH);
if (!current_app_details_exists) {
logger.warn(`Failed to get youtube-dl binary details at location '${CONSTS.DETAILS_BIN_PATH}'. Generating file...`);
fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, {"version":"2020.00.00", "downloader": default_downloader});
logger.warn(`Failed to get youtube-dl binary details at location '${DETAILS_BIN_PATH}'. Generating file...`);
fs.writeJSONSync(DETAILS_BIN_PATH, {"version":"2020.00.00", "downloader": default_downloader});
}
let current_app_details = JSON.parse(fs.readFileSync(CONSTS.DETAILS_BIN_PATH));
let current_app_details = JSON.parse(fs.readFileSync(DETAILS_BIN_PATH));
let current_version = current_app_details['version'];
let current_downloader = current_app_details['downloader'];
let stored_binary_path = current_app_details['path'];
if (!stored_binary_path || typeof stored_binary_path !== 'string') {
// logger.info(`INFO: Failed to get youtube-dl binary path at location: ${CONSTS.DETAILS_BIN_PATH}, attempting to guess actual path...`);
const guessed_base_path = 'node_modules/youtube-dl/bin/';
const guessed_file_path = guessed_base_path + 'youtube-dl' + (is_windows ? '.exe' : '');
// logger.info(`INFO: Failed to get youtube-dl binary path at location: ${DETAILS_BIN_PATH}, attempting to guess actual path...`);
const guessed_base_path = YOUTUBE_DL_PATH;
const guessed_file_path = path.join(guessed_base_path, 'youtube-dl' + (is_windows ? '.exe' : ''));
if (fs.existsSync(guessed_file_path)) {
stored_binary_path = guessed_file_path;
// logger.info('INFO: Guess successful! Update process continuing...')
@@ -1348,7 +1439,7 @@ async function downloadLatestYoutubeDLBinary(new_version) {
const file_ext = is_windows ? '.exe' : '';
const download_url = `https://github.com/ytdl-org/youtube-dl/releases/latest/download/youtube-dl${file_ext}`;
const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`;
const output_path = path.join(YOUTUBE_DL_PATH, `youtube-dl${file_ext}`);
await fetchFile(download_url, output_path, `youtube-dl ${new_version}`);
@@ -1359,7 +1450,7 @@ async function downloadLatestYoutubeDLCBinary(new_version) {
const file_ext = is_windows ? '.exe' : '';
const download_url = `https://github.com/blackjack4494/yt-dlc/releases/latest/download/youtube-dlc${file_ext}`;
const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`;
const output_path = path.join(YOUTUBE_DL_PATH, `youtube-dl${file_ext}`);
await fetchFile(download_url, output_path, `youtube-dlc ${new_version}`);
@@ -1370,7 +1461,8 @@ async function downloadLatestYoutubeDLPBinary(new_version) {
const file_ext = is_windows ? '.exe' : '';
const download_url = `https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp${file_ext}`;
const output_path = `node_modules/youtube-dl/bin/youtube-dl${file_ext}`;
const output_path = path.join(YOUTUBE_DL_PATH, `youtube-dl${file_ext}`);
await fetchFile(download_url, output_path, `yt-dlp ${new_version}`);
@@ -1378,10 +1470,15 @@ async function downloadLatestYoutubeDLPBinary(new_version) {
}
function updateDetailsJSON(new_version, downloader) {
const details_json = fs.readJSONSync(CONSTS.DETAILS_BIN_PATH);
const details_json = fs.readJSONSync(DETAILS_BIN_PATH);
if (new_version) details_json['version'] = new_version;
details_json['downloader'] = downloader;
fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, details_json);
fs.writeJSONSync(DETAILS_BIN_PATH, details_json);
}
function getCurrentDownloader() {
const details_json = fs.readJSONSync(DETAILS_BIN_PATH);
return details_json['downloader'];
}
async function checkExistsWithTimeout(filePath, timeout) {
@@ -2242,7 +2339,7 @@ app.post('/api/downloadFileFromServer', optionalJwt, async (req, res) => {
const file_obj = await db_api.getVideo(uid, uuid, sub_id)
file_path_to_download = file_obj.path;
}
if (!path.isAbsolute(file_path_to_download)) file_path_to_download = path.join(__dirname, file_path_to_download);
if (!path.isAbsolute(file_path_to_download)) file_path_to_download = path.join(file_path_to_download);
res.sendFile(file_path_to_download, function (err) {
if (err) {
logger.error(err);
@@ -2271,9 +2368,9 @@ app.post('/api/downloadArchive', async (req, res) => {
});
var upload_multer = multer({ dest: __dirname + '/appdata/' });
var upload_multer = multer({ dest: './appdata/' });
app.post('/api/uploadCookies', upload_multer.single('cookies'), async (req, res) => {
const new_path = path.join(__dirname, 'appdata', 'cookies.txt');
const new_path = path.join('appdata', 'cookies.txt');
if (await fs.pathExists(req.file.path)) {
await fs.rename(req.file.path, new_path);
@@ -2382,9 +2479,10 @@ app.get('/api/stream', optionalJwt, async (req, res) => {
}
});
app.get('/api/thumbnail/:path', optionalJwt, async (req, res) => {
let file_path = decodeURIComponent(req.params.path);
if (fs.existsSync(file_path)) path.isAbsolute(file_path) ? res.sendFile(file_path) : res.sendFile(path.join(__dirname, file_path));
app.get('/api/thumbnail/:uid', optionalJwt, async (req, res) => {
const file_obj = await db_api.getRecord('files', {uid: req.params.uid});
const file_path = file_obj['thumbnailPath'];
if (fs.existsSync(file_path)) res.sendFile(file_path, { root: '.' });
else res.sendStatus(404);
});
@@ -2555,7 +2653,7 @@ app.post('/api/deleteUser', optionalJwt, async (req, res) => {
try {
let success = false;
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
const user_folder = path.join(__dirname, usersFileFolder, uid);
const user_folder = path.join(usersFileFolder, uid);
const user_db_obj = await db_api.getRecord('users', {uid: uid});
if (user_db_obj) {
// user exists, let's delete
@@ -2615,12 +2713,12 @@ app.use(function(req, res, next) {
return next();
}
let index_path = path.join(__dirname, 'public', 'index.html');
let index_path = path.join('public', 'index.html');
fs.createReadStream(index_path).pipe(res);
});
let public_dir = path.join(__dirname, 'public');
let public_dir = path.join('public');
app.use(express.static(public_dir));

View File

@@ -55,7 +55,7 @@
}
},
"Database": {
"use_local_db": true,
"use_local_db": false,
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
},
"Advanced": {

View File

@@ -232,7 +232,7 @@ DEFAULT_CONFIG = {
}
},
"Database": {
"use_local_db": true,
"use_local_db": false,
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
},
"Advanced": {

View File

@@ -207,11 +207,8 @@ AVAILABLE_PERMISSIONS = [
'downloads_manager'
];
const DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'
module.exports = {
CONFIG_ITEMS: CONFIG_ITEMS,
AVAILABLE_PERMISSIONS: AVAILABLE_PERMISSIONS,
CURRENT_VERSION: 'v4.2',
DETAILS_BIN_PATH: DETAILS_BIN_PATH
CURRENT_VERSION: 'v4.2'
}

View File

@@ -75,7 +75,7 @@ exports.initialize = (input_db, input_users_db, input_logger) => {
}
exports.connectToDB = async (retries = 5, no_fallback = false, custom_connection_string = null) => {
if (using_local_db && !custom_connection_string) return;
if (using_local_db) return;
const success = await exports._connectToDB(custom_connection_string);
if (success) return true;
@@ -234,7 +234,7 @@ function generateFileObject(id, type, customPath = null, sub = null) {
const ext = (type === 'audio') ? '.mp3' : '.mp4'
const file_path = utils.getTrueFileName(jsonobj['_filename'], type); // path.join(type === 'audio' ? audioFolderPath : videoFolderPath, id + ext);
// console.
var stats = fs.statSync(path.join(__dirname, file_path));
var stats = fs.statSync(path.join(file_path));
var title = jsonobj.title;
var url = jsonobj.webpage_url;
@@ -256,9 +256,6 @@ function generateFileObject2(file_path, type) {
var jsonobj = utils.getJSON(file_path, type);
if (!jsonobj) {
return null;
} else if (!jsonobj['_filename']) {
logger.error(`Failed to get filename from info JSON! File ${jsonobj['title']} could not be added.`);
return null;
}
const ext = (type === 'audio') ? '.mp3' : '.mp4'
const true_file_path = utils.getTrueFileName(jsonobj['_filename'], type);
@@ -522,8 +519,8 @@ exports.deleteFile = async (uid, uuid = null, blacklistMode = false) => {
var thumbnailPath = `${filePathNoExtension}.webp`;
var altThumbnailPath = `${filePathNoExtension}.jpg`;
jsonPath = path.join(__dirname, jsonPath);
altJSONPath = path.join(__dirname, altJSONPath);
jsonPath = path.join(jsonPath);
altJSONPath = path.join(altJSONPath);
let jsonExists = await fs.pathExists(jsonPath);
let thumbnailExists = await fs.pathExists(thumbnailPath);
@@ -784,26 +781,26 @@ exports.removeRecord = async (table, filter_obj) => {
return !!(output['result']['ok']);
}
exports.removeAllRecords = async (table = null, filter_obj = null) => {
exports.removeAllRecords = async (table = null) => {
// local db override
const tables_to_remove = table ? [table] : tables_list;
logger.debug(`Removing all records from: ${tables_to_remove} with filter: ${JSON.stringify(filter_obj)}`)
if (using_local_db) {
logger.debug(`Removing all records from: ${tables_to_remove}`)
for (let i = 0; i < tables_to_remove.length; i++) {
const table_to_remove = tables_to_remove[i];
if (filter_obj) applyFilterLocalDB(local_db.get(table), filter_obj, 'remove').write();
else local_db.assign({[table_to_remove]: []}).write();
logger.debug(`Successfully removed records from ${table_to_remove}`);
local_db.assign({[table_to_remove]: []}).write();
logger.debug(`Removed all records from ${table_to_remove}`);
}
return true;
}
let success = true;
logger.debug(`Removing all records from: ${tables_to_remove}`)
for (let i = 0; i < tables_to_remove.length; i++) {
const table_to_remove = tables_to_remove[i];
const output = await database.collection(table_to_remove).deleteMany(filter_obj ? filter_obj : {});
logger.debug(`Successfully removed records from ${table_to_remove}`);
const output = await database.collection(table_to_remove).deleteMany({});
logger.debug(`Removed all records from ${table_to_remove}`);
success &= !!(output['result']['ok']);
}
return success;
@@ -991,8 +988,6 @@ exports.transferDB = async (local_to_remote) => {
config_api.setConfigItem('ytdl_use_local_db', using_local_db);
logger.debug('Transfer finished!');
return success;
}
@@ -1018,16 +1013,4 @@ const applyFilterLocalDB = (db_path, filter_obj, operation) => {
return filtered;
});
return return_val;
}
// archive helper functions
async function writeToBlacklist(type, line) {
const archivePath = path.join(__dirname, 'appdata', 'archives');
let blacklistPath = path.join(archivePath, (type === 'audio') ? 'blacklist_audio.txt' : 'blacklist_video.txt');
// adds newline to the beginning of the line
line.replace('\n', '');
line.replace('\r', '');
line = '\n' + line;
await fs.appendFile(blacklistPath, line);
}
}

View File

@@ -1,6 +1,7 @@
const { app, BrowserWindow } = require('electron');
const path = require('path');
const url = require('url');
const server = require('./app');
let win;
@@ -8,13 +9,7 @@ function createWindow() {
win = new BrowserWindow({ width: 800, height: 600 });
// load the dist folder from Angular
win.loadURL(
url.format({
pathname: path.join(__dirname, `/dist/index.html`),
protocol: 'file:',
slashes: true
})
);
win.loadURL('http://localhost:17442') //ADD THIS
// The following is optional and will open the DevTools:
// win.webContents.openDevTools()

1578
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,11 +2,14 @@
"name": "backend",
"version": "1.0.0",
"description": "backend for YoutubeDL-Material",
"main": "index.js",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon app.js",
"debug": "set YTDL_MODE=debug && node app.js"
"debug": "set YTDL_MODE=debug && node app.js",
"electron": "electron main.js",
"pack": "electron-builder --dir",
"dist": "electron-builder"
},
"nodemonConfig": {
"ignore": [
@@ -19,6 +22,13 @@
"restart_general.json"
]
},
"build": {
"appId": "youtubedl.material",
"mac": {
"category": "public.app-category.utilities"
},
"files": ["!audio/*", "!video/*", "!users/*", "!subscriptions/*", "!appdata/*"]
},
"repository": {
"type": "git",
"url": ""
@@ -65,5 +75,9 @@
"uuidv4": "^6.0.6",
"winston": "^3.2.1",
"youtube-dl": "^3.0.2"
},
"devDependencies": {
"electron": "^13.1.7",
"electron-builder": "^22.11.7"
}
}

View File

@@ -72,7 +72,7 @@ async function getSubscriptionInfo(sub, user_uid = null) {
let downloadConfig = ['--dump-json', '--playlist-end', '1'];
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
if (useCookies) {
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
if (await fs.pathExists(path.join('appdata', 'cookies.txt'))) {
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
} else {
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
@@ -117,7 +117,7 @@ async function getSubscriptionInfo(sub, user_uid = null) {
const useArchive = config_api.getConfigItem('ytdl_use_youtubedl_archive');
if (useArchive && !sub.archive) {
// must create the archive
const archive_dir = path.join(__dirname, basePath, 'archives', sub.name);
const archive_dir = path.join(basePath, 'archives', sub.name);
const archive_path = path.join(archive_dir, 'archive.txt');
// creates archive directory and text file if it doesn't exist
@@ -185,10 +185,10 @@ async function deleteSubscriptionFile(sub, file, deleteForever, file_uid = null,
let filePath = appendedBasePath;
const ext = (sub.type && sub.type === 'audio') ? '.mp3' : '.mp4'
var jsonPath = path.join(__dirname,filePath,name+'.info.json');
var videoFilePath = path.join(__dirname,filePath,name+ext);
var imageFilePath = path.join(__dirname,filePath,name+'.jpg');
var altImageFilePath = path.join(__dirname,filePath,name+'.webp');
var jsonPath = path.join(filePath,name+'.info.json');
var videoFilePath = path.join(filePath,name+ext);
var imageFilePath = path.join(filePath,name+'.jpg');
var altImageFilePath = path.join(filePath,name+'.webp');
const [jsonExists, videoFileExists, imageFileExists, altImageFileExists] = await Promise.all([
fs.pathExists(jsonPath),
@@ -402,7 +402,7 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
let useCookies = config_api.getConfigItem('ytdl_use_cookies');
if (useCookies) {
if (await fs.pathExists(path.join(__dirname, 'appdata', 'cookies.txt'))) {
if (await fs.pathExists(path.join('appdata', 'cookies.txt'))) {
downloadConfig.push('--cookies', path.join('appdata', 'cookies.txt'));
} else {
logger.warn('Cookies file could not be found. You can either upload one, or disable \'use cookies\' in the Advanced tab in the settings.');
@@ -413,11 +413,6 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
downloadConfig.push('--write-thumbnail');
}
const default_downloader = utils.getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
if (default_downloader === 'yt-dlp') {
downloadConfig.push('--no-clean-infojson');
}
return downloadConfig;
}

View File

@@ -1,7 +1,6 @@
const fs = require('fs-extra')
const path = require('path')
const config_api = require('./config');
const CONSTS = require('./consts')
const archiver = require('archiver');
const is_windows = process.platform === 'win32';
@@ -316,11 +315,6 @@ function addUIDsToCategory(category, files) {
return files_that_match;
}
function getCurrentDownloader() {
const details_json = fs.readJSONSync(CONSTS.DETAILS_BIN_PATH);
return details_json['downloader'];
}
async function recFindByExt(base,ext,files,result)
{
files = files || (await fs.readdir(base))
@@ -396,7 +390,6 @@ module.exports = {
durationStringToNumber: durationStringToNumber,
getMatchingCategoryFiles: getMatchingCategoryFiles,
addUIDsToCategory: addUIDsToCategory,
getCurrentDownloader: getCurrentDownloader,
recFindByExt: recFindByExt,
removeFileExtension: removeFileExtension,
wait: wait,

View File

@@ -7,14 +7,12 @@ import { SubscriptionComponent } from './subscription/subscription/subscription.
import { PostsService } from './posts.services';
import { LoginComponent } from './components/login/login.component';
import { DownloadsComponent } from './components/downloads/downloads.component';
import { SettingsComponent } from './settings/settings.component';
const routes: Routes = [
{ path: 'home', component: MainComponent, canActivate: [PostsService] },
{ path: 'player', component: PlayerComponent, canActivate: [PostsService]},
{ path: 'subscriptions', component: SubscriptionsComponent, canActivate: [PostsService] },
{ path: 'subscription', component: SubscriptionComponent, canActivate: [PostsService] },
{ path: 'settings', component: SettingsComponent, canActivate: [PostsService] },
{ path: 'login', component: LoginComponent },
{ path: 'downloads', component: DownloadsComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' }

View File

@@ -23,10 +23,10 @@
<span i18n="Dark mode toggle label">Dark</span>
<mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle>
</button>
<!-- <button *ngIf="postsService.config && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('settings')))" (click)="openSettingsDialog()" mat-menu-item>
<button *ngIf="postsService.config && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('settings')))" (click)="openSettingsDialog()" mat-menu-item>
<mat-icon>settings</mat-icon>
<span i18n="Settings menu label">Settings</span>
</button> -->
</button>
<button (click)="openAboutDialog()" mat-menu-item>
<mat-icon>info</mat-icon>
<span i18n="About menu label">About</span>
@@ -42,14 +42,10 @@
<mat-nav-list>
<a *ngIf="postsService.config && (!postsService.config.Advanced.multi_user_mode || postsService.isLoggedIn)" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/home'><ng-container i18n="Navigation menu Home Page title">Home</ng-container></a>
<a *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode && !postsService.isLoggedIn" mat-list-item (click)="sidenav.close()" routerLink='/login'><ng-container i18n="Navigation menu Login Page title">Login</ng-container></a>
<a *ngIf="postsService.config && allowSubscriptions && postsService.hasPermission('subscriptions')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
<a *ngIf="postsService.config && enableDownloadsManager && postsService.hasPermission('downloads_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
<ng-container *ngIf="postsService.config && postsService.hasPermission('settings')">
<a *ngIf="postsService.config && allowSubscriptions && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('subscriptions')))" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
<a *ngIf="postsService.config && enableDownloadsManager && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('downloads_manager')))" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
<ng-container *ngIf="postsService.config && allowSubscriptions && postsService.subscriptions && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('subscriptions')))">
<mat-divider></mat-divider>
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/settings'><ng-container i18n="Settings menu label">Settings</ng-container></a>
</ng-container>
<ng-container *ngIf="postsService.config && allowSubscriptions && postsService.subscriptions && postsService.hasPermission('subscriptions')">
<mat-divider *ngIf="postsService.subscriptions.length > 0"></mat-divider>
<a *ngFor="let subscription of postsService.subscriptions" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" [routerLink]="['/subscription', { id: subscription.id }]"><ngx-avatar [style.margin-right]="'10px'" size="32" [name]="subscription.name"></ngx-avatar>{{subscription.name}}</a>
</ng-container>
</mat-nav-list>

View File

@@ -1,6 +1,9 @@
import { Component, OnInit, ElementRef, ViewChild, HostBinding, AfterViewInit } from '@angular/core';
import {MatDialogRef} from '@angular/material/dialog';
import {PostsService} from './posts.services';
import {FileCardComponent} from './file-card/file-card.component';
import { Observable } from 'rxjs/Observable';
import {FormControl, Validators} from '@angular/forms';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import { MatDialog } from '@angular/material/dialog';
import { MatSidenav } from '@angular/material/sidenav';
import { MatSnackBar } from '@angular/material/snack-bar';
@@ -13,6 +16,7 @@ import 'rxjs/add/operator/filter'
import 'rxjs/add/operator/debounceTime'
import 'rxjs/add/operator/do'
import 'rxjs/add/operator/switch'
import { YoutubeSearchService, Result } from './youtube-search.service';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { OverlayContainer } from '@angular/cdk/overlay';
import { THEMES_CONFIG } from '../themes';
@@ -24,11 +28,7 @@ import { SetDefaultAdminDialogComponent } from './dialogs/set-default-admin-dial
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [{
provide: MatDialogRef,
useValue: {}
}]
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterViewInit {

View File

@@ -1,5 +1,5 @@
<mat-card class="login-card">
<mat-tab-group style="margin-bottom: 20px" [(selectedIndex)]="selectedTabIndex">
<mat-tab-group [(selectedIndex)]="selectedTabIndex">
<mat-tab label="Login">
<div style="margin-top: 10px;">
<mat-form-field>
@@ -11,6 +11,9 @@
<input [(ngModel)]="loginPasswordInput" (keyup.enter)="login()" type="password" matInput placeholder="Password">
</mat-form-field>
</div>
<div style="margin-bottom: 10px; margin-top: 10px;">
<button [disabled]="loggingIn" color="primary" (click)="login()" mat-raised-button><ng-container i18n="Login">Login</ng-container></button>
</div>
</mat-tab>
<mat-tab *ngIf="registrationEnabled" label="Register">
<div style="margin-top: 10px;">
@@ -28,14 +31,9 @@
<input [(ngModel)]="registrationPasswordConfirmationInput" type="password" matInput placeholder="Confirm Password">
</mat-form-field>
</div>
<div style="margin-bottom: 10px; margin-top: 10px;">
<button [disabled]="registering" color="primary" (click)="register()" mat-raised-button><ng-container i18n="Register">Register</ng-container></button>
</div>
</mat-tab>
</mat-tab-group>
<div *ngIf="selectedTabIndex === 0" class="login-button-div">
<button [disabled]="loggingIn" color="primary" (click)="login()" mat-raised-button><ng-container i18n="Login">Login</ng-container></button>
<mat-progress-bar *ngIf="loggingIn" class="login-progress-bar" mode="indeterminate"></mat-progress-bar>
</div>
<div *ngIf="selectedTabIndex === 1" class="login-button-div">
<button [disabled]="registering" color="primary" (click)="register()" mat-raised-button><ng-container i18n="Register">Register</ng-container></button>
<mat-progress-bar *ngIf="registering" class="login-progress-bar" mode="indeterminate"></mat-progress-bar>
</div>
</mat-card>

View File

@@ -1,33 +1,6 @@
.login-card {
max-width: 400px;
max-width: 600px;
width: 80%;
margin: 0 auto;
margin-top: 20px;
padding-top: 8px;
}
.login-div {
height: calc(100% - 170px);
overflow-y: auto;
}
.login-button-div {
margin-bottom: 10px;
margin-top: 10px;
margin-left: -16px;
margin-right: -16px;
bottom: 0px;
width: 100%;
position: absolute;
}
.login-button-div > button {
width: 100%;
border-radius: 0px 0px 4px 4px !important;
}
.login-progress-bar {
position: absolute;
bottom: 0px;
border-radius: 0px 0px 4px 4px;
}

View File

@@ -1,4 +1,4 @@
<div style="height: 100%;">
<div style="height: 275px;">
<div *ngIf="logs_loading" style="z-index: 999; position: absolute; top: 40%; left: 50%">
<mat-spinner [diameter]="32"></mat-spinner>
</div>
@@ -10,7 +10,7 @@
</cdk-virtual-scroll-viewport>-->
<!-- Non-virtual mode (slow, bug-free) -->
<div style="height: 100%; overflow-y: auto">
<div style="height: 274px; overflow-y: auto">
<div *ngFor="let log of logs; let i = index" class="example-item">
<span [ngStyle]="{'color':log.color}">{{log.text}}</span>
</div>

View File

@@ -1,7 +1,7 @@
<div *ngIf="dataSource; else loading">
<div style="padding: 15px">
<div class="row">
<div class="table table-responsive pb-4 pt-2">
<div class="table table-responsive px-5 pb-4 pt-2">
<div class="example-header">
<mat-form-field>
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Search" i18n-placeholder="search field description">

View File

@@ -1,4 +1,5 @@
.edit-role {
position: relative;
top: -50px;
left: 35px;
}

View File

@@ -28,7 +28,7 @@
</div>
</div>
<div>
<div class="container" style="margin-bottom: 16px">
<div class="container">
<div class="row justify-content-center">
<ng-container *ngIf="normal_files_received && paged_data">
<div *ngFor="let file of paged_data; let i = index" class="mb-2 mt-2 d-flex justify-content-center" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 small-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 medium-col' : '', postsService.card_size === 'large' ? 'col-12 large-col' : '' ]">

View File

@@ -72,7 +72,7 @@ export class UnifiedFileCardComponent implements OnInit {
}
if (this.file_obj && this.file_obj.thumbnailPath) {
this.thumbnailBlobURL = `${this.baseStreamPath}thumbnail/${encodeURIComponent(this.file_obj.thumbnailPath)}${this.jwtString}`;
this.thumbnailBlobURL = `${this.baseStreamPath}thumbnail/${this.file_obj.uid}${this.jwtString}`;
/*const mime = getMimeByFilename(this.file_obj.thumbnailPath);
const blob = new Blob([new Uint8Array(this.file_obj.thumbnailBlob.data)], {type: mime});
const bloburl = URL.createObjectURL(blob);

View File

@@ -133,16 +133,12 @@ mat-form-field.mat-form-field {
top: -5px;
}
.border-radius-both {
border-radius: 16px;
}
.no-border-radius-bottom {
border-radius: 16px 16px 0px 0px;
border-radius: 4px 4px 0px 0px;
}
.no-border-radius-top {
border-radius: 0px 0px 16px 16px;
border-radius: 0px 0px 4px 4px;
}
@media (max-width: 576px) {

View File

@@ -1,6 +1,6 @@
<br/>
<div class="big demo-basic">
<mat-card id="card" style="margin-right: 20px; margin-left: 20px;" [ngClass]="(allowAdvancedDownload) ? 'no-border-radius-bottom' : 'border-radius-both'">
<mat-card id="card" style="margin-right: 20px; margin-left: 20px;" [ngClass]="(allowAdvancedDownload) ? 'no-border-radius-bottom' : null">
<mat-card-content style="padding: 0px 8px 0px 8px;">
<div style="position: relative; margin-right: 15px;">
<form class="example-form">

View File

@@ -230,7 +230,7 @@ export class MainComponent implements OnInit {
async loadConfig() {
// loading config
this.fileManagerEnabled = this.postsService.config['Extra']['file_manager_enabled']
&& this.postsService.hasPermission('filemanager');
&& (!this.postsService.isLoggedIn || this.postsService.permissions.includes('filemanager'));
this.downloadOnlyMode = this.postsService.config['Extra']['download_only_mode'];
this.allowMultiDownloadMode = this.postsService.config['Extra']['allow_multi_download_mode'];
this.audioFolderPath = this.postsService.config['Downloader']['path-audio'];
@@ -242,7 +242,7 @@ export class MainComponent implements OnInit {
this.youtubeAPIKey = this.youtubeSearchEnabled ? this.postsService.config['API']['youtube_API_key'] : null;
this.allowQualitySelect = this.postsService.config['Extra']['allow_quality_select'];
this.allowAdvancedDownload = this.postsService.config['Advanced']['allow_advanced_download']
&& this.postsService.hasPermission('advanced_download');
&& (!this.postsService.isLoggedIn || this.postsService.permissions.includes('advanced_download'));
this.useDefaultDownloadingAgent = this.postsService.config['Advanced']['use_default_downloading_agent'];
this.customDownloadingAgent = this.postsService.config['Advanced']['custom_downloading_agent'];

View File

@@ -511,12 +511,6 @@ export class PostsService implements CanActivate {
this.resetHttpParams();
}
hasPermission(permission) {
// assume not logged in users never have permission
if (this.config.Advanced.multi_user_mode && !this.isLoggedIn) return false;
return this.config.Advanced.multi_user_mode ? this.permissions.includes(permission) : true;
}
// user methods
register(username, password) {
const call = this.http.post(this.path + 'auth/register', {userid: username,

View File

@@ -1,12 +1,13 @@
<h4 class="settings-title" i18n="Settings title">Settings</h4>
<h4 i18n="Settings title" mat-dialog-title>Settings</h4>
<!-- <ng-container i18n="Allow subscriptions setting"></ng-container> -->
<mat-dialog-content>
<!-- Language
<div style="margin-bottom: 10px;">
</div> -->
<mat-tab-group style="height: 76vh" mat-align-tabs="center">
<mat-tab-group>
<!-- Server -->
<mat-tab label="Main" i18n-label="Main settings label">
<ng-template matTabContent style="padding: 15px;">
@@ -390,7 +391,7 @@
<app-updater></app-updater>
</div>
<mat-divider></mat-divider>
<div *ngIf="new_config" class="container-fluid">
<div *ngIf="new_config" class="container">
<div class="row">
<div class="col-12 mt-4">
<button (click)="restartServer()" mat-stroked-button color="warn"><ng-container i18n="Restart server button">Restart server</ng-container></button>
@@ -400,7 +401,8 @@
</ng-template>
</mat-tab>
<mat-tab *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode" label="Users" i18n-label="Users settings label">
<div *ngIf="new_config" style="margin-top: 24px; margin-bottom: -25px;">
<div style="margin-left: 48px; margin-top: 24px; margin-bottom: -25px;">
<div>
<mat-checkbox color="accent" [(ngModel)]="new_config['Users']['allow_registration']"><ng-container i18n="Allow registration setting">Allow user registration</ng-container></mat-checkbox>
</div>
@@ -444,23 +446,25 @@
</div>
<mat-divider></mat-divider>
</div>
<app-modify-users *ngIf="new_config"></app-modify-users>
<app-modify-users></app-modify-users>
</mat-tab>
<mat-tab *ngIf="postsService.config" label="Logs" i18n-label="Logs settings label">
<ng-template matTabContent>
<div style="margin-top: 15px; height: 84%;">
<div style="margin-left: 48px; margin-top: 24px; height: 340px">
<app-logs-viewer></app-logs-viewer>
</div>
</ng-template>
</mat-tab>
</mat-tab-group>
</mat-dialog-content>
<div class="action-buttons">
<button style="margin-left: 10px; height: 37.3px" color="accent" (click)="saveSettings()" [disabled]="settingsSame()" mat-raised-button><mat-icon>done</mat-icon>&nbsp;&nbsp;
<ng-container i18n="Settings save button">Save</ng-container>
</button>
<button style="margin-left: 10px;" mat-flat-button (click)="cancelSettings()" [disabled]="settingsSame()"><mat-icon>cancel</mat-icon>&nbsp;&nbsp;
<span i18n="Settings cancel button">Cancel</span>
</button>
</div>
<mat-dialog-actions>
<div style="margin-bottom: 10px;">
<button color="accent" (click)="saveSettings()" [disabled]="settingsSame()" mat-raised-button><mat-icon>done</mat-icon>&nbsp;&nbsp;
<ng-container i18n="Settings save button">Save</ng-container>
</button>
<button mat-flat-button [mat-dialog-close]="false"><mat-icon>cancel</mat-icon>&nbsp;&nbsp;
<span i18n="Settings cancel and close button">{settingsAreTheSame + "", select, true {Close} false {Cancel} other {otha}}</span>
</button>
</div>
</mat-dialog-actions>

View File

@@ -2,15 +2,6 @@
margin-bottom: 20px;
}
.settings-title {
text-align: center;
margin-top: 15px;
}
::ng-deep .mat-tab-body {
margin-left: 15px;
}
.ext-divider {
margin-bottom: 14px;
}
@@ -99,9 +90,4 @@
.transfer-db-div {
margin-bottom: 10px;
}
.action-buttons {
position: absolute;
bottom: 15px;
}

View File

@@ -51,17 +51,8 @@ export class SettingsComponent implements OnInit {
private dialog: MatDialog) { }
ngOnInit() {
if (this.postsService.initialized) {
this.getConfig();
this.getDBInfo();
} else {
this.postsService.service_initialized.subscribe(init => {
if (init) {
this.getConfig();
this.getDBInfo();
}
});
}
this.getConfig();
this.getDBInfo();
this.generated_bookmarklet_code = this.sanitizer.bypassSecurityTrustUrl(this.generateBookmarkletCode());
@@ -94,10 +85,6 @@ export class SettingsComponent implements OnInit {
})
}
cancelSettings() {
this.new_config = JSON.parse(JSON.stringify(this.initial_config));
}
dropCategory(event: CdkDragDrop<string[]>) {
moveItemInArray(this.postsService.categories, event.previousIndex, event.currentIndex);
this.postsService.updateCategories(this.postsService.categories).subscribe(res => {

View File

@@ -42,10 +42,6 @@ $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
@include angular-material-theme($dark-theme);
}
.mat-stroked-button, .mat-raised-button, .mat-flat-button {
border-radius: 24px !important
}
// Light theme
$light-primary: mat-palette($mat-grey, 200, 500, 300);
$light-accent: mat-palette($mat-brown, 200);
@@ -54,7 +50,7 @@ $light-warn: mat-palette($mat-deep-orange, 200);
$light-theme: mat-light-theme($light-primary, $light-accent, $light-warn);
.light-theme {
@include angular-material-theme($light-theme);
@include angular-material-theme($light-theme)
}
.no-outline {