diff --git a/cmd/enbas/main.go b/cmd/enbas/main.go index e5105dd..c288602 100644 --- a/cmd/enbas/main.go +++ b/cmd/enbas/main.go @@ -125,6 +125,12 @@ func run() error { executor.CommandLogin, executor.CommandSummaryLookup(executor.CommandLogin), ), + executor.CommandMute: executor.NewMuteOrUnmuteExecutor( + printer, + configDir, + executor.CommandMute, + executor.CommandSummaryLookup(executor.CommandMute), + ), executor.CommandReject: executor.NewAcceptOrRejectExecutor( printer, configDir, @@ -149,6 +155,12 @@ func run() error { executor.CommandUnfollow, executor.CommandSummaryLookup(executor.CommandUnfollow), ), + executor.CommandUnmute: executor.NewMuteOrUnmuteExecutor( + printer, + configDir, + executor.CommandUnmute, + executor.CommandSummaryLookup(executor.CommandUnmute), + ), executor.CommandUnblock: executor.NewBlockOrUnblockExecutor( printer, configDir, diff --git a/internal/client/accounts.go b/internal/client/accounts.go index 1f9161c..55c1983 100644 --- a/internal/client/accounts.go +++ b/internal/client/accounts.go @@ -223,3 +223,51 @@ func (g *Client) RejectFollowRequest(accountID string) error { return nil } + +func (g *Client) GetMutedAccounts(limit int) (model.AccountList, error) { + url := g.Authentication.Instance + fmt.Sprintf("/api/v1/mutes?limit=%d", limit) + + var accounts []model.Account + + if err := g.sendRequest(http.MethodGet, url, nil, &accounts); err != nil { + return model.AccountList{}, fmt.Errorf("received an error after sending the request to get the list of muted accounts: %w", err) + } + + muted := model.AccountList{ + Type: model.AccountListMuted, + Accounts: accounts, + } + + return muted, nil +} + +type MuteAccountForm struct { + Notifications bool `json:"notifications"` + Duration int `json:"duration"` +} + +func (g *Client) MuteAccount(accountID string, form MuteAccountForm) error { + data, err := json.Marshal(form) + if err != nil { + return fmt.Errorf("unable to marshal the form: %w", err) + } + + requestBody := bytes.NewBuffer(data) + url := g.Authentication.Instance + "/api/v1/accounts/" + accountID + "/mute" + + if err := g.sendRequest(http.MethodPost, url, requestBody, nil); err != nil { + return fmt.Errorf("received an error after sending the request to mute the account: %w", err) + } + + return nil +} + +func (g *Client) UnmuteAccount(accountID string) error { + url := g.Authentication.Instance + "/api/v1/accounts/" + accountID + "/unmute" + + if err := g.sendRequest(http.MethodPost, url, nil, nil); err != nil { + return fmt.Errorf("received an error after sending the request to unmute the account: %w", err) + } + + return nil +} diff --git a/internal/executor/block_or_unblock.go b/internal/executor/block_or_unblock.go index c129bb5..d8b4e0c 100644 --- a/internal/executor/block_or_unblock.go +++ b/internal/executor/block_or_unblock.go @@ -58,6 +58,10 @@ func (b *BlockOrUnblockExecutor) Execute() error { } func (b *BlockOrUnblockExecutor) blockOrUnblockAccount(gtsClient *client.Client) error { + if b.accountName == "" { + return FlagNotSetError{flagText: flagAccountName} + } + accountID, err := getAccountID(gtsClient, false, b.accountName, b.configDir) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) diff --git a/internal/executor/commands.go b/internal/executor/commands.go index fe5726f..a94673b 100644 --- a/internal/executor/commands.go +++ b/internal/executor/commands.go @@ -13,12 +13,14 @@ const ( CommandEdit string = "edit" CommandFollow string = "follow" CommandLogin string = "login" + CommandMute string = "mute" CommandReject string = "reject" CommandRemove string = "remove" CommandShow string = "show" CommandSwitch string = "switch" CommandUnblock string = "unblock" CommandUnfollow string = "unfollow" + CommandUnmute string = "unmute" CommandVersion string = "version" CommandWhoami string = "whoami" @@ -30,12 +32,14 @@ const ( commandEditSummary string = "Edit a specific resource" commandFollowSummary string = "Follow a resource (e.g. an account)" commandLoginSummary string = "Login to an account on GoToSocial" + commandMuteSummary string = "Mute a resource (e.g. an account)" commandRejectSummary string = "Reject a request (e.g. a follow request)" commandRemoveSummary string = "Remove a resource from another resource" commandShowSummary string = "Print details about a specified resource" commandSwitchSummary string = "Perform a switch operation (e.g. switch logged in accounts)" commandUnblockSummary string = "Unblock a resource (e.g. an account)" commandUnfollowSummary string = "Unfollow a resource (e.g. an account)" + commandUnmuteSummary string = "Unmute a resource (e.g. an account)" commandVersionSummary string = "Print the application's version and build information" commandWhoamiSummary string = "Print the account that you are currently logged in to" ) @@ -50,12 +54,14 @@ func CommandSummaryMap() map[string]string { CommandEdit: commandEditSummary, CommandFollow: commandFollowSummary, CommandLogin: commandLoginSummary, + CommandMute: commandMuteSummary, CommandReject: commandRejectSummary, CommandRemove: commandRemoveSummary, CommandShow: commandShowSummary, CommandSwitch: commandSwitchSummary, CommandUnblock: commandUnblockSummary, CommandUnfollow: commandUnfollowSummary, + CommandUnmute: commandUnmuteSummary, CommandVersion: commandVersionSummary, CommandWhoami: commandWhoamiSummary, } diff --git a/internal/executor/flags.go b/internal/executor/flags.go index 9faf44d..ac1115f 100644 --- a/internal/executor/flags.go +++ b/internal/executor/flags.go @@ -32,6 +32,8 @@ const ( flagListTitle = "list-title" flagListRepliesPolicy = "list-replies-policy" flagMyAccount = "my-account" + flagMuteDuration = "mute-duration" + flagMuteNotifications = "mute-notifications" flagNotify = "notify" flagPollAllowsMultipleChoices = "poll-allows-multiple-choices" flagPollExpiresIn = "poll-expires-in" diff --git a/internal/executor/mute_or_unmute.go b/internal/executor/mute_or_unmute.go new file mode 100644 index 0000000..ff49f5e --- /dev/null +++ b/internal/executor/mute_or_unmute.go @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2024 Dan Anglin +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" +) + +type MuteOrUnmuteExecutor struct { + *flag.FlagSet + + printer *printer.Printer + accountName string + configDir string + command string + resourceType string + muteDuration TimeDurationFlagValue + muteNotifications bool +} + +func NewMuteOrUnmuteExecutor(printer *printer.Printer, configDir, name, summary string) *MuteOrUnmuteExecutor { + exe := MuteOrUnmuteExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + printer: printer, + configDir: configDir, + command: name, + } + + exe.StringVar(&exe.accountName, flagAccountName, "", "Specify the account name in full (username@domain)") + exe.StringVar(&exe.resourceType, flagType, "", "Specify the type of resource to mute or unmute") + exe.BoolVar(&exe.muteNotifications, flagMuteNotifications, false, "Mute notifications as well as posts") + exe.Var(&exe.muteDuration, flagMuteDuration, "Specify how long the mute should last for. To mute indefinitely, set this to 0s") + + exe.Usage = commandUsageFunc(name, summary, exe.FlagSet) + + return &exe +} + +func (m *MuteOrUnmuteExecutor) Execute() error { + funcMap := map[string]func(*client.Client) error{ + resourceAccount: m.muteOrUnmuteAccount, + } + + doFunc, ok := funcMap[m.resourceType] + if !ok { + return UnsupportedTypeError{resourceType: m.resourceType} + } + + gtsClient, err := client.NewClientFromConfig(m.configDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client: %w", err) + } + + return doFunc(gtsClient) +} + +func (m *MuteOrUnmuteExecutor) muteOrUnmuteAccount(gtsClient *client.Client) error { + if m.accountName == "" { + return FlagNotSetError{flagText: flagAccountName} + } + + accountID, err := getAccountID(gtsClient, false, m.accountName, m.configDir) + if err != nil { + return fmt.Errorf("received an error while getting the account ID: %w", err) + } + + switch m.command { + case CommandMute: + return m.muteAccount(gtsClient, accountID) + case CommandUnmute: + return m.unmuteAccount(gtsClient, accountID) + default: + return nil + } +} + +func (m *MuteOrUnmuteExecutor) muteAccount(gtsClient *client.Client, accountID string) error { + form := client.MuteAccountForm{ + Notifications: m.muteNotifications, + Duration: int(m.muteDuration.Duration.Seconds()), + } + + if err := gtsClient.MuteAccount(accountID, form); err != nil { + return fmt.Errorf("unable to mute the account: %w", err) + } + + m.printer.PrintSuccess("Successfully muted the account.") + + return nil +} + +func (m *MuteOrUnmuteExecutor) unmuteAccount(gtsClient *client.Client, accountID string) error { + if err := gtsClient.UnmuteAccount(accountID); err != nil { + return fmt.Errorf("unable to unmute the account: %w", err) + } + + m.printer.PrintSuccess("Successfully unmuted the account.") + + return nil +} diff --git a/internal/executor/resources.go b/internal/executor/resources.go index a634113..b0d336a 100644 --- a/internal/executor/resources.go +++ b/internal/executor/resources.go @@ -16,6 +16,7 @@ const ( resourceLike = "like" resourceLiked = "liked" resourceList = "list" + resourceMutedAccounts = "muted-accounts" resourceNote = "note" resourcePoll = "poll" resourceStatus = "status" diff --git a/internal/executor/show.go b/internal/executor/show.go index 03a48e8..2bcbbe7 100644 --- a/internal/executor/show.go +++ b/internal/executor/show.go @@ -78,6 +78,7 @@ func (s *ShowExecutor) Execute() error { resourceStarred: s.showLiked, resourceFollowRequest: s.showFollowRequests, resourcePoll: s.showPoll, + resourceMutedAccounts: s.showMutedAccounts, } doFunc, ok := funcMap[s.resourceType] @@ -386,3 +387,18 @@ func (s *ShowExecutor) showPoll(gtsClient *client.Client) error { return nil } + +func (s *ShowExecutor) showMutedAccounts(gtsClient *client.Client) error { + muted, err := gtsClient.GetMutedAccounts(s.limit) + if err != nil { + return fmt.Errorf("unable to retrieve the list of muted accounts: %w", err) + } + + if len(muted.Accounts) > 0 { + s.printer.PrintAccountList(muted) + } else { + s.printer.PrintInfo("You have not muted any accounts.\n") + } + + return nil +} diff --git a/internal/model/account.go b/internal/model/account.go index 6a1f520..e89266b 100644 --- a/internal/model/account.go +++ b/internal/model/account.go @@ -84,6 +84,7 @@ const ( AccountListFollowing AccountListBlockedAccount AccountListFollowRequests + AccountListMuted ) type AccountList struct { diff --git a/internal/printer/account.go b/internal/printer/account.go index 655ca0d..10e935e 100644 --- a/internal/printer/account.go +++ b/internal/printer/account.go @@ -117,6 +117,8 @@ func (p Printer) PrintAccountList(list model.AccountList) { builder.WriteString(p.headerFormat("Blocked accounts:")) case model.AccountListFollowRequests: builder.WriteString(p.headerFormat("Accounts that have requested to follow you:")) + case model.AccountListMuted: + builder.WriteString(p.headerFormat("Muted accounts:")) default: builder.WriteString(p.headerFormat("Accounts:")) }