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.
This commit is contained in:
Dan Anglin 2024-06-18 04:59:32 +01:00
parent 48666645c7
commit 56445601a3
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
10 changed files with 199 additions and 0 deletions

View file

@ -125,6 +125,12 @@ func run() error {
executor.CommandLogin, executor.CommandLogin,
executor.CommandSummaryLookup(executor.CommandLogin), executor.CommandSummaryLookup(executor.CommandLogin),
), ),
executor.CommandMute: executor.NewMuteOrUnmuteExecutor(
printer,
configDir,
executor.CommandMute,
executor.CommandSummaryLookup(executor.CommandMute),
),
executor.CommandReject: executor.NewAcceptOrRejectExecutor( executor.CommandReject: executor.NewAcceptOrRejectExecutor(
printer, printer,
configDir, configDir,
@ -149,6 +155,12 @@ func run() error {
executor.CommandUnfollow, executor.CommandUnfollow,
executor.CommandSummaryLookup(executor.CommandUnfollow), executor.CommandSummaryLookup(executor.CommandUnfollow),
), ),
executor.CommandUnmute: executor.NewMuteOrUnmuteExecutor(
printer,
configDir,
executor.CommandUnmute,
executor.CommandSummaryLookup(executor.CommandUnmute),
),
executor.CommandUnblock: executor.NewBlockOrUnblockExecutor( executor.CommandUnblock: executor.NewBlockOrUnblockExecutor(
printer, printer,
configDir, configDir,

View file

@ -223,3 +223,51 @@ func (g *Client) RejectFollowRequest(accountID string) error {
return nil 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
}

View file

@ -58,6 +58,10 @@ func (b *BlockOrUnblockExecutor) Execute() error {
} }
func (b *BlockOrUnblockExecutor) blockOrUnblockAccount(gtsClient *client.Client) 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) accountID, err := getAccountID(gtsClient, false, b.accountName, b.configDir)
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

@ -13,12 +13,14 @@ const (
CommandEdit string = "edit" CommandEdit string = "edit"
CommandFollow string = "follow" CommandFollow string = "follow"
CommandLogin string = "login" CommandLogin string = "login"
CommandMute string = "mute"
CommandReject string = "reject" CommandReject string = "reject"
CommandRemove string = "remove" CommandRemove string = "remove"
CommandShow string = "show" CommandShow string = "show"
CommandSwitch string = "switch" CommandSwitch string = "switch"
CommandUnblock string = "unblock" CommandUnblock string = "unblock"
CommandUnfollow string = "unfollow" CommandUnfollow string = "unfollow"
CommandUnmute string = "unmute"
CommandVersion string = "version" CommandVersion string = "version"
CommandWhoami string = "whoami" CommandWhoami string = "whoami"
@ -30,12 +32,14 @@ const (
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)"
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)"
commandRejectSummary string = "Reject a request (e.g. a follow request)" commandRejectSummary string = "Reject a request (e.g. a follow request)"
commandRemoveSummary string = "Remove a resource from another resource" commandRemoveSummary string = "Remove a resource from another resource"
commandShowSummary string = "Print details about a specified resource" commandShowSummary string = "Print details about a specified resource"
commandSwitchSummary string = "Perform a switch operation (e.g. switch logged in accounts)" commandSwitchSummary string = "Perform a switch operation (e.g. switch logged in accounts)"
commandUnblockSummary string = "Unblock a resource (e.g. an account)" commandUnblockSummary string = "Unblock a resource (e.g. an account)"
commandUnfollowSummary string = "Unfollow 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" commandVersionSummary string = "Print the application's version and build information"
commandWhoamiSummary string = "Print the account that you are currently logged in to" commandWhoamiSummary string = "Print the account that you are currently logged in to"
) )
@ -50,12 +54,14 @@ func CommandSummaryMap() map[string]string {
CommandEdit: commandEditSummary, CommandEdit: commandEditSummary,
CommandFollow: commandFollowSummary, CommandFollow: commandFollowSummary,
CommandLogin: commandLoginSummary, CommandLogin: commandLoginSummary,
CommandMute: commandMuteSummary,
CommandReject: commandRejectSummary, CommandReject: commandRejectSummary,
CommandRemove: commandRemoveSummary, CommandRemove: commandRemoveSummary,
CommandShow: commandShowSummary, CommandShow: commandShowSummary,
CommandSwitch: commandSwitchSummary, CommandSwitch: commandSwitchSummary,
CommandUnblock: commandUnblockSummary, CommandUnblock: commandUnblockSummary,
CommandUnfollow: commandUnfollowSummary, CommandUnfollow: commandUnfollowSummary,
CommandUnmute: commandUnmuteSummary,
CommandVersion: commandVersionSummary, CommandVersion: commandVersionSummary,
CommandWhoami: commandWhoamiSummary, CommandWhoami: commandWhoamiSummary,
} }

View file

@ -32,6 +32,8 @@ const (
flagListTitle = "list-title" flagListTitle = "list-title"
flagListRepliesPolicy = "list-replies-policy" flagListRepliesPolicy = "list-replies-policy"
flagMyAccount = "my-account" flagMyAccount = "my-account"
flagMuteDuration = "mute-duration"
flagMuteNotifications = "mute-notifications"
flagNotify = "notify" flagNotify = "notify"
flagPollAllowsMultipleChoices = "poll-allows-multiple-choices" flagPollAllowsMultipleChoices = "poll-allows-multiple-choices"
flagPollExpiresIn = "poll-expires-in" flagPollExpiresIn = "poll-expires-in"

View file

@ -0,0 +1,107 @@
// 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/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
}

View file

@ -16,6 +16,7 @@ const (
resourceLike = "like" resourceLike = "like"
resourceLiked = "liked" resourceLiked = "liked"
resourceList = "list" resourceList = "list"
resourceMutedAccounts = "muted-accounts"
resourceNote = "note" resourceNote = "note"
resourcePoll = "poll" resourcePoll = "poll"
resourceStatus = "status" resourceStatus = "status"

View file

@ -78,6 +78,7 @@ func (s *ShowExecutor) Execute() error {
resourceStarred: s.showLiked, resourceStarred: s.showLiked,
resourceFollowRequest: s.showFollowRequests, resourceFollowRequest: s.showFollowRequests,
resourcePoll: s.showPoll, resourcePoll: s.showPoll,
resourceMutedAccounts: s.showMutedAccounts,
} }
doFunc, ok := funcMap[s.resourceType] doFunc, ok := funcMap[s.resourceType]
@ -386,3 +387,18 @@ func (s *ShowExecutor) showPoll(gtsClient *client.Client) error {
return nil 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
}

View file

@ -84,6 +84,7 @@ const (
AccountListFollowing AccountListFollowing
AccountListBlockedAccount AccountListBlockedAccount
AccountListFollowRequests AccountListFollowRequests
AccountListMuted
) )
type AccountList struct { type AccountList struct {

View file

@ -117,6 +117,8 @@ func (p Printer) PrintAccountList(list model.AccountList) {
builder.WriteString(p.headerFormat("Blocked accounts:")) builder.WriteString(p.headerFormat("Blocked accounts:"))
case model.AccountListFollowRequests: case model.AccountListFollowRequests:
builder.WriteString(p.headerFormat("Accounts that have requested to follow you:")) builder.WriteString(p.headerFormat("Accounts that have requested to follow you:"))
case model.AccountListMuted:
builder.WriteString(p.headerFormat("Muted accounts:"))
default: default:
builder.WriteString(p.headerFormat("Accounts:")) builder.WriteString(p.headerFormat("Accounts:"))
} }