From 5fb55ed2cfc4f01444022d4a586e3481cefbde26 Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Mon, 10 Jun 2024 10:58:43 +0100 Subject: [PATCH] feat: accept or reject follow requests Add support to allow users to accept or reject follow requests. --- cmd/enbas/main.go | 32 +++++++++- internal/client/accounts.go | 39 +++++++++++- internal/executor/accept_or_reject.go | 91 +++++++++++++++++++++++++++ internal/executor/const.go | 31 ++++----- internal/executor/show.go | 38 +++++++---- internal/model/account.go | 3 + 6 files changed, 205 insertions(+), 29 deletions(-) create mode 100644 internal/executor/accept_or_reject.go diff --git a/cmd/enbas/main.go b/cmd/enbas/main.go index 814abfa..33e996b 100644 --- a/cmd/enbas/main.go +++ b/cmd/enbas/main.go @@ -28,6 +28,8 @@ const ( commandUnfollow string = "unfollow" commandBlock string = "block" commandUnblock string = "unblock" + commandAccept string = "accept" + commandReject string = "reject" ) var ( @@ -60,6 +62,8 @@ func run() error { commandUnfollow: "Unfollow a resource (e.g. an account)", commandBlock: "Block a resource (e.g. an account)", commandUnblock: "Unblock a resource (e.g. an account)", + commandAccept: "Accept a request (e.g. a follow request)", + commandReject: "Reject a request (e.g. a follow request)", } topLevelFlags := executor.TopLevelFlags{ @@ -108,6 +112,13 @@ func run() error { var err error switch command { + case commandAccept: + exe := executor.NewAcceptOrRejectExecutor( + topLevelFlags, + commandAccept, + commandSummaries[commandAccept], + ) + err = executor.Execute(exe, args) case commandAdd: exe := executor.NewAddExecutor( topLevelFlags, @@ -159,6 +170,13 @@ func run() error { commandSummaries[commandLogin], ) err = executor.Execute(exe, args) + case commandReject: + exe := executor.NewAcceptOrRejectExecutor( + topLevelFlags, + commandReject, + commandSummaries[commandReject], + ) + err = executor.Execute(exe, args) case commandRemove: exe := executor.NewRemoveExecutor( topLevelFlags, @@ -174,10 +192,20 @@ func run() error { ) err = executor.Execute(exe, args) case commandUnfollow: - exe := executor.NewFollowExecutor(topLevelFlags, commandUnfollow, commandSummaries[commandUnfollow], true) + exe := executor.NewFollowExecutor( + topLevelFlags, + commandUnfollow, + commandSummaries[commandUnfollow], + true, + ) err = executor.Execute(exe, args) case commandUnblock: - exe := executor.NewBlockExecutor(topLevelFlags, commandUnblock, commandSummaries[commandUnblock], true) + exe := executor.NewBlockExecutor( + topLevelFlags, + commandUnblock, + commandSummaries[commandUnblock], + true, + ) err = executor.Execute(exe, args) case commandShow: exe := executor.NewShowExecutor(topLevelFlags, commandShow, commandSummaries[commandShow]) diff --git a/internal/client/accounts.go b/internal/client/accounts.go index 9dc00d0..85a3d39 100644 --- a/internal/client/accounts.go +++ b/internal/client/accounts.go @@ -145,7 +145,7 @@ func (g *Client) UnblockAccount(accountID string) error { func (g *Client) GetBlockedAccounts(limit int) (model.AccountList, error) { url := g.Authentication.Instance + fmt.Sprintf("/api/v1/blocks?limit=%d", limit) - accounts := make([]model.Account, 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 blocked accounts: %w", err) @@ -180,3 +180,40 @@ func (g *Client) SetPrivateNote(accountID, note string) error { return nil } + +func (g *Client) GetFollowRequests(limit int) (model.AccountList, error) { + url := g.Authentication.Instance + fmt.Sprintf("/api/v1/follow_requests?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 follow requests: %w", err) + } + + requests := model.AccountList{ + Type: model.AccountListFollowRequests, + Accounts: accounts, + } + + return requests, nil +} + +func (g *Client) AcceptFollowRequest(accountID string) error { + url := g.Authentication.Instance + "/api/v1/follow_requests/" + accountID + "/authorize" + + if err := g.sendRequest(http.MethodPost, url, nil, nil); err != nil { + return fmt.Errorf("received an error after sending the request to accept the follow request: %w", err) + } + + return nil +} + +func (g *Client) RejectFollowRequest(accountID string) error { + url := g.Authentication.Instance + "/api/v1/follow_requests/" + accountID + "/reject" + + if err := g.sendRequest(http.MethodPost, url, nil, nil); err != nil { + return fmt.Errorf("received an error after sending the request to reject the follow request: %w", err) + } + + return nil +} diff --git a/internal/executor/accept_or_reject.go b/internal/executor/accept_or_reject.go new file mode 100644 index 0000000..9b318c8 --- /dev/null +++ b/internal/executor/accept_or_reject.go @@ -0,0 +1,91 @@ +// 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" +) + +type AcceptOrRejectExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + accountName string + action string +} + +func NewAcceptOrRejectExecutor(tlf TopLevelFlags, name, summary string) *AcceptOrRejectExecutor { + acceptExe := AcceptOrRejectExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + topLevelFlags: tlf, + action: name, + } + + acceptExe.StringVar(&acceptExe.resourceType, flagType, "", "Specify the type of resource to accept or reject") + acceptExe.StringVar(&acceptExe.accountName, flagAccountName, "", "Specify the account name in full (username@domain)") + + acceptExe.Usage = commandUsageFunc(name, summary, acceptExe.FlagSet) + + return &acceptExe +} + +func (a *AcceptOrRejectExecutor) Execute() error { + funcMap := map[string]func(*client.Client) error{ + resourceFollowRequest: a.acceptOrRejectFollowRequest, + } + + doFunc, ok := funcMap[a.resourceType] + if !ok { + return UnsupportedTypeError{resourceType: a.resourceType} + } + + gtsClient, err := client.NewClientFromConfig(a.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client: %w", err) + } + + return doFunc(gtsClient) +} + +func (a *AcceptOrRejectExecutor) acceptOrRejectFollowRequest(gtsClient *client.Client) error { + accountID, err := getAccountID(gtsClient, false, a.accountName, a.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("received an error while getting the account ID: %w", err) + } + + switch a.action { + case "accept": + return a.acceptFollowRequest(gtsClient, accountID) + case "reject": + return a.rejectFollowRequest(gtsClient, accountID) + default: + return nil + } +} + +func (a *AcceptOrRejectExecutor) acceptFollowRequest(gtsClient *client.Client, accountID string) error { + if err := gtsClient.AcceptFollowRequest(accountID); err != nil { + return fmt.Errorf("unable to accept the follow request: %w", err) + } + + fmt.Println("Successfully accepted the follow request.") + + return nil +} + +func (a *AcceptOrRejectExecutor) rejectFollowRequest(gtsClient *client.Client, accountID string) error { + if err := gtsClient.RejectFollowRequest(accountID); err != nil { + return fmt.Errorf("unable to reject the follow request: %w", err) + } + + fmt.Println("Successfully rejected the follow request.") + + return nil +} diff --git a/internal/executor/const.go b/internal/executor/const.go index 4ff4af2..8cfc3a0 100644 --- a/internal/executor/const.go +++ b/internal/executor/const.go @@ -35,19 +35,20 @@ const ( flagType = "type" flagVisibility = "visibility" - resourceAccount = "account" - resourceBlocked = "blocked" - resourceBookmarks = "bookmarks" - resourceBoost = "boost" - resourceFollowers = "followers" - resourceFollowing = "following" - resourceInstance = "instance" - resourceLike = "like" - resourceLiked = "liked" - resourceList = "list" - resourceNote = "note" - resourceStatus = "status" - resourceStar = "star" - resourceStarred = "starred" - resourceTimeline = "timeline" + resourceAccount = "account" + resourceBlocked = "blocked" + resourceBookmarks = "bookmarks" + resourceBoost = "boost" + resourceFollowers = "followers" + resourceFollowing = "following" + resourceFollowRequest = "follow-request" + resourceInstance = "instance" + resourceLike = "like" + resourceLiked = "liked" + resourceList = "list" + resourceNote = "note" + resourceStatus = "status" + resourceStar = "star" + resourceStarred = "starred" + resourceTimeline = "timeline" ) diff --git a/internal/executor/show.go b/internal/executor/show.go index cbacb47..b4a5660 100644 --- a/internal/executor/show.go +++ b/internal/executor/show.go @@ -58,17 +58,18 @@ func (s *ShowExecutor) Execute() error { } funcMap := map[string]func(*client.Client) error{ - resourceInstance: s.showInstance, - resourceAccount: s.showAccount, - resourceStatus: s.showStatus, - resourceTimeline: s.showTimeline, - resourceList: s.showList, - resourceFollowers: s.showFollowers, - resourceFollowing: s.showFollowing, - resourceBlocked: s.showBlocked, - resourceBookmarks: s.showBookmarks, - resourceLiked: s.showLiked, - resourceStarred: s.showLiked, + resourceInstance: s.showInstance, + resourceAccount: s.showAccount, + resourceStatus: s.showStatus, + resourceTimeline: s.showTimeline, + resourceList: s.showList, + resourceFollowers: s.showFollowers, + resourceFollowing: s.showFollowing, + resourceBlocked: s.showBlocked, + resourceBookmarks: s.showBookmarks, + resourceLiked: s.showLiked, + resourceStarred: s.showLiked, + resourceFollowRequest: s.showFollowRequests, } doFunc, ok := funcMap[s.resourceType] @@ -346,3 +347,18 @@ func (s *ShowExecutor) showLiked(gtsClient *client.Client) error { return nil } + +func (s *ShowExecutor) showFollowRequests(gtsClient *client.Client) error { + accounts, err := gtsClient.GetFollowRequests(s.limit) + if err != nil { + return fmt.Errorf("unable to retrieve the list of follow requests: %w", err) + } + + if len(accounts.Accounts) > 0 { + utilities.Display(accounts, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + } else { + fmt.Println("You have no follow requests.") + } + + return nil +} diff --git a/internal/model/account.go b/internal/model/account.go index 53fa953..7bda900 100644 --- a/internal/model/account.go +++ b/internal/model/account.go @@ -186,6 +186,7 @@ const ( AccountListFollowers AccountListType = iota AccountListFollowing AccountListBlockedAccount + AccountListFollowRequests ) type AccountList struct { @@ -203,6 +204,8 @@ func (a AccountList) Display(noColor bool) string { output += utilities.HeaderFormat(noColor, "FOLLOWING:") case AccountListBlockedAccount: output += utilities.HeaderFormat(noColor, "BLOCKED ACCOUNTS:") + case AccountListFollowRequests: + output += utilities.HeaderFormat(noColor, "ACCOUNTS THAT HAVE REQUESTED TO FOLLOW YOU:") default: output += utilities.HeaderFormat(noColor, "ACCOUNTS:") }