diff --git a/core/archiver.go b/core/archiver.go index 6e2a48844b0..cc1621de4c6 100644 --- a/core/archiver.go +++ b/core/archiver.go @@ -91,8 +91,10 @@ func (a *archiver) albumFilename(mf model.MediaFile, format string, isMultDisc b func (a *archiver) ZipShare(ctx context.Context, id string, out io.Writer) error { s, err := a.shares.Load(ctx, id) + if !s.Downloadable { + return model.ErrNotAuthorized + } if err != nil { - log.Error(ctx, "Error loading mediafiles from share", "id", id, err) return err } log.Debug(ctx, "Zipping share", "name", s.ID, "format", s.Format, "bitrate", s.MaxBitRate, "numTracks", len(s.Tracks)) diff --git a/server/public/handle_shares.go b/server/public/handle_shares.go index 89dde771393..aa09cfa17a8 100644 --- a/server/public/handle_shares.go +++ b/server/public/handle_shares.go @@ -45,6 +45,9 @@ func checkShareError(ctx context.Context, w http.ResponseWriter, err error, id s case errors.Is(err, model.ErrNotFound): log.Error(ctx, "Share not found", "id", id, err) http.Error(w, "Share not found", http.StatusNotFound) + case errors.Is(err, model.ErrNotAuthorized): + log.Error(ctx, "Share is not downloadable", "id", id, err) + http.Error(w, "This share is not downloadable", http.StatusForbidden) case err != nil: log.Error(ctx, "Error retrieving share", "id", id, err) http.Error(w, "Error retrieving share", http.StatusInternalServerError) diff --git a/server/serve_index.go b/server/serve_index.go index 192b8257ed5..cb4271b4e71 100644 --- a/server/serve_index.go +++ b/server/serve_index.go @@ -122,8 +122,10 @@ func getIndexTemplate(r *http.Request, fs fs.FS) (*template.Template, error) { } type shareData struct { - Description string `json:"description"` - Tracks []shareTrack `json:"tracks"` + ID string `json:"id"` + Description string `json:"description"` + Downloadable bool `json:"downloadable"` + Tracks []shareTrack `json:"tracks"` } type shareTrack struct { @@ -141,7 +143,9 @@ func addShareData(r *http.Request, data map[string]interface{}, shareInfo *model return } sd := shareData{ - Description: shareInfo.Description, + ID: shareInfo.ID, + Description: shareInfo.Description, + Downloadable: shareInfo.Downloadable, } sd.Tracks = slice.Map(shareInfo.Tracks, func(mf model.MediaFile) shareTrack { return shareTrack{ diff --git a/ui/src/App.js b/ui/src/App.js index 6a876afe824..6d185334949 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -37,7 +37,7 @@ import config, { shareInfo } from './config' import { setDispatch, startEventStream, stopEventStream } from './eventStream' import { keyMap } from './hotkeys' import useChangeThemeColor from './useChangeThemeColor' -import SharePlayer from './SharePlayer' +import SharePlayer from './share/SharePlayer' const history = createHashHistory() diff --git a/ui/src/SharePlayer.js b/ui/src/share/SharePlayer.js similarity index 73% rename from ui/src/SharePlayer.js rename to ui/src/share/SharePlayer.js index 9410fbc23f1..387b6fcd4e6 100644 --- a/ui/src/SharePlayer.js +++ b/ui/src/share/SharePlayer.js @@ -1,6 +1,6 @@ import ReactJkMusicPlayer from 'navidrome-music-player' -import { shareInfo } from './config' -import { shareCoverUrl, shareStreamUrl } from './utils' +import { shareInfo } from '../config' +import { shareCoverUrl, shareDownloadUrl, shareStreamUrl } from '../utils' import { makeStyles } from '@material-ui/core/styles' @@ -34,12 +34,17 @@ const SharePlayer = () => { duration: s.duration, } }) + const onBeforeAudioDownload = () => { + return Promise.resolve({ + src: shareDownloadUrl(shareInfo?.id), + }) + } const options = { audioLists: list, mode: 'full', toggleMode: false, mobileMediaQuery: '', - showDownload: false, + showDownload: shareInfo?.downloadable, showReload: false, showMediaSession: true, theme: 'auto', @@ -49,7 +54,13 @@ const SharePlayer = () => { spaceBar: true, volumeFade: { fadeIn: 200, fadeOut: 200 }, } - return + return ( + + ) } export default SharePlayer diff --git a/ui/src/utils/urls.js b/ui/src/utils/urls.js index aba22f47258..fe6c19c6f34 100644 --- a/ui/src/utils/urls.js +++ b/ui/src/utils/urls.js @@ -19,6 +19,10 @@ export const shareStreamUrl = (id) => { return baseUrl(config.publicBaseUrl + '/s/' + id) } +export const shareDownloadUrl = (id) => { + return baseUrl(config.publicBaseUrl + '/d/' + id) +} + export const shareCoverUrl = (id) => { return baseUrl(config.publicBaseUrl + '/img/' + id + '?size=300') }