fix(breaking): update project structure

Move all executors to the internal folder package. This PR also comes
with additional breaking changes.

Changes:

- refactor: move all executors to the internal/executor package.
- refactor: update naming patterns for constants, variables, custom
  types, etc.
- fix(breaking): renamed the update command to edit.
- fix(breaking): update the flags for the switch command to make it
  more generic.
- fix(breaking): renamed the show-account-relationship flag to
  show-relationship.
- fix: update the print message from the whoami command.
This commit is contained in:
Dan Anglin 2024-05-23 18:06:49 +01:00
parent 3d17c30d0f
commit 55b93c6586
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
29 changed files with 1014 additions and 916 deletions

View file

@ -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, resourceTypeFlag, "", "specify the resource type to add (e.g. account, note)")
command.StringVar(&command.toResourceType, addToFlag, "", "specify the target resource type to add to (e.g. list, account, etc)")
command.StringVar(&command.listID, listIDFlag, "", "the ID of the list to add to")
command.Var(&command.accountNames, accountNameFlag, "the name of the account to add to the resource")
command.StringVar(&command.content, contentFlag, "", "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: addToFlag}
}
funcMap := map[string]func(*client.Client) error{
listResource: c.addToList,
accountResource: 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{
accountResource: 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: listIDFlag}
}
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{
noteResource: 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
}

View file

@ -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, resourceTypeFlag, "", "specify the type of resource to block or unblock")
command.StringVar(&command.accountName, accountNameFlag, "", "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{
accountResource: 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
}

View file

@ -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, resourceTypeFlag, "", "specify the type of resource to delete")
command.StringVar(&command.listID, listIDFlag, "", "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: resourceTypeFlag}
}
funcMap := map[string]func(*client.Client) error{
listResource: 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: listIDFlag}
}
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
}

View file

@ -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 + "'"
}

View file

@ -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
}

View file

