From 56445601a394e343ffeb5e87f3879746ab2df692 Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Tue, 18 Jun 2024 04:59:32 +0100 Subject: [PATCH] feat: add support for muting and unmuting accounts Now that muting and unmuting accounts are now supported in GoToSocial (as of version 0.16.0) Enbas can now allow users to do that. Users can also see a list of accounts that they've muted. --- cmd/enbas/main.go | 12 +++ internal/client/accounts.go | 48 ++++++++++++ internal/executor/block_or_unblock.go | 4 + internal/executor/commands.go | 6 ++ internal/executor/flags.go | 2 + internal/executor/mute_or_unmute.go | 107 ++++++++++++++++++++++++++ internal/executor/resources.go | 1 + internal/executor/show.go | 16 ++++ internal/model/account.go | 1 + internal/printer/account.go | 2 + 10 files changed, 199 insertions(+) create mode 100644 internal/executor/mute_or_unmute.go 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:")) }