diff --git a/docs/manual.md b/docs/manual.md index 05e1cce..48332f6 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -716,16 +716,23 @@ video player in your configuration file respectively. See the [configuration reference page](configuration.md#integration) on how to set up integration with your media players. -``` -enbas show --type media --from status --status-id 01J0N11V4V7PWH0DDRAVT7TCFK --attachment-id 01J0N0RQSJ7CFGKHA30F7GBQXT -``` +- View a specific media attachment from a specific status + ``` + enbas show --type media --from status --status-id 01J0N11V4V7PWH0DDRAVT7TCFK --attachment-id 01J0N0RQSJ7CFGKHA30F7GBQXT + ``` +- View all image attachments from a specific status + ``` + enbas show --type media --from status --status-id 01J0N11V4V7PWH0DDRAVT7TCFK --all-images + ``` | flag | type | required | description | default | |------|------|----------|-------------|---------| | `type` | string | true | The resource you want to view.
Here this should be `media`. | | | `from` | string | true | The resource you want to view the media from.
Here this should be `status`. | | | `status-id` | string | true | The ID of the status that you want to view the media from. | | -| `attachment-id` | string | true | The ID of the media attachment to download and view.
Use this flag multiple times to specify multiple media attachments. | | +| `attachment-id` | string | false | The ID of the media attachment to download and view.
Use this flag multiple times to specify multiple media attachments. | | +| `all-images` | boolean | false | Set to `true` to show all images from the status. | false | +| `all-videos` | boolean | false | Set to `true` to show all videos from the status. | false | ## Bookmarks diff --git a/internal/executor/errors.go b/internal/executor/errors.go index 92a99e3..f32927c 100644 --- a/internal/executor/errors.go +++ b/internal/executor/errors.go @@ -111,11 +111,3 @@ type NotFollowingError struct { func (e NotFollowingError) Error() string { return "you are not following " + e.Account } - -type UnknownMediaAttachmentError struct { - AttachmentID string -} - -func (e UnknownMediaAttachmentError) Error() string { - return "unknown media attachment '" + e.AttachmentID + "'" -} diff --git a/internal/executor/flags.go b/internal/executor/flags.go index 28eba46..ceebaa4 100644 --- a/internal/executor/flags.go +++ b/internal/executor/flags.go @@ -10,6 +10,8 @@ import ( const ( flagAddPoll = "add-poll" flagAccountName = "account-name" + flagAllImages = "all-images" + flagAllVideos = "all-videos" flagAttachmentID = "attachment-id" flagBrowser = "browser" flagContentType = "content-type" diff --git a/internal/executor/show.go b/internal/executor/show.go index 09e740d..ea1244e 100644 --- a/internal/executor/show.go +++ b/internal/executor/show.go @@ -4,10 +4,10 @@ import ( "flag" "fmt" "path/filepath" - "strings" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/config" + "codeflow.dananglin.me.uk/apollo/enbas/internal/media" "codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" @@ -28,6 +28,8 @@ type ShowExecutor struct { showUserPreferences bool showStatuses bool skipAccountRelationship bool + getAllImages bool + getAllVideos bool resourceType string accountName string statusID string @@ -58,6 +60,8 @@ func NewShowExecutor(printer *printer.Printer, config *config.Config, name, summ showExe.BoolVar(&showExe.onlyPinned, flagOnlyPinned, false, "Set to true to show only the account's pinned statuses") showExe.BoolVar(&showExe.onlyMedia, flagOnlyMedia, false, "Set to true to show only the statuses with media attachments") showExe.BoolVar(&showExe.onlyPublic, flagOnlyPublic, false, "Set to true to show only the account's public posts") + showExe.BoolVar(&showExe.getAllImages, flagAllImages, false, "Set to true to show all images from a status") + showExe.BoolVar(&showExe.getAllVideos, flagAllVideos, false, "Set to true to show all videos from a status") showExe.StringVar(&showExe.resourceType, flagType, "", "Specify the type of resource to display") showExe.StringVar(&showExe.accountName, flagAccountName, "", "Specify the account name in full (username@domain)") showExe.StringVar(&showExe.statusID, flagStatusID, "", "Specify the ID of the status to display") @@ -525,10 +529,6 @@ func (s *ShowExecutor) showMedia(gtsClient *client.Client) error { } func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error { - if len(s.attachmentIDs) == 0 { - return FlagNotSetError{flagText: flagAttachmentID} - } - if s.statusID == "" { return FlagNotSetError{flagText: flagStatusID} } @@ -547,65 +547,26 @@ func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error { return fmt.Errorf("unable to ensure the existence of the directory %q: %w", cacheDir, err) } - type media struct { - url string - mediaType string - } - - attachmentsHashMap := make(map[string]media) - imageFiles := make([]string, 0) - videoFiles := make([]string, 0) - - for _, statusAttachment := range status.MediaAttachments { - attachmentsHashMap[statusAttachment.ID] = media{ - url: statusAttachment.URL, - mediaType: statusAttachment.Type, - } - } - - for _, attachmentID := range s.attachmentIDs { - mediaObj, ok := attachmentsHashMap[attachmentID] - if !ok { - return UnknownMediaAttachmentError{AttachmentID: attachmentID} - } - - split := strings.Split(mediaObj.url, "/") - filename := split[len(split)-1] - filePath := filepath.Join(cacheDir, filename) - - fileExists, err := utilities.FileExists(filePath) - if err != nil { - return fmt.Errorf( - "unable to check if the media file is already downloaded for %s: %w", - attachmentID, - err, - ) - } - - if !fileExists { - if err := gtsClient.DownloadMedia(mediaObj.url, filePath); err != nil { - return fmt.Errorf( - "unable to download the media attachment for %s: %w", - attachmentID, - err, - ) - } - } - - switch mediaObj.mediaType { - case "image": - imageFiles = append(imageFiles, filePath) - case "video": - videoFiles = append(videoFiles, filePath) - } + mediaBundle := media.NewBundle( + cacheDir, + status.MediaAttachments, + s.getAllImages, + s.getAllVideos, + s.attachmentIDs, + ) + + if err := mediaBundle.Download(gtsClient); err != nil { + return fmt.Errorf("unable to download the media bundle: %w", err) } + imageFiles := mediaBundle.ImageFiles() if len(imageFiles) > 0 { if err := utilities.OpenMedia(s.config.Integrations.ImageViewer, imageFiles); err != nil { return fmt.Errorf("unable to open the image viewer: %w", err) } } + videoFiles := mediaBundle.VideoFiles() if len(videoFiles) > 0 { if err := utilities.OpenMedia(s.config.Integrations.VideoPlayer, videoFiles); err != nil { return fmt.Errorf("unable to open the video player: %w", err) diff --git a/internal/media/media.go b/internal/media/media.go new file mode 100644 index 0000000..92fd92e --- /dev/null +++ b/internal/media/media.go @@ -0,0 +1,170 @@ +package media + +import ( + "fmt" + "path/filepath" + "strings" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/model" + "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" +) + +const ( + mediaTypeImage string = "image" + mediaTypeVideo string = "video" +) + +type media struct { + source string + destination string + mediaType string +} + +func (m *media) download(gtsClient *client.Client) error { + fileExists, err := utilities.FileExists(m.destination) + if err != nil { + return fmt.Errorf( + "unable to determine if %s exists: %w", + m.destination, + err, + ) + } + + if fileExists { + return nil + } + + if err := gtsClient.DownloadMedia(m.source, m.destination); err != nil { + return fmt.Errorf( + "downloading %s -> %s failed: %w", + m.source, + m.destination, + err, + ) + } + + return nil +} + +func newMediaHashmap(cacheDir string, attachments []model.Attachment) map[string]media { + hashmap := make(map[string]media) + + for ind := range attachments { + hashmap[attachments[ind].ID] = media{ + source: attachments[ind].URL, + destination: mediaFilepath(cacheDir, attachments[ind].URL), + mediaType: attachments[ind].Type, + } + } + + return hashmap +} + +type Bundle struct { + images []media + videos []media +} + +func NewBundle( + cacheDir string, + attachments []model.Attachment, + getAllImages bool, + getAllVideos bool, + attachmentIDs []string, +) Bundle { + mediaHashmap := newMediaHashmap(cacheDir, attachments) + images := make([]media, 0) + videos := make([]media, 0) + + if !getAllImages && !getAllVideos && len(attachmentIDs) == 0 { + return Bundle{ + images: images, + videos: videos, + } + } + + if getAllImages || getAllVideos { + if getAllImages { + for _, m := range mediaHashmap { + if m.mediaType == mediaTypeImage { + images = append(images, m) + } + } + } + + if getAllVideos { + for _, m := range mediaHashmap { + if m.mediaType == mediaTypeVideo { + videos = append(videos, m) + } + } + } + + return Bundle{ + images: images, + videos: videos, + } + } + + for _, attachmentID := range attachmentIDs { + obj, ok := mediaHashmap[attachmentID] + if !ok { + continue + } + + switch obj.mediaType { + case mediaTypeImage: + images = append(images, obj) + case mediaTypeVideo: + videos = append(videos, obj) + } + } + + return Bundle{ + images: images, + videos: videos, + } +} + +func (m *Bundle) Download(gtsClient *client.Client) error { + for ind := range m.images { + if err := m.images[ind].download(gtsClient); err != nil { + return fmt.Errorf("received an error trying to download the image files: %w", err) + } + } + + for ind := range m.videos { + if err := m.videos[ind].download(gtsClient); err != nil { + return fmt.Errorf("received an error trying to download the video files: %w", err) + } + } + + return nil +} + +func (m *Bundle) ImageFiles() []string { + filepaths := make([]string, len(m.images)) + + for ind := range m.images { + filepaths[ind] = m.images[ind].destination + } + + return filepaths +} + +func (m *Bundle) VideoFiles() []string { + filepaths := make([]string, len(m.videos)) + + for ind := range m.videos { + filepaths[ind] = m.videos[ind].destination + } + + return filepaths +} + +func mediaFilepath(cacheDir, mediaURL string) string { + split := strings.Split(mediaURL, "/") + + return filepath.Join(cacheDir, split[len(split)-1]) +}