feat: add configuration support to enbas #32

Manually merged
dananglin merged 1 commit from json-configuration into main 2024-06-25 12:46:11 +01:00
30 changed files with 425 additions and 220 deletions
Showing only changes of commit 42251f6df8 - Show all commits

View file

@ -31,5 +31,8 @@ linters-settings:
linters: linters:
enable-all: true enable-all: true
disable: disable:
#- json - exhaustivestruct
- exhaustruct
- gomnd
- tagliatelle
fast: false fast: false

View file

@ -10,15 +10,16 @@ import (
"os" "os"
"strconv" "strconv"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/executor" "codeflow.dananglin.me.uk/apollo/enbas/internal/executor"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
) )
var ( var (
binaryVersion string binaryVersion string //nolint:gochecknoglobals
buildTime string buildTime string //nolint:gochecknoglobals
goVersion string goVersion string //nolint:gochecknoglobals
gitCommit string gitCommit string //nolint:gochecknoglobals
) )
func main() { func main() {
@ -29,22 +30,11 @@ func main() {
func run() error { func run() error {
var ( var (
configDir string configDir string
cacheDir string noColor *bool
pager string
imageViewer string
videoPlayer string
maxTerminalWidth int
noColor *bool
) )
flag.StringVar(&configDir, "config-dir", "", "Specify your config directory") flag.StringVar(&configDir, "config-dir", "", "Specify your config directory")
flag.StringVar(&cacheDir, "cache-dir", "", "Specify your cache directory")
flag.StringVar(&pager, "pager", "", "Specify your preferred pager to page through long outputs. This is disabled by default.")
flag.StringVar(&imageViewer, "image-viewer", "", "Specify your favourite image viewer.")
flag.StringVar(&videoPlayer, "video-player", "", "Specify your favourite video player.")
flag.IntVar(&maxTerminalWidth, "max-terminal-width", 80, "Specify the maximum terminal width when displaying resources on screen.")
flag.BoolFunc("no-color", "Disable ANSI colour output when displaying text on screen", func(value string) error { flag.BoolFunc("no-color", "Disable ANSI colour output when displaying text on screen", func(value string) error {
boolVal, err := strconv.ParseBool(value) boolVal, err := strconv.ParseBool(value)
if err != nil { if err != nil {
@ -67,7 +57,8 @@ func run() error {
return nil return nil
} }
// If NoColor is still unspecified, check to see if the NO_COLOR environment variable is set // If NoColor is still unspecified,
// check to see if the NO_COLOR environment variable is set
if noColor == nil { if noColor == nil {
noColor = new(bool) noColor = new(bool)
if os.Getenv("NO_COLOR") != "" { if os.Getenv("NO_COLOR") != "" {
@ -80,110 +71,132 @@ func run() error {
command := flag.Arg(0) command := flag.Arg(0)
args := flag.Args()[1:] args := flag.Args()[1:]
printer := printer.NewPrinter(*noColor, pager, maxTerminalWidth) var (
enbasConfig *config.Config
enbasPrinter *printer.Printer
err error
)
switch command {
case executor.CommandInit, executor.CommandVersion:
enbasPrinter = printer.NewPrinter(*noColor, "", 0)
default:
enbasConfig, err = config.NewConfigFromFile(configDir)
if err != nil {
enbasPrinter = printer.NewPrinter(*noColor, "", 0)
enbasPrinter.PrintFailure("unable to load the configuration: " + err.Error() + ".")
return err
}
enbasPrinter = printer.NewPrinter(*noColor, enbasConfig.Integrations.Pager, enbasConfig.LineWrapMaxWidth)
}
executorMap := map[string]executor.Executor{ executorMap := map[string]executor.Executor{
executor.CommandAccept: executor.NewAcceptOrRejectExecutor( executor.CommandAccept: executor.NewAcceptOrRejectExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandAccept, executor.CommandAccept,
executor.CommandSummaryLookup(executor.CommandAccept), executor.CommandSummaryLookup(executor.CommandAccept),
), ),
executor.CommandAdd: executor.NewAddExecutor( executor.CommandAdd: executor.NewAddExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandAdd, executor.CommandAdd,
executor.CommandSummaryLookup(executor.CommandAdd), executor.CommandSummaryLookup(executor.CommandAdd),
), ),
executor.CommandBlock: executor.NewBlockOrUnblockExecutor( executor.CommandBlock: executor.NewBlockOrUnblockExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandBlock, executor.CommandBlock,
executor.CommandSummaryLookup(executor.CommandBlock), executor.CommandSummaryLookup(executor.CommandBlock),
), ),
executor.CommandCreate: executor.NewCreateExecutor( executor.CommandCreate: executor.NewCreateExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandCreate, executor.CommandCreate,
executor.CommandSummaryLookup(executor.CommandCreate), executor.CommandSummaryLookup(executor.CommandCreate),
), ),
executor.CommandDelete: executor.NewDeleteExecutor( executor.CommandDelete: executor.NewDeleteExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandDelete, executor.CommandDelete,
executor.CommandSummaryLookup(executor.CommandDelete), executor.CommandSummaryLookup(executor.CommandDelete),
), ),
executor.CommandEdit: executor.NewEditExecutor( executor.CommandEdit: executor.NewEditExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandEdit, executor.CommandEdit,
executor.CommandSummaryLookup(executor.CommandEdit), executor.CommandSummaryLookup(executor.CommandEdit),
), ),
executor.CommandFollow: executor.NewFollowOrUnfollowExecutor( executor.CommandFollow: executor.NewFollowOrUnfollowExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandFollow, executor.CommandFollow,
executor.CommandSummaryLookup(executor.CommandFollow), executor.CommandSummaryLookup(executor.CommandFollow),
), ),
executor.CommandLogin: executor.NewLoginExecutor( executor.CommandInit: executor.NewInitExecutor(
printer, enbasPrinter,
configDir, configDir,
executor.CommandInit,
executor.CommandSummaryLookup(executor.CommandInit),
),
executor.CommandLogin: executor.NewLoginExecutor(
enbasPrinter,
enbasConfig,
executor.CommandLogin, executor.CommandLogin,
executor.CommandSummaryLookup(executor.CommandLogin), executor.CommandSummaryLookup(executor.CommandLogin),
), ),
executor.CommandMute: executor.NewMuteOrUnmuteExecutor( executor.CommandMute: executor.NewMuteOrUnmuteExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandMute, executor.CommandMute,
executor.CommandSummaryLookup(executor.CommandMute), executor.CommandSummaryLookup(executor.CommandMute),
), ),
executor.CommandReject: executor.NewAcceptOrRejectExecutor( executor.CommandReject: executor.NewAcceptOrRejectExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandReject, executor.CommandReject,
executor.CommandSummaryLookup(executor.CommandReject), executor.CommandSummaryLookup(executor.CommandReject),
), ),
executor.CommandRemove: executor.NewRemoveExecutor( executor.CommandRemove: executor.NewRemoveExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandRemove, executor.CommandRemove,
executor.CommandSummaryLookup(executor.CommandRemove), executor.CommandSummaryLookup(executor.CommandRemove),
), ),
executor.CommandSwitch: executor.NewSwitchExecutor( executor.CommandSwitch: executor.NewSwitchExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandSwitch, executor.CommandSwitch,
executor.CommandSummaryLookup(executor.CommandSwitch), executor.CommandSummaryLookup(executor.CommandSwitch),
), ),
executor.CommandUnfollow: executor.NewFollowOrUnfollowExecutor( executor.CommandUnfollow: executor.NewFollowOrUnfollowExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandUnfollow, executor.CommandUnfollow,
executor.CommandSummaryLookup(executor.CommandUnfollow), executor.CommandSummaryLookup(executor.CommandUnfollow),
), ),
executor.CommandUnmute: executor.NewMuteOrUnmuteExecutor( executor.CommandUnmute: executor.NewMuteOrUnmuteExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandUnmute, executor.CommandUnmute,
executor.CommandSummaryLookup(executor.CommandUnmute), executor.CommandSummaryLookup(executor.CommandUnmute),
), ),
executor.CommandUnblock: executor.NewBlockOrUnblockExecutor( executor.CommandUnblock: executor.NewBlockOrUnblockExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandUnblock, executor.CommandUnblock,
executor.CommandSummaryLookup(executor.CommandUnblock), executor.CommandSummaryLookup(executor.CommandUnblock),
), ),
executor.CommandShow: executor.NewShowExecutor( executor.CommandShow: executor.NewShowExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
cacheDir,
imageViewer,
videoPlayer,
executor.CommandShow, executor.CommandShow,
executor.CommandSummaryLookup(executor.CommandShow), executor.CommandSummaryLookup(executor.CommandShow),
), ),
executor.CommandVersion: executor.NewVersionExecutor( executor.CommandVersion: executor.NewVersionExecutor(
printer, enbasPrinter,
executor.CommandVersion, executor.CommandVersion,
executor.CommandSummaryLookup(executor.CommandVersion), executor.CommandSummaryLookup(executor.CommandVersion),
binaryVersion, binaryVersion,
@ -192,8 +205,8 @@ func run() error {
gitCommit, gitCommit,
), ),
executor.CommandWhoami: executor.NewWhoAmIExecutor( executor.CommandWhoami: executor.NewWhoAmIExecutor(
printer, enbasPrinter,
configDir, enbasConfig,
executor.CommandWhoami, executor.CommandWhoami,
executor.CommandSummaryLookup(executor.CommandWhoami), executor.CommandSummaryLookup(executor.CommandWhoami),
), ),
@ -201,16 +214,16 @@ func run() error {
exe, ok := executorMap[command] exe, ok := executorMap[command]
if !ok { if !ok {
err := executor.UnknownCommandError{Command: command} err = executor.UnknownCommandError{Command: command}
printer.PrintFailure(err.Error() + ".") enbasPrinter.PrintFailure(err.Error() + ".")
flag.Usage() flag.Usage()
return err return err
} }
if err := executor.Execute(exe, args); err != nil { if err = executor.Execute(exe, args); err != nil {
printer.PrintFailure("(" + command + ") " + err.Error() + ".") enbasPrinter.PrintFailure("(" + command + ") " + err.Error() + ".")
return err return err
} }

View file

@ -25,8 +25,8 @@ type Client struct {
Timeout time.Duration Timeout time.Duration
} }
func NewClientFromConfig(configDir string) (*Client, error) { func NewClientFromFile(path string) (*Client, error) {
config, err := config.NewCredentialsConfigFromFile(configDir) config, err := config.NewCredentialsConfigFromFile(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to get the authentication configuration: %w", err) return nil, fmt.Errorf("unable to get the authentication configuration: %w", err)
} }
@ -51,7 +51,7 @@ func NewClient(authentication config.Credentials) *Client {
func (g *Client) AuthCodeURL() string { func (g *Client) AuthCodeURL() string {
format := "%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code" format := "%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code"
escapedRedirectURI := url.QueryEscape(internal.RedirectUri) escapedRedirectURI := url.QueryEscape(internal.RedirectURI)
return fmt.Sprintf( return fmt.Sprintf(
format, format,

View file

@ -24,7 +24,7 @@ type registerRequest struct {
func (g *Client) Register() error { func (g *Client) Register() error {
params := registerRequest{ params := registerRequest{
ClientName: internal.ApplicationName, ClientName: internal.ApplicationName,
RedirectUris: internal.RedirectUri, RedirectUris: internal.RedirectURI,
Scopes: "read write", Scopes: "read write",
Website: internal.ApplicationWebsite, Website: internal.ApplicationWebsite,
} }

View file

@ -17,7 +17,7 @@ import (
var errEmptyAccessToken = errors.New("received an empty access token") var errEmptyAccessToken = errors.New("received an empty access token")
type tokenRequest struct { type tokenRequest struct {
RedirectUri string `json:"redirect_uri"` RedirectURI string `json:"redirect_uri"`
ClientID string `json:"client_id"` ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"` ClientSecret string `json:"client_secret"`
GrantType string `json:"grant_type"` GrantType string `json:"grant_type"`
@ -33,7 +33,7 @@ type tokenResponse struct {
func (g *Client) UpdateToken(code string) error { func (g *Client) UpdateToken(code string) error {
params := tokenRequest{ params := tokenRequest{
RedirectUri: internal.RedirectUri, RedirectURI: internal.RedirectURI,
ClientID: g.Authentication.ClientID, ClientID: g.Authentication.ClientID,
ClientSecret: g.Authentication.ClientSecret, ClientSecret: g.Authentication.ClientSecret,
GrantType: "authorization_code", GrantType: "authorization_code",

108
internal/config/config.go Normal file
View file

@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: 2024 Dan Anglin <d.n.i.anglin@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later
package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
)
const (
configFileName = "config.json"
)
type Config struct {
CredentialsFile string `json:"credentialsFile"`
CacheDirectory string `json:"cacheDirectory"`
LineWrapMaxWidth int `json:"lineWrapMaxWidth"`
HTTP HTTPConfig `json:"http"`
Integrations Integrations `json:"integrations"`
}
type HTTPConfig struct {
Timeout int `json:"timeout"`
MediaTimeout int `json:"mediaTimeout"`
}
type Integrations struct {
Browser string `json:"browser"`
Editor string `json:"editor"`
Pager string `json:"pager"`
ImageViewer string `json:"imageViewer"`
VideoPlayer string `json:"videoPlayer"`
}
func NewConfigFromFile(configDir string) (*Config, error) {
path := configFile(configDir)
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("unable to open %s: %w", path, err)
}
defer file.Close()
var config Config
if err := json.NewDecoder(file).Decode(&config); err != nil {
return nil, fmt.Errorf("unable to decode the JSON data: %w", err)
}
return &config, nil
}
func FileExists(configDir string) (bool, error) {
path := configFile(configDir)
return utilities.FileExists(path)
}
func SaveDefaultConfigToFile(configDir string) error {
path := configFile(configDir)
file, err := os.Create(path)
if err != nil {
return fmt.Errorf("unable to create the file at %s: %w", path, err)
}
defer file.Close()
config := defaultConfig(configDir)
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
if err := encoder.Encode(config); err != nil {
return fmt.Errorf("unable to save the JSON data to the config file: %w", err)
}
return nil
}
func configFile(configDir string) string {
return filepath.Join(utilities.CalculateConfigDir(configDir), configFileName)
}
func defaultConfig(configDir string) Config {
credentialsFilePath := defaultCredentialsConfigFile(configDir)
return Config{
CredentialsFile: credentialsFilePath,
CacheDirectory: "",
HTTP: HTTPConfig{
Timeout: 5,
MediaTimeout: 30,
},
LineWrapMaxWidth: 80,
Integrations: Integrations{
Browser: "",
Editor: "",
Pager: "",
ImageViewer: "",
VideoPlayer: "",
},
}
}

View file

@ -10,13 +10,12 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
) )
const ( const (
credentialsFileName = "credentials.json" defaultCredentialsFileName = "credentials.json"
) )
type CredentialsConfig struct { type CredentialsConfig struct {
@ -42,35 +41,29 @@ func (e CredentialsNotFoundError) Error() string {
// SaveCredentials saves the credentials into the credentials file within the specified configuration // SaveCredentials saves the credentials into the credentials file within the specified configuration
// directory. If the directory is not specified then the default directory is used. If the directory // directory. If the directory is not specified then the default directory is used. If the directory
// is not present, it will be created. // is not present, it will be created.
func SaveCredentials(configDir, username string, credentials Credentials) (string, error) { func SaveCredentials(filePath, username string, credentials Credentials) (string, error) {
if err := utilities.EnsureDirectory(utilities.CalculateConfigDir(configDir)); err != nil { directory := filepath.Dir(filePath)
if err := utilities.EnsureDirectory(utilities.CalculateConfigDir(directory)); err != nil {
return "", fmt.Errorf("unable to ensure the configuration directory: %w", err) return "", fmt.Errorf("unable to ensure the configuration directory: %w", err)
} }
var authConfig CredentialsConfig var authConfig CredentialsConfig
filepath := credentialsConfigFile(configDir) if _, err := os.Stat(filePath); err != nil {
if _, err := os.Stat(filepath); err != nil {
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("unknown error received when running stat on %s: %w", filepath, err) return "", fmt.Errorf("unknown error received when running stat on %s: %w", filePath, err)
} }
authConfig.Credentials = make(map[string]Credentials) authConfig.Credentials = make(map[string]Credentials)
} else { } else {
authConfig, err = NewCredentialsConfigFromFile(configDir) authConfig, err = NewCredentialsConfigFromFile(filePath)
if err != nil { if err != nil {
return "", fmt.Errorf("unable to retrieve the existing authentication configuration: %w", err) return "", fmt.Errorf("unable to retrieve the existing authentication configuration: %w", err)
} }
} }
instance := "" instance := utilities.GetFQDN(credentials.Instance)
if strings.HasPrefix(credentials.Instance, "https://") {
instance = strings.TrimPrefix(credentials.Instance, "https://")
} else if strings.HasPrefix(credentials.Instance, "http://") {
instance = strings.TrimPrefix(credentials.Instance, "http://")
}
authenticationName := username + "@" + instance authenticationName := username + "@" + instance
@ -78,15 +71,15 @@ func SaveCredentials(configDir, username string, credentials Credentials) (strin
authConfig.Credentials[authenticationName] = credentials authConfig.Credentials[authenticationName] = credentials
if err := saveCredentialsConfigFile(authConfig, configDir); err != nil { if err := saveCredentialsConfigFile(authConfig, filePath); err != nil {
return "", fmt.Errorf("unable to save the authentication configuration to file: %w", err) return "", fmt.Errorf("unable to save the authentication configuration to file: %w", err)
} }
return authenticationName, nil return authenticationName, nil
} }
func UpdateCurrentAccount(account string, configDir string) error { func UpdateCurrentAccount(account string, filePath string) error {
credentialsConfig, err := NewCredentialsConfigFromFile(configDir) credentialsConfig, err := NewCredentialsConfigFromFile(filePath)
if err != nil { if err != nil {
return fmt.Errorf("unable to retrieve the existing authentication configuration: %w", err) return fmt.Errorf("unable to retrieve the existing authentication configuration: %w", err)
} }
@ -97,19 +90,19 @@ func UpdateCurrentAccount(account string, configDir string) error {
credentialsConfig.CurrentAccount = account credentialsConfig.CurrentAccount = account
if err := saveCredentialsConfigFile(credentialsConfig, configDir); err != nil { if err := saveCredentialsConfigFile(credentialsConfig, filePath); err != nil {
return fmt.Errorf("unable to save the authentication configuration to file: %w", err) return fmt.Errorf("unable to save the authentication configuration to file: %w", err)
} }
return nil return nil
} }
func NewCredentialsConfigFromFile(configDir string) (CredentialsConfig, error) { // NewCredentialsConfigFromFile creates a new CredentialsConfig value from reading
path := credentialsConfigFile(configDir) // the credentials file.
func NewCredentialsConfigFromFile(filePath string) (CredentialsConfig, error) {
file, err := os.Open(path) file, err := os.Open(filePath)
if err != nil { if err != nil {
return CredentialsConfig{}, fmt.Errorf("unable to open %s, %w", path, err) return CredentialsConfig{}, fmt.Errorf("unable to open %s: %w", filePath, err)
} }
defer file.Close() defer file.Close()
@ -122,14 +115,11 @@ func NewCredentialsConfigFromFile(configDir string) (CredentialsConfig, error) {
return authConfig, nil return authConfig, nil
} }
func saveCredentialsConfigFile(authConfig CredentialsConfig, configDir string) error { func saveCredentialsConfigFile(authConfig CredentialsConfig, filePath string) error {
path := credentialsConfigFile(configDir) file, err := os.Create(filePath)
file, err := os.Create(path)
if err != nil { if err != nil {
return fmt.Errorf("unable to open %s: %w", path, err) return fmt.Errorf("unable to create the file at %s: %w", filePath, err)
} }
defer file.Close() defer file.Close()
encoder := json.NewEncoder(file) encoder := json.NewEncoder(file)
@ -142,6 +132,6 @@ func saveCredentialsConfigFile(authConfig CredentialsConfig, configDir string) e
return nil return nil
} }
func credentialsConfigFile(configDir string) string { func defaultCredentialsConfigFile(configDir string) string {
return filepath.Join(utilities.CalculateConfigDir(configDir), credentialsFileName) return filepath.Join(utilities.CalculateConfigDir(configDir), defaultCredentialsFileName)
} }

View file

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
) )
@ -16,19 +17,19 @@ type AcceptOrRejectExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
configDir string config *config.Config
resourceType string resourceType string
accountName string accountName string
command string command string
} }
func NewAcceptOrRejectExecutor(enbasPrinter *printer.Printer, configDir, name, summary string) *AcceptOrRejectExecutor { func NewAcceptOrRejectExecutor(enbasPrinter *printer.Printer, config *config.Config, name, summary string) *AcceptOrRejectExecutor {
acceptExe := AcceptOrRejectExecutor{ acceptExe := AcceptOrRejectExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: enbasPrinter, printer: enbasPrinter,
configDir: configDir, config: config,
command: name, command: name,
} }
acceptExe.StringVar(&acceptExe.resourceType, flagType, "", "Specify the type of resource to accept or reject") acceptExe.StringVar(&acceptExe.resourceType, flagType, "", "Specify the type of resource to accept or reject")
@ -49,7 +50,7 @@ func (a *AcceptOrRejectExecutor) Execute() error {
return UnsupportedTypeError{resourceType: a.resourceType} return UnsupportedTypeError{resourceType: a.resourceType}
} }
gtsClient, err := client.NewClientFromConfig(a.configDir) gtsClient, err := client.NewClientFromFile(a.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err) return fmt.Errorf("unable to create the GoToSocial client: %w", err)
} }
@ -58,7 +59,7 @@ func (a *AcceptOrRejectExecutor) Execute() error {
} }
func (a *AcceptOrRejectExecutor) acceptOrRejectFollowRequest(gtsClient *client.Client) error { func (a *AcceptOrRejectExecutor) acceptOrRejectFollowRequest(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, a.accountName, a.configDir) accountID, err := getAccountID(gtsClient, false, a.accountName, a.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err) return fmt.Errorf("received an error while getting the account ID: %w", err)
} }

View file

@ -12,7 +12,7 @@ import (
"codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/model"
) )
func getAccountID(gtsClient *client.Client, myAccount bool, accountName, configDir string) (string, error) { func getAccountID(gtsClient *client.Client, myAccount bool, accountName, path string) (string, error) {
var ( var (
accountID string accountID string
err error err error
@ -20,7 +20,7 @@ func getAccountID(gtsClient *client.Client, myAccount bool, accountName, configD
switch { switch {
case myAccount: case myAccount:
accountID, err = getMyAccountID(gtsClient, configDir) accountID, err = getMyAccountID(gtsClient, path)
if err != nil { if err != nil {
return "", fmt.Errorf("unable to get your account ID: %w", err) return "", fmt.Errorf("unable to get your account ID: %w", err)
} }
@ -45,8 +45,8 @@ func getTheirAccountID(gtsClient *client.Client, accountURI string) (string, err
return account.ID, nil return account.ID, nil
} }
func getMyAccountID(gtsClient *client.Client, configDir string) (string, error) { func getMyAccountID(gtsClient *client.Client, path string) (string, error) {
account, err := getMyAccount(gtsClient, configDir) account, err := getMyAccount(gtsClient, path)
if err != nil { if err != nil {
return "", fmt.Errorf("received an error while getting your account details: %w", err) return "", fmt.Errorf("received an error while getting your account details: %w", err)
} }
@ -54,8 +54,8 @@ func getMyAccountID(gtsClient *client.Client, configDir string) (string, error)
return account.ID, nil return account.ID, nil
} }
func getMyAccount(gtsClient *client.Client, configDir string) (model.Account, error) { func getMyAccount(gtsClient *client.Client, path string) (model.Account, error) {
authConfig, err := config.NewCredentialsConfigFromFile(configDir) authConfig, err := config.NewCredentialsConfigFromFile(path)
if err != nil { if err != nil {
return model.Account{}, fmt.Errorf("unable to retrieve the authentication configuration: %w", err) return model.Account{}, fmt.Errorf("unable to retrieve the authentication configuration: %w", err)
} }

View file

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
) )
@ -17,7 +18,7 @@ type AddExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
configDir string config *config.Config
resourceType string resourceType string
toResourceType string toResourceType string
listID string listID string
@ -28,14 +29,14 @@ type AddExecutor struct {
content string content string
} }
func NewAddExecutor(printer *printer.Printer, configDir, name, summary string) *AddExecutor { func NewAddExecutor(printer *printer.Printer, config *config.Config, name, summary string) *AddExecutor {
emptyArr := make([]string, 0, 3) emptyArr := make([]string, 0, 3)
addExe := AddExecutor{ addExe := AddExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
accountNames: MultiStringFlagValue(emptyArr), accountNames: MultiStringFlagValue(emptyArr),
} }
@ -71,7 +72,7 @@ func (a *AddExecutor) Execute() error {
return UnsupportedTypeError{resourceType: a.toResourceType} return UnsupportedTypeError{resourceType: a.toResourceType}
} }
gtsClient, err := client.NewClientFromConfig(a.configDir) gtsClient, err := client.NewClientFromFile(a.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err) return fmt.Errorf("unable to create the GoToSocial client: %w", err)
} }
@ -154,7 +155,7 @@ func (a *AddExecutor) addNoteToAccount(gtsClient *client.Client) error {
return fmt.Errorf("unexpected number of accounts specified: want 1, got %d", len(a.accountNames)) return fmt.Errorf("unexpected number of accounts specified: want 1, got %d", len(a.accountNames))
} }
accountID, err := getAccountID(gtsClient, false, a.accountNames[0], a.configDir) accountID, err := getAccountID(gtsClient, false, a.accountNames[0], a.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err) return fmt.Errorf("received an error while getting the account ID: %w", err)
} }

View file

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
) )
@ -16,19 +17,19 @@ type BlockOrUnblockExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
configDir string config *config.Config
resourceType string resourceType string
accountName string accountName string
command string command string
} }
func NewBlockOrUnblockExecutor(printer *printer.Printer, configDir, name, summary string) *BlockOrUnblockExecutor { func NewBlockOrUnblockExecutor(printer *printer.Printer, config *config.Config, name, summary string) *BlockOrUnblockExecutor {
blockExe := BlockOrUnblockExecutor{ blockExe := BlockOrUnblockExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
command: name, command: name,
} }
blockExe.StringVar(&blockExe.resourceType, flagType, "", "Specify the type of resource to block or unblock") blockExe.StringVar(&blockExe.resourceType, flagType, "", "Specify the type of resource to block or unblock")
@ -49,7 +50,7 @@ func (b *BlockOrUnblockExecutor) Execute() error {
return UnsupportedTypeError{resourceType: b.resourceType} return UnsupportedTypeError{resourceType: b.resourceType}
} }
gtsClient, err := client.NewClientFromConfig(b.configDir) gtsClient, err := client.NewClientFromFile(b.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err) return fmt.Errorf("unable to create the GoToSocial client: %w", err)
} }
@ -62,7 +63,7 @@ func (b *BlockOrUnblockExecutor) blockOrUnblockAccount(gtsClient *client.Client)
return FlagNotSetError{flagText: flagAccountName} return FlagNotSetError{flagText: flagAccountName}
} }
accountID, err := getAccountID(gtsClient, false, b.accountName, b.configDir) accountID, err := getAccountID(gtsClient, false, b.accountName, b.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err) return fmt.Errorf("received an error while getting the account ID: %w", err)
} }

View file

@ -12,6 +12,7 @@ const (
CommandDelete string = "delete" CommandDelete string = "delete"
CommandEdit string = "edit" CommandEdit string = "edit"
CommandFollow string = "follow" CommandFollow string = "follow"
CommandInit string = "init"
CommandLogin string = "login" CommandLogin string = "login"
CommandMute string = "mute" CommandMute string = "mute"
CommandReject string = "reject" CommandReject string = "reject"
@ -31,6 +32,7 @@ const (
commandDeleteSummary string = "Delete a specific resource" commandDeleteSummary string = "Delete a specific resource"
commandEditSummary string = "Edit a specific resource" commandEditSummary string = "Edit a specific resource"
commandFollowSummary string = "Follow a resource (e.g. an account)" commandFollowSummary string = "Follow a resource (e.g. an account)"
commandInitSummary string = "Create a new configuration file in the specified configuration directory"
commandLoginSummary string = "Login to an account on GoToSocial" commandLoginSummary string = "Login to an account on GoToSocial"
commandMuteSummary string = "Mute a resource (e.g. an account)" commandMuteSummary string = "Mute a resource (e.g. an account)"
commandRejectSummary string = "Reject a request (e.g. a follow request)" commandRejectSummary string = "Reject a request (e.g. a follow request)"
@ -53,6 +55,7 @@ func CommandSummaryMap() map[string]string {
CommandDelete: commandDeleteSummary, CommandDelete: commandDeleteSummary,
CommandEdit: commandEditSummary, CommandEdit: commandEditSummary,
CommandFollow: commandFollowSummary, CommandFollow: commandFollowSummary,
CommandInit: commandInitSummary,
CommandLogin: commandLoginSummary, CommandLogin: commandLoginSummary,
CommandMute: commandMuteSummary, CommandMute: commandMuteSummary,
CommandReject: commandRejectSummary, CommandReject: commandRejectSummary,

View file

@ -10,6 +10,7 @@ import (
"strconv" "strconv"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/model"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
@ -19,6 +20,7 @@ type CreateExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
config *config.Config
addPoll bool addPoll bool
boostable bool boostable bool
federated bool federated bool
@ -27,7 +29,6 @@ type CreateExecutor struct {
pollHidesVoteCounts bool pollHidesVoteCounts bool
replyable bool replyable bool
sensitive *bool sensitive *bool
configDir string
content string content string
contentType string contentType string
fromFile string fromFile string
@ -41,12 +42,12 @@ type CreateExecutor struct {
pollOptions MultiStringFlagValue pollOptions MultiStringFlagValue
} }
func NewCreateExecutor(printer *printer.Printer, configDir, name, summary string) *CreateExecutor { func NewCreateExecutor(printer *printer.Printer, config *config.Config, name, summary string) *CreateExecutor {
createExe := CreateExecutor{ createExe := CreateExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
} }
createExe.BoolVar(&createExe.boostable, flagEnableReposts, true, "Specify if the status can be reposted/boosted by others") createExe.BoolVar(&createExe.boostable, flagEnableReposts, true, "Specify if the status can be reposted/boosted by others")
@ -90,7 +91,7 @@ func (c *CreateExecutor) Execute() error {
return FlagNotSetError{flagText: flagType} return FlagNotSetError{flagText: flagType}
} }
gtsClient, err := client.NewClientFromConfig(c.configDir) gtsClient, err := client.NewClientFromFile(c.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err) return fmt.Errorf("unable to create the GoToSocial client: %w", err)
} }

View file

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
) )
@ -16,17 +17,17 @@ type DeleteExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
configDir string config *config.Config
resourceType string resourceType string
listID string listID string
} }
func NewDeleteExecutor(printer *printer.Printer, configDir, name, summary string) *DeleteExecutor { func NewDeleteExecutor(printer *printer.Printer, config *config.Config, name, summary string) *DeleteExecutor {
deleteExe := DeleteExecutor{ deleteExe := DeleteExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
} }
deleteExe.StringVar(&deleteExe.resourceType, flagType, "", "Specify the type of resource to delete") deleteExe.StringVar(&deleteExe.resourceType, flagType, "", "Specify the type of resource to delete")
@ -51,7 +52,7 @@ func (d *DeleteExecutor) Execute() error {
return UnsupportedTypeError{resourceType: d.resourceType} return UnsupportedTypeError{resourceType: d.resourceType}
} }
gtsClient, err := client.NewClientFromConfig(d.configDir) gtsClient, err := client.NewClientFromFile(d.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err) return fmt.Errorf("unable to create the GoToSocial client: %w", err)
} }

