Compare commits

...

3 commits

5 changed files with 235 additions and 42 deletions

View file

@ -1,8 +1,14 @@
package client package client
import ( import (
"bytes"
"encoding/json"
"fmt" "fmt"
"io"
"mime/multipart"
"net/http" "net/http"
"os"
"path/filepath"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/model"
) )
@ -31,12 +37,123 @@ func (g *Client) GetMediaAttachment(mediaAttachmentID string) (model.Attachment,
return attachment, nil return attachment, nil
} }
//type CreateMediaAttachmentForm struct { func (g *Client) CreateMediaAttachment(path, description, focus string) (model.Attachment, error) {
// Description string file, err := os.Open(path)
// Focus string if err != nil {
// Filepath string return model.Attachment{}, fmt.Errorf("unable to open the file: %w", err)
//} }
// defer file.Close()
//func (g *Client) CreateMediaAttachment(form CreateMediaAttachmentForm) (model.Attachment, error) {
// return model.Attachment{}, nil // create the request body using a writer from the multipart package
//} requestBody := bytes.Buffer{}
requestBodyWriter := multipart.NewWriter(&requestBody)
filename := filepath.Base(path)
part, err := requestBodyWriter.CreateFormFile("file", filename)
if err != nil {
return model.Attachment{}, fmt.Errorf("unable to create the new part: %w", err)
}
if _, err := io.Copy(part, file); err != nil {
return model.Attachment{}, fmt.Errorf("unable to copy the file contents to the form: %w", err)
}
// add the description
if description != "" {
descriptionFormFieldWriter, err := requestBodyWriter.CreateFormField("description")
if err != nil {
return model.Attachment{}, fmt.Errorf(
"unable to create the writer for the 'description' form field: %w",
err,
)
}
if _, err := io.WriteString(descriptionFormFieldWriter, description); err != nil {
return model.Attachment{}, fmt.Errorf(
"unable to write the description to the form: %w",
err,
)
}
}
// add the focus values
if focus != "" {
focusFormFieldWriter, err := requestBodyWriter.CreateFormField("focus")
if err != nil {
return model.Attachment{}, fmt.Errorf(
"unable to create the writer for the 'focus' form field: %w",
err,
)
}
if _, err := io.WriteString(focusFormFieldWriter, focus); err != nil {
return model.Attachment{}, fmt.Errorf(
"unable to write the focus values to the form: %w",
err,
)
}
}
if err := requestBodyWriter.Close(); err != nil {
return model.Attachment{}, fmt.Errorf("unable to close the writer: %w", err)
}
url := g.Authentication.Instance + baseMediaPath
var attachment model.Attachment
params := requestParameters{
httpMethod: http.MethodPost,
url: url,
requestBody: &requestBody,
contentType: requestBodyWriter.FormDataContentType(),
output: &attachment,
}
if err := g.sendRequest(params); err != nil {
return model.Attachment{}, fmt.Errorf(
"received an error after sending the request to create the media attachment: %w",
err,
)
}
return attachment, nil
}
func (g *Client) UpdateMediaAttachment(mediaAttachmentID, description, focus string) (model.Attachment, error) {
form := struct {
Description string `json:"description"`
Focus string `json:"focus"`
}{
Description: description,
Focus: focus,
}
data, err := json.Marshal(form)
if err != nil {
return model.Attachment{}, fmt.Errorf("unable to marshal the form: %w", err)
}
requestBody := bytes.NewBuffer(data)
url := g.Authentication.Instance + baseMediaPath + "/" + mediaAttachmentID
var updatedMediaAttachment model.Attachment
params := requestParameters{
httpMethod: http.MethodPut,
url: url,
requestBody: requestBody,
contentType: applicationJSON,
output: &updatedMediaAttachment,
}
if err := g.sendRequest(params); err != nil {
return model.Attachment{}, fmt.Errorf(
"received an error after sending the request to update the media attachment: %w",
err,
)
}
return updatedMediaAttachment, nil
}

View file

