diff --git a/docs/manual.md b/docs/manual.md
index 95ebaf3..3c44007 100644
--- a/docs/manual.md
+++ b/docs/manual.md
@@ -54,8 +54,10 @@
- [Remove accounts from a list](#remove-accounts-from-a-list)
- [Timelines](#timelines)
- [View a timeline](#view-a-timeline)
-- [Media Attachment](#media-attachment)
- - [View media attachment](#view-media-attachment)
+- [Media Attachments](#media-attachments)
+ - [Create a media attachment](#create-a-media-attachment)
+ - [Edit a media attachment](#edit-a-media-attachment)
+ - [View a media attachment](#view-a-media-attachment)
- [Media](#media)
- [View media from a status](#view-media-from-a-status)
- [Bookmarks](#bookmarks)
@@ -421,10 +423,28 @@ enbas show --type status --status-id 01J1Z9PT0243JT9QNQ5W96Z8CA
--poll-option "other (please comment)"
```
![A screenshot of a status with a poll](../assets/images/created_poll.png "A status with a poll")
+- Create a status with a media attachment that you own.
+ ```
+ enbas create \
+ --type status \
+ --attachment-id 01J5BDHYJ7MWMMG76FP49H7SWD \
+ --content "I went out for a walk in the woods and found this interesting looking wooden bench."
+ ```
+- Upload and attach 4 media files to a new status. The number of `media-description` and `media-focus` flags **must** match the number of `media-file` flags defined.
+ The first `media-description` and `media-focus` flags correspond to the value defined in the first `media-file` flag and so on.
+ ```
+ enbas create --type status --visibility public \
+ --content "This post has a picture of a cat, a dog, a bee and a bird." \
+ --media-file cat.jpg --media-description file@cat.txt --media-focus "0,0" \
+ --media-file dog.jpg --media-description file@dog.txt --media-focus "-0.1,0.25" \
+ --media-file bee.jpg --media-description file@bee.txt --media-focus "1,1" \
+ --media-file bird.webp --media-description file@bird.txt --media-focus "0,0"
+ ```
| flag | type | required | description | default |
|------|------|----------|-------------|---------|
| `type` | string | true | The resource you want to create.
Here this should be `status`. | |
+| `attachment-id` | string | false | The ID of the media attachment to attach to the status.
Use this flag multiple times to attach multiple media. |
| `content` | string | false | The content of the status.
This flag takes precedence over `from-file`.| |
| `content-type` | string | false | The format that the content is created in.
Valid values are `plain` and `markdown`. | plain |
| `enable-reposts` | boolean | false | The status can be reposted (boosted) by others. | true |
@@ -434,6 +454,9 @@ enbas show --type status --status-id 01J1Z9PT0243JT9QNQ5W96Z8CA
| `from-file` | string | false | The path to the file where to read the contents of the status from. | |
| `in-reply-to` | string | false | The ID of the status that you want to reply to. | |
| `language` | string | false | The ISO 639 language code that the status is written in.
If this is not specified then the default language from your posting preferences will be used. | |
+| `media-file` | string | false | The path to the media file.
Use this flag multiple times to upload multiple media files. | |
+| `media-description` | string | false | The description of the media attachment which will be used as the media's alt-text.
To use a description from a text file, use the `flag@` prefix followed by the path to the file (e.g. `file@description.txt`)
Use this flag multiple times to set multiple descriptions.| |
+| `media-focus` | string | false | The media's focus values. This should be in the form of two comma-separated numbers between -1 and 1 (e.g. 0.25,-0.34).
Use this flag multiple times to set multiple focus values. | |
| `sensitive` | string | false | The status should be marked as sensitive.
If this is not specified then the default sensitivity from your posting preferences will be used. | |
| `spoiler-text` | string | false | The text to display as the status' warning or subject. | |
| `visibility` | string | false | The visibility of the status.
Valid values are `public`, `private`, `unlisted`, `mutuals_only` and `direct`.
If this is not specified then the default visibility from your posting preferences will be used. | |
@@ -683,9 +706,51 @@ Prints a list of statuses from a timeline.
| `tag` | string | false | The hashtag you want to view.
This is only required if `timeline-category` is set to `tag`. | |
| `limit` | integer | false | The maximum number of statuses to print. | 20 |
-## Media Attachment
+## Media Attachments
-### View media attachment
+### Create a media attachment
+
+Uploads media from a file to the instance and creates a media attachment.
+You can write a description of the media to a text file and specify the its path with the `media-description` flag (see the examples below).
+
+- Create a media attachment with a simple description and a focus of x=-0.1, y=0.5
+ ```
+ enbas create --type media-attachment \
+ --media-file picture.png \
+ --media-description "A picture of an old, slanted wooden bench in front of the woods." \
+ --media-focus "-0.1,0.5"
+ ```
+- Create a media attachment using a description that has been written to the `description.txt` text file.
+ ```
+ enbas create --type media-attachment \
+ --media-file picture.png \
+ --media-description file@description.txt
+ ```
+
+| flag | type | required | description | default |
+|------|------|----------|-------------|---------|
+| `type` | string | true | The resource you want to create.
Here this should be `media-attachment`. | |
+| `media-file` | string | true | The path to the media file. | |
+| `media-description` | string | false | The description of the media attachment which will be used as the media's alt-text.
To use a description from a text file, use the `flag@` prefix followed by the path to the file (e.g. `file@description.txt`)| |
+| `media-focus` | string | false | The media's focus values. This should be in the form of two comma-separated numbers between -1 and 1 (e.g. 0.25,-0.34) | |
+
+### Edit a media attachment
+
+Edits the description and/or the focus of a media attachment that you own.
+
+```
+enbas edit --type media-attachment \
+ --attachment-id 01J5B9A8WFK59W11MS6AHPYWBR \
+ --media-description "An updated description of a picture."
+```
+
+| flag | type | required | description | default |
+|------|------|----------|-------------|---------|
+| `type` | string | true | The resource you want to edit.
Here this should be `media-attachment`. | |
+| `media-description` | string | false | The description of the media attachment to edit.
To use a description from a text file, use the `flag@` prefix followed by the path to the file (e.g. `file@description.txt`)| |
+| `media-focus` | string | false | The media's focus values. This should be in the form of two comma-separated numbers between -1 and 1 (e.g. 0.25,-0.34) | |
+
+### View a media attachment
Prints information about a given media attachment that you own.
You can only see information about the media attachment that you own.
diff --git a/internal/client/media.go b/internal/client/media.go
index a855071..182c7d8 100644
--- a/internal/client/media.go
+++ b/internal/client/media.go
@@ -1,8 +1,14 @@
package client
import (
+ "bytes"
+ "encoding/json"
"fmt"
+ "io"
+ "mime/multipart"
"net/http"
+ "os"
+ "path/filepath"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
)
@@ -31,12 +37,123 @@ func (g *Client) GetMediaAttachment(mediaAttachmentID string) (model.Attachment,
return attachment, nil
}
-//type CreateMediaAttachmentForm struct {
-// Description string
-// Focus string
-// Filepath string
-//}
-//
-//func (g *Client) CreateMediaAttachment(form CreateMediaAttachmentForm) (model.Attachment, error) {
-// return model.Attachment{}, nil
-//}
+func (g *Client) CreateMediaAttachment(path, description, focus string) (model.Attachment, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return model.Attachment{}, fmt.Errorf("unable to open the file: %w", err)
+ }
+ defer file.Close()
+
+ // 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
+}
diff --git a/internal/client/statuses.go b/internal/client/statuses.go
index 6e1a59e..0b8d60a 100644
--- a/internal/client/statuses.go
+++ b/internal/client/statuses.go
@@ -38,18 +38,19 @@ func (g *Client) GetStatus(statusID string) (model.Status, error) {
}
type CreateStatusForm struct {
- Content string `json:"status"`
- InReplyTo string `json:"in_reply_to_id"`
- Language string `json:"language"`
- SpoilerText string `json:"spoiler_text"`
- Boostable bool `json:"boostable"`
- Federated bool `json:"federated"`
- Likeable bool `json:"likeable"`
- Replyable bool `json:"replyable"`
- Sensitive bool `json:"sensitive"`
- Poll *CreateStatusPollForm `json:"poll,omitempty"`
- ContentType model.StatusContentType `json:"content_type"`
- Visibility model.StatusVisibility `json:"visibility"`
+ Content string `json:"status"`
+ InReplyTo string `json:"in_reply_to_id"`
+ Language string `json:"language"`
+ SpoilerText string `json:"spoiler_text"`
+ Boostable bool `json:"boostable"`
+ Federated bool `json:"federated"`
+ Likeable bool `json:"likeable"`
+ Replyable bool `json:"replyable"`
+ Sensitive bool `json:"sensitive"`
+ Poll *CreateStatusPollForm `json:"poll,omitempty"`
+ ContentType model.StatusContentType `json:"content_type"`
+ Visibility model.StatusVisibility `json:"visibility"`
+ AttachmentIDs []string `json:"media_ids,omitempty"`
}
type CreateStatusPollForm struct {
diff --git a/internal/executor/create.go b/internal/executor/create.go
index 194400a..6e0472a 100644
--- a/internal/executor/create.go
+++ b/internal/executor/create.go
@@ -1,6 +1,7 @@
package executor
import (
+ "errors"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
@@ -19,8 +20,9 @@ func (c *CreateExecutor) Execute() error {
}
funcMap := map[string]func(*client.Client) error{
- resourceList: c.createList,
- resourceStatus: c.createStatus,
+ resourceList: c.createList,
+ resourceStatus: c.createStatus,
+ resourceMediaAttachment: c.createMediaAttachment,
}
doFunc, ok := funcMap[c.resourceType]
@@ -66,21 +68,95 @@ func (c *CreateExecutor) createStatus(gtsClient *client.Client) error {
sensitive bool
)
+ attachmentIDs := []string(c.attachmentIDs)
+
+ if !c.mediaFiles.Empty() {
+ descriptionsExists := false
+ focusValuesExists := false
+ expectedLength := len(c.mediaFiles)
+ mediaDescriptions := make([]string, expectedLength)
+
+ if !c.mediaDescriptions.Empty() {
+ descriptionsExists = true
+
+ if !c.mediaDescriptions.ExpectedLength(expectedLength) {
+ return errors.New("the number of media descriptions does not match the number of media files")
+ }
+ }
+
+ if !c.mediaFocusValues.Empty() {
+ focusValuesExists = true
+
+ if !c.mediaFocusValues.ExpectedLength(expectedLength) {
+ return errors.New("the number of media focus values does not match the number of media files")
+ }
+ }
+
+ if descriptionsExists {
+ for ind := 0; ind < expectedLength; ind++ {
+ content, err := utilities.ReadContents(c.mediaDescriptions[ind])
+ if err != nil {
+ return fmt.Errorf("unable to read the contents from %s: %w", c.mediaDescriptions[ind], err)
+ }
+
+ mediaDescriptions[ind] = content
+ }
+ }
+
+ for ind := 0; ind < expectedLength; ind++ {
+ var (
+ mediaFile string
+ description string
+ focus string
+ )
+
+ mediaFile = c.mediaFiles[ind]
+
+ if descriptionsExists {
+ description = mediaDescriptions[ind]
+ }
+
+ if focusValuesExists {
+ focus = c.mediaFocusValues[ind]
+ }
+
+ attachment, err := gtsClient.CreateMediaAttachment(
+ mediaFile,
+ description,
+ focus,
+ )
+ if err != nil {
+ return fmt.Errorf("unable to create the media attachment for %s: %w", mediaFile, err)
+ }
+
+ attachmentIDs = append(attachmentIDs, attachment.ID)
+ }
+ }
+
switch {
case c.content != "":
content = c.content
case c.fromFile != "":
- content, err = utilities.ReadFile(c.fromFile)
+ content, err = utilities.ReadTextFile(c.fromFile)
if err != nil {
return fmt.Errorf("unable to get the status contents from %q: %w", c.fromFile, err)
}
default:
- return EmptyContentError{
- ResourceType: resourceStatus,
- Hint: "please use --" + flagContent + " or --" + flagFromFile,
+ if len(attachmentIDs) == 0 {
+ // TODO: revisit this error type
+ return EmptyContentError{
+ ResourceType: resourceStatus,
+ Hint: "please use --" + flagContent + " or --" + flagFromFile,
+ }
}
}
+ numAttachmentIDs := len(attachmentIDs)
+
+ if c.addPoll && numAttachmentIDs > 0 {
+ return fmt.Errorf("attaching media to a poll is not allowed")
+ }
+
preferences, err := gtsClient.GetUserPreferences()
if err != nil {
fmt.Println("WARNING: Unable to get your posting preferences: %w", err)
@@ -115,18 +191,23 @@ func (c *CreateExecutor) createStatus(gtsClient *client.Client) error {
}
form := client.CreateStatusForm{
- Content: content,
- ContentType: parsedContentType,
- Language: language,
- SpoilerText: c.spoilerText,
- Boostable: c.boostable,
- Federated: c.federated,
- InReplyTo: c.inReplyTo,
- Likeable: c.likeable,
- Replyable: c.replyable,
- Sensitive: sensitive,
- Visibility: parsedVisibility,
- Poll: nil,
+ Content: content,
+ ContentType: parsedContentType,
+ Language: language,
+ SpoilerText: c.spoilerText,
+ Boostable: c.boostable,
+ Federated: c.federated,
+ InReplyTo: c.inReplyTo,
+ Likeable: c.likeable,
+ Replyable: c.replyable,
+ Sensitive: sensitive,
+ Visibility: parsedVisibility,
+ Poll: nil,
+ AttachmentIDs: nil,
+ }
+
+ if numAttachmentIDs > 0 {
+ form.AttachmentIDs = attachmentIDs
}
if c.addPoll {
@@ -153,3 +234,56 @@ func (c *CreateExecutor) createStatus(gtsClient *client.Client) error {
return nil
}
+
+func (c *CreateExecutor) createMediaAttachment(gtsClient *client.Client) error {
+ expectedNumValues := 1
+ if !c.mediaFiles.ExpectedLength(expectedNumValues) {
+ return fmt.Errorf(
+ "received an unexpected number of media files: want %d",
+ expectedNumValues,
+ )
+ }
+
+ description := ""
+ if !c.mediaDescriptions.Empty() {
+ if !c.mediaDescriptions.ExpectedLength(expectedNumValues) {
+ return fmt.Errorf(
+ "received an unexpected number of media descriptions: want %d",
+ expectedNumValues,
+ )
+ }
+
+ var err error
+ description, err = utilities.ReadContents(c.mediaDescriptions[0])
+ if err != nil {
+ return fmt.Errorf(
+ "unable to read the contents from %s: %w",
+ c.mediaDescriptions[0],
+ )
+ }
+ }
+
+ focus := ""
+ if !c.mediaFocusValues.Empty() {
+ if !c.mediaFocusValues.ExpectedLength(expectedNumValues) {
+ return fmt.Errorf(
+ "received an unexpected number of media focus values: want %d",
+ expectedNumValues,
+ )
+ }
+ focus = c.mediaFocusValues[0]
+ }
+
+ attachment, err := gtsClient.CreateMediaAttachment(
+ c.mediaFiles[0],
+ description,
+ 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
+}
diff --git a/internal/executor/edit.go b/internal/executor/edit.go
index 50c7247..b6097de 100644
--- a/internal/executor/edit.go
+++ b/internal/executor/edit.go
@@ -5,6 +5,7 @@ import (
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
+ "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
)
func (e *EditExecutor) Execute() error {
@@ -13,7 +14,8 @@ func (e *EditExecutor) Execute() error {
}
funcMap := map[string]func(*client.Client) error{
- resourceList: e.editList,
+ resourceList: e.editList,
+ resourceMediaAttachment: e.editMediaAttachment,
}
doFunc, ok := funcMap[e.resourceType]
@@ -57,8 +59,62 @@ func (e *EditExecutor) editList(gtsClient *client.Client) error {
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)
return nil
}
+
+func (e *EditExecutor) editMediaAttachment(gtsClient *client.Client) error {
+ expectedNumValues := 1
+
+ if !e.attachmentIDs.ExpectedLength(expectedNumValues) {
+ return fmt.Errorf(
+ "received an unexpected number of media attachment IDs: want %d",
+ expectedNumValues,
+ )
+ }
+
+ attachment, err := gtsClient.GetMediaAttachment(e.attachmentIDs[0])
+ if err != nil {
+ return fmt.Errorf("unable to get the media attachment: %w", err)
+ }
+
+ description := attachment.Description
+ if !e.mediaDescriptions.Empty() {
+ if !e.mediaDescriptions.ExpectedLength(expectedNumValues) {
+ return fmt.Errorf(
+ "received an unexpected number of media descriptions: want %d",
+ expectedNumValues,
+ )
+ }
+
+ var err error
+ description, err = utilities.ReadContents(e.mediaDescriptions[0])
+ if err != nil {
+ return fmt.Errorf(
+ "unable to read the contents from %s: %w",
+ e.mediaDescriptions[0],
+ )
+ }
+ }
+
+ focus := fmt.Sprintf("%f,%f", attachment.Meta.Focus.X, attachment.Meta.Focus.Y)
+ if !e.mediaFocusValues.Empty() {
+ if !e.mediaFocusValues.ExpectedLength(expectedNumValues) {
+ return fmt.Errorf(
+ "received an unexpected number of media focus values: want %d",
+ expectedNumValues,
+ )
+ }
+ focus = e.mediaFocusValues[0]
+ }
+
+ if _, err = gtsClient.UpdateMediaAttachment(attachment.ID, 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
+}
diff --git a/internal/executor/executors.go b/internal/executor/executors.go
index 4ce5cfc..1ef1ee1 100644
--- a/internal/executor/executors.go
+++ b/internal/executor/executors.go
@@ -121,6 +121,7 @@ type CreateExecutor struct {
printer *printer.Printer
config *config.Config
addPoll bool
+ attachmentIDs internalFlag.StringSliceValue
content string
contentType string
federated bool
@@ -132,6 +133,9 @@ type CreateExecutor struct {
language string
listRepliesPolicy string
listTitle string
+ mediaDescriptions internalFlag.StringSliceValue
+ mediaFocusValues internalFlag.StringSliceValue
+ mediaFiles internalFlag.StringSliceValue
pollAllowsMultipleChoices bool
pollExpiresIn internalFlag.TimeDurationValue
pollHidesVoteCounts bool
@@ -147,17 +151,22 @@ func NewCreateExecutor(
config *config.Config,
) *CreateExecutor {
exe := CreateExecutor{
- FlagSet: flag.NewFlagSet("create", flag.ExitOnError),
- printer: printer,
- config: config,
- pollExpiresIn: internalFlag.NewTimeDurationValue(),
- pollOptions: internalFlag.NewStringSliceValue(),
- sensitive: internalFlag.NewBoolPtrValue(),
+ FlagSet: flag.NewFlagSet("create", flag.ExitOnError),
+ printer: printer,
+ config: config,
+ attachmentIDs: internalFlag.NewStringSliceValue(),
+ mediaDescriptions: internalFlag.NewStringSliceValue(),
+ mediaFocusValues: internalFlag.NewStringSliceValue(),
+ mediaFiles: internalFlag.NewStringSliceValue(),
+ pollExpiresIn: internalFlag.NewTimeDurationValue(),
+ pollOptions: internalFlag.NewStringSliceValue(),
+ sensitive: internalFlag.NewBoolPtrValue(),
}
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.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.contentType, "content-type", "plain", "The type that the contents should be parsed from (valid values are plain and markdown)")
exe.BoolVar(&exe.federated, "enable-federation", true, "Set to true to federate the status beyond the local timelines")
@@ -169,6 +178,9 @@ func NewCreateExecutor(
exe.StringVar(&exe.language, "language", "", "The ISO 639 language code for this status")
exe.StringVar(&exe.listRepliesPolicy, "list-replies-policy", "list", "The replies policy of the list")
exe.StringVar(&exe.listTitle, "list-title", "", "The title of the list")
+ exe.Var(&exe.mediaDescriptions, "media-description", "The description of the media attachment which will be used as the alt-text")
+ exe.Var(&exe.mediaFocusValues, "media-focus", "The focus of the media file")
+ exe.Var(&exe.mediaFiles, "media-file", "The path to the media file")
exe.BoolVar(&exe.pollAllowsMultipleChoices, "poll-allows-multiple-choices", false, "Set to true to allow viewers to make multiple choices in the poll")
exe.Var(&exe.pollExpiresIn, "poll-expires-in", "The duration in which the poll is open for")
exe.BoolVar(&exe.pollHidesVoteCounts, "poll-hides-vote-counts", false, "Set to true to hide the vote count until the poll is closed")
@@ -213,9 +225,12 @@ type EditExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
+ attachmentIDs internalFlag.StringSliceValue
listID string
listTitle string
listRepliesPolicy string
+ mediaDescriptions internalFlag.StringSliceValue
+ mediaFocusValues internalFlag.StringSliceValue
resourceType string
}
@@ -224,16 +239,22 @@ func NewEditExecutor(
config *config.Config,
) *EditExecutor {
exe := EditExecutor{
- FlagSet: flag.NewFlagSet("edit", flag.ExitOnError),
- printer: printer,
- config: config,
+ FlagSet: flag.NewFlagSet("edit", flag.ExitOnError),
+ printer: printer,
+ config: config,
+ attachmentIDs: internalFlag.NewStringSliceValue(),
+ mediaDescriptions: internalFlag.NewStringSliceValue(),
+ mediaFocusValues: internalFlag.NewStringSliceValue(),
}
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.listID, "list-id", "", "The ID of the list in question")
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.Var(&exe.mediaDescriptions, "media-description", "The description of the media attachment which will be used as the alt-text")
+ exe.Var(&exe.mediaFocusValues, "media-focus", "The focus of the media file")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
diff --git a/internal/utilities/file.go b/internal/utilities/file.go
index d6e29a7..ffbdc2c 100644
--- a/internal/utilities/file.go
+++ b/internal/utilities/file.go
@@ -5,9 +5,20 @@ import (
"errors"
"fmt"
"os"
+ "strings"
)
-func ReadFile(path string) (string, error) {
+const filePrefix string = "file@"
+
+func ReadContents(text string) (string, error) {
+ if !strings.HasPrefix(text, filePrefix) {
+ return text, nil
+ }
+
+ return ReadTextFile(strings.TrimPrefix(text, filePrefix))
+}
+
+func ReadTextFile(path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", fmt.Errorf("unable to open %q: %w", path, err)
diff --git a/schema/enbas_cli_schema.json b/schema/enbas_cli_schema.json
index bbed96d..3199e0a 100644
--- a/schema/enbas_cli_schema.json
+++ b/schema/enbas_cli_schema.json
@@ -96,6 +96,18 @@
"type": "string",
"description": "The replies policy of the list"
},
+ "media-description": {
+ "type": "StringSliceValue",
+ "description": "The description of the media attachment which will be used as the alt-text"
+ },
+ "media-file": {
+ "type": "StringSliceValue",
+ "description": "The path to the media file"
+ },
+ "media-focus": {
+ "type": "StringSliceValue",
+ "description": "The focus of the media file"
+ },
"mute-duration": {
"type": "TimeDurationValue",
"description": "Specify how long the mute should last for. To mute indefinitely, set this to 0s"
@@ -234,6 +246,7 @@
"additionalFields": [],
"flags": [
{ "flag": "add-poll", "default": "false" },
+ { "flag": "attachment-id", "fieldName": "attachmentIDs" },
{ "flag": "content", "default": "" },
{ "flag": "content-type", "default": "plain" },
{ "flag": "enable-federation", "fieldName": "federated", "default": "true" },
@@ -245,6 +258,9 @@
{ "flag": "language", "default": "" },
{ "flag": "list-replies-policy", "default": "list" },
{ "flag": "list-title", "default": "" },
+ { "flag": "media-description", "fieldName": "mediaDescriptions" },
+ { "flag": "media-focus", "fieldName": "mediaFocusValues" },
+ { "flag": "media-file", "fieldName": "mediaFiles" },
{ "flag": "poll-allows-multiple-choices", "default": "false" },
{ "flag": "poll-expires-in" },
{ "flag": "poll-hides-vote-counts", "default": "false" },
@@ -271,9 +287,12 @@
"edit": {
"additionalFields": [],
"flags": [
+ { "flag": "attachment-id", "fieldName": "attachmentIDs" },
{ "flag": "list-id", "fieldName": "listID", "default": ""},
{ "flag": "list-title", "default": "" },
{ "flag": "list-replies-policy", "default": "" },
+ { "flag": "media-description", "fieldName": "mediaDescriptions" },
+ { "flag": "media-focus", "fieldName": "mediaFocusValues" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Edit a specific resource",