checkpoint: create and edit media attachments

This commit is contained in:
Dan Anglin 2024-08-14 13:24:39 +01:00
parent eb016b96e9
commit f3a5887cb9
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
5 changed files with 211 additions and 16 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

@ -19,8 +19,9 @@ 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]
@ -153,3 +154,18 @@ 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 media attachment with ID: " + attachment.ID)
return nil
}

View file

@ -13,7 +13,8 @@ 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]
@ -57,8 +58,41 @@ 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 {
expectedNumMediaAttachmentIDs := 1
if !e.attachmentIDs.ExpectedLength(expectedNumMediaAttachmentIDs) {
return fmt.Errorf(
"received an unexpected number of media attachment IDs: want %d",
expectedNumMediaAttachmentIDs,
)
}
attachment, err := gtsClient.GetMediaAttachment(e.attachmentIDs[0])
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)
}
if _, err = gtsClient.UpdateMediaAttachment(e.attachmentIDs[0], description, focus); err != nil {
return fmt.Errorf("unable to update the media attachment: %w", err)
}
e.printer.PrintSuccess("Successfully edited the media attachment.")
return nil
}

View file

@ -121,12 +121,15 @@ type CreateExecutor struct {
printer *printer.Printer printer *printer.Printer
config *config.Config config *config.Config
addPoll bool addPoll bool
attachmentIDs internalFlag.StringSliceValue
content string content string
contentType string contentType string
description string
federated bool federated bool
likeable bool likeable bool
replyable bool replyable bool
boostable bool boostable bool
focus string
fromFile string fromFile string
inReplyTo string inReplyTo string
language string language string
@ -150,6 +153,7 @@ func NewCreateExecutor(
FlagSet: flag.NewFlagSet("create", flag.ExitOnError), FlagSet: flag.NewFlagSet("create", flag.ExitOnError),
printer: printer, printer: printer,
config: config, config: config,
attachmentIDs: internalFlag.NewStringSliceValue(),
pollExpiresIn: internalFlag.NewTimeDurationValue(), pollExpiresIn: internalFlag.NewTimeDurationValue(),
pollOptions: internalFlag.NewStringSliceValue(), pollOptions: internalFlag.NewStringSliceValue(),
sensitive: internalFlag.NewBoolPtrValue(), sensitive: internalFlag.NewBoolPtrValue(),
@ -158,12 +162,15 @@ func NewCreateExecutor(
exe.Usage = usage.ExecutorUsageFunc("create", "Creates a specific resource", exe.FlagSet) exe.Usage = usage.ExecutorUsageFunc("create", "Creates a specific resource", exe.FlagSet)
exe.BoolVar(&exe.addPoll, "add-poll", false, "Set to true to add a poll when creating a status") exe.BoolVar(&exe.addPoll, "add-poll", false, "Set to true to add a poll when creating a status")
exe.Var(&exe.attachmentIDs, "attachment-id", "The ID of the media attachment")
exe.StringVar(&exe.content, "content", "", "The content of the created resource") exe.StringVar(&exe.content, "content", "", "The content of the created resource")
exe.StringVar(&exe.contentType, "content-type", "plain", "The type that the contents should be parsed from (valid values are plain and markdown)") exe.StringVar(&exe.contentType, "content-type", "plain", "The type that the contents should be parsed from (valid values are plain and markdown)")
exe.StringVar(&exe.description, "description", "", "The description of the media attachment that will be used as the alt-text")
exe.BoolVar(&exe.federated, "enable-federation", true, "Set to true to federate the status beyond the local timelines") exe.BoolVar(&exe.federated, "enable-federation", true, "Set to true to federate the status beyond the local timelines")
exe.BoolVar(&exe.likeable, "enable-likes", true, "Set to true to allow the status to be liked (favourited)") exe.BoolVar(&exe.likeable, "enable-likes", true, "Set to true to allow the status to be liked (favourited)")
exe.BoolVar(&exe.replyable, "enable-replies", true, "Set to true to allow viewers to reply to the status") exe.BoolVar(&exe.replyable, "enable-replies", true, "Set to true to allow viewers to reply to the status")
exe.BoolVar(&exe.boostable, "enable-reposts", true, "Set to true to allow the status to be reposted (boosted) by others") exe.BoolVar(&exe.boostable, "enable-reposts", true, "Set to true to allow the status to be reposted (boosted) by others")
exe.StringVar(&exe.focus, "focus", "", "The focus of the media file")
exe.StringVar(&exe.fromFile, "from-file", "", "The file path where to read the contents from") exe.StringVar(&exe.fromFile, "from-file", "", "The file path where to read the contents from")
exe.StringVar(&exe.inReplyTo, "in-reply-to", "", "The ID of the status that you want to reply to") exe.StringVar(&exe.inReplyTo, "in-reply-to", "", "The ID of the status that you want to reply to")
exe.StringVar(&exe.language, "language", "", "The ISO 639 language code for this status") exe.StringVar(&exe.language, "language", "", "The ISO 639 language code for this status")
@ -213,6 +220,9 @@ type EditExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
config *config.Config config *config.Config
attachmentIDs internalFlag.StringSliceValue
description string
focus string
listID string listID string
listTitle string listTitle string
listRepliesPolicy string listRepliesPolicy string
@ -224,13 +234,17 @@ func NewEditExecutor(
config *config.Config, config *config.Config,
) *EditExecutor { ) *EditExecutor {
exe := EditExecutor{ exe := EditExecutor{
FlagSet: flag.NewFlagSet("edit", flag.ExitOnError), FlagSet: flag.NewFlagSet("edit", flag.ExitOnError),
printer: printer, printer: printer,
config: config, config: config,
attachmentIDs: internalFlag.NewStringSliceValue(),
} }
exe.Usage = usage.ExecutorUsageFunc("edit", "Edit a specific resource", exe.FlagSet) exe.Usage = usage.ExecutorUsageFunc("edit", "Edit a specific resource", exe.FlagSet)
exe.Var(&exe.attachmentIDs, "attachment-id", "The ID of the media attachment")
exe.StringVar(&exe.description, "description", "", "The description of the media attachment that will be used as the alt-text")
exe.StringVar(&exe.focus, "focus", "", "The focus of the media file")
exe.StringVar(&exe.listID, "list-id", "", "The ID of the list in question") exe.StringVar(&exe.listID, "list-id", "", "The ID of the list in question")
exe.StringVar(&exe.listTitle, "list-title", "", "The title of the list") exe.StringVar(&exe.listTitle, "list-title", "", "The title of the list")
exe.StringVar(&exe.listRepliesPolicy, "list-replies-policy", "", "The replies policy of the list") exe.StringVar(&exe.listRepliesPolicy, "list-replies-policy", "", "The replies policy of the list")

View file

@ -32,6 +32,10 @@
"type": "string", "type": "string",
"description": "The type that the contents should be parsed from (valid values are plain and markdown)" "description": "The type that the contents should be parsed from (valid values are plain and markdown)"
}, },
"description": {
"type": "string",
"description": "The description of the media attachment that will be used as the alt-text"
},
"enable-federation": { "enable-federation": {
"type": "bool", "type": "bool",
"description": "Set to true to federate the status beyond the local timelines" "description": "Set to true to federate the status beyond the local timelines"
@ -56,6 +60,10 @@
"type": "bool", "type": "bool",
"description": "Set to true to exclude statuses that are a reply to another status" "description": "Set to true to exclude statuses that are a reply to another status"
}, },
"focus": {
"type": "string",
"description": "The focus of the media file"
},
"from": { "from": {
"type": "string", "type": "string",
"description": "The resource type to action the target resource from (e.g. status)" "description": "The resource type to action the target resource from (e.g. status)"
@ -234,12 +242,15 @@
"additionalFields": [], "additionalFields": [],
"flags": [ "flags": [
{ "flag": "add-poll", "default": "false" }, { "flag": "add-poll", "default": "false" },
{ "flag": "attachment-id", "fieldName": "attachmentIDs" },
{ "flag": "content", "default": "" }, { "flag": "content", "default": "" },
{ "flag": "content-type", "default": "plain" }, { "flag": "content-type", "default": "plain" },
{ "flag": "description", "default": "" },
{ "flag": "enable-federation", "fieldName": "federated", "default": "true" }, { "flag": "enable-federation", "fieldName": "federated", "default": "true" },
{ "flag": "enable-likes", "fieldName": "likeable", "default": "true" }, { "flag": "enable-likes", "fieldName": "likeable", "default": "true" },
{ "flag": "enable-replies", "fieldName": "replyable", "default": "true" }, { "flag": "enable-replies", "fieldName": "replyable", "default": "true" },
{ "flag": "enable-reposts", "fieldName": "boostable", "default": "true" }, { "flag": "enable-reposts", "fieldName": "boostable", "default": "true" },
{ "flag": "focus", "default": "" },
{ "flag": "from-file", "default": "" }, { "flag": "from-file", "default": "" },
{ "flag": "in-reply-to", "default": "" }, { "flag": "in-reply-to", "default": "" },
{ "flag": "language", "default": "" }, { "flag": "language", "default": "" },
@ -271,6 +282,9 @@
"edit": { "edit": {
"additionalFields": [], "additionalFields": [],
"flags": [ "flags": [
{ "flag": "attachment-id", "fieldName": "attachmentIDs" },
{ "flag": "description", "default": "" },
{ "flag": "focus", "default": "" },
{ "flag": "list-id", "fieldName": "listID", "default": ""}, { "flag": "list-id", "fieldName": "listID", "default": ""},
{ "flag": "list-title", "default": "" }, { "flag": "list-title", "default": "" },
{ "flag": "list-replies-policy", "default": "" }, { "flag": "list-replies-policy", "default": "" },