feat: view media attachment information

View media attachment information in the timeline and status views.
Users can also show detailed information of the attachments via the
attachment ID.
This commit is contained in:
Dan Anglin 2024-06-18 19:58:59 +01:00
parent 2a386fcda5
commit e8114f8d22
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
7 changed files with 160 additions and 41 deletions

20
internal/client/media.go Normal file
View file

@ -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
}

View file

@ -14,6 +14,7 @@ import (
const ( const (
flagAddPoll = "add-poll" flagAddPoll = "add-poll"
flagAccountName = "account-name" flagAccountName = "account-name"
flagAttachmentID = "attachment-id"
flagBrowser = "browser" flagBrowser = "browser"
flagChoose = "choose" flagChoose = "choose"
flagContentType = "content-type" flagContentType = "content-type"

View file

@ -16,6 +16,8 @@ const (
resourceLike = "like" resourceLike = "like"
resourceLiked = "liked" resourceLiked = "liked"
resourceList = "list" resourceList = "list"
resourceMedia = "media"
resourceMediaAttachment = "media-attachment"
resourceMutedAccounts = "muted-accounts" resourceMutedAccounts = "muted-accounts"
resourceNote = "note" resourceNote = "note"
resourcePoll = "poll" resourcePoll = "poll"

View file

@ -30,6 +30,7 @@ type ShowExecutor struct {
listID string listID string
tag string tag string
pollID string pollID string
attachmentID string
limit int 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.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.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.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.IntVar(&showExe.limit, flagLimit, 20, "Specify the limit of items to display")
showExe.Usage = commandUsageFunc(name, summary, showExe.FlagSet) showExe.Usage = commandUsageFunc(name, summary, showExe.FlagSet)
@ -79,6 +81,8 @@ func (s *ShowExecutor) Execute() error {
resourceFollowRequest: s.showFollowRequests, resourceFollowRequest: s.showFollowRequests,
resourcePoll: s.showPoll, resourcePoll: s.showPoll,
resourceMutedAccounts: s.showMutedAccounts, resourceMutedAccounts: s.showMutedAccounts,
resourceMedia: s.showMediaAttachment,
resourceMediaAttachment: s.showMediaAttachment,
} }
doFunc, ok := funcMap[s.resourceType] doFunc, ok := funcMap[s.resourceType]
@ -134,8 +138,8 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error {
} }
var ( var (
relationship *model.AccountRelationship = nil relationship *model.AccountRelationship
preferences *model.Preferences = nil preferences *model.Preferences
) )
if !s.myAccount && !s.skipAccountRelationship { if !s.myAccount && !s.skipAccountRelationship {
@ -402,3 +406,18 @@ func (s *ShowExecutor) showMutedAccounts(gtsClient *client.Client) error {
return nil 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
}

View file

@ -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())
}

View file

@ -14,6 +14,7 @@ import (
const ( const (
minTerminalWidth = 40 minTerminalWidth = 40
noMediaDescription = "This media attachment has no description."
) )
type theme struct { type theme struct {
@ -39,6 +40,7 @@ type Printer struct {
failureSymbol string failureSymbol string
dateFormat string dateFormat string
dateTimeFormat string dateTimeFormat string
imageIcon string
} }
func NewPrinter( func NewPrinter(
@ -62,6 +64,7 @@ func NewPrinter(
} }
return &Printer{ return &Printer{
theme: theme,
noColor: noColor, noColor: noColor,
maxTerminalWidth: maxTerminalWidth, maxTerminalWidth: maxTerminalWidth,
pager: pager, pager: pager,
@ -72,7 +75,7 @@ func NewPrinter(
failureSymbol: "\u2717", failureSymbol: "\u2717",
dateFormat: "02 Jan 2006", dateFormat: "02 Jan 2006",
dateTimeFormat: "02 Jan 2006, 15:04 (MST)", dateTimeFormat: "02 Jan 2006, 15:04 (MST)",
theme: theme, imageIcon: "\uf03e",
} }
} }

View file

@ -18,19 +18,37 @@ func (p Printer) PrintStatus(status model.Status) {
// The account information // The account information
builder.WriteString("\n" + p.fullDisplayNameFormat(status.Account.DisplayName, status.Account.Acct)) 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. // The content of the status.
builder.WriteString("\n\n" + p.headerFormat("CONTENT:")) builder.WriteString("\n\n" + p.headerFormat("CONTENT:"))
builder.WriteString(utilities.WrapLines(utilities.ConvertHTMLToText(status.Content), "\n", p.maxTerminalWidth)) 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 a poll exists in a status, write the contents to the builder.
if status.Poll != nil { if status.Poll != nil {
builder.WriteString(p.pollOptions(*status.Poll)) 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 // Status creation time
builder.WriteString("\n\n" + p.headerFormat("CREATED AT:")) builder.WriteString("\n\n" + p.headerFormat("CREATED AT:"))
builder.WriteString("\n" + p.formatDateTime(status.CreatedAt)) builder.WriteString("\n" + p.formatDateTime(status.CreatedAt))
@ -79,6 +97,17 @@ func (p Printer) PrintStatusList(list model.StatusList) {
builder.WriteString(p.pollOptions(*status.Poll)) 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( builder.WriteString(
"\n\n" + "\n\n" +
p.fieldFormat("Status ID:") + " " + statusID + "\t" + p.fieldFormat("Status ID:") + " " + statusID + "\t" +