View file

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/model"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
) )
@ -17,19 +18,19 @@ type EditExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
configDir string config *config.Config
resourceType string resourceType string
listID string listID string
listTitle string listTitle string
listRepliesPolicy string listRepliesPolicy string
} }
func NewEditExecutor(printer *printer.Printer, configDir, name, summary string) *EditExecutor { func NewEditExecutor(printer *printer.Printer, config *config.Config, name, summary string) *EditExecutor {
editExe := EditExecutor{ editExe := EditExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
} }
editExe.StringVar(&editExe.resourceType, flagType, "", "Specify the type of resource to update") editExe.StringVar(&editExe.resourceType, flagType, "", "Specify the type of resource to update")
@ -56,7 +57,7 @@ func (e *EditExecutor) Execute() error {
return UnsupportedTypeError{resourceType: e.resourceType} return UnsupportedTypeError{resourceType: e.resourceType}
} }
gtsClient, err := client.NewClientFromConfig(e.configDir) gtsClient, err := client.NewClientFromFile(e.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err) return fmt.Errorf("unable to create the GoToSocial client: %w", err)
} }

View file

@ -102,3 +102,11 @@ type NotFollowingError struct {
func (e NotFollowingError) Error() string { func (e NotFollowingError) Error() string {
return "you are not following " + e.Account return "you are not following " + e.Account
} }
type UnknownMediaAttachmentError struct {
AttachmentID string
}
func (e UnknownMediaAttachmentError) Error() string {
return "unknown media attachment '" + e.AttachmentID + "'"
}

View file

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
) )
@ -16,7 +17,7 @@ type FollowOrUnfollowExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
configDir string config *config.Config
resourceType string resourceType string
accountName string accountName string
showReposts bool showReposts bool
@ -24,13 +25,13 @@ type FollowOrUnfollowExecutor struct {
action string action string
} }
func NewFollowOrUnfollowExecutor(printer *printer.Printer, configDir, name, summary string) *FollowOrUnfollowExecutor { func NewFollowOrUnfollowExecutor(printer *printer.Printer, config *config.Config, name, summary string) *FollowOrUnfollowExecutor {
command := FollowOrUnfollowExecutor{ command := FollowOrUnfollowExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
action: name, action: name,
} }
command.StringVar(&command.resourceType, flagType, "", "Specify the type of resource to follow") command.StringVar(&command.resourceType, flagType, "", "Specify the type of resource to follow")
@ -53,7 +54,7 @@ func (f *FollowOrUnfollowExecutor) Execute() error {
return UnsupportedTypeError{resourceType: f.resourceType} return UnsupportedTypeError{resourceType: f.resourceType}
} }
gtsClient, err := client.NewClientFromConfig(f.configDir) gtsClient, err := client.NewClientFromFile(f.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err) return fmt.Errorf("unable to create the GoToSocial client: %w", err)
} }
@ -62,7 +63,7 @@ func (f *FollowOrUnfollowExecutor) Execute() error {
} }
func (f *FollowOrUnfollowExecutor) followOrUnfollowAccount(gtsClient *client.Client) error { func (f *FollowOrUnfollowExecutor) followOrUnfollowAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, f.accountName, f.configDir) accountID, err := getAccountID(gtsClient, false, f.accountName, f.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err) return fmt.Errorf("received an error while getting the account ID: %w", err)
} }

