From 1361482dd4eeb6658c861d751208329fabad861b Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Wed, 21 Feb 2024 13:20:32 +0000 Subject: [PATCH] add subcommand for instance details --- cmd/enbas/instance.go | 64 ++++++++++++++++++++ cmd/enbas/main.go | 12 ++-- internal/client/client.go | 32 ++++++++++ internal/config/config.go | 4 +- internal/model/instance_v2.go | 106 ++++++++++++++++++++++++++++++++++ 5 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 cmd/enbas/instance.go create mode 100644 internal/model/instance_v2.go diff --git a/cmd/enbas/instance.go b/cmd/enbas/instance.go new file mode 100644 index 0000000..c8094f6 --- /dev/null +++ b/cmd/enbas/instance.go @@ -0,0 +1,64 @@ +package main + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" +) + +var instanceDetailsFormat = `INSTANCE: + %s - %s + +DOMAIN: + %s + +VERSION: + Running GoToSocial %s + +CONTACT: + name: %s + username: %s + email: %s +` + +type instanceCommand struct { + *flag.FlagSet + summary string +} + +func newInstanceCommand(name, summary string) *instanceCommand { + command := instanceCommand{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + summary: summary, + } + + command.Usage = commandUsageFunc(command.Name(), command.summary, command.FlagSet) + + return &command +} + +func (c *instanceCommand) Execute() error { + gtsClient, err := client.NewClientFromConfig() + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + instance, err := gtsClient.GetInstance() + if err != nil { + return fmt.Errorf("unable to retrieve the instance details; %w", err) + } + + fmt.Printf( + instanceDetailsFormat, + instance.Title, + instance.Description, + instance.Domain, + instance.Version, + instance.Contact.Account.DisplayName, + instance.Contact.Account.Username, + instance.Contact.Email, + ) + + return nil +} diff --git a/cmd/enbas/main.go b/cmd/enbas/main.go index 88fbdf8..2753cbb 100644 --- a/cmd/enbas/main.go +++ b/cmd/enbas/main.go @@ -9,8 +9,9 @@ import ( ) const ( - login string = "login" - version string = "version" + login string = "login" + version string = "version" + instance string = "instance" ) type Executor interface { @@ -21,8 +22,9 @@ type Executor interface { func main() { summaries := map[string]string{ - login: "login to an account on GoToSocial", - version: "print the application's version and build information", + login: "login to an account on GoToSocial", + version: "print the application's version and build information", + instance: "print the instance information", } flag.Usage = enbasUsageFunc(summaries) @@ -44,6 +46,8 @@ func main() { executor = newLoginCommand(login, summaries[login]) case version: executor = newVersionCommand(version, summaries[version]) + case instance: + executor = newInstanceCommand(instance, summaries[instance]) default: fmt.Printf("ERROR: Unknown subcommand: %s\n", subcommand) flag.Usage() diff --git a/internal/client/client.go b/internal/client/client.go index 8f95490..566cf39 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -19,6 +19,17 @@ type Client struct { Timeout time.Duration } +func NewClientFromConfig() (*Client, error) { + config, err := config.NewAuthenticationConfigFromFile() + if err != nil { + return nil, fmt.Errorf("unable to get the authentication configuration; %w", err) + } + + currentAuthentication := config.Authentications[config.CurrentAccount] + + return NewClient(currentAuthentication), nil +} + func NewClient(authentication config.Authentication) *Client { httpClient := http.Client{} @@ -53,6 +64,27 @@ func (g *Client) VerifyCredentials() (model.Account, error) { return account, nil } +func (g *Client) GetInstance() (model.InstanceV2, error) { + path := "/api/v2/instance" + url := g.Authentication.Instance + path + + ctx, cancel := context.WithTimeout(context.Background(), g.Timeout) + defer cancel() + + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return model.InstanceV2{}, fmt.Errorf("unable to create the HTTP request, %w", err) + } + + var instance model.InstanceV2 + + if err := g.sendRequest(request, &instance); err != nil { + return model.InstanceV2{}, fmt.Errorf("received an error after sending the request to get the instance details; %w", err) + } + + return instance, nil +} + func (g *Client) sendRequest(request *http.Request, object any) error { request.Header.Set("Content-Type", "application/json; charset=utf-8") request.Header.Set("Accept", "application/json; charset=utf-8") diff --git a/internal/config/config.go b/internal/config/config.go index da6ea8f..78b30c9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -39,7 +39,7 @@ func SaveAuthentication(username string, authentication Authentication) (string, config.Authentications = make(map[string]Authentication) } else { - config, err = newAuthenticationConfigFromFile() + config, err = NewAuthenticationConfigFromFile() if err != nil { return "", fmt.Errorf("unable to retrieve the existing authentication configuration; %w", err) } @@ -71,7 +71,7 @@ func SaveAuthentication(username string, authentication Authentication) (string, return authenticationName, nil } -func newAuthenticationConfigFromFile() (AuthenticationConfig, error) { +func NewAuthenticationConfigFromFile() (AuthenticationConfig, error) { path := authenticationConfigFile() file, err := os.Open(path) diff --git a/internal/model/instance_v2.go b/internal/model/instance_v2.go new file mode 100644 index 0000000..7ade86a --- /dev/null +++ b/internal/model/instance_v2.go @@ -0,0 +1,106 @@ +package model + +type InstanceV2 struct { + AccountDomain string `json:"account_domain"` + Configuration InstanceConfiguration `json:"configuration"` + Contact InstanceV2Contact `json:"contact"` + Description string `json:"description"` + Domain string `json:"domain"` + Languages []string `json:"languages"` + Registrations InstanceV2Registrations `json:"registrations"` + Rules []InstanceRule `json:"rules"` + SourceURL string `json:"source_url"` + Terms string `json:"terms"` + Thumbnail InstanceV2Thumbnail `json:"thumbnail"` + Title string `json:"title"` + Usage InstanceV2Usage `json:"usage"` + Version string `json:"version"` +} + +type InstanceConfiguration struct { + Accounts InstanceConfigurationAccounts `json:"accounts"` + Emojis InstanceConfigurationEmojis `json:"emojis"` + MediaAttachments InstanceConfigurationMediaAttachments `json:"media_attachments"` + Polls InstanceConfigurationPolls `json:"polls"` + Statuses InstanceConfigurationStatuses `json:"statuses"` + Translation InstanceV2ConfigurationTranslation `json:"translation"` + URLs InstanceV2URLs `json:"urls"` +} + +type InstanceConfigurationAccounts struct { + AllowCustomCSS bool `json:"allow_custom_css"` + MaxFeaturedTags int `json:"max_featured_tags"` + MaxProfileFields int `json:"max_profile_fields"` +} + +type InstanceConfigurationEmojis struct { + EmojiSizeLimit int `json:"emoji_size_limit"` +} + +type InstanceConfigurationMediaAttachments struct { + ImageMatrixLimit int `json:"image_matrix_limit"` + ImageSizeLimit int `json:"image_size_limit"` + SupportedMimeTypes []string `json:"supported_mime_types"` + VideoFrameRateLimit int `json:"video_frame_rate_limit"` + VideoMatrixLimit int `json:"video_matrix_limit"` + VideoSizeLimit int `json:"video_size_limit"` +} + +type InstanceConfigurationPolls struct { + MaxCharactersPerOption int `json:"max_characters_per_option"` + MaxExpiration int `json:"max_expiration"` + MaxOptions int `json:"max_options"` + MinExpiration int `json:"min_expiration"` +} + +type InstanceConfigurationStatuses struct { + CharactersReservedPerURL int `json:"characters_reserved_per_url"` + MaxCharacters int `json:"max_characters"` + MaxMediaAttachments int `json:"max_media_attachments"` + SupportedMimeTypes []string `json:"supported_mime_types"` +} + +type InstanceV2ConfigurationTranslation struct { + Enabled bool `json:"enabled"` +} + +type InstanceV2URLs struct { + Streaming string `json:"streaming"` +} + +type InstanceV2Contact struct { + Account Account `json:"account"` + Email string `json:"email"` +} + +type InstanceV2Registrations struct { + ApprovalRequired bool `json:"approval_required"` + Enabled bool `json:"enabled"` + Message string `json:"message"` +} + +type InstanceRule struct { + ID string `json:"id"` + Text string `json:"text"` +} + +type InstanceV2Thumbnail struct { + BlurHash string `json:"blurhash"` + ThumbnailDescription string `json:"thumbnail_description"` + ThumbnailType string `json:"thumbnail_type"` + URL string `json:"url"` + Version InstanceV2ThumbnailVersions `json:"versions"` +} + +type InstanceV2ThumbnailVersions struct { + Size1URL string `json:"@1x"` + Size2URL string `json:"@2x"` +} + +type InstanceV2Usage struct { + Users InstanceV2Users `json:"users"` +} + +type InstanceV2Users struct { + ActiveMonth int `json:"active_month"` +}