diff --git a/internal/client/media.go b/internal/client/media.go new file mode 100644 index 0000000..3be293d --- /dev/null +++ b/internal/client/media.go @@ -0,0 +1,20 @@ +package client + +import ( + "fmt" + "net/http" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/model" +) + +func (g *Client) GetMediaAttachment(mediaAttachmentID string) (model.Attachment, error) { + url := g.Authentication.Instance + "/api/v1/media/" + mediaAttachmentID + + var attachment model.Attachment + + if err := g.sendRequest(http.MethodGet, url, nil, &attachment); err != nil { + return model.Attachment{}, fmt.Errorf("received an error after sending the request to get the media attachment: %w", err) + } + + return attachment, nil +} diff --git a/internal/executor/flags.go b/internal/executor/flags.go index ac1115f..cba9dd2 100644 --- a/internal/executor/flags.go +++ b/internal/executor/flags.go @@ -14,6 +14,7 @@ import ( const ( flagAddPoll = "add-poll" flagAccountName = "account-name" + flagAttachmentID = "attachment-id" flagBrowser = "browser" flagChoose = "choose" flagContentType = "content-type" diff --git a/internal/executor/resources.go b/internal/executor/resources.go index b0d336a..12b02f6 100644 --- a/internal/executor/resources.go +++ b/internal/executor/resources.go @@ -5,23 +5,25 @@ package executor const ( - resourceAccount = "account" - resourceBlocked = "blocked" - resourceBookmarks = "bookmarks" - resourceBoost = "boost" - resourceFollowers = "followers" - resourceFollowing = "following" - resourceFollowRequest = "follow-request" - resourceInstance = "instance" - resourceLike = "like" - resourceLiked = "liked" - resourceList = "list" - resourceMutedAccounts = "muted-accounts" - resourceNote = "note" - resourcePoll = "poll" - resourceStatus = "status" - resourceStar = "star" - resourceStarred = "starred" - resourceTimeline = "timeline" - resourceVote = "vote" + resourceAccount = "account" + resourceBlocked = "blocked" + resourceBookmarks = "bookmarks" + resourceBoost = "boost" + resourceFollowers = "followers" + resourceFollowing = "following" + resourceFollowRequest = "follow-request" + resourceInstance = "instance" + resourceLike = "like" + resourceLiked = "liked" + resourceList = "list" + resourceMedia = "media" + resourceMediaAttachment = "media-attachment" + resourceMutedAccounts = "muted-accounts" + resourceNote = "note" + resourcePoll = "poll" + resourceStatus = "status" + resourceStar = "star" + resourceStarred = "starred" + resourceTimeline = "timeline" + resourceVote = "vote" ) diff --git a/internal/executor/show.go b/internal/executor/show.go index 2bcbbe7..1c01162 100644 --- a/internal/executor/show.go +++ b/internal/executor/show.go @@ -30,6 +30,7 @@ type ShowExecutor struct { listID string tag string pollID string + attachmentID string limit int } @@ -52,6 +53,7 @@ func NewShowExecutor(printer *printer.Printer, configDir, name, summary string) showExe.StringVar(&showExe.listID, flagListID, "", "Specify the ID of the list to display") showExe.StringVar(&showExe.tag, flagTag, "", "Specify the name of the tag to use") showExe.StringVar(&showExe.pollID, flagPollID, "", "Specify the ID of the poll to display") + showExe.StringVar(&showExe.attachmentID, flagAttachmentID, "", "Specify the ID of the media attachment to display") showExe.IntVar(&showExe.limit, flagLimit, 20, "Specify the limit of items to display") showExe.Usage = commandUsageFunc(name, summary, showExe.FlagSet) @@ -65,20 +67,22 @@ func (s *ShowExecutor) Execute() error { } funcMap := map[string]func(*client.Client) error{ - resourceInstance: s.showInstance, - resourceAccount: s.showAccount, - resourceStatus: s.showStatus, - resourceTimeline: s.showTimeline, - resourceList: s.showList, - resourceFollowers: s.showFollowers, - resourceFollowing: s.showFollowing, - resourceBlocked: s.showBlocked, - resourceBookmarks: s.showBookmarks, - resourceLiked: s.showLiked, - resourceStarred: s.showLiked, - resourceFollowRequest: s.showFollowRequests, - resourcePoll: s.showPoll, - resourceMutedAccounts: s.showMutedAccounts, + resourceInstance: s.showInstance, + resourceAccount: s.showAccount, + resourceStatus: s.showStatus, + resourceTimeline: s.showTimeline, + resourceList: s.showList, + resourceFollowers: s.showFollowers, + resourceFollowing: s.showFollowing, + resourceBlocked: s.showBlocked, + resourceBookmarks: s.showBookmarks, + resourceLiked: s.showLiked, + resourceStarred: s.showLiked, + resourceFollowRequest: s.showFollowRequests, + resourcePoll: s.showPoll, + resourceMutedAccounts: s.showMutedAccounts, + resourceMedia: s.showMediaAttachment, + resourceMediaAttachment: s.showMediaAttachment, } doFunc, ok := funcMap[s.resourceType] @@ -134,8 +138,8 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error { } var ( - relationship *model.AccountRelationship = nil - preferences *model.Preferences = nil + relationship *model.AccountRelationship + preferences *model.Preferences ) if !s.myAccount && !s.skipAccountRelationship { @@ -402,3 +406,18 @@ func (s *ShowExecutor) showMutedAccounts(gtsClient *client.Client) error { return nil } + +func (s *ShowExecutor) showMediaAttachment(gtsClient *client.Client) error { + if s.attachmentID == "" { + return FlagNotSetError{flagText: flagAttachmentID} + } + + attachment, err := gtsClient.GetMediaAttachment(s.attachmentID) + if err != nil { + return fmt.Errorf("unable to retrieve the media attachment: %w", err) + } + + s.printer.PrintMediaAttachment(attachment) + + return nil +} diff --git a/internal/printer/media_attachment.go b/internal/printer/media_attachment.go new file mode 100644 index 0000000..0e0be83 --- /dev/null +++ b/internal/printer/media_attachment.go @@ -0,0 +1,45 @@ +package printer + +import ( + "strconv" + "strings" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/model" +) + +func (p Printer) PrintMediaAttachment(attachement model.Attachment) { + var builder strings.Builder + + // The ID of the media attachment + builder.WriteString("\n" + p.headerFormat("MEDIA ATTACHMENT ID:")) + builder.WriteString("\n" + attachement.ID) + + // The media attachment type + builder.WriteString("\n\n" + p.headerFormat("TYPE:")) + builder.WriteString("\n" + attachement.Type) + + // The description that came with the media attachment (if any) + description := attachement.Description + if description == "" { + description = noMediaDescription + } + + builder.WriteString("\n\n" + p.headerFormat("DESCRIPTION:")) + builder.WriteString("\n" + description) + + // The original size of the media attachment + builder.WriteString("\n\n" + p.headerFormat("ORIGINAL SIZE:")) + builder.WriteString("\n" + attachement.Meta.Original.Size) + + // The media attachment's focus + builder.WriteString("\n\n" + p.headerFormat("FOCUS:")) + builder.WriteString("\n" + p.fieldFormat("x:") + " " + strconv.FormatFloat(attachement.Meta.Focus.X, 'f', 1, 64)) + builder.WriteString("\n" + p.fieldFormat("y:") + " " + strconv.FormatFloat(attachement.Meta.Focus.Y, 'f', 1, 64)) + + // The URL to the source of the media attachment + builder.WriteString("\n\n" + p.headerFormat("URL:")) + builder.WriteString("\n" + attachement.URL) + builder.WriteString("\n\n") + + p.print(builder.String()) +} diff --git a/internal/printer/printer.go b/internal/printer/printer.go index 72cc97a..2411558 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -13,7 +13,8 @@ import ( ) const ( - minTerminalWidth = 40 + minTerminalWidth = 40 + noMediaDescription = "This media attachment has no description." ) type theme struct { @@ -39,6 +40,7 @@ type Printer struct { failureSymbol string dateFormat string dateTimeFormat string + imageIcon string } func NewPrinter( @@ -62,6 +64,7 @@ func NewPrinter( } return &Printer{ + theme: theme, noColor: noColor, maxTerminalWidth: maxTerminalWidth, pager: pager, @@ -72,7 +75,7 @@ func NewPrinter( failureSymbol: "\u2717", dateFormat: "02 Jan 2006", dateTimeFormat: "02 Jan 2006, 15:04 (MST)", - theme: theme, + imageIcon: "\uf03e", } } diff --git a/internal/printer/status.go b/internal/printer/status.go index 7dbf9c3..083cc5a 100644 --- a/internal/printer/status.go +++ b/internal/printer/status.go @@ -18,19 +18,37 @@ func (p Printer) PrintStatus(status model.Status) { // The account information builder.WriteString("\n" + p.fullDisplayNameFormat(status.Account.DisplayName, status.Account.Acct)) + // The ID of the status + builder.WriteString("\n\n" + p.headerFormat("STATUS ID:")) + builder.WriteString("\n" + status.ID) + // The content of the status. builder.WriteString("\n\n" + p.headerFormat("CONTENT:")) builder.WriteString(utilities.WrapLines(utilities.ConvertHTMLToText(status.Content), "\n", p.maxTerminalWidth)) + // Details of media attachments (if any). + if len(status.MediaAttachments) > 0 { + builder.WriteString("\n\n" + p.headerFormat("MEDIA ATTACHMENTS:")) + + for ind, media := range status.MediaAttachments { + builder.WriteString("\n\n[" + strconv.Itoa(ind) + "] " + p.fieldFormat("ID:") + " " + media.ID) + builder.WriteString("\n " + p.fieldFormat("Type:") + " " + media.Type) + + description := media.Description + if description == "" { + description = noMediaDescription + } + + builder.WriteString("\n " + p.fieldFormat("Description:") + " " + description) + builder.WriteString("\n " + p.fieldFormat("Media URL:") + " " + media.URL) + } + } + // If a poll exists in a status, write the contents to the builder. if status.Poll != nil { builder.WriteString(p.pollOptions(*status.Poll)) } - // The ID of the status - builder.WriteString("\n\n" + p.headerFormat("STATUS ID:")) - builder.WriteString("\n" + status.ID) - // Status creation time builder.WriteString("\n\n" + p.headerFormat("CREATED AT:")) builder.WriteString("\n" + p.formatDateTime(status.CreatedAt)) @@ -79,6 +97,17 @@ func (p Printer) PrintStatusList(list model.StatusList) { builder.WriteString(p.pollOptions(*status.Poll)) } + for _, media := range status.MediaAttachments { + builder.WriteString("\n\n" + p.imageIcon + " Media (" + media.ID + ")" + "\n ") + + description := media.Description + if description == "" { + description = noMediaDescription + } + + builder.WriteString(utilities.WrapLines(description, "\n ", p.maxTerminalWidth-3)) + } + builder.WriteString( "\n\n" + p.fieldFormat("Status ID:") + " " + statusID + "\t" +