feat: add ability to interact with lists #4
8 changed files with 485 additions and 15 deletions
75
cmd/enbas/create.go
Normal file
75
cmd/enbas/create.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||
)
|
||||
|
||||
type createCommand struct {
|
||||
*flag.FlagSet
|
||||
|
||||
resourceType string
|
||||
listTitle string
|
||||
listRepliesPolicy string
|
||||
}
|
||||
|
||||
func newCreateCommand(name, summary string) *createCommand {
|
||||
command := createCommand{
|
||||
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
||||
}
|
||||
|
||||
command.StringVar(&command.resourceType, "type", "", "specify the type of resource to create")
|
||||
command.StringVar(&command.listTitle, "list-title", "", "specify the title of the list")
|
||||
command.StringVar(&command.listRepliesPolicy, "list-replies-policy", "list", "specify the policy of the replies for this list (valid values are followed, list and none)")
|
||||
|
||||
command.Usage = commandUsageFunc(name, summary, command.FlagSet)
|
||||
|
||||
return &command
|
||||
}
|
||||
|
||||
func (c *createCommand) Execute() error {
|
||||
if c.resourceType == "" {
|
||||
return errors.New("the type field is not set")
|
||||
}
|
||||
|
||||
gtsClient, err := client.NewClientFromConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the GoToSocial client; %w", err)
|
||||
}
|
||||
|
||||
funcMap := map[string]func(*client.Client) error{
|
||||
"lists": c.createLists,
|
||||
}
|
||||
|
||||
doFunc, ok := funcMap[c.resourceType]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported type %q", c.resourceType)
|
||||
}
|
||||
|
||||
return doFunc(gtsClient)
|
||||
}
|
||||
|
||||
func (c *createCommand) createLists(gtsClient *client.Client) error {
|
||||
if c.listTitle == "" {
|
||||
return errors.New("the list-title flag is not set")
|
||||
}
|
||||
|
||||
repliesPolicy, err := model.ParseListRepliesPolicy(c.listRepliesPolicy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse the list replies policy; %w", err)
|
||||
}
|
||||
|
||||
list, err := gtsClient.CreateList(c.listTitle, repliesPolicy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the list; %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Successfully created the following list:")
|
||||
fmt.Printf("\n%s\n", list)
|
||||
|
||||
return nil
|
||||
}
|
65
cmd/enbas/delete.go
Normal file
65
cmd/enbas/delete.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
|
||||
)
|
||||
|
||||
type deleteCommand struct {
|
||||
*flag.FlagSet
|
||||
|
||||
resourceType string
|
||||
listID string
|
||||
}
|
||||
|
||||
func newDeleteCommand(name, summary string) *deleteCommand {
|
||||
command := deleteCommand{
|
||||
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
||||
}
|
||||
|
||||
command.StringVar(&command.resourceType, "type", "", "specify the type of resource to delete")
|
||||
command.StringVar(&command.listID, "list-id", "", "specify the ID of the list to delete")
|
||||
|
||||
command.Usage = commandUsageFunc(name, summary, command.FlagSet)
|
||||
|
||||
return &command
|
||||
}
|
||||
|
||||
func (c *deleteCommand) Execute() error {
|
||||
if c.resourceType == "" {
|
||||
return errors.New("the type field is not set")
|
||||
}
|
||||
|
||||
gtsClient, err := client.NewClientFromConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the GoToSocial client; %w", err)
|
||||
}
|
||||
|
||||
funcMap := map[string]func(*client.Client) error{
|
||||
"lists": c.deleteList,
|
||||
}
|
||||
|
||||
doFunc, ok := funcMap[c.resourceType]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported resource type %q", c.resourceType)
|
||||
}
|
||||
|
||||
return doFunc(gtsClient)
|
||||
}
|
||||
|
||||
func (c *deleteCommand) deleteList(gtsClient *client.Client) error {
|
||||
if c.listID == "" {
|
||||
return errors.New("the list-id flag is not set")
|
||||
}
|
||||
|
||||
if err := gtsClient.DeleteList(c.listID); err != nil {
|
||||
return fmt.Errorf("unable to delete the list; %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("The list was successfully deleted.")
|
||||
|
||||
return nil
|
||||
}
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
type Executor interface {
|
||||
Name() string
|
||||
Parse([]string) error
|
||||
Parse(args []string) error
|
||||
Execute() error
|
||||
}
|
||||
|
||||
|
@ -21,17 +21,23 @@ func main() {
|
|||
|
||||
func run() error {
|
||||
const (
|
||||
login string = "login"
|
||||
version string = "version"
|
||||
show string = "show"
|
||||
switchAccount string = "switch"
|
||||
login string = "login"
|
||||
version string = "version"
|
||||
showResource string = "show"
|
||||
switchAccount string = "switch"
|
||||
createResource string = "create"
|
||||
deleteResource string = "delete"
|
||||
updateResource string = "update"
|
||||
)
|
||||
|
||||
summaries := map[string]string{
|
||||
login: "login to an account on GoToSocial",
|
||||
version: "print the application's version and build information",
|
||||
show: "print details about a specified resource",
|
||||
switchAccount: "switch to an account",
|
||||
login: "login to an account on GoToSocial",
|
||||
version: "print the application's version and build information",
|
||||
showResource: "print details about a specified resource",
|
||||
switchAccount: "switch to an account",
|
||||
createResource: "create a specific resource",
|
||||
deleteResource: "delete a specific resource",
|
||||
updateResource: "update a specific resource",
|
||||
}
|
||||
|
||||
flag.Usage = enbasUsageFunc(summaries)
|
||||
|
@ -54,10 +60,16 @@ func run() error {
|
|||
executor = newLoginCommand(login, summaries[login])
|
||||
case version:
|
||||
executor = newVersionCommand(version, summaries[version])
|
||||
case show:
|
||||
executor = newShowCommand(show, summaries[show])
|
||||
case showResource:
|
||||
executor = newShowCommand(showResource, summaries[showResource])
|
||||
case switchAccount:
|
||||
executor = newSwitchCommand(switchAccount, summaries[switchAccount])
|
||||
case createResource:
|
||||
executor = newCreateCommand(createResource, summaries[createResource])
|
||||
case deleteResource:
|
||||
executor = newDeleteCommand(deleteResource, summaries[deleteResource])
|
||||
case updateResource:
|
||||
executor = newUpdateCommand(updateResource, summaries[updateResource])
|
||||
default:
|
||||
flag.Usage()
|
||||
return fmt.Errorf("unknown subcommand %q", subcommand)
|
||||
|
|
|
@ -8,12 +8,13 @@ import (
|
|||
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
||||
)
|
||||
|
||||
type showCommand struct {
|
||||
*flag.FlagSet
|
||||
myAccount bool
|
||||
targetType string
|
||||
resourceType string
|
||||
account string
|
||||
statusID string
|
||||
timelineType string
|
||||
|
@ -28,7 +29,7 @@ func newShowCommand(name, summary string) *showCommand {
|
|||
}
|
||||
|
||||
command.BoolVar(&command.myAccount, "my-account", false, "set to true to lookup your account")
|
||||
command.StringVar(&command.targetType, "type", "", "specify the type of resource to display")
|
||||
command.StringVar(&command.resourceType, "type", "", "specify the type of resource to display")
|
||||
command.StringVar(&command.account, "account", "", "specify the account URI to lookup")
|
||||
command.StringVar(&command.statusID, "status-id", "", "specify the ID of the status to display")
|
||||
command.StringVar(&command.timelineType, "timeline-type", "home", "specify the type of timeline to display (valid values are home, public, list and tag)")
|
||||
|
@ -41,6 +42,10 @@ func newShowCommand(name, summary string) *showCommand {
|
|||
}
|
||||
|
||||
func (c *showCommand) Execute() error {
|
||||
if c.resourceType == "" {
|
||||
return errors.New("the type field is not set")
|
||||
}
|
||||
|
||||
gtsClient, err := client.NewClientFromConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the GoToSocial client; %w", err)
|
||||
|
@ -51,11 +56,12 @@ func (c *showCommand) Execute() error {
|
|||
"account": c.showAccount,
|
||||
"status": c.showStatus,
|
||||
"timeline": c.showTimeline,
|
||||
"lists": c.showLists,
|
||||
}
|
||||
|
||||
doFunc, ok := funcMap[c.targetType]
|
||||
doFunc, ok := funcMap[c.resourceType]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported type %q", c.targetType)
|
||||
return fmt.Errorf("unsupported resource type %q", c.resourceType)
|
||||
}
|
||||
|
||||
return doFunc(gtsClient)
|
||||
|
@ -156,3 +162,24 @@ func (c *showCommand) showTimeline(gts *client.Client) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *showCommand) showLists(gts *client.Client) error {
|
||||
lists, err := gts.GetAllLists()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to retrieve the lists; %w", err)
|
||||
}
|
||||
|
||||
if len(lists) == 0 {
|
||||
fmt.Println("You have no lists.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println(utilities.HeaderFormat("LISTS"))
|
||||
|
||||
for i := range lists {
|
||||
fmt.Printf("\n%s\n", lists[i])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
90
cmd/enbas/update.go
Normal file
90
cmd/enbas/update.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||
)
|
||||
|
||||
type updateCommand struct {
|
||||
*flag.FlagSet
|
||||
|
||||
resourceType string
|
||||
listID string
|
||||
listTitle string
|
||||
listRepliesPolicy string
|
||||
}
|
||||
|
||||
func newUpdateCommand(name, summary string) *updateCommand {
|
||||
command := updateCommand{
|
||||
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
||||
}
|
||||
|
||||
command.StringVar(&command.resourceType, "type", "", "specify the type of resource to update")
|
||||
command.StringVar(&command.listID, "list-id", "", "specify the ID of the list to update")
|
||||
command.StringVar(&command.listTitle, "list-title", "", "specify the title of the list")
|
||||
command.StringVar(&command.listRepliesPolicy, "list-replies-policy", "", "specify the policy of the replies for this list (valid values are followed, list and none)")
|
||||
|
||||
command.Usage = commandUsageFunc(name, summary, command.FlagSet)
|
||||
|
||||
return &command
|
||||
}
|
||||
|
||||
func (c *updateCommand) Execute() error {
|
||||
if c.resourceType == "" {
|
||||
return errors.New("the type field is not set")
|
||||
}
|
||||
|
||||
gtsClient, err := client.NewClientFromConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create the GoToSocial client; %w", err)
|
||||
}
|
||||
|
||||
funcMap := map[string]func(*client.Client) error{
|
||||
"lists": c.updateList,
|
||||
}
|
||||
|
||||
doFunc, ok := funcMap[c.resourceType]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported resource type %q", c.resourceType)
|
||||
}
|
||||
|
||||
return doFunc(gtsClient)
|
||||
}
|
||||
|
||||
func (c *updateCommand) updateList(gtsClient *client.Client) error {
|
||||
if c.listID == "" {
|
||||
return errors.New("the list-id flag is not set")
|
||||
}
|
||||
|
||||
list, err := gtsClient.GetList(c.listID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get the list; %w", err)
|
||||
}
|
||||
|
||||
if c.listTitle != "" {
|
||||
list.Title = c.listTitle
|
||||
}
|
||||
|
||||
if c.listRepliesPolicy != "" {
|
||||
repliesPolicy, err := model.ParseListRepliesPolicy(c.listRepliesPolicy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse the list replies policy; %w", err)
|
||||
}
|
||||
|
||||
list.RepliesPolicy = repliesPolicy
|
||||
}
|
||||
|
||||
updatedList, err := gtsClient.UpdateList(list)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update the list; %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Successfully updated the list.")
|
||||
fmt.Printf("\n%s\n", updatedList)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -198,6 +198,10 @@ func (g *Client) sendRequest(method string, url string, requestBody io.Reader, o
|
|||
)
|
||||
}
|
||||
|
||||
if object == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(response.Body).Decode(object); err != nil {
|
||||
return fmt.Errorf(
|
||||
"unable to decode the response from the GoToSocial server; %w",
|
||||
|
|
108
internal/client/lists.go
Normal file
108
internal/client/lists.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||
)
|
||||
|
||||
const (
|
||||
listPath string = "/api/v1/lists"
|
||||
)
|
||||
|
||||
func (g *Client) GetAllLists() ([]model.List, error) {
|
||||
url := g.Authentication.Instance + listPath
|
||||
|
||||
var lists []model.List
|
||||
|
||||
if err := g.sendRequest(http.MethodGet, url, nil, &lists); err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"received an error after sending the request to get the list of lists; %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return lists, nil
|
||||
}
|
||||
|
||||
func (g *Client) GetList(listID string) (model.List, error) {
|
||||
url := g.Authentication.Instance + listPath + "/" + listID
|
||||
|
||||
var list model.List
|
||||
|
||||
if err := g.sendRequest(http.MethodGet, url, nil, &list); err != nil {
|
||||
return model.List{}, fmt.Errorf(
|
||||
"received an error after sending the request to get the list; %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (g *Client) CreateList(title string, repliesPolicy model.ListRepliesPolicy) (model.List, error) {
|
||||
params := struct {
|
||||
Title string `json:"title"`
|
||||
RepliesPolicy model.ListRepliesPolicy `json:"replies_policy"`
|
||||
}{
|
||||
Title: title,
|
||||
RepliesPolicy: repliesPolicy,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return model.List{}, fmt.Errorf("unable to marshal the request body; %w", err)
|
||||
}
|
||||
|
||||
requestBody := bytes.NewBuffer(data)
|
||||
url := g.Authentication.Instance + "/api/v1/lists"
|
||||
|
||||
var list model.List
|
||||
|
||||
if err := g.sendRequest(http.MethodPost, url, requestBody, &list); err != nil {
|
||||
return model.List{}, fmt.Errorf(
|
||||
"received an error after sending the request to create the list; %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (g *Client) UpdateList(listToUpdate model.List) (model.List, error) {
|
||||
params := struct {
|
||||
Title string `json:"title"`
|
||||
RepliesPolicy model.ListRepliesPolicy `json:"replies_policy"`
|
||||
}{
|
||||
Title: listToUpdate.Title,
|
||||
RepliesPolicy: listToUpdate.RepliesPolicy,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return model.List{}, fmt.Errorf("unable to marshal the request body; %w", err)
|
||||
}
|
||||
|
||||
requestBody := bytes.NewBuffer(data)
|
||||
url := g.Authentication.Instance + listPath + "/" + listToUpdate.ID
|
||||
|
||||
var updatedList model.List
|
||||
|
||||
if err := g.sendRequest(http.MethodPut, url, requestBody, &updatedList); err != nil {
|
||||
return model.List{}, fmt.Errorf(
|
||||
"received an error after sending the request to update the list; %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return updatedList, nil
|
||||
}
|
||||
|
||||
func (g *Client) DeleteList(listID string) error {
|
||||
url := g.Authentication.Instance + "/api/v1/lists/" + listID
|
||||
|
||||
return g.sendRequest(http.MethodDelete, url, nil, nil)
|
||||
}
|
89
internal/model/list.go
Normal file
89
internal/model/list.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
||||
)
|
||||
|
||||
type ListRepliesPolicy int
|
||||
|
||||
const (
|
||||
ListRepliesPolicyFollowed ListRepliesPolicy = iota
|
||||
ListRepliesPolicyList
|
||||
ListRepliesPolicyNone
|
||||
)
|
||||
|
||||
func ParseListRepliesPolicy(policy string) (ListRepliesPolicy, error) {
|
||||
switch policy {
|
||||
case "followed":
|
||||
return ListRepliesPolicyFollowed, nil
|
||||
case "list":
|
||||
return ListRepliesPolicyList, nil
|
||||
case "none":
|
||||
return ListRepliesPolicyNone, nil
|
||||
}
|
||||
|
||||
return ListRepliesPolicy(-1), errors.New("invalid list replies policy")
|
||||
}
|
||||
|
||||
func (l ListRepliesPolicy) String() string {
|
||||
switch l {
|
||||
case ListRepliesPolicyFollowed:
|
||||
return "followed"
|
||||
case ListRepliesPolicyList:
|
||||
return "list"
|
||||
case ListRepliesPolicyNone:
|
||||
return "none"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l ListRepliesPolicy) MarshalJSON() ([]byte, error) {
|
||||
str := l.String()
|
||||
if str == "" {
|
||||
return nil, errors.New("invalid list replies policy")
|
||||
}
|
||||
|
||||
return json.Marshal(str)
|
||||
}
|
||||
|
||||
func (l *ListRepliesPolicy) UnmarshalJSON(data []byte) error {
|
||||
var (
|
||||
value string
|
||||
err error
|
||||
)
|
||||
|
||||
if err = json.Unmarshal(data, &value); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal the data; %w", err)
|
||||
}
|
||||
|
||||
*l, err = ParseListRepliesPolicy(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse %s as a list replies policy; %w", value, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type List struct {
|
||||
ID string `json:"id"`
|
||||
RepliesPolicy ListRepliesPolicy `json:"replies_policy"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
func (l List) String() string {
|
||||
format := `%s %s
|
||||
%s %s
|
||||
%s %s`
|
||||
|
||||
return fmt.Sprintf(
|
||||
format,
|
||||
utilities.FieldFormat("List ID:"), l.ID,
|
||||
utilities.FieldFormat("Title:"), l.Title,
|
||||
utilities.FieldFormat("Replies Policy:"), l.RepliesPolicy,
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue