mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-17 10:10:56 +03:00
Compare commits
19 Commits
python3-do
...
cleaner-pl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2756cfae17 | ||
|
|
dac5919ffb | ||
|
|
34245bd339 | ||
|
|
8d6ec819e6 | ||
|
|
b03b4d173b | ||
|
|
c8f219d5b0 | ||
|
|
ec3ab17507 | ||
|
|
5124e3b333 | ||
|
|
d09b244bc2 | ||
|
|
c0a385ce78 | ||
|
|
258d5ff495 | ||
|
|
fb5c13db27 | ||
|
|
92413bd360 | ||
|
|
7174ef5f57 | ||
|
|
73b9cf7893 | ||
|
|
7ff906fd35 | ||
|
|
6e084bd94a | ||
|
|
21b97911e8 | ||
|
|
117255b0b7 |
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Environment**
|
||||||
|
- YoutubeDL-Material version
|
||||||
|
- Docker tag: <tag> (optional)
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here. For example, a YouTube link.
|
||||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: "[FEATURE]"
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
162
backend/app.js
162
backend/app.js
@@ -18,19 +18,15 @@ var mergeFiles = require('merge-files');
|
|||||||
const low = require('lowdb')
|
const low = require('lowdb')
|
||||||
var ProgressBar = require('progress');
|
var ProgressBar = require('progress');
|
||||||
const NodeID3 = require('node-id3')
|
const NodeID3 = require('node-id3')
|
||||||
const downloader = require('youtube-dl/lib/downloader')
|
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
var URL = require('url').URL;
|
var URL = require('url').URL;
|
||||||
const shortid = require('shortid')
|
|
||||||
const url_api = require('url');
|
const url_api = require('url');
|
||||||
const CONSTS = require('./consts')
|
const CONSTS = require('./consts')
|
||||||
const { spawn } = require('child_process')
|
|
||||||
const read_last_lines = require('read-last-lines');
|
const read_last_lines = require('read-last-lines');
|
||||||
var ps = require('ps-node');
|
var ps = require('ps-node');
|
||||||
|
|
||||||
// needed if bin/details somehow gets deleted
|
// needed if bin/details somehow gets deleted
|
||||||
const DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'
|
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"})
|
||||||
if (!fs.existsSync(DETAILS_BIN_PATH)) fs.writeJSONSync(DETAILS_BIN_PATH, {"version":"2000.06.06","path":"node_modules\\youtube-dl\\bin\\youtube-dl.exe","exec":"youtube-dl.exe","downloader":"youtube-dl"})
|
|
||||||
|
|
||||||
var youtubedl = require('youtube-dl');
|
var youtubedl = require('youtube-dl');
|
||||||
|
|
||||||
@@ -136,7 +132,6 @@ var downloadOnlyMode = null;
|
|||||||
var useDefaultDownloadingAgent = null;
|
var useDefaultDownloadingAgent = null;
|
||||||
var customDownloadingAgent = null;
|
var customDownloadingAgent = null;
|
||||||
var allowSubscriptions = null;
|
var allowSubscriptions = null;
|
||||||
var subscriptionsCheckInterval = null;
|
|
||||||
var archivePath = path.join(__dirname, 'appdata', 'archives');
|
var archivePath = path.join(__dirname, 'appdata', 'archives');
|
||||||
|
|
||||||
// other needed values
|
// other needed values
|
||||||
@@ -627,10 +622,13 @@ async function loadConfig() {
|
|||||||
let subscriptions = await subscriptions_api.getAllSubscriptions();
|
let subscriptions = await subscriptions_api.getAllSubscriptions();
|
||||||
subscriptions_api.updateSubscriptionPropertyMultiple(subscriptions, {downloading: false});
|
subscriptions_api.updateSubscriptionPropertyMultiple(subscriptions, {downloading: false});
|
||||||
// runs initially, then runs every ${subscriptionCheckInterval} seconds
|
// runs initially, then runs every ${subscriptionCheckInterval} seconds
|
||||||
watchSubscriptions();
|
const watchSubscriptionsInterval = function() {
|
||||||
setInterval(() => {
|
|
||||||
watchSubscriptions();
|
watchSubscriptions();
|
||||||
}, subscriptionsCheckInterval * 1000);
|
const subscriptionsCheckInterval = config_api.getConfigItem('ytdl_subscriptions_check_interval');
|
||||||
|
setTimeout(watchSubscriptionsInterval, subscriptionsCheckInterval*1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
watchSubscriptionsInterval();
|
||||||
}
|
}
|
||||||
|
|
||||||
await db_api.importUnregisteredFiles();
|
await db_api.importUnregisteredFiles();
|
||||||
@@ -653,7 +651,6 @@ function loadConfigValues() {
|
|||||||
useDefaultDownloadingAgent = config_api.getConfigItem('ytdl_use_default_downloading_agent');
|
useDefaultDownloadingAgent = config_api.getConfigItem('ytdl_use_default_downloading_agent');
|
||||||
customDownloadingAgent = config_api.getConfigItem('ytdl_custom_downloading_agent');
|
customDownloadingAgent = config_api.getConfigItem('ytdl_custom_downloading_agent');
|
||||||
allowSubscriptions = config_api.getConfigItem('ytdl_allow_subscriptions');
|
allowSubscriptions = config_api.getConfigItem('ytdl_allow_subscriptions');
|
||||||
subscriptionsCheckInterval = config_api.getConfigItem('ytdl_subscriptions_check_interval');
|
|
||||||
|
|
||||||
if (!useDefaultDownloadingAgent && validDownloadingAgents.indexOf(customDownloadingAgent) !== -1 ) {
|
if (!useDefaultDownloadingAgent && validDownloadingAgents.indexOf(customDownloadingAgent) !== -1 ) {
|
||||||
logger.info(`Using non-default downloading agent \'${customDownloadingAgent}\'`)
|
logger.info(`Using non-default downloading agent \'${customDownloadingAgent}\'`)
|
||||||
@@ -678,6 +675,7 @@ function loadConfigValues() {
|
|||||||
|
|
||||||
function calculateSubcriptionRetrievalDelay(subscriptions_amount) {
|
function calculateSubcriptionRetrievalDelay(subscriptions_amount) {
|
||||||
// frequency is once every 5 mins by default
|
// frequency is once every 5 mins by default
|
||||||
|
const subscriptionsCheckInterval = config_api.getConfigItem('ytdl_subscriptions_check_interval');
|
||||||
let interval_in_ms = subscriptionsCheckInterval * 1000;
|
let interval_in_ms = subscriptionsCheckInterval * 1000;
|
||||||
const subinterval_in_ms = interval_in_ms/subscriptions_amount;
|
const subinterval_in_ms = interval_in_ms/subscriptions_amount;
|
||||||
return subinterval_in_ms;
|
return subinterval_in_ms;
|
||||||
@@ -722,6 +720,7 @@ async function watchSubscriptions() {
|
|||||||
}, current_delay);
|
}, current_delay);
|
||||||
subscription_timeouts[sub.id] = true;
|
subscription_timeouts[sub.id] = true;
|
||||||
current_delay += delay_interval;
|
current_delay += delay_interval;
|
||||||
|
const subscriptionsCheckInterval = config_api.getConfigItem('ytdl_subscriptions_check_interval');
|
||||||
if (current_delay >= subscriptionsCheckInterval * 1000) current_delay = 0;
|
if (current_delay >= subscriptionsCheckInterval * 1000) current_delay = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -751,81 +750,6 @@ function generateEnvVarConfigItem(key) {
|
|||||||
return {key: key, value: process['env'][key]};
|
return {key: key, value: process['env'][key]};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getThumbnailMp3(name)
|
|
||||||
{
|
|
||||||
var obj = utils.getJSONMp3(name, audioFolderPath);
|
|
||||||
var thumbnailLink = obj.thumbnail;
|
|
||||||
return thumbnailLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getThumbnailMp4(name)
|
|
||||||
{
|
|
||||||
var obj = utils.getJSONMp4(name, videoFolderPath);
|
|
||||||
var thumbnailLink = obj.thumbnail;
|
|
||||||
return thumbnailLink;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
function getVideoFormatID(name)
|
||||||
{
|
{
|
||||||
var jsonPath = videoFolderPath+name+".info.json";
|
var jsonPath = videoFolderPath+name+".info.json";
|
||||||
@@ -1058,7 +982,7 @@ async function downloadFileByURL_exec(url, type, options, sessionID = null) {
|
|||||||
// create playlist
|
// create playlist
|
||||||
const playlist_name = file_objs.map(file_obj => file_obj.title).join(', ');
|
const playlist_name = file_objs.map(file_obj => file_obj.title).join(', ');
|
||||||
const duration = file_objs.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0);
|
const duration = file_objs.reduce((a, b) => a + utils.durationStringToNumber(b.duration), 0);
|
||||||
container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), type, file_objs[0]['thumbnailURL'], options.user);
|
container = await db_api.createPlaylist(playlist_name, file_objs.map(file_obj => file_obj.uid), type, options.user);
|
||||||
} else if (file_objs.length === 1) {
|
} else if (file_objs.length === 1) {
|
||||||
container = file_objs[0];
|
container = file_objs[0];
|
||||||
} else {
|
} else {
|
||||||
@@ -1191,7 +1115,7 @@ async function generateArgs(url, type, options) {
|
|||||||
downloadConfig = downloadConfig.concat(globalArgs.split(',,'));
|
downloadConfig = downloadConfig.concat(globalArgs.split(',,'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const default_downloader = getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
|
const default_downloader = utils.getCurrentDownloader() || config_api.getConfigItem('ytdl_default_downloader');
|
||||||
if (default_downloader === 'yt-dlp') {
|
if (default_downloader === 'yt-dlp') {
|
||||||
downloadConfig.push('--no-clean-infojson');
|
downloadConfig.push('--no-clean-infojson');
|
||||||
}
|
}
|
||||||
@@ -1295,17 +1219,6 @@ 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
|
// download management functions
|
||||||
|
|
||||||
async function updateDownloads() {
|
async function updateDownloads() {
|
||||||
@@ -1372,17 +1285,17 @@ async function autoUpdateYoutubeDL() {
|
|||||||
const default_downloader = config_api.getConfigItem('ytdl_default_downloader');
|
const default_downloader = config_api.getConfigItem('ytdl_default_downloader');
|
||||||
const tags_url = download_sources[default_downloader]['tags_url'];
|
const tags_url = download_sources[default_downloader]['tags_url'];
|
||||||
// get current version
|
// get current version
|
||||||
let current_app_details_exists = fs.existsSync(DETAILS_BIN_PATH);
|
let current_app_details_exists = fs.existsSync(CONSTS.DETAILS_BIN_PATH);
|
||||||
if (!current_app_details_exists) {
|
if (!current_app_details_exists) {
|
||||||
logger.warn(`Failed to get youtube-dl binary details at location '${DETAILS_BIN_PATH}'. Generating file...`);
|
logger.warn(`Failed to get youtube-dl binary details at location '${CONSTS.DETAILS_BIN_PATH}'. Generating file...`);
|
||||||
fs.writeJSONSync(DETAILS_BIN_PATH, {"version":"2020.00.00", "downloader": default_downloader});
|
fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, {"version":"2020.00.00", "downloader": default_downloader});
|
||||||
}
|
}
|
||||||
let current_app_details = JSON.parse(fs.readFileSync(DETAILS_BIN_PATH));
|
let current_app_details = JSON.parse(fs.readFileSync(CONSTS.DETAILS_BIN_PATH));
|
||||||
let current_version = current_app_details['version'];
|
let current_version = current_app_details['version'];
|
||||||
let current_downloader = current_app_details['downloader'];
|
let current_downloader = current_app_details['downloader'];
|
||||||
let stored_binary_path = current_app_details['path'];
|
let stored_binary_path = current_app_details['path'];
|
||||||
if (!stored_binary_path || typeof stored_binary_path !== 'string') {
|
if (!stored_binary_path || typeof stored_binary_path !== 'string') {
|
||||||
// logger.info(`INFO: Failed to get youtube-dl binary path at location: ${DETAILS_BIN_PATH}, attempting to guess actual path...`);
|
// 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_base_path = 'node_modules/youtube-dl/bin/';
|
||||||
const guessed_file_path = guessed_base_path + 'youtube-dl' + (is_windows ? '.exe' : '');
|
const guessed_file_path = guessed_base_path + 'youtube-dl' + (is_windows ? '.exe' : '');
|
||||||
if (fs.existsSync(guessed_file_path)) {
|
if (fs.existsSync(guessed_file_path)) {
|
||||||
@@ -1465,15 +1378,10 @@ async function downloadLatestYoutubeDLPBinary(new_version) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateDetailsJSON(new_version, downloader) {
|
function updateDetailsJSON(new_version, downloader) {
|
||||||
const details_json = fs.readJSONSync(DETAILS_BIN_PATH);
|
const details_json = fs.readJSONSync(CONSTS.DETAILS_BIN_PATH);
|
||||||
if (new_version) details_json['version'] = new_version;
|
if (new_version) details_json['version'] = new_version;
|
||||||
details_json['downloader'] = downloader;
|
details_json['downloader'] = downloader;
|
||||||
fs.writeJSONSync(DETAILS_BIN_PATH, details_json);
|
fs.writeJSONSync(CONSTS.DETAILS_BIN_PATH, details_json);
|
||||||
}
|
|
||||||
|
|
||||||
function getCurrentDownloader() {
|
|
||||||
const details_json = fs.readJSONSync(DETAILS_BIN_PATH);
|
|
||||||
return details_json['downloader'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkExistsWithTimeout(filePath, timeout) {
|
async function checkExistsWithTimeout(filePath, timeout) {
|
||||||
@@ -1611,9 +1519,10 @@ app.post('/api/transferDB', optionalJwt, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/testConnectionString', optionalJwt, async (req, res) => {
|
app.post('/api/testConnectionString', optionalJwt, async (req, res) => {
|
||||||
|
const connection_string = req.body.connection_string;
|
||||||
let success = null;
|
let success = null;
|
||||||
let error = '';
|
let error = '';
|
||||||
success = await db_api.connectToDB(0, true);
|
success = await db_api.connectToDB(0, true, connection_string);
|
||||||
if (!success) error = 'Connection string failed.';
|
if (!success) error = 'Connection string failed.';
|
||||||
|
|
||||||
res.send({success: success, error: error});
|
res.send({success: success, error: error});
|
||||||
@@ -2178,9 +2087,8 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => {
|
|||||||
let playlistName = req.body.playlistName;
|
let playlistName = req.body.playlistName;
|
||||||
let uids = req.body.uids;
|
let uids = req.body.uids;
|
||||||
let type = req.body.type;
|
let type = req.body.type;
|
||||||
let thumbnailURL = req.body.thumbnailURL;
|
|
||||||
|
|
||||||
const new_playlist = await db_api.createPlaylist(playlistName, uids, type, thumbnailURL, req.isAuthenticated() ? req.user.uid : null);
|
const new_playlist = await db_api.createPlaylist(playlistName, uids, type, req.isAuthenticated() ? req.user.uid : null);
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
new_playlist: new_playlist,
|
new_playlist: new_playlist,
|
||||||
@@ -2213,8 +2121,18 @@ app.post('/api/getPlaylist', optionalJwt, async (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/getPlaylists', optionalJwt, async (req, res) => {
|
||||||
|
const uuid = req.isAuthenticated() ? req.user.uid : null;
|
||||||
|
|
||||||
|
const playlists = await db_api.getRecords('playlists', {user_uid: uuid});
|
||||||
|
|
||||||
|
res.send({
|
||||||
|
playlists: playlists
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
||||||
let playlistID = req.body.playlistID;
|
let playlistID = req.body.playlist_id;
|
||||||
let uids = req.body.uids;
|
let uids = req.body.uids;
|
||||||
|
|
||||||
let success = false;
|
let success = false;
|
||||||
@@ -2235,6 +2153,20 @@ app.post('/api/updatePlaylistFiles', optionalJwt, async (req, res) => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/addFileToPlaylist', optionalJwt, async (req, res) => {
|
||||||
|
let playlist_id = req.body.playlist_id;
|
||||||
|
let file_uid = req.body.file_uid;
|
||||||
|
|
||||||
|
const playlist = await db_api.getRecord('playlists', {id: playlist_id});
|
||||||
|
|
||||||
|
playlist.uids.push(file_uid);
|
||||||
|
|
||||||
|
let success = await db_api.updatePlaylist(playlist);
|
||||||
|
res.send({
|
||||||
|
success: success
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/api/updatePlaylist', optionalJwt, async (req, res) => {
|
app.post('/api/updatePlaylist', optionalJwt, async (req, res) => {
|
||||||
let playlist = req.body.playlist;
|
let playlist = req.body.playlist;
|
||||||
let success = await db_api.updatePlaylist(playlist, req.user && req.user.uid);
|
let success = await db_api.updatePlaylist(playlist, req.user && req.user.uid);
|
||||||
@@ -2244,7 +2176,7 @@ app.post('/api/updatePlaylist', optionalJwt, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/deletePlaylist', optionalJwt, async (req, res) => {
|
app.post('/api/deletePlaylist', optionalJwt, async (req, res) => {
|
||||||
let playlistID = req.body.playlistID;
|
let playlistID = req.body.playlist_id;
|
||||||
|
|
||||||
let success = null;
|
let success = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Database": {
|
"Database": {
|
||||||
"use_local_db": false,
|
"use_local_db": true,
|
||||||
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
|
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
|
||||||
},
|
},
|
||||||
"Advanced": {
|
"Advanced": {
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ DEFAULT_CONFIG = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Database": {
|
"Database": {
|
||||||
"use_local_db": false,
|
"use_local_db": true,
|
||||||
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
|
"mongodb_connection_string": "mongodb://127.0.0.1:27017/?compressors=zlib"
|
||||||
},
|
},
|
||||||
"Advanced": {
|
"Advanced": {
|
||||||
|
|||||||
@@ -207,8 +207,11 @@ AVAILABLE_PERMISSIONS = [
|
|||||||
'downloads_manager'
|
'downloads_manager'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const DETAILS_BIN_PATH = 'node_modules/youtube-dl/bin/details'
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
CONFIG_ITEMS: CONFIG_ITEMS,
|
CONFIG_ITEMS: CONFIG_ITEMS,
|
||||||
AVAILABLE_PERMISSIONS: AVAILABLE_PERMISSIONS,
|
AVAILABLE_PERMISSIONS: AVAILABLE_PERMISSIONS,
|
||||||
CURRENT_VERSION: 'v4.2'
|
CURRENT_VERSION: 'v4.2',
|
||||||
|
DETAILS_BIN_PATH: DETAILS_BIN_PATH
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const local_db_defaults = {}
|
|||||||
tables_list.forEach(table => {local_db_defaults[table] = []});
|
tables_list.forEach(table => {local_db_defaults[table] = []});
|
||||||
local_db.defaults(local_db_defaults).write();
|
local_db.defaults(local_db_defaults).write();
|
||||||
|
|
||||||
let using_local_db = config_api.getConfigItem('ytdl_use_local_db');
|
let using_local_db = null;
|
||||||
|
|
||||||
function setDB(input_db, input_users_db) {
|
function setDB(input_db, input_users_db) {
|
||||||
db = input_db; users_db = input_users_db;
|
db = input_db; users_db = input_users_db;
|
||||||
@@ -69,11 +69,14 @@ function setLogger(input_logger) {
|
|||||||
exports.initialize = (input_db, input_users_db, input_logger) => {
|
exports.initialize = (input_db, input_users_db, input_logger) => {
|
||||||
setDB(input_db, input_users_db);
|
setDB(input_db, input_users_db);
|
||||||
setLogger(input_logger);
|
setLogger(input_logger);
|
||||||
|
|
||||||
|
// must be done here to prevent getConfigItem from being called before init
|
||||||
|
using_local_db = config_api.getConfigItem('ytdl_use_local_db');
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.connectToDB = async (retries = 5, no_fallback = false) => {
|
exports.connectToDB = async (retries = 5, no_fallback = false, custom_connection_string = null) => {
|
||||||
if (using_local_db) return;
|
if (using_local_db && !custom_connection_string) return;
|
||||||
const success = await exports._connectToDB();
|
const success = await exports._connectToDB(custom_connection_string);
|
||||||
if (success) return true;
|
if (success) return true;
|
||||||
|
|
||||||
if (retries) {
|
if (retries) {
|
||||||
@@ -105,8 +108,8 @@ exports.connectToDB = async (retries = 5, no_fallback = false) => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports._connectToDB = async () => {
|
exports._connectToDB = async (custom_connection_string = null) => {
|
||||||
const uri = config_api.getConfigItem('ytdl_mongodb_connection_string'); // "mongodb://127.0.0.1:27017/?compressors=zlib&gssapiServiceName=mongodb";
|
const uri = !custom_connection_string ? config_api.getConfigItem('ytdl_mongodb_connection_string') : custom_connection_string; // "mongodb://127.0.0.1:27017/?compressors=zlib&gssapiServiceName=mongodb";
|
||||||
const client = new MongoClient(uri, {
|
const client = new MongoClient(uri, {
|
||||||
useNewUrlParser: true,
|
useNewUrlParser: true,
|
||||||
useUnifiedTopology: true,
|
useUnifiedTopology: true,
|
||||||
@@ -115,6 +118,10 @@ exports._connectToDB = async () => {
|
|||||||
try {
|
try {
|
||||||
await client.connect();
|
await client.connect();
|
||||||
database = client.db('ytdl_material');
|
database = client.db('ytdl_material');
|
||||||
|
|
||||||
|
// avoid doing anything else if it's just a test
|
||||||
|
if (custom_connection_string) return true;
|
||||||
|
|
||||||
const existing_collections = (await database.listCollections({}, { nameOnly: true }).toArray()).map(collection => collection.name);
|
const existing_collections = (await database.listCollections({}, { nameOnly: true }).toArray()).map(collection => collection.name);
|
||||||
|
|
||||||
const missing_tables = tables_list.filter(table => !(existing_collections.includes(table)));
|
const missing_tables = tables_list.filter(table => !(existing_collections.includes(table)));
|
||||||
@@ -249,6 +256,9 @@ function generateFileObject2(file_path, type) {
|
|||||||
var jsonobj = utils.getJSON(file_path, type);
|
var jsonobj = utils.getJSON(file_path, type);
|
||||||
if (!jsonobj) {
|
if (!jsonobj) {
|
||||||
return null;
|
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 ext = (type === 'audio') ? '.mp3' : '.mp4'
|
||||||
const true_file_path = utils.getTrueFileName(jsonobj['_filename'], type);
|
const true_file_path = utils.getTrueFileName(jsonobj['_filename'], type);
|
||||||
@@ -408,24 +418,27 @@ exports.addMetadataPropertyToDB = async (property_key) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.createPlaylist = async (playlist_name, uids, type, thumbnail_url, user_uid = null) => {
|
exports.createPlaylist = async (playlist_name, uids, type, user_uid = null) => {
|
||||||
|
const first_video = await exports.getVideo(uids[0]);
|
||||||
|
const thumbnailToUse = first_video['thumbnailURL'];
|
||||||
|
|
||||||
let new_playlist = {
|
let new_playlist = {
|
||||||
name: playlist_name,
|
name: playlist_name,
|
||||||
uids: uids,
|
uids: uids,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
thumbnailURL: thumbnail_url,
|
thumbnailURL: thumbnailToUse,
|
||||||
type: type,
|
type: type,
|
||||||
registered: Date.now(),
|
registered: Date.now(),
|
||||||
randomize_order: false
|
randomize_order: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const duration = await exports.calculatePlaylistDuration(new_playlist, user_uid);
|
|
||||||
new_playlist.duration = duration;
|
|
||||||
|
|
||||||
new_playlist.user_uid = user_uid ? user_uid : undefined;
|
new_playlist.user_uid = user_uid ? user_uid : undefined;
|
||||||
|
|
||||||
await exports.insertRecordIntoTable('playlists', new_playlist);
|
await exports.insertRecordIntoTable('playlists', new_playlist);
|
||||||
|
|
||||||
|
const duration = await exports.calculatePlaylistDuration(new_playlist);
|
||||||
|
await exports.updateRecord('playlists', {id: new_playlist.id}, {duration: duration});
|
||||||
|
|
||||||
return new_playlist;
|
return new_playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -460,10 +473,10 @@ exports.getPlaylist = async (playlist_id, user_uid = null, require_sharing = fal
|
|||||||
return playlist;
|
return playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.updatePlaylist = async (playlist, user_uid = null) => {
|
exports.updatePlaylist = async (playlist) => {
|
||||||
let playlistID = playlist.id;
|
let playlistID = playlist.id;
|
||||||
|
|
||||||
const duration = await exports.calculatePlaylistDuration(playlist, user_uid);
|
const duration = await exports.calculatePlaylistDuration(playlist);
|
||||||
playlist.duration = duration;
|
playlist.duration = duration;
|
||||||
|
|
||||||
return await exports.updateRecord('playlists', {id: playlistID}, playlist);
|
return await exports.updateRecord('playlists', {id: playlistID}, playlist);
|
||||||
@@ -483,12 +496,12 @@ exports.setPlaylistProperty = async (playlist_id, assignment_obj, user_uid = nul
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.calculatePlaylistDuration = async (playlist, uuid, playlist_file_objs = null) => {
|
exports.calculatePlaylistDuration = async (playlist, playlist_file_objs = null) => {
|
||||||
if (!playlist_file_objs) {
|
if (!playlist_file_objs) {
|
||||||
playlist_file_objs = [];
|
playlist_file_objs = [];
|
||||||
for (let i = 0; i < playlist['uids'].length; i++) {
|
for (let i = 0; i < playlist['uids'].length; i++) {
|
||||||
const uid = playlist['uids'][i];
|
const uid = playlist['uids'][i];
|
||||||
const file_obj = await exports.getVideo(uid, uuid);
|
const file_obj = await exports.getVideo(uid);
|
||||||
if (file_obj) playlist_file_objs.push(file_obj);
|
if (file_obj) playlist_file_objs.push(file_obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,7 +598,7 @@ exports.getVideoUIDByID = async (file_id, uuid = null) => {
|
|||||||
return file_obj ? file_obj['uid'] : null;
|
return file_obj ? file_obj['uid'] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getVideo = async (file_uid, uuid = null, sub_id = null) => {
|
exports.getVideo = async (file_uid) => {
|
||||||
return await exports.getRecord('files', {uid: file_uid});
|
return await exports.getRecord('files', {uid: file_uid});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -771,26 +784,26 @@ exports.removeRecord = async (table, filter_obj) => {
|
|||||||
return !!(output['result']['ok']);
|
return !!(output['result']['ok']);
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.removeAllRecords = async (table = null) => {
|
exports.removeAllRecords = async (table = null, filter_obj = null) => {
|
||||||
// local db override
|
// local db override
|
||||||
const tables_to_remove = table ? [table] : tables_list;
|
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) {
|
if (using_local_db) {
|
||||||
logger.debug(`Removing all records from: ${tables_to_remove}`)
|
|
||||||
for (let i = 0; i < tables_to_remove.length; i++) {
|
for (let i = 0; i < tables_to_remove.length; i++) {
|
||||||
const table_to_remove = tables_to_remove[i];
|
const table_to_remove = tables_to_remove[i];
|
||||||
local_db.assign({[table_to_remove]: []}).write();
|
if (filter_obj) applyFilterLocalDB(local_db.get(table), filter_obj, 'remove').write();
|
||||||
logger.debug(`Removed all records from ${table_to_remove}`);
|
else local_db.assign({[table_to_remove]: []}).write();
|
||||||
|
logger.debug(`Successfully removed records from ${table_to_remove}`);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let success = true;
|
let success = true;
|
||||||
logger.debug(`Removing all records from: ${tables_to_remove}`)
|
|
||||||
for (let i = 0; i < tables_to_remove.length; i++) {
|
for (let i = 0; i < tables_to_remove.length; i++) {
|
||||||
const table_to_remove = tables_to_remove[i];
|
const table_to_remove = tables_to_remove[i];
|
||||||
|
|
||||||
const output = await database.collection(table_to_remove).deleteMany({});
|
const output = await database.collection(table_to_remove).deleteMany(filter_obj ? filter_obj : {});
|
||||||
logger.debug(`Removed all records from ${table_to_remove}`);
|
logger.debug(`Successfully removed records from ${table_to_remove}`);
|
||||||
success &= !!(output['result']['ok']);
|
success &= !!(output['result']['ok']);
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
@@ -978,6 +991,8 @@ exports.transferDB = async (local_to_remote) => {
|
|||||||
|
|
||||||
config_api.setConfigItem('ytdl_use_local_db', using_local_db);
|
config_api.setConfigItem('ytdl_use_local_db', using_local_db);
|
||||||
|
|
||||||
|
logger.debug('Transfer finished!');
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1004,3 +1019,15 @@ const applyFilterLocalDB = (db_path, filter_obj, operation) => {
|
|||||||
});
|
});
|
||||||
return return_val;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -347,7 +347,9 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
|
|||||||
|
|
||||||
let appendedBasePath = getAppendedBasePath(sub, basePath);
|
let appendedBasePath = getAppendedBasePath(sub, basePath);
|
||||||
|
|
||||||
let fullOutput = `${appendedBasePath}/%(title)s.%(ext)s`;
|
const file_output = config_api.getConfigItem('ytdl_default_file_output') ? config_api.getConfigItem('ytdl_default_file_output') : '%(title)s';
|
||||||
|
|
||||||
|
let fullOutput = `${appendedBasePath}/${file_output}.%(ext)s`;
|
||||||
if (desired_path) {
|
if (desired_path) {
|
||||||
fullOutput = `${desired_path}.%(ext)s`;
|
fullOutput = `${desired_path}.%(ext)s`;
|
||||||
} else if (sub.custom_output) {
|
} else if (sub.custom_output) {
|
||||||
@@ -411,6 +413,11 @@ async function generateArgsForSubscription(sub, user_uid, redownload = false, de
|
|||||||
downloadConfig.push('--write-thumbnail');
|
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;
|
return downloadConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const config_api = require('./config');
|
const config_api = require('./config');
|
||||||
|
const CONSTS = require('./consts')
|
||||||
const archiver = require('archiver');
|
const archiver = require('archiver');
|
||||||
|
|
||||||
const is_windows = process.platform === 'win32';
|
const is_windows = process.platform === 'win32';
|
||||||
@@ -315,6 +316,11 @@ function addUIDsToCategory(category, files) {
|
|||||||
return files_that_match;
|
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)
|
async function recFindByExt(base,ext,files,result)
|
||||||
{
|
{
|
||||||
files = files || (await fs.readdir(base))
|
files = files || (await fs.readdir(base))
|
||||||
@@ -390,6 +396,7 @@ module.exports = {
|
|||||||
durationStringToNumber: durationStringToNumber,
|
durationStringToNumber: durationStringToNumber,
|
||||||
getMatchingCategoryFiles: getMatchingCategoryFiles,
|
getMatchingCategoryFiles: getMatchingCategoryFiles,
|
||||||
addUIDsToCategory: addUIDsToCategory,
|
addUIDsToCategory: addUIDsToCategory,
|
||||||
|
getCurrentDownloader: getCurrentDownloader,
|
||||||
recFindByExt: recFindByExt,
|
recFindByExt: recFindByExt,
|
||||||
removeFileExtension: removeFileExtension,
|
removeFileExtension: removeFileExtension,
|
||||||
wait: wait,
|
wait: wait,
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import { SubscriptionComponent } from './subscription/subscription/subscription.
|
|||||||
import { PostsService } from './posts.services';
|
import { PostsService } from './posts.services';
|
||||||
import { LoginComponent } from './components/login/login.component';
|
import { LoginComponent } from './components/login/login.component';
|
||||||
import { DownloadsComponent } from './components/downloads/downloads.component';
|
import { DownloadsComponent } from './components/downloads/downloads.component';
|
||||||
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: 'home', component: MainComponent, canActivate: [PostsService] },
|
{ path: 'home', component: MainComponent, canActivate: [PostsService] },
|
||||||
{ path: 'player', component: PlayerComponent, canActivate: [PostsService]},
|
{ path: 'player', component: PlayerComponent, canActivate: [PostsService]},
|
||||||
{ path: 'subscriptions', component: SubscriptionsComponent, canActivate: [PostsService] },
|
{ path: 'subscriptions', component: SubscriptionsComponent, canActivate: [PostsService] },
|
||||||
{ path: 'subscription', component: SubscriptionComponent, canActivate: [PostsService] },
|
{ path: 'subscription', component: SubscriptionComponent, canActivate: [PostsService] },
|
||||||
|
{ path: 'settings', component: SettingsComponent, canActivate: [PostsService] },
|
||||||
{ path: 'login', component: LoginComponent },
|
{ path: 'login', component: LoginComponent },
|
||||||
{ path: 'downloads', component: DownloadsComponent },
|
{ path: 'downloads', component: DownloadsComponent },
|
||||||
{ path: '', redirectTo: '/home', pathMatch: 'full' }
|
{ path: '', redirectTo: '/home', pathMatch: 'full' }
|
||||||
|
|||||||
@@ -23,10 +23,10 @@
|
|||||||
<span i18n="Dark mode toggle label">Dark</span>
|
<span i18n="Dark mode toggle label">Dark</span>
|
||||||
<mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle>
|
<mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle>
|
||||||
</button>
|
</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>
|
<mat-icon>settings</mat-icon>
|
||||||
<span i18n="Settings menu label">Settings</span>
|
<span i18n="Settings menu label">Settings</span>
|
||||||
</button>
|
</button> -->
|
||||||
<button (click)="openAboutDialog()" mat-menu-item>
|
<button (click)="openAboutDialog()" mat-menu-item>
|
||||||
<mat-icon>info</mat-icon>
|
<mat-icon>info</mat-icon>
|
||||||
<span i18n="About menu label">About</span>
|
<span i18n="About menu label">About</span>
|
||||||
@@ -42,10 +42,14 @@
|
|||||||
<mat-nav-list>
|
<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)="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 && 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.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 && 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.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>
|
<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 && allowSubscriptions && postsService.subscriptions && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('subscriptions')))">
|
<ng-container *ngIf="postsService.config && postsService.hasPermission('settings')">
|
||||||
<mat-divider></mat-divider>
|
<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>
|
<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>
|
</ng-container>
|
||||||
</mat-nav-list>
|
</mat-nav-list>
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Component, OnInit, ElementRef, ViewChild, HostBinding, AfterViewInit } from '@angular/core';
|
import { Component, OnInit, ElementRef, ViewChild, HostBinding, AfterViewInit } from '@angular/core';
|
||||||
|
import {MatDialogRef} from '@angular/material/dialog';
|
||||||
import {PostsService} from './posts.services';
|
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 { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatSidenav } from '@angular/material/sidenav';
|
import { MatSidenav } from '@angular/material/sidenav';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
@@ -16,7 +13,6 @@ import 'rxjs/add/operator/filter'
|
|||||||
import 'rxjs/add/operator/debounceTime'
|
import 'rxjs/add/operator/debounceTime'
|
||||||
import 'rxjs/add/operator/do'
|
import 'rxjs/add/operator/do'
|
||||||
import 'rxjs/add/operator/switch'
|
import 'rxjs/add/operator/switch'
|
||||||
import { YoutubeSearchService, Result } from './youtube-search.service';
|
|
||||||
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
|
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
|
||||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||||
import { THEMES_CONFIG } from '../themes';
|
import { THEMES_CONFIG } from '../themes';
|
||||||
@@ -28,7 +24,11 @@ import { SetDefaultAdminDialogComponent } from './dialogs/set-default-admin-dial
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css'],
|
||||||
|
providers: [{
|
||||||
|
provide: MatDialogRef,
|
||||||
|
useValue: {}
|
||||||
|
}]
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit, AfterViewInit {
|
export class AppComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,17 @@ export class CustomPlaylistsComponent implements OnInit {
|
|||||||
this.getAllPlaylists();
|
this.getAllPlaylists();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.postsService.playlists_changed.subscribe(changed => {
|
||||||
|
if (changed) {
|
||||||
|
this.getAllPlaylists();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllPlaylists() {
|
getAllPlaylists() {
|
||||||
this.playlists_received = false;
|
this.playlists_received = false;
|
||||||
|
// must call getAllFiles as we need to get category playlists as well
|
||||||
this.postsService.getAllFiles().subscribe(res => {
|
this.postsService.getAllFiles().subscribe(res => {
|
||||||
this.playlists = res['playlists'];
|
this.playlists = res['playlists'];
|
||||||
this.playlists_received = true;
|
this.playlists_received = true;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<mat-card class="login-card">
|
<mat-card class="login-card">
|
||||||
<mat-tab-group [(selectedIndex)]="selectedTabIndex">
|
<mat-tab-group style="margin-bottom: 20px" [(selectedIndex)]="selectedTabIndex">
|
||||||
<mat-tab label="Login">
|
<mat-tab label="Login">
|
||||||
<div style="margin-top: 10px;">
|
<div style="margin-top: 10px;">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
@@ -11,9 +11,6 @@
|
|||||||
<input [(ngModel)]="loginPasswordInput" (keyup.enter)="login()" type="password" matInput placeholder="Password">
|
<input [(ngModel)]="loginPasswordInput" (keyup.enter)="login()" type="password" matInput placeholder="Password">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</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>
|
||||||
<mat-tab *ngIf="registrationEnabled" label="Register">
|
<mat-tab *ngIf="registrationEnabled" label="Register">
|
||||||
<div style="margin-top: 10px;">
|
<div style="margin-top: 10px;">
|
||||||
@@ -31,9 +28,14 @@
|
|||||||
<input [(ngModel)]="registrationPasswordConfirmationInput" type="password" matInput placeholder="Confirm Password">
|
<input [(ngModel)]="registrationPasswordConfirmationInput" type="password" matInput placeholder="Confirm Password">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</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>
|
||||||
</mat-tab-group>
|
</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>
|
</mat-card>
|
||||||
@@ -1,6 +1,33 @@
|
|||||||
.login-card {
|
.login-card {
|
||||||
max-width: 600px;
|
max-width: 400px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
margin-top: 20px;
|
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;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div style="height: 275px;">
|
<div style="height: 100%;">
|
||||||
<div *ngIf="logs_loading" style="z-index: 999; position: absolute; top: 40%; left: 50%">
|
<div *ngIf="logs_loading" style="z-index: 999; position: absolute; top: 40%; left: 50%">
|
||||||
<mat-spinner [diameter]="32"></mat-spinner>
|
<mat-spinner [diameter]="32"></mat-spinner>
|
||||||
</div>
|
</div>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
</cdk-virtual-scroll-viewport>-->
|
</cdk-virtual-scroll-viewport>-->
|
||||||
|
|
||||||
<!-- Non-virtual mode (slow, bug-free) -->
|
<!-- Non-virtual mode (slow, bug-free) -->
|
||||||
<div style="height: 274px; overflow-y: auto">
|
<div style="height: 100%; overflow-y: auto">
|
||||||
<div *ngFor="let log of logs; let i = index" class="example-item">
|
<div *ngFor="let log of logs; let i = index" class="example-item">
|
||||||
<span [ngStyle]="{'color':log.color}">{{log.text}}</span>
|
<span [ngStyle]="{'color':log.color}">{{log.text}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div *ngIf="dataSource; else loading">
|
<div *ngIf="dataSource; else loading">
|
||||||
<div style="padding: 15px">
|
<div style="padding: 15px">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="table table-responsive px-5 pb-4 pt-2">
|
<div class="table table-responsive pb-4 pt-2">
|
||||||
<div class="example-header">
|
<div class="example-header">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Search" i18n-placeholder="search field description">
|
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Search" i18n-placeholder="search field description">
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
.edit-role {
|
.edit-role {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -50px;
|
top: -50px;
|
||||||
left: 35px;
|
|
||||||
}
|
}
|
||||||
@@ -28,11 +28,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="container">
|
<div class="container" style="margin-bottom: 16px">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<ng-container *ngIf="normal_files_received && paged_data">
|
<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' : '' ]">
|
<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' : '' ]">
|
||||||
<app-unified-file-card [index]="i" [card_size]="postsService.card_size" [locale]="postsService.locale" (goToFile)="goToFile($event)" (goToSubscription)="goToSubscription($event)" [file_obj]="file" [use_youtubedl_archive]="postsService.config['Downloader']['use_youtubedl_archive']" [loading]="false" (deleteFile)="deleteFile($event)" [baseStreamPath]="postsService.path" [jwtString]="postsService.isLoggedIn ? '?jwt=' + this.postsService.token : ''"></app-unified-file-card>
|
<app-unified-file-card [index]="i" [card_size]="postsService.card_size" [locale]="postsService.locale" (goToFile)="goToFile($event)" (goToSubscription)="goToSubscription($event)" [file_obj]="file" [use_youtubedl_archive]="postsService.config['Downloader']['use_youtubedl_archive']" [availablePlaylists]="playlists" (addFileToPlaylist)="addFileToPlaylist($event)" [loading]="false" (deleteFile)="deleteFile($event)" [baseStreamPath]="postsService.path" [jwtString]="postsService.isLoggedIn ? '?jwt=' + this.postsService.token : ''"></app-unified-file-card>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="filtered_files.length === 0">
|
<div *ngIf="filtered_files.length === 0">
|
||||||
<ng-container i18n="No videos found">No videos found.</ng-container>
|
<ng-container i18n="No videos found">No videos found.</ng-container>
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export class RecentVideosComponent implements OnInit {
|
|||||||
};
|
};
|
||||||
filterProperty = this.filterProperties['upload_date'];
|
filterProperty = this.filterProperties['upload_date'];
|
||||||
|
|
||||||
|
playlists = null;
|
||||||
|
|
||||||
pageSize = 10;
|
pageSize = 10;
|
||||||
paged_data = null;
|
paged_data = null;
|
||||||
|
|
||||||
@@ -68,14 +70,27 @@ export class RecentVideosComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.postsService.initialized) {
|
if (this.postsService.initialized) {
|
||||||
this.getAllFiles();
|
this.getAllFiles();
|
||||||
|
this.getAllPlaylists();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.postsService.service_initialized.subscribe(init => {
|
this.postsService.service_initialized.subscribe(init => {
|
||||||
if (init) {
|
if (init) {
|
||||||
this.getAllFiles();
|
this.getAllFiles();
|
||||||
|
this.getAllPlaylists();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.postsService.files_changed.subscribe(changed => {
|
||||||
|
if (changed) {
|
||||||
|
this.getAllFiles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.postsService.playlists_changed.subscribe(changed => {
|
||||||
|
if (changed) {
|
||||||
|
this.getAllPlaylists();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// set filter property to cached
|
// set filter property to cached
|
||||||
const cached_filter_property = localStorage.getItem('filter_property');
|
const cached_filter_property = localStorage.getItem('filter_property');
|
||||||
@@ -84,6 +99,12 @@ export class RecentVideosComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllPlaylists() {
|
||||||
|
this.postsService.getPlaylists().subscribe(res => {
|
||||||
|
this.playlists = res['playlists'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// search
|
// search
|
||||||
|
|
||||||
onSearchInputChanged(newvalue) {
|
onSearchInputChanged(newvalue) {
|
||||||
@@ -288,6 +309,23 @@ export class RecentVideosComponent implements OnInit {
|
|||||||
this.filterByProperty(this.filterProperty['property']);
|
this.filterByProperty(this.filterProperty['property']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addFileToPlaylist(info_obj) {
|
||||||
|
const file = info_obj['file'];
|
||||||
|
const playlist_id = info_obj['playlist_id'];
|
||||||
|
const playlist = this.playlists.find(potential_playlist => potential_playlist['id'] === playlist_id);
|
||||||
|
this.postsService.addFileToPlaylist(playlist_id, file['uid']).subscribe(res => {
|
||||||
|
if (res['success']) {
|
||||||
|
this.postsService.openSnackBar(`Successfully added ${file.title} to ${playlist.title}!`);
|
||||||
|
this.postsService.playlists_changed.next(true);
|
||||||
|
} else {
|
||||||
|
this.postsService.openSnackBar(`Failed to add ${file.title} to ${playlist.title}! Unknown error.`);
|
||||||
|
}
|
||||||
|
}, err => {
|
||||||
|
console.error(err);
|
||||||
|
this.postsService.openSnackBar(`Failed to add ${file.title} to ${playlist.title}! See browser console for error.`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// sorting and filtering
|
// sorting and filtering
|
||||||
|
|
||||||
sortFiles(a, b) {
|
sortFiles(a, b) {
|
||||||
|
|||||||
@@ -23,6 +23,12 @@
|
|||||||
<ng-container *ngIf="!is_playlist && !loading">
|
<ng-container *ngIf="!is_playlist && !loading">
|
||||||
<button (click)="openFileInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Video info button">Info</ng-container></button>
|
<button (click)="openFileInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Video info button">Info</ng-container></button>
|
||||||
<button (click)="navigateToSubscription()" mat-menu-item *ngIf="file_obj.sub_id"><mat-icon>{{file_obj.isAudio ? 'library_music' : 'video_library'}}</mat-icon> <ng-container i18n="Go to subscription menu item">Go to subscription</ng-container></button>
|
<button (click)="navigateToSubscription()" mat-menu-item *ngIf="file_obj.sub_id"><mat-icon>{{file_obj.isAudio ? 'library_music' : 'video_library'}}</mat-icon> <ng-container i18n="Go to subscription menu item">Go to subscription</ng-container></button>
|
||||||
|
<button *ngIf="availablePlaylists" [matMenuTriggerFor]="addtoplaylist" mat-menu-item><mat-icon>playlist_add</mat-icon> <ng-container i18n="Add to playlist menu item">Add to playlist</ng-container></button>
|
||||||
|
<mat-menu #addtoplaylist="matMenu">
|
||||||
|
<ng-container *ngFor="let playlist of availablePlaylists">
|
||||||
|
<button *ngIf="(playlist.type === 'audio') === file_obj.isAudio" [disabled]="playlist.uids?.includes(file_obj.uid)" (click)="emitAddFileToPlaylist(playlist.id)" mat-menu-item>{{playlist.name}}</button>
|
||||||
|
</ng-container>
|
||||||
|
</mat-menu>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<button *ngIf="file_obj.sub_id" (click)="emitDeleteFile()" mat-menu-item>
|
<button *ngIf="file_obj.sub_id" (click)="emitDeleteFile()" mat-menu-item>
|
||||||
<mat-icon>restore</mat-icon><ng-container i18n="Delete and redownload subscription video button">Delete and redownload</ng-container>
|
<mat-icon>restore</mat-icon><ng-container i18n="Delete and redownload subscription video button">Delete and redownload</ng-container>
|
||||||
|
|||||||
@@ -46,9 +46,11 @@ export class UnifiedFileCardComponent implements OnInit {
|
|||||||
@Input() locale = null;
|
@Input() locale = null;
|
||||||
@Input() baseStreamPath = null;
|
@Input() baseStreamPath = null;
|
||||||
@Input() jwtString = null;
|
@Input() jwtString = null;
|
||||||
|
@Input() availablePlaylists = null;
|
||||||
@Output() goToFile = new EventEmitter<any>();
|
@Output() goToFile = new EventEmitter<any>();
|
||||||
@Output() goToSubscription = new EventEmitter<any>();
|
@Output() goToSubscription = new EventEmitter<any>();
|
||||||
@Output() deleteFile = new EventEmitter<any>();
|
@Output() deleteFile = new EventEmitter<any>();
|
||||||
|
@Output() addFileToPlaylist = new EventEmitter<any>();
|
||||||
@Output() editPlaylist = new EventEmitter<any>();
|
@Output() editPlaylist = new EventEmitter<any>();
|
||||||
|
|
||||||
|
|
||||||
@@ -86,6 +88,13 @@ export class UnifiedFileCardComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitAddFileToPlaylist(playlist_id) {
|
||||||
|
this.addFileToPlaylist.emit({
|
||||||
|
file: this.file_obj,
|
||||||
|
playlist_id: playlist_id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
navigateToFile(event) {
|
navigateToFile(event) {
|
||||||
this.goToFile.emit({file: this.file_obj, event: event});
|
this.goToFile.emit({file: this.file_obj, event: event});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export class ModifyPlaylistComponent implements OnInit {
|
|||||||
this.playlist_updated = true;
|
this.playlist_updated = true;
|
||||||
this.postsService.openSnackBar('Playlist updated successfully.');
|
this.postsService.openSnackBar('Playlist updated successfully.');
|
||||||
this.getPlaylist();
|
this.getPlaylist();
|
||||||
|
this.postsService.playlists_changed.next(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +78,7 @@ export class ModifyPlaylistComponent implements OnInit {
|
|||||||
|
|
||||||
addContent(file) {
|
addContent(file) {
|
||||||
this.playlist_file_objs.push(file);
|
this.playlist_file_objs.push(file);
|
||||||
|
this.playlist.uids.push(file.uid);
|
||||||
this.processFiles();
|
this.processFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,12 +133,16 @@ mat-form-field.mat-form-field {
|
|||||||
top: -5px;
|
top: -5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-radius-both {
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.no-border-radius-bottom {
|
.no-border-radius-bottom {
|
||||||
border-radius: 4px 4px 0px 0px;
|
border-radius: 16px 16px 0px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-border-radius-top {
|
.no-border-radius-top {
|
||||||
border-radius: 0px 0px 4px 4px;
|
border-radius: 0px 0px 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<br/>
|
<br/>
|
||||||
<div class="big demo-basic">
|
<div class="big demo-basic">
|
||||||
<mat-card id="card" style="margin-right: 20px; margin-left: 20px;" [ngClass]="(allowAdvancedDownload) ? 'no-border-radius-bottom' : null">
|
<mat-card id="card" style="margin-right: 20px; margin-left: 20px;" [ngClass]="(allowAdvancedDownload) ? 'no-border-radius-bottom' : 'border-radius-both'">
|
||||||
<mat-card-content style="padding: 0px 8px 0px 8px;">
|
<mat-card-content style="padding: 0px 8px 0px 8px;">
|
||||||
<div style="position: relative; margin-right: 15px;">
|
<div style="position: relative; margin-right: 15px;">
|
||||||
<form class="example-form">
|
<form class="example-form">
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ export class MainComponent implements OnInit {
|
|||||||
async loadConfig() {
|
async loadConfig() {
|
||||||
// loading config
|
// loading config
|
||||||
this.fileManagerEnabled = this.postsService.config['Extra']['file_manager_enabled']
|
this.fileManagerEnabled = this.postsService.config['Extra']['file_manager_enabled']
|
||||||
&& (!this.postsService.isLoggedIn || this.postsService.permissions.includes('filemanager'));
|
&& this.postsService.hasPermission('filemanager');
|
||||||
this.downloadOnlyMode = this.postsService.config['Extra']['download_only_mode'];
|
this.downloadOnlyMode = this.postsService.config['Extra']['download_only_mode'];
|
||||||
this.allowMultiDownloadMode = this.postsService.config['Extra']['allow_multi_download_mode'];
|
this.allowMultiDownloadMode = this.postsService.config['Extra']['allow_multi_download_mode'];
|
||||||
this.audioFolderPath = this.postsService.config['Downloader']['path-audio'];
|
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.youtubeAPIKey = this.youtubeSearchEnabled ? this.postsService.config['API']['youtube_API_key'] : null;
|
||||||
this.allowQualitySelect = this.postsService.config['Extra']['allow_quality_select'];
|
this.allowQualitySelect = this.postsService.config['Extra']['allow_quality_select'];
|
||||||
this.allowAdvancedDownload = this.postsService.config['Advanced']['allow_advanced_download']
|
this.allowAdvancedDownload = this.postsService.config['Advanced']['allow_advanced_download']
|
||||||
&& (!this.postsService.isLoggedIn || this.postsService.permissions.includes('advanced_download'));
|
&& this.postsService.hasPermission('advanced_download');
|
||||||
this.useDefaultDownloadingAgent = this.postsService.config['Advanced']['use_default_downloading_agent'];
|
this.useDefaultDownloadingAgent = this.postsService.config['Advanced']['use_default_downloading_agent'];
|
||||||
this.customDownloadingAgent = this.postsService.config['Advanced']['custom_downloading_agent'];
|
this.customDownloadingAgent = this.postsService.config['Advanced']['custom_downloading_agent'];
|
||||||
|
|
||||||
@@ -951,8 +951,6 @@ export class MainComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reloadRecentVideos() {
|
reloadRecentVideos() {
|
||||||
if (this.recentVideos) {
|
this.postsService.files_changed.next(true);
|
||||||
this.recentVideos.getAllFiles();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ export class PostsService implements CanActivate {
|
|||||||
settings_changed = new BehaviorSubject<boolean>(false);
|
settings_changed = new BehaviorSubject<boolean>(false);
|
||||||
open_create_default_admin_dialog = new BehaviorSubject<boolean>(false);
|
open_create_default_admin_dialog = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
files_changed = new BehaviorSubject<boolean>(false);
|
||||||
|
playlists_changed = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
// app status
|
// app status
|
||||||
initialized = false;
|
initialized = false;
|
||||||
|
|
||||||
@@ -192,8 +195,8 @@ export class PostsService implements CanActivate {
|
|||||||
return this.http.post(this.path + 'transferDB', {local_to_remote: local_to_remote}, this.httpOptions);
|
return this.http.post(this.path + 'transferDB', {local_to_remote: local_to_remote}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
testConnectionString() {
|
testConnectionString(connection_string) {
|
||||||
return this.http.post(this.path + 'testConnectionString', {}, this.httpOptions);
|
return this.http.post(this.path + 'testConnectionString', {connection_string: connection_string}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
killAllDownloads() {
|
killAllDownloads() {
|
||||||
@@ -334,18 +337,28 @@ export class PostsService implements CanActivate {
|
|||||||
include_file_metadata: include_file_metadata}, this.httpOptions);
|
include_file_metadata: include_file_metadata}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPlaylists() {
|
||||||
|
return this.http.post(this.path + 'getPlaylists', {}, this.httpOptions);
|
||||||
|
}
|
||||||
|
|
||||||
updatePlaylist(playlist) {
|
updatePlaylist(playlist) {
|
||||||
return this.http.post(this.path + 'updatePlaylist', {playlist: playlist}, this.httpOptions);
|
return this.http.post(this.path + 'updatePlaylist', {playlist: playlist}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlaylistFiles(playlistID, fileNames, type) {
|
updatePlaylistFiles(playlist_id, fileNames, type) {
|
||||||
return this.http.post(this.path + 'updatePlaylistFiles', {playlistID: playlistID,
|
return this.http.post(this.path + 'updatePlaylistFiles', {playlist_id: playlist_id,
|
||||||
fileNames: fileNames,
|
fileNames: fileNames,
|
||||||
type: type}, this.httpOptions);
|
type: type}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
removePlaylist(playlistID, type) {
|
addFileToPlaylist(playlist_id, file_uid) {
|
||||||
return this.http.post(this.path + 'deletePlaylist', {playlistID: playlistID, type: type}, this.httpOptions);
|
return this.http.post(this.path + 'addFileToPlaylist', {playlist_id: playlist_id,
|
||||||
|
file_uid: file_uid},
|
||||||
|
this.httpOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
removePlaylist(playlist_id, type) {
|
||||||
|
return this.http.post(this.path + 'deletePlaylist', {playlist_id: playlist_id, type: type}, this.httpOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// categories
|
// categories
|
||||||
@@ -498,6 +511,12 @@ export class PostsService implements CanActivate {
|
|||||||
this.resetHttpParams();
|
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
|
// user methods
|
||||||
register(username, password) {
|
register(username, password) {
|
||||||
const call = this.http.post(this.path + 'auth/register', {userid: username,
|
const call = this.http.post(this.path + 'auth/register', {userid: username,
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<h4 i18n="Settings title" mat-dialog-title>Settings</h4>
|
<h4 class="settings-title" i18n="Settings title">Settings</h4>
|
||||||
<!-- <ng-container i18n="Allow subscriptions setting"></ng-container> -->
|
<!-- <ng-container i18n="Allow subscriptions setting"></ng-container> -->
|
||||||
<mat-dialog-content>
|
|
||||||
|
|
||||||
<!-- Language
|
<!-- Language
|
||||||
<div style="margin-bottom: 10px;">
|
<div style="margin-bottom: 10px;">
|
||||||
|
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<mat-tab-group>
|
<mat-tab-group style="height: 76vh" mat-align-tabs="center">
|
||||||
<!-- Server -->
|
<!-- Server -->
|
||||||
<mat-tab label="Main" i18n-label="Main settings label">
|
<mat-tab label="Main" i18n-label="Main settings label">
|
||||||
<ng-template matTabContent style="padding: 15px;">
|
<ng-template matTabContent style="padding: 15px;">
|
||||||
@@ -301,7 +300,7 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<div class="test-connection-div">
|
<div class="test-connection-div">
|
||||||
<button (click)="testConnectionString()" [disabled]="testing_connection_string" mat-flat-button color="accent"><ng-container i18n="Test connection string button">Test connection string</ng-container></button>
|
<button (click)="testConnectionString(new_config['Database']['mongodb_connection_string'])" [disabled]="testing_connection_string" mat-flat-button color="accent"><ng-container i18n="Test connection string button">Test connection string</ng-container></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="transfer-db-div">
|
<div class="transfer-db-div">
|
||||||
@@ -391,7 +390,7 @@
|
|||||||
<app-updater></app-updater>
|
<app-updater></app-updater>
|
||||||
</div>
|
</div>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<div *ngIf="new_config" class="container">
|
<div *ngIf="new_config" class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 mt-4">
|
<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>
|
<button (click)="restartServer()" mat-stroked-button color="warn"><ng-container i18n="Restart server button">Restart server</ng-container></button>
|
||||||
@@ -401,8 +400,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode" label="Users" i18n-label="Users settings label">
|
<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>
|
<div>
|
||||||
<mat-checkbox color="accent" [(ngModel)]="new_config['Users']['allow_registration']"><ng-container i18n="Allow registration setting">Allow user registration</ng-container></mat-checkbox>
|
<mat-checkbox color="accent" [(ngModel)]="new_config['Users']['allow_registration']"><ng-container i18n="Allow registration setting">Allow user registration</ng-container></mat-checkbox>
|
||||||
</div>
|
</div>
|
||||||
@@ -446,25 +444,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
</div>
|
</div>
|
||||||
<app-modify-users></app-modify-users>
|
<app-modify-users *ngIf="new_config"></app-modify-users>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab *ngIf="postsService.config" label="Logs" i18n-label="Logs settings label">
|
<mat-tab *ngIf="postsService.config" label="Logs" i18n-label="Logs settings label">
|
||||||
<ng-template matTabContent>
|
<ng-template matTabContent>
|
||||||
<div style="margin-left: 48px; margin-top: 24px; height: 340px">
|
<div style="margin-top: 15px; height: 84%;">
|
||||||
<app-logs-viewer></app-logs-viewer>
|
<app-logs-viewer></app-logs-viewer>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
</mat-dialog-content>
|
|
||||||
|
|
||||||
<mat-dialog-actions>
|
<div class="action-buttons">
|
||||||
<div style="margin-bottom: 10px;">
|
<button style="margin-left: 10px; height: 37.3px" color="accent" (click)="saveSettings()" [disabled]="settingsSame()" mat-raised-button><mat-icon>done</mat-icon>
|
||||||
<button color="accent" (click)="saveSettings()" [disabled]="settingsSame()" mat-raised-button><mat-icon>done</mat-icon>
|
<ng-container i18n="Settings save button">Save</ng-container>
|
||||||
<ng-container i18n="Settings save button">Save</ng-container>
|
</button>
|
||||||
</button>
|
<button style="margin-left: 10px;" mat-flat-button (click)="cancelSettings()" [disabled]="settingsSame()"><mat-icon>cancel</mat-icon>
|
||||||
<button mat-flat-button [mat-dialog-close]="false"><mat-icon>cancel</mat-icon>
|
<span i18n="Settings cancel button">Cancel</span>
|
||||||
<span i18n="Settings cancel and close button">{settingsAreTheSame + "", select, true {Close} false {Cancel} other {otha}}</span>
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
|
||||||
</mat-dialog-actions>
|
|
||||||
|
|||||||
@@ -2,6 +2,15 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-title {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-tab-body {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.ext-divider {
|
.ext-divider {
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
}
|
}
|
||||||
@@ -91,3 +100,8 @@
|
|||||||
.transfer-db-div {
|
.transfer-db-div {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 15px;
|
||||||
|
}
|
||||||
@@ -51,8 +51,17 @@ export class SettingsComponent implements OnInit {
|
|||||||
private dialog: MatDialog) { }
|
private dialog: MatDialog) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.getConfig();
|
if (this.postsService.initialized) {
|
||||||
this.getDBInfo();
|
this.getConfig();
|
||||||
|
this.getDBInfo();
|
||||||
|
} else {
|
||||||
|
this.postsService.service_initialized.subscribe(init => {
|
||||||
|
if (init) {
|
||||||
|
this.getConfig();
|
||||||
|
this.getDBInfo();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.generated_bookmarklet_code = this.sanitizer.bypassSecurityTrustUrl(this.generateBookmarkletCode());
|
this.generated_bookmarklet_code = this.sanitizer.bypassSecurityTrustUrl(this.generateBookmarkletCode());
|
||||||
|
|
||||||
@@ -85,6 +94,10 @@ export class SettingsComponent implements OnInit {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelSettings() {
|
||||||
|
this.new_config = JSON.parse(JSON.stringify(this.initial_config));
|
||||||
|
}
|
||||||
|
|
||||||
dropCategory(event: CdkDragDrop<string[]>) {
|
dropCategory(event: CdkDragDrop<string[]>) {
|
||||||
moveItemInArray(this.postsService.categories, event.previousIndex, event.currentIndex);
|
moveItemInArray(this.postsService.categories, event.previousIndex, event.currentIndex);
|
||||||
this.postsService.updateCategories(this.postsService.categories).subscribe(res => {
|
this.postsService.updateCategories(this.postsService.categories).subscribe(res => {
|
||||||
@@ -307,9 +320,9 @@ export class SettingsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
testConnectionString() {
|
testConnectionString(connection_string) {
|
||||||
this.testing_connection_string = true;
|
this.testing_connection_string = true;
|
||||||
this.postsService.testConnectionString().subscribe(res => {
|
this.postsService.testConnectionString(connection_string).subscribe(res => {
|
||||||
this.testing_connection_string = false;
|
this.testing_connection_string = false;
|
||||||
if (res['success']) {
|
if (res['success']) {
|
||||||
this.postsService.openSnackBar('Connection successful!');
|
this.postsService.openSnackBar('Connection successful!');
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
|
|||||||
@include angular-material-theme($dark-theme);
|
@include angular-material-theme($dark-theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-stroked-button, .mat-raised-button, .mat-flat-button {
|
||||||
|
border-radius: 24px !important
|
||||||
|
}
|
||||||
|
|
||||||
// Light theme
|
// Light theme
|
||||||
$light-primary: mat-palette($mat-grey, 200, 500, 300);
|
$light-primary: mat-palette($mat-grey, 200, 500, 300);
|
||||||
$light-accent: mat-palette($mat-brown, 200);
|
$light-accent: mat-palette($mat-brown, 200);
|
||||||
@@ -50,7 +54,7 @@ $light-warn: mat-palette($mat-deep-orange, 200);
|
|||||||
$light-theme: mat-light-theme($light-primary, $light-accent, $light-warn);
|
$light-theme: mat-light-theme($light-primary, $light-accent, $light-warn);
|
||||||
|
|
||||||
.light-theme {
|
.light-theme {
|
||||||
@include angular-material-theme($light-theme)
|
@include angular-material-theme($light-theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-outline {
|
.no-outline {
|
||||||
|
|||||||
Reference in New Issue
Block a user