From 4e6d68d9e6b276e605db7cd4cd8267f9def95628 Mon Sep 17 00:00:00 2001 From: Tzahi12345 Date: Sat, 2 May 2020 20:36:30 -0400 Subject: [PATCH] Updated video playing/sharing logic to support sharing of playlists in multi user mode and when multi user mode is disabled Fixed bug that caused normal archive to be used in multi-user mode Updated login logic when username is not found or user file is missing Fixed bug that prevented playlist sharing from working Added ability to use timestamps when sharing videos --- backend/app.js | 18 ++++++--- backend/authentication/auth.js | 12 ++++-- backend/subscriptions.js | 2 +- .../share-media-dialog.component.html | 6 +++ .../share-media-dialog.component.ts | 26 ++++++++++++- src/app/main/main.component.ts | 4 +- src/app/player/player.component.ts | 39 +++++++++++++------ src/app/posts.services.ts | 6 +-- 8 files changed, 84 insertions(+), 29 deletions(-) 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) {