@ -4,48 +4,34 @@ import (
"flag"
"fmt"
"os"
"codeflow.dananglin.me.uk/apollo/enbas/internal/executor"
)
const (
accountNameFlag = "account-name"
addToFlag = "to"
contentFlag = "content"
instanceFlag = "instance"
limitFlag = "limit"
listIDFlag = "list-id"
listTitleFlag = "list-title"
listRepliesPolicyFlag = "list-replies-policy"
myAccountFlag = "my-account"
notifyFlag = "notify"
removeFromFlag = "from"
resourceTypeFlag = "type"
showAccountRelationshipFlag = "show-account-relationship"
showUserPreferencesFlag = "show-preferences"
showRepostsFlag = "show-reposts"
statusIDFlag = "status-id"
tagFlag = "tag"
timelineCategoryFlag = "timeline-category"
toAccountFlag = "to-account"
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 (
accountResource = "account"
blockedResource = "blocked"
followersResource = "followers"
followingResource = "following"
instanceResource = "instance"
listResource = "list"
noteResource = "note"
statusResource = "status"
timelineResource = "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)
@ -54,45 +40,28 @@ func main() {
}
func run() error {
const (
login string = "login"
version string = "version"
showResource string = "show"
switchAccount string = "switch"
createResource string = "create"
deleteResource string = "delete"
updateResource string = "update"
whoami string = "whoami"
add string = "add"
remove string = "remove"
follow string = "follow"
unfollow string = "unfollow"
block string = "block"
unblock string = "unblock"
)
summaries := map[string]string{
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",
whoami: "print the account that you are currently logged in to",
add: "add a resource to another resource",
remove: "remove a resource from another resource",
follow: "follow a resource (e.g. an account)",
unfollow: "unfollow a resource (e.g. an account)",
block: "block a resource (e.g. an account)",
unblock: "unblock a resource (e.g. an account)",
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",
commandSwitch: "perform a switch operation (e.g. switch logged in accounts)",
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(summaries)
flag.Usage = usageFunc(commandSummaries)
flag.Parse()
@ -102,52 +71,107 @@ func run() error {
return nil
}
subcommand := flag.Arg(0)
command := flag.Arg(0)
args := flag.Args()[1:]
var executor Executor
var err error
switch subcommand {
case login:
executor = newLoginCommand(tlf, login, summaries[login])
case version:
executor = newVersionCommand(version, summaries[version])
case showResource:
executor = newShowCommand(tlf, showResource, summaries[showResource])
case switchAccount:
executor = newSwitchCommand(tlf, switchAccount, summaries[switchAccount])
case createResource:
executor = newCreateCommand(tlf, createResource, summaries[createResource])
case deleteResource:
executor = newDeleteCommand(tlf, deleteResource, summaries[deleteResource])
case updateResource:
executor = newUpdateCommand(tlf, updateResource, summaries[updateResource])
case whoami:
executor = newWhoAmICommand(tlf, whoami, summaries[whoami])
case add:
executor = newAddCommand(tlf, add, summaries[add])
case remove:
executor = newRemoveCommand(tlf, remove, summaries[remove])
case follow:
executor = newFollowCommand(tlf, follow, summaries[follow], false)
case unfollow:
executor = newFollowCommand(tlf, unfollow, summaries[unfollow], true)
case block:
executor = newBlockCommand(tlf, block, summaries[block], false)
case unblock:
executor = newBlockCommand(tlf, unblock, summaries[unblock], true)
switch command {
case commandAdd:
exe := executor.NewAddExecutor(
topLevelFlags,
commandAdd,
commandSummaries[commandAdd],
)
err = executor.Execute(exe, args)
case commandBlock:
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:
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{subcommand}
return unknownCommandError{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

View file

@ -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, resourceTypeFlag, "", "specify the resource type to remove (e.g. account, note)")
command.StringVar(&command.fromResourceType, removeFromFlag, "", "specify the resource type to remove from (e.g. list, account, etc)")
command.StringVar(&command.listID, listIDFlag, "", "the ID of the list to remove from")
command.Var(&command.accountNames, accountNameFlag, "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: removeFromFlag}
}
funcMap := map[string]func(*client.Client) error{
listResource: c.removeFromList,
accountResource: 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{
accountResource: 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: listIDFlag}
}
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{
noteResource: 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
}

View file

@ -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, toAccountFlag, "", "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: toAccountFlag}
}
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
}

View file

@ -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, resourceTypeFlag, "", "specify the type of resource to update")
command.StringVar(&command.listID, listIDFlag, "", "specify the ID of the list to update")
command.StringVar(&command.listTitle, listTitleFlag, "", "specify the title of the list")
command.StringVar(&command.listRepliesPolicy, listRepliesPolicyFlag, "", "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: resourceTypeFlag}
}
funcMap := map[string]func(*client.Client) error{
listResource: 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: listIDFlag}
}
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
}

View file

@ -7,36 +7,7 @@ import (
"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() {
func usageFunc(summaries map[string]string) func() {
cmds := make([]string, len(summaries))
ind := 0

View file

@ -1,36 +0,0 @@
package main
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
)
type whoAmICommand struct {
*flag.FlagSet
topLevelFlags topLevelFlags
}
func newWhoAmICommand(tlf topLevelFlags, name, summary string) *whoAmICommand {
command := whoAmICommand{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
topLevelFlags: tlf,
}
command.Usage = commandUsageFunc(name, summary, command.FlagSet)
return &command
}
func (c *whoAmICommand) Execute() error {
config, err := config.NewCredentialsConfigFromFile(c.topLevelFlags.configDir)
if err != nil {
return fmt.Errorf("unable to load the credential config; %w", err)
}
fmt.Printf("You are %s\n", config.CurrentAccount)
return nil
}

View file

@ -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

146
internal/executor/add.go Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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"
)

View file

@ -1,4 +1,4 @@
package main
package executor
import (
"flag"
@ -8,56 +8,56 @@ 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, resourceTypeFlag, "", "specify the type of resource to create")
command.StringVar(&command.listTitle, listTitleFlag, "", "specify the title of the list")
command.StringVar(&command.listRepliesPolicy, listRepliesPolicyFlag, "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: resourceTypeFlag}
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)
}
funcMap := map[string]func(*client.Client) error{
listResource: c.createList,
resourceList: c.createList,
}
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: listTitleFlag}
return FlagNotSetError{flagText: flagListTitle}
}
repliesPolicy, err := model.ParseListRepliesPolicy(c.listRepliesPolicy)

View file

@ -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
}

View file

@ -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"
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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,34 +18,34 @@ 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,
}
command.StringVar(&command.resourceType, resourceTypeFlag, "", "specify the type of resource to follow")
command.StringVar(&command.accountName, accountNameFlag, "", "specify the account name in full (username@domain)")
command.BoolVar(&command.showReposts, showRepostsFlag, true, "show reposts from the account you want to follow")
command.BoolVar(&command.notify, notifyFlag, false, "get notifications when the account you want to follow posts a status")
command.StringVar(&command.resourceType, flagType, "", "specify the type of resource to follow")
command.StringVar(&command.accountName, flagAccountName, "", "specify the account name in full (username@domain)")
command.BoolVar(&command.showReposts, flagShowReposts, true, "show reposts from the account you want to follow")
command.BoolVar(&command.notify, flagNotify, false, "get notifications when the account you want to follow posts a status")
command.Usage = commandUsageFunc(name, summary, command.FlagSet)
return &command
}
func (c *followCommand) Execute() error {
func (c *FollowExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
accountResource: c.followAccount,
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)
}

View file