61
internal/executor/init.go Normal file
View file

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2024 Dan Anglin <d.n.i.anglin@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
)
type InitExecutor struct {
*flag.FlagSet
printer *printer.Printer
configDir string
}
func NewInitExecutor(printer *printer.Printer, configDir, name, summary string) *InitExecutor {
initExe := InitExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
configDir: configDir,
}
initExe.Usage = commandUsageFunc(name, summary, initExe.FlagSet)
return &initExe
}
func (i *InitExecutor) Execute() error {
if err := utilities.EnsureDirectory(i.configDir); err != nil {
return fmt.Errorf("unable to ensure that the configuration directory is present: %w", err)
}
i.printer.PrintSuccess("The configuration directory is present.")
fileExists, err := config.FileExists(i.configDir)
if err != nil {
return fmt.Errorf("unable to check if the config file exists: %w", err)
}
if fileExists {
i.printer.PrintInfo("The configuration file is already present in " + i.configDir + "\n")
return nil
}
if err := config.SaveDefaultConfigToFile(i.configDir); err != nil {
return fmt.Errorf("unable to create a new configuration file in %s: %w", i.configDir, err)
}
i.printer.PrintSuccess("Successfully created a new configuration file in " + i.configDir)
return nil
}

View file

@ -18,18 +18,18 @@ import (
type LoginExecutor struct { type LoginExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
configDir string config *config.Config
instance string instance string
} }
func NewLoginExecutor(printer *printer.Printer, configDir, name, summary string) *LoginExecutor { func NewLoginExecutor(printer *printer.Printer, config *config.Config, name, summary string) *LoginExecutor {
command := LoginExecutor{ command := LoginExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
instance: "", instance: "",
} }
command.StringVar(&command.instance, flagInstance, "", "Specify the instance that you want to login to.") command.StringVar(&command.instance, flagInstance, "", "Specify the instance that you want to login to.")
@ -95,7 +95,7 @@ func (c *LoginExecutor) Execute() error {
return fmt.Errorf("unable to verify the credentials: %w", err) return fmt.Errorf("unable to verify the credentials: %w", err)
} }
loginName, err := config.SaveCredentials(c.configDir, account.Username, gtsClient.Authentication) loginName, err := config.SaveCredentials(c.config.CredentialsFile, account.Username, gtsClient.Authentication)
if err != nil { if err != nil {
return fmt.Errorf("unable to save the authentication details: %w", err) return fmt.Errorf("unable to save the authentication details: %w", err)
} }

