diff --git a/backend/app.js b/backend/app.js
index 0dc80bf..87c7b17 100644
--- a/backend/app.js
+++ b/backend/app.js
@@ -1762,8 +1762,14 @@ const optionalJwt = function (req, res, next) {
const uuid = using_body ? req.body.uuid : req.query.uuid;
const uid = using_body ? req.body.uid : req.query.uid;
const type = using_body ? req.body.type : req.query.type;
- const is_shared = auth_api.getUserVideo(uuid, uid, type, true);
- if (is_shared) return next();
+ const is_shared = !req.query.id ? auth_api.getUserVideo(uuid, uid, type, true) : auth_api.getUserPlaylist(uuid, req.query.id, null, true);
+ if (is_shared) {
+ req.can_watch = true;
+ return next();
+ } else {
+ res.sendStatus(401);
+ return;
+ }
} else if (multiUserMode && !(req.path.includes('/api/auth/register') && !req.query.jwt)) { // registration should get passed through
if (!req.query.jwt) {
res.sendStatus(401);
@@ -1983,7 +1989,6 @@ app.post('/api/enableSharing', optionalJwt, function(req, res) {
if (req.isAuthenticated()) {
// if multi user mode, use this method instead
success = auth_api.changeSharingMode(req.user.uid, uid, type, is_playlist, true);
- console.log(success);
res.send({success: success});
return;
}
@@ -2262,11 +2267,12 @@ app.post('/api/createPlaylist', optionalJwt, async (req, res) => {
app.post('/api/getPlaylist', optionalJwt, async (req, res) => {
let playlistID = req.body.playlistID;
let type = req.body.type;
+ let uuid = req.body.uuid;
let playlist = null;
if (req.isAuthenticated()) {
- playlist = auth_api.getUserPlaylist(req.user.uid, playlistID, type);
+ playlist = auth_api.getUserPlaylist(uuid ? uuid : req.user.uid, playlistID, type);
type = playlist.type;
} else {
if (!type) {
@@ -2563,13 +2569,13 @@ app.get('/api/video/:id', optionalJwt, function(req , res){
let optionalParams = url_api.parse(req.url,true).query;
let id = decodeURIComponent(req.params.id);
let file_path = videoFolderPath + id + '.mp4';
- if (req.isAuthenticated()) {
+ if (req.isAuthenticated() || req.can_watch) {
let usersFileFolder = config_api.getConfigItem('ytdl_users_base_path');
if (optionalParams['subName']) {
const isPlaylist = optionalParams['subPlaylist'];
file_path = path.join(usersFileFolder, req.user.uid, 'subscriptions', (isPlaylist === 'true' ? 'playlists/' : 'channels/'),optionalParams['subName'], id + '.mp4')
} else {
- file_path = path.join(usersFileFolder, req.user.uid, 'video', id + '.mp4');
+ file_path = path.join(usersFileFolder, req.query.uuid ? req.query.uuid : req.user.uid, 'video', id + '.mp4');
}
} else if (optionalParams['subName']) {
let basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
diff --git a/backend/authentication/auth.js b/backend/authentication/auth.js
index aabb978..5098738 100644
--- a/backend/authentication/auth.js
+++ b/backend/authentication/auth.js
@@ -157,7 +157,7 @@ exports.passport.use(new LocalStrategy({
passwordField: 'password'},
function(username, password, done) {
const user = users_db.get('users').find({name: username}).value();
- if (!user) { console.log('user not found'); return done(null, false); }
+ if (!user) { logger.error(`User ${username} not found`); return done(null, false); }
if (user) {
return done(null, bcrypt.compareSync(password, user.passhash) ? user : false);
}
@@ -346,7 +346,7 @@ exports.getUserPlaylists = function(user_uid, type) {
return user['playlists'][type];
}
-exports.getUserPlaylist = function(user_uid, playlistID, type) {
+exports.getUserPlaylist = function(user_uid, playlistID, type, requireSharing = false) {
let playlist = null;
if (!type) {
playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.audio`).find({id: playlistID}).value();
@@ -358,6 +358,10 @@ exports.getUserPlaylist = function(user_uid, playlistID, type) {
}
}
if (!playlist) playlist = users_db.get('users').find({uid: user_uid}).get(`playlists.${type}`).find({id: playlistID}).value();
+
+ // prevent unauthorized users from accessing the file info
+ if (requireSharing && !playlist['sharingEnabled']) playlist = null;
+
return playlist;
}
@@ -434,7 +438,7 @@ exports.deleteUserFile = function(user_uid, file_uid, type, blacklistMode = fals
success = true;
} else {
success = false;
- console.log('file does not exist!');
+ logger.warn(`User file ${file_uid} does not exist!`);
}
return success;
@@ -444,7 +448,7 @@ exports.changeSharingMode = function(user_uid, file_uid, type, is_playlist, enab
let success = false;
const user_db_obj = users_db.get('users').find({uid: user_uid});
if (user_db_obj.value()) {
- const file_db_obj = is_playlist ? user_db_obj.get(`playlists.${type}`).find({uid: file_uid}) : user_db_obj.get(`files.${type}`).find({uid: file_uid});
+ const file_db_obj = is_playlist ? user_db_obj.get(`playlists.${type}`).find({id: file_uid}) : user_db_obj.get(`files.${type}`).find({uid: file_uid});
if (file_db_obj.value()) {
success = true;
file_db_obj.assign({sharingEnabled: enabled}).write();
diff --git a/backend/subscriptions.js b/backend/subscriptions.js
index cccd224..91cc7a6 100644
--- a/backend/subscriptions.js
+++ b/backend/subscriptions.js
@@ -49,7 +49,7 @@ async function subscribe(sub, user_uid = null) {
else
db.get('subscriptions').push(sub).write();
- let success = await getSubscriptionInfo(sub);
+ let success = await getSubscriptionInfo(sub, user_uid);
result_obj.success = success;
result_obj.sub = sub;
getVideosForSub(sub, user_uid);
diff --git a/src/app/dialogs/share-media-dialog/share-media-dialog.component.html b/src/app/dialogs/share-media-dialog/share-media-dialog.component.html
index 02dff40..1609af0 100644
--- a/src/app/dialogs/share-media-dialog/share-media-dialog.component.html
+++ b/src/app/dialogs/share-media-dialog/share-media-dialog.component.html
@@ -9,6 +9,12 @@
Enable sharing
+
+ Use timestamp
+
+
+
+
diff --git a/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts b/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts
index d6771f5..137e8fc 100644
--- a/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts
+++ b/src/app/dialogs/share-media-dialog/share-media-dialog.component.ts
@@ -15,8 +15,11 @@ export class ShareMediaDialogComponent implements OnInit {
uid = null;
uuid = null;
share_url = null;
+ default_share_url = null;
sharing_enabled = null;
is_playlist = null;
+ current_timestamp = null
+ timestamp_enabled = false;
constructor(@Inject(MAT_DIALOG_DATA) public data: any, public router: Router, private snackBar: MatSnackBar,
private postsService: PostsService) { }
@@ -28,15 +31,34 @@ export class ShareMediaDialogComponent implements OnInit {
this.uuid = this.data.uuid;
this.sharing_enabled = this.data.sharing_enabled;
this.is_playlist = this.data.is_playlist;
+ this.current_timestamp = (this.data.current_timestamp / 1000).toFixed(2);
const arg = (this.is_playlist ? ';id=' : ';uid=');
- this.share_url = window.location.href.split(';')[0] + arg + this.uid;
+ this.default_share_url = window.location.href.split(';')[0] + arg + this.uid;
if (this.uuid) {
- this.share_url += ';uuid=' + this.uuid;
+ this.default_share_url += ';uuid=' + this.uuid;
}
+ this.share_url = this.default_share_url;
}
}
+ timestampInputChanged(change) {
+ if (!this.timestamp_enabled) {
+ this.share_url = this.default_share_url;
+ return;
+ }
+ const new_val = change.target.value;
+ if (new_val > 0) {
+ this.share_url = this.default_share_url + ';timestamp=' + new_val;
+ } else {
+ this.share_url = this.default_share_url;
+ }
+ }
+
+ useTimestampChanged() {
+ this.timestampInputChanged({target: {value: this.current_timestamp}})
+ }
+
copiedToClipboard() {
this.openSnackBar('Copied to clipboard!');
}
diff --git a/src/app/main/main.component.ts b/src/app/main/main.component.ts
index c7b20ec..30b79a8 100644
--- a/src/app/main/main.component.ts
+++ b/src/app/main/main.component.ts
@@ -491,7 +491,7 @@ export class MainComponent implements OnInit {
if (is_playlist) {
this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'audio'}]);
} else {
- this.router.navigate(['/player', {fileNames: name, type: 'audio', uid: uid}]);
+ this.router.navigate(['/player', {type: 'audio', uid: uid}]);
}
}
}
@@ -528,7 +528,7 @@ export class MainComponent implements OnInit {
if (is_playlist) {
this.router.navigate(['/player', {fileNames: name.join('|nvr|'), type: 'video'}]);
} else {
- this.router.navigate(['/player', {fileNames: name, type: 'video', uid: uid}]);
+ this.router.navigate(['/player', {type: 'video', uid: uid}]);
}
}
}
diff --git a/src/app/player/player.component.ts b/src/app/player/player.component.ts
index 2cad9c6..9b0c316 100644
--- a/src/app/player/player.component.ts
+++ b/src/app/player/player.component.ts
@@ -40,6 +40,7 @@ export class PlayerComponent implements OnInit {
subscriptionName = null;
subPlaylist = null;
uuid = null; // used for sharing in multi-user mode, uuid is the user that downloaded the video
+ timestamp = null;
is_shared = false;
@@ -77,6 +78,7 @@ export class PlayerComponent implements OnInit {
this.url = this.route.snapshot.paramMap.get('url');
this.name = this.route.snapshot.paramMap.get('name');
this.uuid = this.route.snapshot.paramMap.get('uuid');
+ this.timestamp = this.route.snapshot.paramMap.get('timestamp');
// loading config
if (this.postsService.initialized) {
@@ -102,7 +104,7 @@ export class PlayerComponent implements OnInit {
this.subscriptionFolderPath = this.postsService.config['Subscriptions']['subscriptions_base_path'];
this.fileNames = this.route.snapshot.paramMap.get('fileNames') ? this.route.snapshot.paramMap.get('fileNames').split('|nvr|') : null;
- if (!this.fileNames) {
+ if (!this.fileNames && !this.type) {
this.is_shared = true;
}
@@ -149,7 +151,7 @@ export class PlayerComponent implements OnInit {
if (!already_has_filenames) { this.parseFileNames(); }
}
}
- if (this.db_file['sharingEnabled']) {
+ if (this.db_file['sharingEnabled'] || !this.uuid) {
this.show_player = true;
} else if (!already_has_filenames) {
this.openSnackBar('Error: Sharing has been disabled for this video!', 'Dismiss');
@@ -158,12 +160,18 @@ export class PlayerComponent implements OnInit {
}
getPlaylistFiles() {
- this.postsService.getPlaylist(this.id, null).subscribe(res => {
- this.db_playlist = res['playlist'];
- this.fileNames = this.db_playlist['fileNames'];
- this.type = res['type'];
- this.show_player = true;
- this.parseFileNames();
+ this.postsService.getPlaylist(this.id, null, this.uuid).subscribe(res => {
+ if (res['playlist']) {
+ this.db_playlist = res['playlist'];
+ this.fileNames = this.db_playlist['fileNames'];
+ this.type = res['type'];
+ this.show_player = true;
+ this.parseFileNames();
+ } else {
+ this.openSnackBar('Failed to load playlist!', '');
+ }
+ }, err => {
+ this.openSnackBar('Failed to load playlist!', '');
});
}
@@ -196,11 +204,15 @@ export class PlayerComponent implements OnInit {
}
// adds user token if in multi-user-mode
+ const uuid_str = this.uuid ? `&uuid=${this.uuid}` : '';
+ const uid_str = (this.id || !this.db_file) ? '' : `&uid=${this.db_file.uid}`;
+ const type_str = (this.id || !this.db_file) ? '' : `&type=${this.db_file.type}`
+ const id_str = this.id ? `&id=${this.id}` : '';
if (this.postsService.isLoggedIn) {
fullLocation += (this.subscriptionName ? '&' : '?') + `jwt=${this.postsService.token}`;
- if (this.is_shared) { fullLocation += `&uuid=${this.uuid}&uid=${this.db_file.uid}&type=${this.db_file.type}`; }
+ if (this.is_shared) { fullLocation += `${uuid_str}${uid_str}${type_str}${id_str}`; }
} else if (this.is_shared) {
- fullLocation += (this.subscriptionName ? '&' : '?') + `uuid=${this.uuid}&uid=${this.db_file.uid}&type=${this.db_file.type}`;
+ fullLocation += (this.subscriptionName ? '&' : '?') + `test=test${uuid_str}${uid_str}${type_str}${id_str}`;
}
// if it has a slash (meaning it's in a directory), only get the file name for the label
let label = null;
@@ -228,6 +240,10 @@ export class PlayerComponent implements OnInit {
this.api.getDefaultMedia().subscriptions.loadedMetadata.subscribe(this.playVideo.bind(this));
this.api.getDefaultMedia().subscriptions.ended.subscribe(this.nextVideo.bind(this));
+
+ if (this.timestamp) {
+ this.api.seekTime(+this.timestamp);
+ }
}
nextVideo() {
@@ -381,7 +397,8 @@ export class PlayerComponent implements OnInit {
type: this.type,
sharing_enabled: this.id ? this.db_playlist.sharingEnabled : this.db_file.sharingEnabled,
is_playlist: !!this.id,
- uuid: this.postsService.isLoggedIn ? this.postsService.user.uid : null
+ uuid: this.postsService.isLoggedIn ? this.postsService.user.uid : null,
+ current_timestamp: this.api.time.current
},
width: '60vw'
});
diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts
index 84c8edc..b793345 100644
--- a/src/app/posts.services.ts
+++ b/src/app/posts.services.ts
@@ -258,9 +258,9 @@ export class PostsService implements CanActivate {
thumbnailURL: thumbnailURL}, this.httpOptions);
}
- getPlaylist(playlistID, type) {
+ getPlaylist(playlistID, type, uuid = null) {
return this.http.post(this.path + 'getPlaylist', {playlistID: playlistID,
- type: type}, this.httpOptions);
+ type: type, uuid: uuid}, this.httpOptions);
}
updatePlaylist(playlistID, fileNames, type) {
@@ -335,6 +335,7 @@ export class PostsService implements CanActivate {
this.permissions = permissions;
this.available_permissions = available_permissions;
this.token = token;
+ this.setInitialized();
localStorage.setItem('jwt_token', this.token);
@@ -365,7 +366,6 @@ export class PostsService implements CanActivate {
call.subscribe(res => {
if (res['token']) {
this.afterLogin(res['user'], res['token'], res['permissions'], res['available_permissions']);
- this.setInitialized();
}
}, err => {
if (err.status === 401) {