@ -50,6 +50,7 @@ type CreateStatusForm struct {
Poll *CreateStatusPollForm `json:"poll,omitempty"` Poll *CreateStatusPollForm `json:"poll,omitempty"`
ContentType model.StatusContentType `json:"content_type"` ContentType model.StatusContentType `json:"content_type"`
Visibility model.StatusVisibility `json:"visibility"` Visibility model.StatusVisibility `json:"visibility"`
AttachmentIDs []string `json:"media_ids,omitempty"`
} }
type CreateStatusPollForm struct { type CreateStatusPollForm struct {

View file

@ -27,7 +27,9 @@ type CreateExecutor struct {
sensitive *bool sensitive *bool
content string content string
contentType string contentType string
description string
fromFile string fromFile string
focus string
inReplyTo string inReplyTo string
language string language string
resourceType string resourceType string
@ -37,6 +39,7 @@ type CreateExecutor struct {
visibility string visibility string
pollExpiresIn TimeDurationFlagValue pollExpiresIn TimeDurationFlagValue
pollOptions MultiStringFlagValue pollOptions MultiStringFlagValue
attachmentIDs MultiStringFlagValue
} }
func NewCreateExecutor(printer *printer.Printer, config *config.Config, name, summary string) *CreateExecutor { func NewCreateExecutor(printer *printer.Printer, config *config.Config, name, summary string) *CreateExecutor {
@ -72,6 +75,7 @@ func NewCreateExecutor(printer *printer.Printer, config *config.Config, name, su
return nil return nil
}) })
createExe.Var(&createExe.attachmentIDs, flagAttachmentID, "Specify the ID of the media attachment to add to the status")
// Flags specifically for polls // Flags specifically for polls
createExe.BoolVar(&createExe.addPoll, flagAddPoll, false, "Add a poll to the status") createExe.BoolVar(&createExe.addPoll, flagAddPoll, false, "Add a poll to the status")
@ -83,6 +87,8 @@ func NewCreateExecutor(printer *printer.Printer, config *config.Config, name, su
// Flags for lists // Flags for lists
createExe.StringVar(&createExe.listTitle, flagListTitle, "", "Specify the title of the list") createExe.StringVar(&createExe.listTitle, flagListTitle, "", "Specify the title of the list")
createExe.StringVar(&createExe.listRepliesPolicy, flagListRepliesPolicy, "list", "Specify the policy of the replies for this list (valid values are followed, list and none)") createExe.StringVar(&createExe.listRepliesPolicy, flagListRepliesPolicy, "list", "Specify the policy of the replies for this list (valid values are followed, list and none)")
createExe.StringVar(&createExe.description, flagDescription, "", "The description of the media attachment that will be used as the alt-text")
createExe.StringVar(&createExe.focus, flagFocus, "", "The focus of the media file")
createExe.Usage = commandUsageFunc(name, summary, createExe.FlagSet) createExe.Usage = commandUsageFunc(name, summary, createExe.FlagSet)
@ -102,6 +108,7 @@ func (c *CreateExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{ funcMap := map[string]func(*client.Client) error{
resourceList: c.createList, resourceList: c.createList,
resourceStatus: c.createStatus, resourceStatus: c.createStatus,
resourceMediaAttachment: c.createMediaAttachment,
} }
doFunc, ok := funcMap[c.resourceType] doFunc, ok := funcMap[c.resourceType]
@ -156,11 +163,18 @@ func (c *CreateExecutor) createStatus(gtsClient *client.Client) error {
return fmt.Errorf("unable to get the status contents from %q: %w", c.fromFile, err) return fmt.Errorf("unable to get the status contents from %q: %w", c.fromFile, err)
} }
default: default:
if len(c.attachmentIDs) == 0 {
// TODO: revisit this error type
return EmptyContentError{ return EmptyContentError{
ResourceType: resourceStatus, ResourceType: resourceStatus,
Hint: "please use --" + flagContent + " or --" + flagFromFile, Hint: "please use --" + flagContent + " or --" + flagFromFile,
} }
} }
}
if len(c.attachmentIDs) > 0 && c.addPoll {
return fmt.Errorf("attaching media to a poll is not allowed")
}
preferences, err := gtsClient.GetUserPreferences() preferences, err := gtsClient.GetUserPreferences()
if err != nil { if err != nil {
@ -208,6 +222,11 @@ func (c *CreateExecutor) createStatus(gtsClient *client.Client) error {
Sensitive: sensitive, Sensitive: sensitive,
Visibility: parsedVisibility, Visibility: parsedVisibility,
Poll: nil, Poll: nil,
AttachmentIDs: nil,
}
if len(c.attachmentIDs) > 0 {
form.AttachmentIDs = c.attachmentIDs
} }
if c.addPoll { if c.addPoll {
@ -235,3 +254,19 @@ func (c *CreateExecutor) createStatus(gtsClient *client.Client) error {
return nil return nil
} }
func (c *CreateExecutor) createMediaAttachment(gtsClient *client.Client) error {
if c.fromFile == "" {
return FlagNotSetError{flagText: flagFromFile}
}
attachment, err := gtsClient.CreateMediaAttachment(c.fromFile, c.description, c.focus)
if err != nil {
return fmt.Errorf("unable to create the media attachment: %w", err)
}
c.printer.PrintSuccess("Successfully created the following media attachment:")
c.printer.PrintMediaAttachment(attachment)
return nil
}

View file

@ -15,6 +15,9 @@ type EditExecutor struct {
printer *printer.Printer printer *printer.Printer
config *config.Config config *config.Config
attachmentID string
description string
focus string
resourceType string resourceType string
listID string listID string
listTitle string listTitle string
@ -29,10 +32,13 @@ func NewEditExecutor(printer *printer.Printer, config *config.Config, name, summ
config: config, config: config,
} }
editExe.StringVar(&editExe.resourceType, flagType, "", "Specify the type of resource to update") editExe.StringVar(&editExe.resourceType, flagType, "", "Specify the type of resource to edit")
editExe.StringVar(&editExe.listID, flagListID, "", "Specify the ID of the list to update") editExe.StringVar(&editExe.attachmentID, flagAttachmentID, "", "Specify the ID of the media attachment to edit")
editExe.StringVar(&editExe.listID, flagListID, "", "Specify the ID of the list to edit")
editExe.StringVar(&editExe.listTitle, flagListTitle, "", "Specify the title of the list") editExe.StringVar(&editExe.listTitle, flagListTitle, "", "Specify the title of the list")
editExe.StringVar(&editExe.listRepliesPolicy, flagListRepliesPolicy, "", "Specify the policy of the replies for this list (valid values are followed, list and none)") editExe.StringVar(&editExe.listRepliesPolicy, flagListRepliesPolicy, "", "Specify the policy of the replies for this list (valid values are followed, list and none)")
editExe.StringVar(&editExe.description, flagDescription, "", "The description of the media attachment that will be used as the alt-text")
editExe.StringVar(&editExe.focus, flagFocus, "", "The focus of the media file")
editExe.Usage = commandUsageFunc(name, summary, editExe.FlagSet) editExe.Usage = commandUsageFunc(name, summary, editExe.FlagSet)
@ -46,6 +52,7 @@ func (e *EditExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{ funcMap := map[string]func(*client.Client) error{
resourceList: e.editList, resourceList: e.editList,
resourceMediaAttachment: e.editMediaAttachment,
} }
doFunc, ok := funcMap[e.resourceType] doFunc, ok := funcMap[e.resourceType]
@ -89,8 +96,39 @@ func (e *EditExecutor) editList(gtsClient *client.Client) error {
return fmt.Errorf("unable to update the list: %w", err) return fmt.Errorf("unable to update the list: %w", err)
} }
e.printer.PrintSuccess("Successfully updated the list.") e.printer.PrintSuccess("Successfully edited the list.")
e.printer.PrintList(updatedList) e.printer.PrintList(updatedList)
return nil return nil
} }
func (e *EditExecutor) editMediaAttachment(gtsClient *client.Client) error {
if e.attachmentID == "" {
return FlagNotSetError{flagText: flagAttachmentID}
}
attachment, err := gtsClient.GetMediaAttachment(e.attachmentID)
if err != nil {
return fmt.Errorf("unable to get the media attachment: %w", err)
}
description := e.description
if description == "" {
description = attachment.Description
}
focus := e.focus
if focus == "" {
focus = fmt.Sprintf("%f,%f", attachment.Meta.Focus.X, attachment.Meta.Focus.Y)
}
updatedAttachment, err := gtsClient.UpdateMediaAttachment(e.attachmentID, description, focus)
if err != nil {
return fmt.Errorf("unable to update the media attachment: %w", err)
}
e.printer.PrintSuccess("Successfully edited the media attachment.")
e.printer.PrintMediaAttachment(updatedAttachment)
return nil
}

View file

@ -16,12 +16,14 @@ const (
flagBrowser = "browser" flagBrowser = "browser"
flagContentType = "content-type" flagContentType = "content-type"
flagContent = "content" flagContent = "content"
flagDescription = "description"
flagEnableFederation = "enable-federation" flagEnableFederation = "enable-federation"
flagEnableLikes = "enable-likes" flagEnableLikes = "enable-likes"
flagEnableReplies = "enable-replies" flagEnableReplies = "enable-replies"
flagEnableReposts = "enable-reposts" flagEnableReposts = "enable-reposts"
flagExcludeBoosts = "exclude-boosts" flagExcludeBoosts = "exclude-boosts"
flagExcludeReplies = "exclude-replies" flagExcludeReplies = "exclude-replies"
flagFocus = "focus"
flagFrom = "from" flagFrom = "from"
flagFromFile = "from-file" flagFromFile = "from-file"
flagFull = "full" flagFull = "full"