View file

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
) )
@ -16,21 +17,21 @@ type MuteOrUnmuteExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
config *config.Config
accountName string accountName string
configDir string
command string command string
resourceType string resourceType string
muteDuration TimeDurationFlagValue muteDuration TimeDurationFlagValue
muteNotifications bool muteNotifications bool
} }
func NewMuteOrUnmuteExecutor(printer *printer.Printer, configDir, name, summary string) *MuteOrUnmuteExecutor { func NewMuteOrUnmuteExecutor(printer *printer.Printer, config *config.Config, name, summary string) *MuteOrUnmuteExecutor {
exe := MuteOrUnmuteExecutor{ exe := MuteOrUnmuteExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
command: name, command: name,
} }
exe.StringVar(&exe.accountName, flagAccountName, "", "Specify the account name in full (username@domain)") exe.StringVar(&exe.accountName, flagAccountName, "", "Specify the account name in full (username@domain)")
@ -53,7 +54,7 @@ func (m *MuteOrUnmuteExecutor) Execute() error {
return UnsupportedTypeError{resourceType: m.resourceType} return UnsupportedTypeError{resourceType: m.resourceType}
} }
gtsClient, err := client.NewClientFromConfig(m.configDir) gtsClient, err := client.NewClientFromFile(m.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err) return fmt.Errorf("unable to create the GoToSocial client: %w", err)
} }
@ -66,7 +67,7 @@ func (m *MuteOrUnmuteExecutor) muteOrUnmuteAccount(gtsClient *client.Client) err
return FlagNotSetError{flagText: flagAccountName} return FlagNotSetError{flagText: flagAccountName}
} }
accountID, err := getAccountID(gtsClient, false, m.accountName, m.configDir) accountID, err := getAccountID(gtsClient, false, m.accountName, m.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err) return fmt.Errorf("received an error while getting the account ID: %w", err)
} }