@ -1,4 +1,4 @@
package main
package executor
import (
"flag"
@ -10,32 +10,32 @@ import (
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
)
type loginCommand struct {
type LoginExecutor struct {
*flag.FlagSet
topLevelFlags topLevelFlags
topLevelFlags TopLevelFlags
instance string
}
func newLoginCommand(tlf topLevelFlags, name, summary string) *loginCommand {
command := loginCommand{
func NewLoginExecutor(tlf TopLevelFlags, name, summary string) *LoginExecutor {
command := LoginExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
topLevelFlags: tlf,
instance: "",
}
command.StringVar(&command.instance, instanceFlag, "", "specify the instance that you want to login to.")
command.StringVar(&command.instance, flagInstance, "", "specify the instance that you want to login to.")
command.Usage = commandUsageFunc(name, summary, command.FlagSet)
return &command
}
func (c *loginCommand) Execute() error {
func (c *LoginExecutor) Execute() error {
var err error
if c.instance == "" {
return flagNotSetError{flagText: instanceFlag}
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)
}

140
internal/executor/remove.go Normal file
View file

@ -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
}

View file

@ -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,50 +24,50 @@ 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,
}
command.BoolVar(&command.myAccount, myAccountFlag, false, "set to true to lookup your account")
command.BoolVar(&command.showAccountRelationship, showAccountRelationshipFlag, false, "show your relationship to the specified account")
command.BoolVar(&command.showUserPreferences, showUserPreferencesFlag, false, "show your preferences")
command.StringVar(&command.resourceType, resourceTypeFlag, "", "specify the type of resource to display")
command.StringVar(&command.accountName, accountNameFlag, "", "specify the account name in full (username@domain)")
command.StringVar(&command.statusID, statusIDFlag, "", "specify the ID of the status to display")
command.StringVar(&command.timelineCategory, timelineCategoryFlag, "home", "specify the type of timeline to display (valid values are home, public, list and tag)")
command.StringVar(&command.listID, listIDFlag, "", "specify the ID of the list to display")
command.StringVar(&command.tag, tagFlag, "", "specify the name of the tag to use")
command.IntVar(&command.limit, limitFlag, 20, "specify the limit of items to display")
command.BoolVar(&command.myAccount, flagMyAccount, false, "set to true to lookup your account")
command.BoolVar(&command.showAccountRelationship, flagShowRelationship, false, "show your relationship to the specified account")
command.BoolVar(&command.showUserPreferences, flagShowPreferences, false, "show your preferences")
command.StringVar(&command.resourceType, flagType, "", "specify the type of resource to display")
command.StringVar(&command.accountName, flagAccountName, "", "specify the account name in full (username@domain)")
command.StringVar(&command.statusID, flagStatusID, "", "specify the ID of the status to display")
command.StringVar(&command.timelineCategory, flagTimelineCategory, "home", "specify the type of timeline to display (valid values are home, public, list and tag)")
command.StringVar(&command.listID, flagListID, "", "specify the ID of the list to display")
command.StringVar(&command.tag, flagTag, "", "specify the name of the tag to use")
command.IntVar(&command.limit, flagLimit, 20, "specify the limit of items to display")
command.Usage = commandUsageFunc(name, summary, command.FlagSet)
return &command
}
func (c *showCommand) Execute() error {
func (c *ShowExecutor) Execute() error {
if c.resourceType == "" {
return flagNotSetError{flagText: resourceTypeFlag}
return FlagNotSetError{flagText: flagType}
}
funcMap := map[string]func(*client.Client) error{
instanceResource: c.showInstance,
accountResource: c.showAccount,
statusResource: c.showStatus,
timelineResource: c.showTimeline,
listResource: c.showList,
followersResource: c.showFollowers,
followingResource: c.showFollowing,
blockedResource: c.showBlocked,
resourceInstance: c.showInstance,
resourceAccount: c.showAccount,
resourceStatus: c.showStatus,
resourceTimeline: c.showTimeline,
resourceList: c.showList,
resourceFollowers: c.showFollowers,
resourceFollowing: c.showFollowing,
resourceBlocked: c.showBlocked,
}
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: accountNameFlag}
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: statusIDFlag}
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: listIDFlag}
return FlagNotSetError{flagText: flagListID}
}
timeline, err = gtsClient.GetListTimeline(c.listID, c.limit)
case "tag":
if c.tag == "" {
return flagNotSetError{flagText: tagFlag}
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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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())
}
}

View file

@ -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 {

View file

@ -0,0 +1,36 @@
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
)
type WhoAmIExecutor struct {
*flag.FlagSet
topLevelFlags TopLevelFlags
}
func NewWhoAmIExecutor(tlf TopLevelFlags, name, summary string) *WhoAmIExecutor {
whoExe := WhoAmIExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
topLevelFlags: tlf,
}
whoExe.Usage = commandUsageFunc(name, summary, whoExe.FlagSet)
return &whoExe
}
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)
}
fmt.Printf("You are logged in as %q.\n", config.CurrentAccount)
return nil
}