diff --git a/cmd/enbas/add.go b/cmd/enbas/add.go deleted file mode 100644 index f852d1c..0000000 --- a/cmd/enbas/add.go +++ /dev/null @@ -1,147 +0,0 @@ -package main - -import ( - "errors" - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/client" -) - -type addCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - resourceType string - toResourceType string - listID string - accountNames accountNames - content string -} - -func newAddCommand(tlf topLevelFlags, name, summary string) *addCommand { - emptyArr := make([]string, 0, 3) - - command := addCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - accountNames: accountNames(emptyArr), - topLevelFlags: tlf, - } - - command.StringVar(&command.resourceType, flagType, "", "specify the resource type to add (e.g. account, note)") - command.StringVar(&command.toResourceType, flagTo, "", "specify the target resource type to add to (e.g. list, account, etc)") - command.StringVar(&command.listID, flagListID, "", "the ID of the list to add to") - command.Var(&command.accountNames, flagAccountName, "the name of the account to add to the resource") - command.StringVar(&command.content, flagContent, "", "the content of the note") - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *addCommand) Execute() error { - if c.toResourceType == "" { - return flagNotSetError{flagText: flagTo} - } - - funcMap := map[string]func(*client.Client) error{ - resourceList: c.addToList, - resourceAccount: c.addToAccount, - } - - doFunc, ok := funcMap[c.toResourceType] - if !ok { - return unsupportedResourceTypeError{resourceType: c.toResourceType} - } - - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to create the GoToSocial client; %w", err) - } - - return doFunc(gtsClient) -} - -func (c *addCommand) addToList(gtsClient *client.Client) error { - funcMap := map[string]func(*client.Client) error{ - resourceAccount: c.addAccountsToList, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedAddOperationError{ - ResourceType: c.resourceType, - AddToResourceType: c.toResourceType, - } - } - - return doFunc(gtsClient) -} - -func (c *addCommand) addAccountsToList(gtsClient *client.Client) error { - if c.listID == "" { - return flagNotSetError{flagText: flagListID} - } - - if len(c.accountNames) == 0 { - return noAccountSpecifiedError{} - } - - accountIDs := make([]string, len(c.accountNames)) - - for i := range c.accountNames { - accountID, err := getTheirAccountID(gtsClient, c.accountNames[i]) - if err != nil { - return fmt.Errorf("unable to get the account ID for %s, %w", c.accountNames[i], err) - } - - accountIDs[i] = accountID - } - - if err := gtsClient.AddAccountsToList(c.listID, accountIDs); err != nil { - return fmt.Errorf("unable to add the accounts to the list; %w", err) - } - - fmt.Println("Successfully added the account(s) to the list.") - - return nil -} - -func (c *addCommand) addToAccount(gtsClient *client.Client) error { - funcMap := map[string]func(*client.Client) error{ - resourceNote: c.addNoteToAccount, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedAddOperationError{ - ResourceType: c.resourceType, - AddToResourceType: c.toResourceType, - } - } - - return doFunc(gtsClient) -} - -func (c *addCommand) addNoteToAccount(gtsClient *client.Client) error { - if len(c.accountNames) != 1 { - return fmt.Errorf("unexpected number of accounts specified; want 1, got %d", len(c.accountNames)) - } - - accountID, err := getAccountID(gtsClient, false, c.accountNames[0], c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("received an error while getting the account ID; %w", err) - } - - if c.content == "" { - return errors.New("the note content should not be empty") - } - - if err := gtsClient.SetPrivateNote(accountID, c.content); err != nil { - return fmt.Errorf("unable to add the private note to the account; %w", err) - } - - fmt.Println("Successfully added the private note to the account.") - - return nil -} diff --git a/cmd/enbas/block.go b/cmd/enbas/block.go deleted file mode 100644 index 1d6201e..0000000 --- a/cmd/enbas/block.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/client" -) - -type blockCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - resourceType string - accountName string - unblock bool -} - -func newBlockCommand(tlf topLevelFlags, name, summary string, unblock bool) *blockCommand { - command := blockCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - - topLevelFlags: tlf, - unblock: unblock, - } - - command.StringVar(&command.resourceType, flagType, "", "specify the type of resource to block or unblock") - command.StringVar(&command.accountName, flagAccountName, "", "specify the account name in full (username@domain)") - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *blockCommand) Execute() error { - funcMap := map[string]func(*client.Client) error{ - resourceAccount: c.blockAccount, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} - } - - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to create the GoToSocial client; %w", err) - } - - return doFunc(gtsClient) -} - -func (c *blockCommand) blockAccount(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, false, c.accountName, c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("received an error while getting the account ID; %w", err) - } - - if c.unblock { - return c.unblockAccount(gtsClient, accountID) - } - - if err := gtsClient.BlockAccount(accountID); err != nil { - return fmt.Errorf("unable to block the account; %w", err) - } - - fmt.Println("Successfully blocked the account.") - - return nil -} - -func (c *blockCommand) unblockAccount(gtsClient *client.Client, accountID string) error { - if err := gtsClient.UnblockAccount(accountID); err != nil { - return fmt.Errorf("unable to unblock the account; %w", err) - } - - fmt.Println("Successfully unblocked the account.") - - return nil -} diff --git a/cmd/enbas/delete.go b/cmd/enbas/delete.go deleted file mode 100644 index a0c4cd3..0000000 --- a/cmd/enbas/delete.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/client" -) - -type deleteCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - resourceType string - listID string -} - -func newDeleteCommand(tlf topLevelFlags, name, summary string) *deleteCommand { - command := deleteCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - - topLevelFlags: tlf, - } - - command.StringVar(&command.resourceType, flagType, "", "specify the type of resource to delete") - command.StringVar(&command.listID, flagListID, "", "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 flagNotSetError{flagText: flagType} - } - - funcMap := map[string]func(*client.Client) error{ - resourceList: c.deleteList, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} - } - - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to create the GoToSocial client; %w", err) - } - - return doFunc(gtsClient) -} - -func (c *deleteCommand) deleteList(gtsClient *client.Client) error { - if c.listID == "" { - return flagNotSetError{flagText: flagListID} - } - - 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 -} diff --git a/cmd/enbas/errors.go b/cmd/enbas/errors.go index 670113d..739acbc 100644 --- a/cmd/enbas/errors.go +++ b/cmd/enbas/errors.go @@ -1,57 +1,9 @@ package main -type flagNotSetError struct { - flagText string -} - -func (e flagNotSetError) Error() string { - return "the flag '" + e.flagText + "' is not set" -} - -type unsupportedResourceTypeError struct { - resourceType string -} - -func (e unsupportedResourceTypeError) Error() string { - return "unsupported resource type '" + e.resourceType + "'" -} - -type invalidTimelineCategoryError struct { - category string -} - -func (e invalidTimelineCategoryError) Error() string { - return "'" + e.category + "' is not a valid timeline category (please choose home, public, tag or list)" -} - -type unknownSubcommandError struct { +type unknownCommandError struct { subcommand string } -func (e unknownSubcommandError) Error() string { - return "unknown subcommand '" + e.subcommand + "'" -} - -type noAccountSpecifiedError struct{} - -func (e noAccountSpecifiedError) Error() string { - return "no account specified in this request" -} - -type unsupportedAddOperationError struct { - ResourceType string - AddToResourceType string -} - -func (e unsupportedAddOperationError) Error() string { - return "adding '" + e.ResourceType + "' to '" + e.AddToResourceType + "' is not supported" -} - -type unsupportedRemoveOperationError struct { - ResourceType string - RemoveFromResourceType string -} - -func (e unsupportedRemoveOperationError) Error() string { - return "removing '" + e.ResourceType + "' from '" + e.RemoveFromResourceType + "' is not supported" +func (e unknownCommandError) Error() string { + return "unknown command '" + e.subcommand + "'" } diff --git a/cmd/enbas/flags.go b/cmd/enbas/flags.go deleted file mode 100644 index 912c9df..0000000 --- a/cmd/enbas/flags.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import "strings" - -type accountNames []string - -func (a *accountNames) String() string { - return strings.Join(*a, ", ") -} - -func (a *accountNames) Set(value string) error { - if len(value) > 0 { - *a = append(*a, value) - } - - return nil -} - -type topLevelFlags struct { - configDir string -} diff --git a/cmd/enbas/main.go b/cmd/enbas/main.go index 54f4d1c..1dcbd40 100644 --- a/cmd/enbas/main.go +++ b/cmd/enbas/main.go @@ -4,47 +4,36 @@ import ( "flag" "fmt" "os" + "slices" + "strings" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/executor" ) const ( - flagAccountName = "account-name" - flagTo = "to" - flagContent = "content" - flagInstance = "instance" - flagLimit = "limit" - flagListID = "list-id" - flagListTitle = "list-title" - flagListRepliesPolicy = "list-replies-policy" - flagMyAccount = "my-account" - flagNotify = "notify" - flagFrom = "from" - flagType = "type" - flagShowRelationship = "show-relationship" - flagShowPreferences = "show-preferences" - flagShowReposts = "show-reposts" - flagStatusID = "status-id" - flagTag = "tag" - flagTimelineCategory = "timeline-category" + commandLogin string = "login" + commandVersion string = "version" + commandShow string = "show" + commandSwitch string = "switch" + commandCreate string = "create" + commandDelete string = "delete" + commandEdit string = "edit" + commandWhoami string = "whoami" + commandAdd string = "add" + commandRemove string = "remove" + commandFollow string = "follow" + commandUnfollow string = "unfollow" + commandBlock string = "block" + commandUnblock string = "unblock" ) -const ( - resourceAccount = "account" - resourceBlocked = "blocked" - resourceFollowers = "followers" - resourceFollowing = "following" - resourceInstance = "instance" - resourceList = "list" - resourceNote = "note" - resourceStatus = "status" - resourceTimeline = "timeline" +var ( + binaryVersion string + buildTime string + goVersion string + gitCommit string ) -type Executor interface { - Name() string - Parse(args []string) error - Execute() error -} - func main() { if err := run(); err != nil { fmt.Printf("ERROR: %v.\n", err) @@ -53,45 +42,28 @@ func main() { } func run() error { - const ( - commandLogin string = "login" - commandVersion string = "version" - commandShow string = "show" - commandSwitchAccount string = "switch-account" - commandCreate string = "create" - commandDelete string = "delete" - commandUpdate string = "update" - commandWhoami string = "whoami" - commandAdd string = "add" - commandRemove string = "remove" - commandFollow string = "follow" - commandUnfollow string = "unfollow" - commandBlock string = "block" - commandUnblock string = "unblock" - ) - commandSummaries := map[string]string{ - commandLogin: "login to an account on GoToSocial", - commandVersion: "print the application's version and build information", - commandShow: "print details about a specified resource", - commandSwitchAccount: "switch to a different account", - commandCreate: "create a specific resource", - commandDelete: "delete a specific resource", - commandUpdate: "update a specific resource", - commandWhoami: "print the account that you are currently logged in to", - commandAdd: "add a resource to another resource", - commandRemove: "remove a resource from another resource", - commandFollow: "follow a resource (e.g. an account)", - commandUnfollow: "unfollow a resource (e.g. an account)", - commandBlock: "block a resource (e.g. an account)", - commandUnblock: "unblock a resource (e.g. an account)", + commandLogin: "login to an account on GoToSocial", + commandVersion: "print the application's version and build information", + commandShow: "print details about a specified resource", + commandSwitch: "switch to a different account", + commandCreate: "create a specific resource", + commandDelete: "delete a specific resource", + commandEdit: "edit a specific resource", + commandWhoami: "print the account that you are currently logged in to", + commandAdd: "add a resource to another resource", + commandRemove: "remove a resource from another resource", + commandFollow: "follow a resource (e.g. an account)", + commandUnfollow: "unfollow a resource (e.g. an account)", + commandBlock: "block a resource (e.g. an account)", + commandUnblock: "unblock a resource (e.g. an account)", } - tlf := topLevelFlags{} + topLevelFlags := executor.TopLevelFlags{} - flag.StringVar(&tlf.configDir, "config-dir", "", "specify your config directory") + flag.StringVar(&topLevelFlags.ConfigDir, "config-dir", "", "specify your config directory") - flag.Usage = enbasUsageFunc(commandSummaries) + flag.Usage = usageFunc(commandSummaries) flag.Parse() @@ -104,54 +76,143 @@ func run() error { command := flag.Arg(0) args := flag.Args()[1:] - // TODO: will not be needed anymore - var executor Executor + var err error switch command { - case commandLogin: - executor = newLoginCommand(tlf, commandLogin, commandSummaries[commandLogin]) - case commandVersion: - executor = newVersionCommand(commandVersion, commandSummaries[commandVersion]) - case commandShow: - executor = newShowCommand(tlf, commandShow, commandSummaries[commandShow]) - case commandSwitchAccount: - executor = newSwitchCommand(tlf, commandSwitchAccount, commandSummaries[commandSwitchAccount]) - case commandCreate: - executor = newCreateCommand(tlf, commandCreate, commandSummaries[commandCreate]) - case commandDelete: - executor = newDeleteCommand(tlf, commandDelete, commandSummaries[commandDelete]) - case commandUpdate: - executor = newUpdateCommand(tlf, commandUpdate, commandSummaries[commandUpdate]) - case commandWhoami: - executor = newWhoAmICommand(tlf, commandWhoami, commandSummaries[commandWhoami]) case commandAdd: - executor = newAddCommand(tlf, commandAdd, commandSummaries[commandAdd]) - case commandRemove: - executor = newRemoveCommand(tlf, commandRemove, commandSummaries[commandRemove]) - case commandFollow: - executor = newFollowCommand(tlf, commandFollow, commandSummaries[commandFollow], false) - case commandUnfollow: - executor = newFollowCommand(tlf, commandUnfollow, commandSummaries[commandUnfollow], true) + exe := executor.NewAddExecutor( + topLevelFlags, + commandAdd, + commandSummaries[commandAdd], + ) + err = executor.Execute(exe, args) case commandBlock: - executor = newBlockCommand(tlf, commandBlock, commandSummaries[commandBlock], false) + exe := executor.NewBlockExecutor( + topLevelFlags, + commandBlock, + commandSummaries[commandBlock], + false, + ) + err = executor.Execute(exe, args) + case commandCreate: + exe := executor.NewCreateExecutor( + topLevelFlags, + commandCreate, + commandSummaries[commandCreate], + ) + err = executor.Execute(exe, args) + case commandDelete: + exe := executor.NewDeleteExecutor( + topLevelFlags, + commandDelete, + commandSummaries[commandDelete], + ) + err = executor.Execute(exe, args) + case commandEdit: + exe := executor.NewEditExecutor( + topLevelFlags, + commandEdit, + commandSummaries[commandEdit], + ) + err = executor.Execute(exe, args) + case commandFollow: + exe := executor.NewFollowExecutor( + topLevelFlags, + commandFollow, + commandSummaries[commandFollow], + false, + ) + err = executor.Execute(exe, args) + case commandLogin: + exe := executor.NewLoginExecutor( + topLevelFlags, + commandLogin, + commandSummaries[commandLogin], + ) + err = executor.Execute(exe, args) + case commandRemove: + exe := executor.NewRemoveExecutor( + topLevelFlags, + commandRemove, + commandSummaries[commandRemove], + ) + err = executor.Execute(exe, args) + case commandSwitch: + exe := executor.NewSwitchExecutor( + topLevelFlags, + commandSwitch, + commandSummaries[commandSwitch], + ) + err = executor.Execute(exe, args) + case commandUnfollow: + exe := executor.NewFollowExecutor(topLevelFlags, commandUnfollow, commandSummaries[commandUnfollow], true) + err = executor.Execute(exe, args) case commandUnblock: - executor = newBlockCommand(tlf, commandUnblock, commandSummaries[commandUnblock], true) + exe := executor.NewBlockExecutor(topLevelFlags, commandUnblock, commandSummaries[commandUnblock], true) + err = executor.Execute(exe, args) + case commandShow: + exe := executor.NewShowExecutor(topLevelFlags, commandShow, commandSummaries[commandShow]) + err = executor.Execute(exe, args) + case commandVersion: + exe := executor.NewVersionExecutor( + commandVersion, + commandSummaries[commandVersion], + binaryVersion, + buildTime, + goVersion, + gitCommit, + ) + err = executor.Execute(exe, args) + case commandWhoami: + exe := executor.NewWhoAmIExecutor(topLevelFlags, commandWhoami, commandSummaries[commandWhoami]) + err = executor.Execute(exe, args) default: flag.Usage() - return unknownSubcommandError{command} + return unknownCommandError{command} } - // To Do: create a function in executor package that will take in concrete type - // as interface and run the parse and execute command. - - if err := executor.Parse(args); err != nil { - return fmt.Errorf("unable to parse the command line flags; %w", err) - } - - if err := executor.Execute(); err != nil { - return fmt.Errorf("received an error after executing %q; %w", executor.Name(), err) + if err != nil { + return fmt.Errorf("received an error executing the command; %w", err) } return nil } + +func usageFunc(summaries map[string]string) func() { + cmds := make([]string, len(summaries)) + ind := 0 + + for k := range summaries { + cmds[ind] = k + ind++ + } + + slices.Sort(cmds) + + return func() { + var builder strings.Builder + + builder.WriteString("SUMMARY:\n enbas - A GoToSocial client for the terminal.\n\n") + + if binaryVersion != "" { + builder.WriteString("VERSION:\n " + binaryVersion + "\n\n") + } + + builder.WriteString("USAGE:\n enbas [flags]\n enbas [command]\n\nCOMMANDS:") + + for _, cmd := range cmds { + fmt.Fprintf(&builder, "\n %s\t%s", cmd, summaries[cmd]) + } + + builder.WriteString("\n\nFLAGS:\n --help\n print the help message\n") + flag.VisitAll(func(f *flag.Flag) { + fmt.Fprintf(&builder, "\n --%s\n %s\n", f.Name, f.Usage) + }) + + builder.WriteString("\nUse \"enbas [command] --help\" for more information about a command.\n") + + w := flag.CommandLine.Output() + fmt.Fprint(w, builder.String()) + } +} diff --git a/cmd/enbas/remove.go b/cmd/enbas/remove.go deleted file mode 100644 index 5f09436..0000000 --- a/cmd/enbas/remove.go +++ /dev/null @@ -1,140 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/client" -) - -type removeCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - resourceType string - fromResourceType string - listID string - accountNames accountNames -} - -func newRemoveCommand(tlf topLevelFlags, name, summary string) *removeCommand { - emptyArr := make([]string, 0, 3) - - command := removeCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - accountNames: accountNames(emptyArr), - topLevelFlags: tlf, - } - - command.StringVar(&command.resourceType, flagType, "", "specify the resource type to remove (e.g. account, note)") - command.StringVar(&command.fromResourceType, flagFrom, "", "specify the resource type to remove from (e.g. list, account, etc)") - command.StringVar(&command.listID, flagListID, "", "the ID of the list to remove from") - command.Var(&command.accountNames, flagAccountName, "the name of the account to remove from the resource") - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *removeCommand) Execute() error { - if c.fromResourceType == "" { - return flagNotSetError{flagText: flagFrom} - } - - funcMap := map[string]func(*client.Client) error{ - resourceList: c.removeFromList, - resourceAccount: c.removeFromAccount, - } - - doFunc, ok := funcMap[c.fromResourceType] - if !ok { - return unsupportedResourceTypeError{resourceType: c.fromResourceType} - } - - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to create the GoToSocial client; %w", err) - } - - return doFunc(gtsClient) -} - -func (c *removeCommand) removeFromList(gtsClient *client.Client) error { - funcMap := map[string]func(*client.Client) error{ - resourceAccount: c.removeAccountsFromList, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedRemoveOperationError{ - ResourceType: c.resourceType, - RemoveFromResourceType: c.fromResourceType, - } - } - - return doFunc(gtsClient) -} - -func (c *removeCommand) removeAccountsFromList(gtsClient *client.Client) error { - if c.listID == "" { - return flagNotSetError{flagText: flagListID} - } - - if len(c.accountNames) == 0 { - return noAccountSpecifiedError{} - } - - accountIDs := make([]string, len(c.accountNames)) - - for i := range c.accountNames { - accountID, err := getTheirAccountID(gtsClient, c.accountNames[i]) - if err != nil { - return fmt.Errorf("unable to get the account ID for %s, %w", c.accountNames[i], err) - } - - accountIDs[i] = accountID - } - - if err := gtsClient.RemoveAccountsFromList(c.listID, accountIDs); err != nil { - return fmt.Errorf("unable to remove the accounts from the list; %w", err) - } - - fmt.Println("Successfully removed the account(s) from the list.") - - return nil -} - -func (c *removeCommand) removeFromAccount(gtsClient *client.Client) error { - funcMap := map[string]func(*client.Client) error{ - resourceNote: c.removeNoteFromAccount, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedRemoveOperationError{ - ResourceType: c.resourceType, - RemoveFromResourceType: c.fromResourceType, - } - } - - return doFunc(gtsClient) -} - -func (c *removeCommand) removeNoteFromAccount(gtsClient *client.Client) error { - if len(c.accountNames) != 1 { - return fmt.Errorf("unexpected number of accounts specified; want 1, got %d", len(c.accountNames)) - } - - accountID, err := getAccountID(gtsClient, false, c.accountNames[0], c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("received an error while getting the account ID; %w", err) - } - - if err := gtsClient.SetPrivateNote(accountID, ""); err != nil { - return fmt.Errorf("unable to remove the private note from the account; %w", err) - } - - fmt.Println("Successfully removed the private note from the account.") - - return nil -} diff --git a/cmd/enbas/switch.go b/cmd/enbas/switch.go deleted file mode 100644 index 71825d9..0000000 --- a/cmd/enbas/switch.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/config" -) - -type switchCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - toAccount string -} - -func newSwitchCommand(tlf topLevelFlags, name, summary string) *switchCommand { - command := switchCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, - } - - command.StringVar(&command.toAccount, flagTo, "", "the account to switch to") - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *switchCommand) Execute() error { - if c.toAccount == "" { - return flagNotSetError{flagText: flagTo} - } - - if err := config.UpdateCurrentAccount(c.toAccount, c.topLevelFlags.configDir); err != nil { - return fmt.Errorf("unable to switch accounts; %w", err) - } - - fmt.Printf("The current account is now set to %q.\n", c.toAccount) - - return nil -} diff --git a/cmd/enbas/update.go b/cmd/enbas/update.go deleted file mode 100644 index 569510c..0000000 --- a/cmd/enbas/update.go +++ /dev/null @@ -1,91 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/client" - "codeflow.dananglin.me.uk/apollo/enbas/internal/model" -) - -type updateCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - resourceType string - listID string - listTitle string - listRepliesPolicy string -} - -func newUpdateCommand(tlf topLevelFlags, name, summary string) *updateCommand { - command := updateCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, - } - - command.StringVar(&command.resourceType, flagType, "", "specify the type of resource to update") - command.StringVar(&command.listID, flagListID, "", "specify the ID of the list to update") - command.StringVar(&command.listTitle, flagListTitle, "", "specify the title of the list") - command.StringVar(&command.listRepliesPolicy, flagListRepliesPolicy, "", "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 flagNotSetError{flagText: flagType} - } - - funcMap := map[string]func(*client.Client) error{ - resourceList: c.updateList, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} - } - - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to create the GoToSocial client; %w", err) - } - - return doFunc(gtsClient) -} - -func (c *updateCommand) updateList(gtsClient *client.Client) error { - if c.listID == "" { - return flagNotSetError{flagText: flagListID} - } - - 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.Println(updatedList) - - return nil -} diff --git a/cmd/enbas/usage.go b/cmd/enbas/usage.go deleted file mode 100644 index e055937..0000000 --- a/cmd/enbas/usage.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "slices" - "strings" -) - -func commandUsageFunc(name, summary string, flagset *flag.FlagSet) func() { - return func() { - var builder strings.Builder - - fmt.Fprintf( - &builder, - "SUMMARY:\n %s - %s\n\nUSAGE:\n enbas %s [flags]\n\nFLAGS:", - name, - summary, - name, - ) - - flagset.VisitAll(func(f *flag.Flag) { - fmt.Fprintf( - &builder, - "\n --%s\n %s", - f.Name, - f.Usage, - ) - }) - - builder.WriteString("\n") - - w := flag.CommandLine.Output() - - fmt.Fprint(w, builder.String()) - } -} - -func enbasUsageFunc(summaries map[string]string) func() { - cmds := make([]string, len(summaries)) - ind := 0 - - for k := range summaries { - cmds[ind] = k - ind++ - } - - slices.Sort(cmds) - - return func() { - var builder strings.Builder - - builder.WriteString("SUMMARY:\n enbas - A GoToSocial client for the terminal.\n\n") - - if binaryVersion != "" { - builder.WriteString("VERSION:\n " + binaryVersion + "\n\n") - } - - builder.WriteString("USAGE:\n enbas [flags]\n enbas [command]\n\nCOMMANDS:") - - for _, cmd := range cmds { - fmt.Fprintf(&builder, "\n %s\t%s", cmd, summaries[cmd]) - } - - builder.WriteString("\n\nFLAGS:\n --help\n print the help message\n") - flag.VisitAll(func(f *flag.Flag) { - fmt.Fprintf(&builder, "\n --%s\n %s\n", f.Name, f.Usage) - }) - - builder.WriteString("\nUse \"enbas [command] --help\" for more information about a command.\n") - - w := flag.CommandLine.Output() - fmt.Fprint(w, builder.String()) - } -} diff --git a/cmd/enbas/account.go b/internal/executor/account.go similarity index 97% rename from cmd/enbas/account.go rename to internal/executor/account.go index 91d29f6..a88ccf0 100644 --- a/cmd/enbas/account.go +++ b/internal/executor/account.go @@ -1,4 +1,4 @@ -package main +package executor import ( "fmt" @@ -26,7 +26,7 @@ func getAccountID(gtsClient *client.Client, myAccount bool, accountName, configD return "", fmt.Errorf("unable to get their account ID; %w", err) } default: - return "", noAccountSpecifiedError{} + return "", NoAccountSpecifiedError{} } return accountID, nil diff --git a/internal/executor/add.go b/internal/executor/add.go new file mode 100644 index 0000000..d7956a8 --- /dev/null +++ b/internal/executor/add.go @@ -0,0 +1,146 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" +) + +type AddExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + toResourceType string + listID string + accountNames AccountNames + content string +} + +func NewAddExecutor(tlf TopLevelFlags, name, summary string) *AddExecutor { + emptyArr := make([]string, 0, 3) + + addExe := AddExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + accountNames: AccountNames(emptyArr), + topLevelFlags: tlf, + } + + addExe.StringVar(&addExe.resourceType, flagType, "", "specify the resource type to add (e.g. account, note)") + addExe.StringVar(&addExe.toResourceType, flagTo, "", "specify the target resource type to add to (e.g. list, account, etc)") + addExe.StringVar(&addExe.listID, flagListID, "", "the ID of the list to add to") + addExe.Var(&addExe.accountNames, flagAccountName, "the name of the account to add to the resource") + addExe.StringVar(&addExe.content, flagContent, "", "the content of the note") + + addExe.Usage = commandUsageFunc(name, summary, addExe.FlagSet) + + return &addExe +} + +func (a *AddExecutor) Execute() error { + if a.toResourceType == "" { + return FlagNotSetError{flagText: flagTo} + } + + funcMap := map[string]func(*client.Client) error{ + resourceList: a.addToList, + resourceAccount: a.addToAccount, + } + + doFunc, ok := funcMap[a.toResourceType] + if !ok { + return UnsupportedTypeError{resourceType: a.toResourceType} + } + + gtsClient, err := client.NewClientFromConfig(a.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + return doFunc(gtsClient) +} + +func (a *AddExecutor) addToList(gtsClient *client.Client) error { + funcMap := map[string]func(*client.Client) error{ + resourceAccount: a.addAccountsToList, + } + + doFunc, ok := funcMap[a.resourceType] + if !ok { + return UnsupportedAddOperationError{ + ResourceType: a.resourceType, + AddToResourceType: a.toResourceType, + } + } + + return doFunc(gtsClient) +} + +func (a *AddExecutor) addAccountsToList(gtsClient *client.Client) error { + if a.listID == "" { + return FlagNotSetError{flagText: flagListID} + } + + if len(a.accountNames) == 0 { + return NoAccountSpecifiedError{} + } + + accountIDs := make([]string, len(a.accountNames)) + + for ind := range a.accountNames { + accountID, err := getTheirAccountID(gtsClient, a.accountNames[ind]) + if err != nil { + return fmt.Errorf("unable to get the account ID for %s, %w", a.accountNames[ind], err) + } + + accountIDs[ind] = accountID + } + + if err := gtsClient.AddAccountsToList(a.listID, accountIDs); err != nil { + return fmt.Errorf("unable to add the accounts to the list; %w", err) + } + + fmt.Println("Successfully added the account(s) to the list.") + + return nil +} + +func (a *AddExecutor) addToAccount(gtsClient *client.Client) error { + funcMap := map[string]func(*client.Client) error{ + resourceNote: a.addNoteToAccount, + } + + doFunc, ok := funcMap[a.resourceType] + if !ok { + return UnsupportedAddOperationError{ + ResourceType: a.resourceType, + AddToResourceType: a.toResourceType, + } + } + + return doFunc(gtsClient) +} + +func (a *AddExecutor) addNoteToAccount(gtsClient *client.Client) error { + if len(a.accountNames) != 1 { + return fmt.Errorf("unexpected number of accounts specified; want 1, got %d", len(a.accountNames)) + } + + accountID, err := getAccountID(gtsClient, false, a.accountNames[0], a.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("received an error while getting the account ID; %w", err) + } + + if a.content == "" { + return EmptyContentError{} + } + + if err := gtsClient.SetPrivateNote(accountID, a.content); err != nil { + return fmt.Errorf("unable to add the private note to the account; %w", err) + } + + fmt.Println("Successfully added the private note to the account.") + + return nil +} diff --git a/internal/executor/block.go b/internal/executor/block.go new file mode 100644 index 0000000..33b73f3 --- /dev/null +++ b/internal/executor/block.go @@ -0,0 +1,80 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" +) + +type BlockExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + accountName string + unblock bool +} + +func NewBlockExecutor(tlf TopLevelFlags, name, summary string, unblock bool) *BlockExecutor { + blockExe := BlockExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + topLevelFlags: tlf, + unblock: unblock, + } + + blockExe.StringVar(&blockExe.resourceType, flagType, "", "specify the type of resource to block or unblock") + blockExe.StringVar(&blockExe.accountName, flagAccountName, "", "specify the account name in full (username@domain)") + + blockExe.Usage = commandUsageFunc(name, summary, blockExe.FlagSet) + + return &blockExe +} + +func (b *BlockExecutor) Execute() error { + funcMap := map[string]func(*client.Client) error{ + resourceAccount: b.blockAccount, + } + + doFunc, ok := funcMap[b.resourceType] + if !ok { + return UnsupportedTypeError{resourceType: b.resourceType} + } + + gtsClient, err := client.NewClientFromConfig(b.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + return doFunc(gtsClient) +} + +func (b *BlockExecutor) blockAccount(gtsClient *client.Client) error { + accountID, err := getAccountID(gtsClient, false, b.accountName, b.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("received an error while getting the account ID; %w", err) + } + + if b.unblock { + return b.unblockAccount(gtsClient, accountID) + } + + if err := gtsClient.BlockAccount(accountID); err != nil { + return fmt.Errorf("unable to block the account; %w", err) + } + + fmt.Println("Successfully blocked the account.") + + return nil +} + +func (b *BlockExecutor) unblockAccount(gtsClient *client.Client, accountID string) error { + if err := gtsClient.UnblockAccount(accountID); err != nil { + return fmt.Errorf("unable to unblock the account; %w", err) + } + + fmt.Println("Successfully unblocked the account.") + + return nil +} diff --git a/internal/executor/const.go b/internal/executor/const.go new file mode 100644 index 0000000..0d61614 --- /dev/null +++ b/internal/executor/const.go @@ -0,0 +1,32 @@ +package executor + +const ( + flagAccountName = "account-name" + flagContent = "content" + flagInstance = "instance" + flagLimit = "limit" + flagListID = "list-id" + flagListTitle = "list-title" + flagListRepliesPolicy = "list-replies-policy" + flagMyAccount = "my-account" + flagNotify = "notify" + flagFrom = "from" + flagType = "type" + flagShowRelationship = "show-relationship" + flagShowPreferences = "show-preferences" + flagShowReposts = "show-reposts" + flagStatusID = "status-id" + flagTag = "tag" + flagTimelineCategory = "timeline-category" + flagTo = "to" + + resourceAccount = "account" + resourceBlocked = "blocked" + resourceFollowers = "followers" + resourceFollowing = "following" + resourceInstance = "instance" + resourceList = "list" + resourceNote = "note" + resourceStatus = "status" + resourceTimeline = "timeline" +) diff --git a/cmd/enbas/create.go b/internal/executor/create.go similarity index 51% rename from cmd/enbas/create.go rename to internal/executor/create.go index ea3b9a8..0dcf6e3 100644 --- a/cmd/enbas/create.go +++ b/internal/executor/create.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -8,37 +8,37 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/model" ) -type createCommand struct { +type CreateExecutor struct { *flag.FlagSet - topLevelFlags topLevelFlags + topLevelFlags TopLevelFlags resourceType string listTitle string listRepliesPolicy string } -func newCreateCommand(tlf topLevelFlags, name, summary string) *createCommand { - command := createCommand{ +func NewCreateExecutor(tlf TopLevelFlags, name, summary string) *CreateExecutor { + createExe := CreateExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), topLevelFlags: tlf, } - command.StringVar(&command.resourceType, flagType, "", "specify the type of resource to create") - command.StringVar(&command.listTitle, flagListTitle, "", "specify the title of the list") - command.StringVar(&command.listRepliesPolicy, flagListRepliesPolicy, "list", "specify the policy of the replies for this list (valid values are followed, list and none)") + createExe.StringVar(&createExe.resourceType, flagType, "", "specify the type of resource to create") + 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)") - command.Usage = commandUsageFunc(name, summary, command.FlagSet) + createExe.Usage = commandUsageFunc(name, summary, createExe.FlagSet) - return &command + return &createExe } -func (c *createCommand) Execute() error { +func (c *CreateExecutor) Execute() error { if c.resourceType == "" { - return flagNotSetError{flagText: flagType} + return FlagNotSetError{flagText: flagType} } - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) + gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client; %w", err) } @@ -49,15 +49,15 @@ func (c *createCommand) Execute() error { doFunc, ok := funcMap[c.resourceType] if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} + return UnsupportedTypeError{resourceType: c.resourceType} } return doFunc(gtsClient) } -func (c *createCommand) createList(gtsClient *client.Client) error { +func (c *CreateExecutor) createList(gtsClient *client.Client) error { if c.listTitle == "" { - return flagNotSetError{flagText: flagListTitle} + return FlagNotSetError{flagText: flagListTitle} } repliesPolicy, err := model.ParseListRepliesPolicy(c.listRepliesPolicy) diff --git a/internal/executor/delete.go b/internal/executor/delete.go new file mode 100644 index 0000000..b95a18b --- /dev/null +++ b/internal/executor/delete.go @@ -0,0 +1,66 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" +) + +type DeleteExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + listID string +} + +func NewDeleteExecutor(tlf TopLevelFlags, name, summary string) *DeleteExecutor { + deleteExe := DeleteExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + topLevelFlags: tlf, + } + + deleteExe.StringVar(&deleteExe.resourceType, flagType, "", "specify the type of resource to delete") + deleteExe.StringVar(&deleteExe.listID, flagListID, "", "specify the ID of the list to delete") + + deleteExe.Usage = commandUsageFunc(name, summary, deleteExe.FlagSet) + + return &deleteExe +} + +func (d *DeleteExecutor) Execute() error { + if d.resourceType == "" { + return FlagNotSetError{flagText: flagType} + } + + funcMap := map[string]func(*client.Client) error{ + resourceList: d.deleteList, + } + + doFunc, ok := funcMap[d.resourceType] + if !ok { + return UnsupportedTypeError{resourceType: d.resourceType} + } + + gtsClient, err := client.NewClientFromConfig(d.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + return doFunc(gtsClient) +} + +func (d *DeleteExecutor) deleteList(gtsClient *client.Client) error { + if d.listID == "" { + return FlagNotSetError{flagText: flagListID} + } + + if err := gtsClient.DeleteList(d.listID); err != nil { + return fmt.Errorf("unable to delete the list; %w", err) + } + + fmt.Println("The list was successfully deleted.") + + return nil +} diff --git a/internal/executor/errors.go b/internal/executor/errors.go new file mode 100644 index 0000000..0f1e2d5 --- /dev/null +++ b/internal/executor/errors.go @@ -0,0 +1,55 @@ +package executor + +type FlagNotSetError struct { + flagText string +} + +func (e FlagNotSetError) Error() string { + return "the flag '" + e.flagText + "' is not set" +} + +type UnsupportedTypeError struct { + resourceType string +} + +func (e UnsupportedTypeError) Error() string { + return "unsupported resource type '" + e.resourceType + "'" +} + +type InvalidTimelineCategoryError struct { + category string +} + +func (e InvalidTimelineCategoryError) Error() string { + return "'" + e.category + "' is not a valid timeline category (please choose home, public, tag or list)" +} + +type NoAccountSpecifiedError struct{} + +func (e NoAccountSpecifiedError) Error() string { + return "no account specified in this request" +} + +type UnsupportedAddOperationError struct { + ResourceType string + AddToResourceType string +} + +func (e UnsupportedAddOperationError) Error() string { + return "adding '" + e.ResourceType + "' to '" + e.AddToResourceType + "' is not supported" +} + +type UnsupportedRemoveOperationError struct { + ResourceType string + RemoveFromResourceType string +} + +func (e UnsupportedRemoveOperationError) Error() string { + return "removing '" + e.ResourceType + "' from '" + e.RemoveFromResourceType + "' is not supported" +} + +type EmptyContentError struct{} + +func (e EmptyContentError) Error() string { + return "content should not be empty" +} diff --git a/internal/executor/executor.go b/internal/executor/executor.go new file mode 100644 index 0000000..3b50789 --- /dev/null +++ b/internal/executor/executor.go @@ -0,0 +1,21 @@ +package executor + +import "fmt" + +type Executor interface { + Name() string + Parse(args []string) error + Execute() error +} + +func Execute(executor Executor, args []string) error { + if err := executor.Parse(args); err != nil { + return fmt.Errorf("unable to parse the command line flags; %w", err) + } + + if err := executor.Execute(); err != nil { + return fmt.Errorf("unable to execute the command %q; %w", executor.Name(), err) + } + + return nil +} diff --git a/internal/executor/flags.go b/internal/executor/flags.go new file mode 100644 index 0000000..9c64dc3 --- /dev/null +++ b/internal/executor/flags.go @@ -0,0 +1,21 @@ +package executor + +import "strings" + +type AccountNames []string + +func (a *AccountNames) String() string { + return strings.Join(*a, ", ") +} + +func (a *AccountNames) Set(value string) error { + if len(value) > 0 { + *a = append(*a, value) + } + + return nil +} + +type TopLevelFlags struct { + ConfigDir string +} diff --git a/cmd/enbas/follow.go b/internal/executor/follow.go similarity index 76% rename from cmd/enbas/follow.go rename to internal/executor/follow.go index 5fbb6ba..ee8291c 100644 --- a/cmd/enbas/follow.go +++ b/internal/executor/follow.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -7,10 +7,10 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/client" ) -type followCommand struct { +type FollowExecutor struct { *flag.FlagSet - topLevelFlags topLevelFlags + topLevelFlags TopLevelFlags resourceType string accountName string showReposts bool @@ -18,8 +18,8 @@ type followCommand struct { unfollow bool } -func newFollowCommand(tlf topLevelFlags, name, summary string, unfollow bool) *followCommand { - command := followCommand{ +func NewFollowExecutor(tlf TopLevelFlags, name, summary string, unfollow bool) *FollowExecutor { + command := FollowExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), unfollow: unfollow, topLevelFlags: tlf, @@ -35,17 +35,17 @@ func newFollowCommand(tlf topLevelFlags, name, summary string, unfollow bool) *f return &command } -func (c *followCommand) Execute() error { +func (c *FollowExecutor) Execute() error { funcMap := map[string]func(*client.Client) error{ resourceAccount: c.followAccount, } doFunc, ok := funcMap[c.resourceType] if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} + return UnsupportedTypeError{resourceType: c.resourceType} } - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) + gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client; %w", err) } @@ -53,8 +53,8 @@ func (c *followCommand) Execute() error { return doFunc(gtsClient) } -func (c *followCommand) followAccount(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, false, c.accountName, c.topLevelFlags.configDir) +func (c *FollowExecutor) followAccount(gtsClient *client.Client) error { + accountID, err := getAccountID(gtsClient, false, c.accountName, c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("received an error while getting the account ID; %w", err) } @@ -72,7 +72,7 @@ func (c *followCommand) followAccount(gtsClient *client.Client) error { return nil } -func (c *followCommand) unfollowAccount(gtsClient *client.Client, accountID string) error { +func (c *FollowExecutor) unfollowAccount(gtsClient *client.Client, accountID string) error { if err := gtsClient.UnfollowAccount(accountID); err != nil { return fmt.Errorf("unable to unfollow the account; %w", err) } diff --git a/cmd/enbas/login.go b/internal/executor/login.go similarity index 82% rename from cmd/enbas/login.go rename to internal/executor/login.go index 427f2f1..3defad4 100644 --- a/cmd/enbas/login.go +++ b/internal/executor/login.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -10,18 +10,18 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" ) -type loginCommand struct { +type LoginExecutor struct { *flag.FlagSet - topLevelFlags topLevelFlags - instance string + topLevelFlags TopLevelFlags + instance string } -func newLoginCommand(tlf topLevelFlags, name, summary string) *loginCommand { - command := loginCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), +func NewLoginExecutor(tlf TopLevelFlags, name, summary string) *LoginExecutor { + command := LoginExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), topLevelFlags: tlf, - instance: "", + instance: "", } command.StringVar(&command.instance, flagInstance, "", "specify the instance that you want to login to.") @@ -31,11 +31,11 @@ func newLoginCommand(tlf topLevelFlags, name, summary string) *loginCommand { return &command } -func (c *loginCommand) Execute() error { +func (c *LoginExecutor) Execute() error { var err error if c.instance == "" { - return flagNotSetError{flagText: flagInstance} + return FlagNotSetError{flagText: flagInstance} } instance := c.instance @@ -91,7 +91,7 @@ Once you have the code please copy and paste it below. return fmt.Errorf("unable to verify the credentials; %w", err) } - loginName, err := config.SaveCredentials(c.topLevelFlags.configDir, account.Username, gtsClient.Authentication) + loginName, err := config.SaveCredentials(c.topLevelFlags.ConfigDir, account.Username, gtsClient.Authentication) if err != nil { return fmt.Errorf("unable to save the authentication details; %w", err) } diff --git a/internal/executor/remove.go b/internal/executor/remove.go new file mode 100644 index 0000000..a8a50bc --- /dev/null +++ b/internal/executor/remove.go @@ -0,0 +1,140 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" +) + +type RemoveExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + fromResourceType string + listID string + accountNames AccountNames +} + +func NewRemoveExecutor(tlf TopLevelFlags, name, summary string) *RemoveExecutor { + emptyArr := make([]string, 0, 3) + + removeExe := RemoveExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + accountNames: AccountNames(emptyArr), + topLevelFlags: tlf, + } + + removeExe.StringVar(&removeExe.resourceType, flagType, "", "specify the resource type to remove (e.g. account, note)") + removeExe.StringVar(&removeExe.fromResourceType, flagFrom, "", "specify the resource type to remove from (e.g. list, account, etc)") + removeExe.StringVar(&removeExe.listID, flagListID, "", "the ID of the list to remove from") + removeExe.Var(&removeExe.accountNames, flagAccountName, "the name of the account to remove from the resource") + + removeExe.Usage = commandUsageFunc(name, summary, removeExe.FlagSet) + + return &removeExe +} + +func (r *RemoveExecutor) Execute() error { + if r.fromResourceType == "" { + return FlagNotSetError{flagText: flagFrom} + } + + funcMap := map[string]func(*client.Client) error{ + resourceList: r.removeFromList, + resourceAccount: r.removeFromAccount, + } + + doFunc, ok := funcMap[r.fromResourceType] + if !ok { + return UnsupportedTypeError{resourceType: r.fromResourceType} + } + + gtsClient, err := client.NewClientFromConfig(r.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + return doFunc(gtsClient) +} + +func (r *RemoveExecutor) removeFromList(gtsClient *client.Client) error { + funcMap := map[string]func(*client.Client) error{ + resourceAccount: r.removeAccountsFromList, + } + + doFunc, ok := funcMap[r.resourceType] + if !ok { + return UnsupportedRemoveOperationError{ + ResourceType: r.resourceType, + RemoveFromResourceType: r.fromResourceType, + } + } + + return doFunc(gtsClient) +} + +func (r *RemoveExecutor) removeAccountsFromList(gtsClient *client.Client) error { + if r.listID == "" { + return FlagNotSetError{flagText: flagListID} + } + + if len(r.accountNames) == 0 { + return NoAccountSpecifiedError{} + } + + accountIDs := make([]string, len(r.accountNames)) + + for i := range r.accountNames { + accountID, err := getTheirAccountID(gtsClient, r.accountNames[i]) + if err != nil { + return fmt.Errorf("unable to get the account ID for %s, %w", r.accountNames[i], err) + } + + accountIDs[i] = accountID + } + + if err := gtsClient.RemoveAccountsFromList(r.listID, accountIDs); err != nil { + return fmt.Errorf("unable to remove the accounts from the list; %w", err) + } + + fmt.Println("Successfully removed the account(s) from the list.") + + return nil +} + +func (r *RemoveExecutor) removeFromAccount(gtsClient *client.Client) error { + funcMap := map[string]func(*client.Client) error{ + resourceNote: r.removeNoteFromAccount, + } + + doFunc, ok := funcMap[r.resourceType] + if !ok { + return UnsupportedRemoveOperationError{ + ResourceType: r.resourceType, + RemoveFromResourceType: r.fromResourceType, + } + } + + return doFunc(gtsClient) +} + +func (r *RemoveExecutor) removeNoteFromAccount(gtsClient *client.Client) error { + if len(r.accountNames) != 1 { + return fmt.Errorf("unexpected number of accounts specified; want 1, got %d", len(r.accountNames)) + } + + accountID, err := getAccountID(gtsClient, false, r.accountNames[0], r.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("received an error while getting the account ID; %w", err) + } + + if err := gtsClient.SetPrivateNote(accountID, ""); err != nil { + return fmt.Errorf("unable to remove the private note from the account; %w", err) + } + + fmt.Println("Successfully removed the private note from the account.") + + return nil +} diff --git a/cmd/enbas/show.go b/internal/executor/show.go similarity index 82% rename from cmd/enbas/show.go rename to internal/executor/show.go index 23d18bc..6930d07 100644 --- a/cmd/enbas/show.go +++ b/internal/executor/show.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -9,9 +9,9 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" ) -type showCommand struct { +type ShowExecutor struct { *flag.FlagSet - topLevelFlags topLevelFlags + topLevelFlags TopLevelFlags myAccount bool showAccountRelationship bool showUserPreferences bool @@ -24,8 +24,8 @@ type showCommand struct { limit int } -func newShowCommand(tlf topLevelFlags, name, summary string) *showCommand { - command := showCommand{ +func NewShowExecutor(tlf TopLevelFlags, name, summary string) *ShowExecutor { + command := ShowExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), topLevelFlags: tlf, } @@ -46,9 +46,9 @@ func newShowCommand(tlf topLevelFlags, name, summary string) *showCommand { return &command } -func (c *showCommand) Execute() error { +func (c *ShowExecutor) Execute() error { if c.resourceType == "" { - return flagNotSetError{flagText: flagType} + return FlagNotSetError{flagText: flagType} } funcMap := map[string]func(*client.Client) error{ @@ -64,10 +64,10 @@ func (c *showCommand) Execute() error { doFunc, ok := funcMap[c.resourceType] if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} + return UnsupportedTypeError{resourceType: c.resourceType} } - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) + gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client; %w", err) } @@ -75,7 +75,7 @@ func (c *showCommand) Execute() error { return doFunc(gtsClient) } -func (c *showCommand) showInstance(gtsClient *client.Client) error { +func (c *ShowExecutor) showInstance(gtsClient *client.Client) error { instance, err := gtsClient.GetInstance() if err != nil { return fmt.Errorf("unable to retrieve the instance details; %w", err) @@ -86,20 +86,20 @@ func (c *showCommand) showInstance(gtsClient *client.Client) error { return nil } -func (c *showCommand) showAccount(gtsClient *client.Client) error { +func (c *ShowExecutor) showAccount(gtsClient *client.Client) error { var ( account model.Account err error ) if c.myAccount { - account, err = getMyAccount(gtsClient, c.topLevelFlags.configDir) + account, err = getMyAccount(gtsClient, c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("received an error while getting the account details; %w", err) } } else { if c.accountName == "" { - return flagNotSetError{flagText: flagAccountName} + return FlagNotSetError{flagText: flagAccountName} } account, err = getAccount(gtsClient, c.accountName) @@ -131,9 +131,9 @@ func (c *showCommand) showAccount(gtsClient *client.Client) error { return nil } -func (c *showCommand) showStatus(gtsClient *client.Client) error { +func (c *ShowExecutor) showStatus(gtsClient *client.Client) error { if c.statusID == "" { - return flagNotSetError{flagText: flagStatusID} + return FlagNotSetError{flagText: flagStatusID} } status, err := gtsClient.GetStatus(c.statusID) @@ -146,7 +146,7 @@ func (c *showCommand) showStatus(gtsClient *client.Client) error { return nil } -func (c *showCommand) showTimeline(gtsClient *client.Client) error { +func (c *ShowExecutor) showTimeline(gtsClient *client.Client) error { var ( timeline model.Timeline err error @@ -159,18 +159,18 @@ func (c *showCommand) showTimeline(gtsClient *client.Client) error { timeline, err = gtsClient.GetPublicTimeline(c.limit) case "list": if c.listID == "" { - return flagNotSetError{flagText: flagListID} + return FlagNotSetError{flagText: flagListID} } timeline, err = gtsClient.GetListTimeline(c.listID, c.limit) case "tag": if c.tag == "" { - return flagNotSetError{flagText: flagTag} + return FlagNotSetError{flagText: flagTag} } timeline, err = gtsClient.GetTagTimeline(c.tag, c.limit) default: - return invalidTimelineCategoryError{category: c.timelineCategory} + return InvalidTimelineCategoryError{category: c.timelineCategory} } if err != nil { @@ -188,7 +188,7 @@ func (c *showCommand) showTimeline(gtsClient *client.Client) error { return nil } -func (c *showCommand) showList(gtsClient *client.Client) error { +func (c *ShowExecutor) showList(gtsClient *client.Client) error { if c.listID == "" { return c.showLists(gtsClient) } @@ -217,7 +217,7 @@ func (c *showCommand) showList(gtsClient *client.Client) error { return nil } -func (c *showCommand) showLists(gtsClient *client.Client) error { +func (c *ShowExecutor) showLists(gtsClient *client.Client) error { lists, err := gtsClient.GetAllLists() if err != nil { return fmt.Errorf("unable to retrieve the lists; %w", err) @@ -235,8 +235,8 @@ func (c *showCommand) showLists(gtsClient *client.Client) error { return nil } -func (c *showCommand) showFollowers(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, c.myAccount, c.accountName, c.topLevelFlags.configDir) +func (c *ShowExecutor) showFollowers(gtsClient *client.Client) error { + accountID, err := getAccountID(gtsClient, c.myAccount, c.accountName, c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("received an error while getting the account ID; %w", err) } @@ -255,8 +255,8 @@ func (c *showCommand) showFollowers(gtsClient *client.Client) error { return nil } -func (c *showCommand) showFollowing(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, c.myAccount, c.accountName, c.topLevelFlags.configDir) +func (c *ShowExecutor) showFollowing(gtsClient *client.Client) error { + accountID, err := getAccountID(gtsClient, c.myAccount, c.accountName, c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("received an error while getting the account ID; %w", err) } @@ -275,7 +275,7 @@ func (c *showCommand) showFollowing(gtsClient *client.Client) error { return nil } -func (c *showCommand) showBlocked(gtsClient *client.Client) error { +func (c *ShowExecutor) showBlocked(gtsClient *client.Client) error { blocked, err := gtsClient.GetBlockedAccounts(c.limit) if err != nil { return fmt.Errorf("unable to retrieve the list of blocked accounts; %w", err) diff --git a/internal/executor/switch.go b/internal/executor/switch.go new file mode 100644 index 0000000..8bd19b2 --- /dev/null +++ b/internal/executor/switch.go @@ -0,0 +1,57 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" +) + +type SwitchExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + toResourceType string + accountName string +} + +func NewSwitchExecutor(tlf TopLevelFlags, name, summary string) *SwitchExecutor { + switchExe := SwitchExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + topLevelFlags: tlf, + } + + switchExe.StringVar(&switchExe.toResourceType, flagTo, "", "the account to switch to") + switchExe.StringVar(&switchExe.accountName, flagAccountName, "", "the name of the account to switch to") + + switchExe.Usage = commandUsageFunc(name, summary, switchExe.FlagSet) + + return &switchExe +} + +func (s *SwitchExecutor) Execute() error { + funcMap := map[string]func() error{ + resourceAccount: s.switchToAccount, + } + + doFunc, ok := funcMap[s.toResourceType] + if !ok { + return UnsupportedTypeError{resourceType: s.toResourceType} + } + + return doFunc() +} + +func (s *SwitchExecutor) switchToAccount() error { + if s.accountName == "" { + return NoAccountSpecifiedError{} + } + + if err := config.UpdateCurrentAccount(s.accountName, s.topLevelFlags.ConfigDir); err != nil { + return fmt.Errorf("unable to switch account to the account; %w", err) + } + + fmt.Printf("The current account is now set to %q.\n", s.accountName) + + return nil +} diff --git a/internal/executor/update.go b/internal/executor/update.go new file mode 100644 index 0000000..44ce4ef --- /dev/null +++ b/internal/executor/update.go @@ -0,0 +1,91 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/model" +) + +type EditExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + listID string + listTitle string + listRepliesPolicy string +} + +func NewEditExecutor(tlf TopLevelFlags, name, summary string) *EditExecutor { + editExe := EditExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + topLevelFlags: tlf, + } + + editExe.StringVar(&editExe.resourceType, flagType, "", "specify the type of resource to update") + editExe.StringVar(&editExe.listID, flagListID, "", "specify the ID of the list to update") + 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.Usage = commandUsageFunc(name, summary, editExe.FlagSet) + + return &editExe +} + +func (e *EditExecutor) Execute() error { + if e.resourceType == "" { + return FlagNotSetError{flagText: flagType} + } + + funcMap := map[string]func(*client.Client) error{ + resourceList: e.updateList, + } + + doFunc, ok := funcMap[e.resourceType] + if !ok { + return UnsupportedTypeError{resourceType: e.resourceType} + } + + gtsClient, err := client.NewClientFromConfig(e.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + return doFunc(gtsClient) +} + +func (e *EditExecutor) updateList(gtsClient *client.Client) error { + if e.listID == "" { + return FlagNotSetError{flagText: flagListID} + } + + list, err := gtsClient.GetList(e.listID) + if err != nil { + return fmt.Errorf("unable to get the list; %w", err) + } + + if e.listTitle != "" { + list.Title = e.listTitle + } + + if e.listRepliesPolicy != "" { + repliesPolicy, err := model.ParseListRepliesPolicy(e.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.Println(updatedList) + + return nil +} diff --git a/internal/executor/usage.go b/internal/executor/usage.go new file mode 100644 index 0000000..7f9275c --- /dev/null +++ b/internal/executor/usage.go @@ -0,0 +1,37 @@ +package executor + +import ( + "flag" + "fmt" + "strings" +) + +// commandUsageFunc returns the function used to print a command's help page. +func commandUsageFunc(name, summary string, flagset *flag.FlagSet) func() { + return func() { + var builder strings.Builder + + fmt.Fprintf( + &builder, + "SUMMARY:\n %s - %s\n\nUSAGE:\n enbas %s [flags]\n\nFLAGS:", + name, + summary, + name, + ) + + flagset.VisitAll(func(f *flag.Flag) { + fmt.Fprintf( + &builder, + "\n --%s\n %s", + f.Name, + f.Usage, + ) + }) + + builder.WriteString("\n") + + w := flag.CommandLine.Output() + + fmt.Fprint(w, builder.String()) + } +} diff --git a/cmd/enbas/version.go b/internal/executor/version.go similarity index 77% rename from cmd/enbas/version.go rename to internal/executor/version.go index f41c65c..07a6952 100644 --- a/cmd/enbas/version.go +++ b/internal/executor/version.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -7,14 +7,7 @@ import ( "strings" ) -var ( - binaryVersion string - buildTime string - goVersion string - gitCommit string -) - -type versionCommand struct { +type VersionExecutor struct { *flag.FlagSet showFullVersion bool binaryVersion string @@ -23,8 +16,8 @@ type versionCommand struct { gitCommit string } -func newVersionCommand(name, summary string) *versionCommand { - command := versionCommand{ +func NewVersionExecutor(name, summary, binaryVersion, buildTime, goVersion, gitCommit string) *VersionExecutor { + command := VersionExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), binaryVersion: binaryVersion, buildTime: buildTime, @@ -40,7 +33,7 @@ func newVersionCommand(name, summary string) *versionCommand { return &command } -func (c *versionCommand) Execute() error { +func (c *VersionExecutor) Execute() error { var builder strings.Builder if c.showFullVersion { diff --git a/cmd/enbas/whoami.go b/internal/executor/whoami.go similarity index 59% rename from cmd/enbas/whoami.go rename to internal/executor/whoami.go index 60fb06c..995dbf8 100644 --- a/cmd/enbas/whoami.go +++ b/internal/executor/whoami.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -7,15 +7,15 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/config" ) -type whoAmICommand struct { +type WhoAmIExecutor struct { *flag.FlagSet - topLevelFlags topLevelFlags + topLevelFlags TopLevelFlags } -func newWhoAmICommand(tlf topLevelFlags, name, summary string) *whoAmICommand { - command := whoAmICommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), +func NewWhoAmIExecutor(tlf TopLevelFlags, name, summary string) *WhoAmIExecutor { + command := WhoAmIExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), topLevelFlags: tlf, } @@ -24,8 +24,8 @@ func newWhoAmICommand(tlf topLevelFlags, name, summary string) *whoAmICommand { return &command } -func (c *whoAmICommand) Execute() error { - config, err := config.NewCredentialsConfigFromFile(c.topLevelFlags.configDir) +func (c *WhoAmIExecutor) Execute() error { + config, err := config.NewCredentialsConfigFromFile(c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("unable to load the credential config; %w", err) }