View file

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
) )
@ -16,7 +17,7 @@ type RemoveExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
configDir string config *config.Config
resourceType string resourceType string
fromResourceType string fromResourceType string
listID string listID string
@ -24,14 +25,14 @@ type RemoveExecutor struct {
accountNames MultiStringFlagValue accountNames MultiStringFlagValue
} }
func NewRemoveExecutor(printer *printer.Printer, configDir, name, summary string) *RemoveExecutor { func NewRemoveExecutor(printer *printer.Printer, config *config.Config, name, summary string) *RemoveExecutor {
emptyArr := make([]string, 0, 3) emptyArr := make([]string, 0, 3)
removeExe := RemoveExecutor{ removeExe := RemoveExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
accountNames: MultiStringFlagValue(emptyArr), accountNames: MultiStringFlagValue(emptyArr),
} }
@ -63,7 +64,7 @@ func (r *RemoveExecutor) Execute() error {
return UnsupportedTypeError{resourceType: r.fromResourceType} return UnsupportedTypeError{resourceType: r.fromResourceType}
} }
gtsClient, err := client.NewClientFromConfig(r.configDir) gtsClient, err := client.NewClientFromFile(r.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err) return fmt.Errorf("unable to create the GoToSocial client: %w", err)
} }
@ -137,7 +138,7 @@ func (r *RemoveExecutor) removeNoteFromAccount(gtsClient *client.Client) error {
return fmt.Errorf("unexpected number of accounts specified: want 1, got %d", len(r.accountNames)) return fmt.Errorf("unexpected number of accounts specified: want 1, got %d", len(r.accountNames))
} }
accountID, err := getAccountID(gtsClient, false, r.accountNames[0], r.configDir) accountID, err := getAccountID(gtsClient, false, r.accountNames[0], r.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err) return fmt.Errorf("received an error while getting the account ID: %w", err)
} }

View file

@ -11,6 +11,7 @@ import (
"strings" "strings"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/model"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
@ -20,12 +21,11 @@ type ShowExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
config *config.Config
myAccount bool myAccount bool
skipAccountRelationship bool skipAccountRelationship bool
showUserPreferences bool showUserPreferences bool
showInBrowser bool showInBrowser bool
configDir string
cacheRoot string
resourceType string resourceType string
accountName string accountName string
statusID string statusID string
@ -34,21 +34,16 @@ type ShowExecutor struct {
tag string tag string
pollID string pollID string
fromResourceType string fromResourceType string
imageViewer string
videoPlayer string
limit int limit int
attachmentIDs MultiStringFlagValue attachmentIDs MultiStringFlagValue
} }
func NewShowExecutor(printer *printer.Printer, configDir, cacheRoot, imageViewer, videoPlayer, name, summary string) *ShowExecutor { func NewShowExecutor(printer *printer.Printer, config *config.Config, name, summary string) *ShowExecutor {
showExe := ShowExecutor{ showExe := ShowExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
cacheRoot: cacheRoot,
imageViewer: imageViewer,
videoPlayer: videoPlayer,
} }
showExe.BoolVar(&showExe.myAccount, flagMyAccount, false, "Set to true to lookup your account") showExe.BoolVar(&showExe.myAccount, flagMyAccount, false, "Set to true to lookup your account")
@ -100,7 +95,7 @@ func (s *ShowExecutor) Execute() error {
return UnsupportedTypeError{resourceType: s.resourceType} return UnsupportedTypeError{resourceType: s.resourceType}
} }
gtsClient, err := client.NewClientFromConfig(s.configDir) gtsClient, err := client.NewClientFromFile(s.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err) return fmt.Errorf("unable to create the GoToSocial client: %w", err)
} }
@ -126,7 +121,7 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error {
) )
if s.myAccount { if s.myAccount {
account, err = getMyAccount(gtsClient, s.configDir) account, err = getMyAccount(gtsClient, s.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("received an error while getting the account details: %w", err) return fmt.Errorf("received an error while getting the account details: %w", err)
} }
@ -288,7 +283,7 @@ func (s *ShowExecutor) showLists(gtsClient *client.Client) error {
} }
func (s *ShowExecutor) showFollowers(gtsClient *client.Client) error { func (s *ShowExecutor) showFollowers(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.configDir) accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err) return fmt.Errorf("received an error while getting the account ID: %w", err)
} }
@ -308,7 +303,7 @@ func (s *ShowExecutor) showFollowers(gtsClient *client.Client) error {
} }
func (s *ShowExecutor) showFollowing(gtsClient *client.Client) error { func (s *ShowExecutor) showFollowing(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.configDir) accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err) return fmt.Errorf("received an error while getting the account ID: %w", err)
} }
@ -471,7 +466,7 @@ func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error {
} }
cacheDir := filepath.Join( cacheDir := filepath.Join(
utilities.CalculateCacheDir(s.cacheRoot, utilities.GetFQDN(gtsClient.Authentication.Instance)), utilities.CalculateCacheDir(s.config.CacheDirectory, utilities.GetFQDN(gtsClient.Authentication.Instance)),
"media", "media",
) )
@ -498,7 +493,7 @@ func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error {
for _, attachmentID := range s.attachmentIDs { for _, attachmentID := range s.attachmentIDs {
mediaObj, ok := attachmentsHashMap[attachmentID] mediaObj, ok := attachmentsHashMap[attachmentID]
if !ok { if !ok {
return fmt.Errorf("unknown media attachment: %s", attachmentID) return UnknownMediaAttachmentError{AttachmentID: attachmentID}
} }
split := strings.Split(mediaObj.url, "/") split := strings.Split(mediaObj.url, "/")
@ -533,13 +528,13 @@ func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error {
} }
if len(imageFiles) > 0 { if len(imageFiles) > 0 {
if err := utilities.OpenMedia(s.imageViewer, imageFiles); err != nil { if err := utilities.OpenMedia(s.config.Integrations.ImageViewer, imageFiles); err != nil {
return fmt.Errorf("unable to open the image viewer: %w", err) return fmt.Errorf("unable to open the image viewer: %w", err)
} }
} }
if len(videoFiles) > 0 { if len(videoFiles) > 0 {
if err := utilities.OpenMedia(s.videoPlayer, videoFiles); err != nil { if err := utilities.OpenMedia(s.config.Integrations.VideoPlayer, videoFiles); err != nil {
return fmt.Errorf("unable to open the video player: %w", err) return fmt.Errorf("unable to open the video player: %w", err)
} }
} }

View file

@ -15,17 +15,17 @@ import (
type SwitchExecutor struct { type SwitchExecutor struct {
*flag.FlagSet *flag.FlagSet
configDir string config *config.Config
toResourceType string toResourceType string
accountName string accountName string
printer *printer.Printer printer *printer.Printer
} }
func NewSwitchExecutor(printer *printer.Printer, configDir, name, summary string) *SwitchExecutor { func NewSwitchExecutor(printer *printer.Printer, config *config.Config, name, summary string) *SwitchExecutor {
switchExe := SwitchExecutor{ switchExe := SwitchExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
} }
switchExe.StringVar(&switchExe.toResourceType, flagTo, "", "The account to switch to") switchExe.StringVar(&switchExe.toResourceType, flagTo, "", "The account to switch to")
@ -54,7 +54,7 @@ func (s *SwitchExecutor) switchToAccount() error {
return NoAccountSpecifiedError{} return NoAccountSpecifiedError{}
} }
if err := config.UpdateCurrentAccount(s.accountName, s.configDir); err != nil { if err := config.UpdateCurrentAccount(s.accountName, s.config.CredentialsFile); err != nil {
return fmt.Errorf("unable to switch account to the account: %w", err) return fmt.Errorf("unable to switch account to the account: %w", err)
} }

View file

@ -6,7 +6,7 @@ package executor
import ( import (
"flag" "flag"
"fmt" "slices"
"strings" "strings"
) )
@ -15,27 +15,41 @@ func commandUsageFunc(name, summary string, flagset *flag.FlagSet) func() {
return func() { return func() {
var builder strings.Builder var builder strings.Builder
fmt.Fprintf( builder.WriteString("SUMMARY:")
&builder, builder.WriteString("\n " + name + " - " + summary)
"SUMMARY:\n %s - %s\n\nUSAGE:\n enbas %s [flags]\n\nFLAGS:", builder.WriteString("\n\nUSAGE:")
name, builder.WriteString("\n enbas " + name)
summary,
name, flagMap := make(map[string]string)
)
flagset.VisitAll(func(f *flag.Flag) { flagset.VisitAll(func(f *flag.Flag) {
fmt.Fprintf( flagMap[f.Name] = f.Usage
&builder,
"\n --%s\n %s",
f.Name,
f.Usage,
)
}) })
if len(flagMap) > 0 {
flags := make([]string, len(flagMap))
ind := 0
for f := range flagMap {
flags[ind] = f
ind++
}
slices.Sort(flags)
builder.WriteString(" [flags]")
builder.WriteString("\n\nFLAGS:")
for _, value := range flags {
builder.WriteString("\n --" + value)
builder.WriteString("\n " + flagMap[value])
}
}
builder.WriteString("\n") builder.WriteString("\n")
w := flag.CommandLine.Output() w := flag.CommandLine.Output()
fmt.Fprint(w, builder.String()) _, _ = w.Write([]byte(builder.String()))
} }
} }

View file

@ -12,6 +12,7 @@ import (
type VersionExecutor struct { type VersionExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
showFullVersion bool showFullVersion bool
binaryVersion string binaryVersion string
@ -21,7 +22,7 @@ type VersionExecutor struct {
} }
func NewVersionExecutor( func NewVersionExecutor(
enbasPrinter *printer.Printer, printer *printer.Printer,
name, name,
summary, summary,
binaryVersion, binaryVersion,
@ -32,7 +33,7 @@ func NewVersionExecutor(
command := VersionExecutor{ command := VersionExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: enbasPrinter, printer: printer,
binaryVersion: binaryVersion, binaryVersion: binaryVersion,
buildTime: buildTime, buildTime: buildTime,
goVersion: goVersion, goVersion: goVersion,

View file

@ -15,16 +15,16 @@ import (
type WhoAmIExecutor struct { type WhoAmIExecutor struct {
*flag.FlagSet *flag.FlagSet
printer *printer.Printer printer *printer.Printer
configDir string config *config.Config
} }
func NewWhoAmIExecutor(printer *printer.Printer, configDir, name, summary string) *WhoAmIExecutor { func NewWhoAmIExecutor(printer *printer.Printer, config *config.Config, name, summary string) *WhoAmIExecutor {
whoExe := WhoAmIExecutor{ whoExe := WhoAmIExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError), FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer, printer: printer,
configDir: configDir, config: config,
} }
whoExe.Usage = commandUsageFunc(name, summary, whoExe.FlagSet) whoExe.Usage = commandUsageFunc(name, summary, whoExe.FlagSet)
@ -33,7 +33,7 @@ func NewWhoAmIExecutor(printer *printer.Printer, configDir, name, summary string
} }
func (c *WhoAmIExecutor) Execute() error { func (c *WhoAmIExecutor) Execute() error {
config, err := config.NewCredentialsConfigFromFile(c.configDir) config, err := config.NewCredentialsConfigFromFile(c.config.CredentialsFile)
if err != nil { if err != nil {
return fmt.Errorf("unable to load the credential config: %w", err) return fmt.Errorf("unable to load the credential config: %w", err)
} }

View file

@ -7,6 +7,6 @@ package internal
const ( const (
ApplicationName = "enbas" ApplicationName = "enbas"
ApplicationWebsite = "https://codeflow.dananglin.me.uk/apollo/enbas" ApplicationWebsite = "https://codeflow.dananglin.me.uk/apollo/enbas"
RedirectUri = "urn:ietf:wg:oauth:2.0:oob" RedirectURI = "urn:ietf:wg:oauth:2.0:oob"
UserAgent = "Enbas/0.0.0" UserAgent = "Enbas/0.0.0"
) )

View file

@ -9,7 +9,7 @@ type Application struct {
ClientSecret string `json:"client_secret"` ClientSecret string `json:"client_secret"`
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
RedirectUri string `json:"redirect_uri"` RedirectURI string `json:"redirect_uri"`
VapidKey string `json:"vapid_key"` VapidKey string `json:"vapid_key"`
Website string `json:"website"` Website string `json:"website"`
} }

View file

@ -95,7 +95,7 @@ type InstanceV2Thumbnail struct {
ThumbnailDescription string `json:"thumbnail_description"` ThumbnailDescription string `json:"thumbnail_description"`
ThumbnailType string `json:"thumbnail_type"` ThumbnailType string `json:"thumbnail_type"`
URL string `json:"url"` URL string `json:"url"`
Versions InstanceV2ThumbnailVersions `json:"versions"` Versions InstanceV2ThumbnailVersions `json:"versions"`
} }
type InstanceV2ThumbnailVersions struct { type InstanceV2ThumbnailVersions struct {

View file

@ -20,7 +20,7 @@ func CalculateConfigDir(configDir string) string {
configRoot, err := os.UserConfigDir() configRoot, err := os.UserConfigDir()
if err != nil { if err != nil {
configRoot = "." return filepath.Join(os.Getenv("HOME"), "."+internal.ApplicationName, "config")
} }
return filepath.Join(configRoot, internal.ApplicationName) return filepath.Join(configRoot, internal.ApplicationName)
@ -33,7 +33,7 @@ func CalculateCacheDir(cacheDir, instanceFQDN string) string {
cacheRoot, err := os.UserCacheDir() cacheRoot, err := os.UserCacheDir()
if err != nil { if err != nil {
cacheRoot = "." return filepath.Join(os.Getenv("HOME"), "."+internal.ApplicationName, "cache")
} }
return filepath.Join(cacheRoot, internal.ApplicationName, instanceFQDN) return filepath.Join(cacheRoot, internal.ApplicationName, instanceFQDN)