Compare commits

...

8 commits

Author SHA1 Message Date
4fabf72b1f
checkpoint: resolved errors after cleanup 2024-08-14 00:14:02 +01:00
b369583fdb
updated manual 2024-08-13 23:52:40 +01:00
0a98a61231
updated manual 2024-08-13 23:52:40 +01:00
1c98c0bc36
checkpoint: now vote via the status 2024-08-13 23:52:40 +01:00
f8d117333c
checkpoint:
- BREAKING: when creating the status, only print the ID on success.
- fix: Only display the poll results under certain conditions.
2024-08-13 23:52:39 +01:00
caa4341ef2
checkpoint:
- BREAKING: user can no longer view a poll directly from the poll ID.
- Added more poll details in status view.
- Poll now shows the user's vote in status/timeline view.
2024-08-13 23:52:39 +01:00
a0eab3b6ae
refactor: clean up the getAccountID function sig
The getAccountID function no longer needs the path to the credentials
file.
2024-08-13 23:51:16 +01:00
878a898d4c
fix: use VerifyCredentials to get account info
Use the clients VerifyCredentials method to get the user's account
information.
2024-08-13 23:41:12 +01:00
21 changed files with 213 additions and 236 deletions

View file

@ -39,6 +39,7 @@
- [View a list of statuses that you've liked](#view-a-list-of-statuses-that-youve-liked) - [View a list of statuses that you've liked](#view-a-list-of-statuses-that-youve-liked)
- [Mute a status](#mute-a-status) - [Mute a status](#mute-a-status)
- [Unmute a status](#unmute-a-status) - [Unmute a status](#unmute-a-status)
- [Vote in a poll within a status](#vote-in-a-poll-within-a-status)
- [Polls](#polls) - [Polls](#polls)
- [Create a poll](#create-a-poll) - [Create a poll](#create-a-poll)
- [View a poll](#view-a-poll) - [View a poll](#view-a-poll)
@ -528,6 +529,21 @@ _Not yet supported_
_Not yet supported_ _Not yet supported_
### Vote in a poll within a status
Adds your vote(s) to a poll within a status.
```
enbas add --type vote --to status --status-id 01J55XVV2MM6MKQ7QHFBAVAE8R --vote 3
```
| flag | type | required | description | default |
|------|------|----------|-------------|---------|
| `type` | string | true | The resource you want to add.<br>Here this should be `vote`. | |
| `to` | string | true | The resource you want to add the vote to.<br>Here this should be `status`. | |
| `status-id` | string | true | The ID of the poll you want to add the votes to. | |
| `vote` | int | true | The ID of the option that you want to vote for.<br>You can use this flag multiple times to vote for more than one option if the poll allows multiple choices. | |
## Polls ## Polls
### Create a poll ### Create a poll
@ -536,31 +552,11 @@ See [Create a status](#create-a-status).
### View a poll ### View a poll
Prints the poll information to the screen. You can view a poll within a [status](#view-a-status) or within a [timeline](#view-a-timeline).
```
enbas show --type poll --poll-id 01J0CEEZBZ6E6AYQSJPHCQYBDA
```
| flag | type | required | description | default |
|------|------|----------|-------------|---------|
| `type` | string | true | The resource you want to view.<br>Here this should be `poll`. | |
| `poll-id` | string | true | The ID of the poll that you want to view. | |
### Vote in a poll ### Vote in a poll
Add your vote(s) to a poll. See [Vote in a poll within a status](#vote-in-a-poll-within-a-status)
```
enbas add --type vote --to poll --poll-id 01J1TVJ705VV3VP02FVVBSMX7E --vote 3
```
| flag | type | required | description | default |
|------|------|----------|-------------|---------|
| `type` | string | true | The resource you want to add.<br>Here this should be `vote`. | |
| `to` | string | true | The resource you want to add the vote to.<br>Here this should be `poll`. | |
| `poll-id` | string | true | The ID of the poll you want to add the votes to. | |
| `vote` | int | true | The ID of the option that you want to vote for.<br>You can use this flag multiple times to vote for more than one option if the poll allows multiple choices. | |
## Lists ## Lists

View file

@ -5,36 +5,34 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
) )
const ( const (
pollPath string = "/api/v1/polls" pollPath string = "/api/v1/polls"
) )
func (g *Client) GetPoll(pollID string) (model.Poll, error) { // func (g *Client) GetPoll(pollID string) (model.Poll, error) {
url := g.Authentication.Instance + pollPath + "/" + pollID // url := g.Authentication.Instance + pollPath + "/" + pollID
//
var poll model.Poll // var poll model.Poll
//
params := requestParameters{ // params := requestParameters{
httpMethod: http.MethodGet, // httpMethod: http.MethodGet,
url: url, // url: url,
requestBody: nil, // requestBody: nil,
contentType: "", // contentType: "",
output: &poll, // output: &poll,
} // }
//
if err := g.sendRequest(params); err != nil { // if err := g.sendRequest(params); err != nil {
return model.Poll{}, fmt.Errorf( // return model.Poll{}, fmt.Errorf(
"received an error after sending the request to get the poll: %w", // "received an error after sending the request to get the poll: %w",
err, // err,
) // )
} // }
//
return poll, nil // return poll, nil
} // }
func (g *Client) VoteInPoll(pollID string, choices []int) error { func (g *Client) VoteInPoll(pollID string, choices []int) error {
form := struct { form := struct {

View file

@ -25,7 +25,7 @@ func (a *AcceptExecutor) Execute() error {
} }
func (a *AcceptExecutor) acceptFollowRequest(gtsClient *client.Client) error { func (a *AcceptExecutor) acceptFollowRequest(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, a.accountName, a.config.CredentialsFile) accountID, err := getAccountID(gtsClient, false, a.accountName)
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

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
internalFlag "codeflow.dananglin.me.uk/apollo/enbas/internal/flag" internalFlag "codeflow.dananglin.me.uk/apollo/enbas/internal/flag"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/model"
) )
@ -13,9 +12,8 @@ func getAccountID(
gtsClient *client.Client, gtsClient *client.Client,
myAccount bool, myAccount bool,
accountNames internalFlag.StringSliceValue, accountNames internalFlag.StringSliceValue,
credentialsFile string,
) (string, error) { ) (string, error) {
account, err := getAccount(gtsClient, myAccount, accountNames, credentialsFile) account, err := getAccount(gtsClient, myAccount, accountNames)
if err != nil { if err != nil {
return "", fmt.Errorf("unable to get the account information: %w", err) return "", fmt.Errorf("unable to get the account information: %w", err)
} }
@ -27,7 +25,6 @@ func getAccount(
gtsClient *client.Client, gtsClient *client.Client,
myAccount bool, myAccount bool,
accountNames internalFlag.StringSliceValue, accountNames internalFlag.StringSliceValue,
credentialsFile string,
) (model.Account, error) { ) (model.Account, error) {
var ( var (
account model.Account account model.Account
@ -36,7 +33,7 @@ func getAccount(
switch { switch {
case myAccount: case myAccount:
account, err = getMyAccount(gtsClient, credentialsFile) account, err = getMyAccount(gtsClient)
if err != nil { if err != nil {
return account, fmt.Errorf("unable to get your account ID: %w", err) return account, fmt.Errorf("unable to get your account ID: %w", err)
} }
@ -52,15 +49,8 @@ func getAccount(
return account, nil return account, nil
} }
func getMyAccount(gtsClient *client.Client, path string) (model.Account, error) { func getMyAccount(gtsClient *client.Client) (model.Account, error) {
authConfig, err := config.NewCredentialsConfigFromFile(path) account, err := gtsClient.VerifyCredentials()
if err != nil {
return model.Account{}, fmt.Errorf("unable to retrieve the authentication configuration: %w", err)
}
accountURI := authConfig.CurrentAccount
account, err := gtsClient.GetAccount(accountURI)
if err != nil { if err != nil {
return model.Account{}, fmt.Errorf("unable to retrieve your account: %w", err) return model.Account{}, fmt.Errorf("unable to retrieve your account: %w", err)
} }

View file

@ -17,7 +17,6 @@ func (a *AddExecutor) Execute() error {
resourceAccount: a.addToAccount, resourceAccount: a.addToAccount,
resourceBookmarks: a.addToBookmarks, resourceBookmarks: a.addToBookmarks,
resourceStatus: a.addToStatus, resourceStatus: a.addToStatus,
resourcePoll: a.addToPoll,
} }
doFunc, ok := funcMap[a.toResourceType] doFunc, ok := funcMap[a.toResourceType]
@ -104,7 +103,7 @@ func (a *AddExecutor) addToAccount(gtsClient *client.Client) error {
} }
func (a *AddExecutor) addNoteToAccount(gtsClient *client.Client) error { func (a *AddExecutor) addNoteToAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, a.accountNames, a.config.CredentialsFile) accountID, err := getAccountID(gtsClient, false, a.accountNames)
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)
} }
@ -164,6 +163,7 @@ func (a *AddExecutor) addToStatus(gtsClient *client.Client) error {
resourceStar: a.addStarToStatus, resourceStar: a.addStarToStatus,
resourceLike: a.addStarToStatus, resourceLike: a.addStarToStatus,
resourceBoost: a.addBoostToStatus, resourceBoost: a.addBoostToStatus,
resourceVote: a.addVoteToStatus,
} }
doFunc, ok := funcMap[a.resourceType] doFunc, ok := funcMap[a.resourceType]
@ -197,45 +197,40 @@ func (a *AddExecutor) addBoostToStatus(gtsClient *client.Client) error {
return nil return nil
} }
func (a *AddExecutor) addToPoll(gtsClient *client.Client) error { func (a *AddExecutor) addVoteToStatus(gtsClient *client.Client) error {
if a.pollID == "" {
return FlagNotSetError{flagText: flagPollID}
}
funcMap := map[string]func(*client.Client) error{
resourceVote: a.addVoteToPoll,
}
doFunc, ok := funcMap[a.resourceType]
if !ok {
return UnsupportedAddOperationError{
ResourceType: a.resourceType,
AddToResourceType: a.toResourceType,
}
}
return doFunc(gtsClient)
}
func (a *AddExecutor) addVoteToPoll(gtsClient *client.Client) error {
if a.votes.Empty() { if a.votes.Empty() {
return errors.New("please use --" + flagVote + " to make a choice in this poll") return errors.New("please use --" + flagVote + " to make a choice in this poll")
} }
poll, err := gtsClient.GetPoll(a.pollID) status, err := gtsClient.GetStatus(a.statusID)
if err != nil { if err != nil {
return fmt.Errorf("unable to retrieve the poll: %w", err) return fmt.Errorf("unable to get the status: %w", err)
} }
if poll.Expired { if status.Poll == nil {
return errors.New("this status does not have a poll")
}
if status.Poll.Expired {
return PollClosedError{} return PollClosedError{}
} }
if !poll.Multiple && !a.votes.ExpectedLength(1) { if !status.Poll.Multiple && !a.votes.ExpectedLength(1) {
return MultipleChoiceError{} return MultipleChoiceError{}
} }
if err := gtsClient.VoteInPoll(a.pollID, []int(a.votes)); err != nil { myAccountID, err := getAccountID(gtsClient, true, nil)
if err != nil {
return fmt.Errorf("unable to get your account ID: %w", err)
}
if status.Account.ID == myAccountID {
return errors.New("you cannot vote in your own poll")
}
pollID := status.Poll.ID
if err := gtsClient.VoteInPoll(pollID, []int(a.votes)); err != nil {
return fmt.Errorf("unable to add your vote(s) to the poll: %w", err) return fmt.Errorf("unable to add your vote(s) to the poll: %w", err)
} }

View file

@ -25,7 +25,7 @@ func (b *BlockExecutor) Execute() error {
} }
func (b *BlockExecutor) blockAccount(gtsClient *client.Client) error { func (b *BlockExecutor) blockAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, b.accountName, b.config.CredentialsFile) accountID, err := getAccountID(gtsClient, false, b.accountName)
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

@ -149,8 +149,7 @@ func (c *CreateExecutor) createStatus(gtsClient *client.Client) error {
return fmt.Errorf("unable to create the status: %w", err) return fmt.Errorf("unable to create the status: %w", err)
} }
c.printer.PrintSuccess("Successfully created the following status:") c.printer.PrintSuccess("Successfully created the status with ID: " + status.ID)
c.printer.PrintStatus(status)
return nil return nil
} }

View file

@ -56,7 +56,6 @@ type AddExecutor struct {
accountNames internalFlag.StringSliceValue accountNames internalFlag.StringSliceValue
content string content string
listID string listID string
pollID string
statusID string statusID string
toResourceType string toResourceType string
resourceType string resourceType string
@ -80,7 +79,6 @@ func NewAddExecutor(
exe.Var(&exe.accountNames, "account-name", "The name of the account") exe.Var(&exe.accountNames, "account-name", "The name of the account")
exe.StringVar(&exe.content, "content", "", "The content of the created resource") exe.StringVar(&exe.content, "content", "", "The content of the created resource")
exe.StringVar(&exe.listID, "list-id", "", "The ID of the list in question") exe.StringVar(&exe.listID, "list-id", "", "The ID of the list in question")
exe.StringVar(&exe.pollID, "poll-id", "", "The ID of the poll")
exe.StringVar(&exe.statusID, "status-id", "", "The ID of the status") exe.StringVar(&exe.statusID, "status-id", "", "The ID of the status")
exe.StringVar(&exe.toResourceType, "to", "", "The resource type to action the target resource to (e.g. status)") exe.StringVar(&exe.toResourceType, "to", "", "The resource type to action the target resource to (e.g. status)")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)") exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
@ -434,7 +432,6 @@ type ShowExecutor struct {
onlyMedia bool onlyMedia bool
onlyPinned bool onlyPinned bool
onlyPublic bool onlyPublic bool
pollID string
showUserPreferences bool showUserPreferences bool
showStatuses bool showStatuses bool
skipAccountRelationship bool skipAccountRelationship bool
@ -472,7 +469,6 @@ func NewShowExecutor(
exe.BoolVar(&exe.onlyMedia, "only-media", false, "Set to true to show only the statuses with media attachments") exe.BoolVar(&exe.onlyMedia, "only-media", false, "Set to true to show only the statuses with media attachments")
exe.BoolVar(&exe.onlyPinned, "only-pinned", false, "Set to true to show only the account's pinned statuses") exe.BoolVar(&exe.onlyPinned, "only-pinned", false, "Set to true to show only the account's pinned statuses")
exe.BoolVar(&exe.onlyPublic, "only-public", false, "Set to true to show only the account's public posts") exe.BoolVar(&exe.onlyPublic, "only-public", false, "Set to true to show only the account's public posts")
exe.StringVar(&exe.pollID, "poll-id", "", "The ID of the poll")
exe.BoolVar(&exe.showUserPreferences, "show-preferences", false, "Set to true to view your posting preferences when viewing your account information") exe.BoolVar(&exe.showUserPreferences, "show-preferences", false, "Set to true to view your posting preferences when viewing your account information")
exe.BoolVar(&exe.showStatuses, "show-statuses", false, "Set to true to view the statuses created from the account you are viewing") exe.BoolVar(&exe.showStatuses, "show-statuses", false, "Set to true to view the statuses created from the account you are viewing")
exe.BoolVar(&exe.skipAccountRelationship, "skip-relationship", false, "Set to true to skip showing your relationship to the account that you are viewing") exe.BoolVar(&exe.skipAccountRelationship, "skip-relationship", false, "Set to true to skip showing your relationship to the account that you are viewing")

View file

@ -25,7 +25,7 @@ func (f *FollowExecutor) Execute() error {
} }
func (f *FollowExecutor) followAccount(gtsClient *client.Client) error { func (f *FollowExecutor) followAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, f.accountName, f.config.CredentialsFile) accountID, err := getAccountID(gtsClient, false, f.accountName)
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

@ -25,7 +25,7 @@ func (m *MuteExecutor) Execute() error {
} }
func (m *MuteExecutor) muteAccount(gtsClient *client.Client) error { func (m *MuteExecutor) muteAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, m.accountName, m.config.CredentialsFile) accountID, err := getAccountID(gtsClient, false, m.accountName)
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

@ -25,7 +25,7 @@ func (r *RejectExecutor) Execute() error {
} }
func (r *RejectExecutor) rejectFollowRequest(gtsClient *client.Client) error { func (r *RejectExecutor) rejectFollowRequest(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, r.accountName, r.config.CredentialsFile) accountID, err := getAccountID(gtsClient, false, r.accountName)
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

@ -93,7 +93,7 @@ func (r *RemoveExecutor) removeFromAccount(gtsClient *client.Client) error {
} }
func (r *RemoveExecutor) removeNoteFromAccount(gtsClient *client.Client) error { func (r *RemoveExecutor) removeNoteFromAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, r.accountNames, r.config.CredentialsFile) accountID, err := getAccountID(gtsClient, false, r.accountNames)
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

@ -28,7 +28,6 @@ func (s *ShowExecutor) Execute() error {
resourceLiked: s.showLiked, resourceLiked: s.showLiked,
resourceStarred: s.showLiked, resourceStarred: s.showLiked,
resourceFollowRequest: s.showFollowRequests, resourceFollowRequest: s.showFollowRequests,
resourcePoll: s.showPoll,
resourceMutedAccounts: s.showMutedAccounts, resourceMutedAccounts: s.showMutedAccounts,
resourceMedia: s.showMedia, resourceMedia: s.showMedia,
resourceMediaAttachment: s.showMediaAttachment, resourceMediaAttachment: s.showMediaAttachment,
@ -59,7 +58,7 @@ func (s *ShowExecutor) showInstance(gtsClient *client.Client) error {
} }
func (s *ShowExecutor) showAccount(gtsClient *client.Client) error { func (s *ShowExecutor) showAccount(gtsClient *client.Client) error {
account, err := getAccount(gtsClient, s.myAccount, s.accountName, s.config.CredentialsFile) account, err := getAccount(gtsClient, s.myAccount, s.accountName)
if err != nil { if err != nil {
return fmt.Errorf("unable to get the account information: %w", err) return fmt.Errorf("unable to get the account information: %w", err)
} }
@ -76,6 +75,7 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error {
relationship *model.AccountRelationship relationship *model.AccountRelationship
preferences *model.Preferences preferences *model.Preferences
statuses *model.StatusList statuses *model.StatusList
myAccountID string
) )
if !s.myAccount && !s.skipAccountRelationship { if !s.myAccount && !s.skipAccountRelationship {
@ -85,10 +85,13 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error {
} }
} }
if s.myAccount && s.showUserPreferences { if s.myAccount {
preferences, err = gtsClient.GetUserPreferences() myAccountID = account.ID
if err != nil { if s.showUserPreferences {
return fmt.Errorf("unable to retrieve the user preferences: %w", err) preferences, err = gtsClient.GetUserPreferences()
if err != nil {
return fmt.Errorf("unable to retrieve the user preferences: %w", err)
}
} }
} }
@ -109,7 +112,7 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error {
} }
} }
s.printer.PrintAccount(account, relationship, preferences, statuses) s.printer.PrintAccount(account, relationship, preferences, statuses, myAccountID)
return nil return nil
} }
@ -132,7 +135,12 @@ func (s *ShowExecutor) showStatus(gtsClient *client.Client) error {
return nil return nil
} }
s.printer.PrintStatus(status) myAccountID, err := getAccountID(gtsClient, true, nil)
if err != nil {
return fmt.Errorf("unable to get your account ID: %w", err)
}
s.printer.PrintStatus(status, myAccountID)
return nil return nil
} }
@ -181,7 +189,12 @@ func (s *ShowExecutor) showTimeline(gtsClient *client.Client) error {
return nil return nil
} }
s.printer.PrintStatusList(timeline) myAccountID, err := getAccountID(gtsClient, true, nil)
if err != nil {
return fmt.Errorf("unable to get your account ID: %w", err)
}
s.printer.PrintStatusList(timeline, myAccountID)
return nil return nil
} }
@ -253,7 +266,7 @@ func (s *ShowExecutor) showFollowers(gtsClient *client.Client) error {
} }
func (s *ShowExecutor) showFollowersFromAccount(gtsClient *client.Client) error { func (s *ShowExecutor) showFollowersFromAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.config.CredentialsFile) accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName)
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)
} }
@ -293,7 +306,7 @@ func (s *ShowExecutor) showFollowing(gtsClient *client.Client) error {
} }
func (s *ShowExecutor) showFollowingFromAccount(gtsClient *client.Client) error { func (s *ShowExecutor) showFollowingFromAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.config.CredentialsFile) accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName)
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)
} }
@ -334,7 +347,12 @@ func (s *ShowExecutor) showBookmarks(gtsClient *client.Client) error {
} }
if len(bookmarks.Statuses) > 0 { if len(bookmarks.Statuses) > 0 {
s.printer.PrintStatusList(bookmarks) myAccountID, err := getAccountID(gtsClient, true, nil)
if err != nil {
return fmt.Errorf("unable to get your account ID: %w", err)
}
s.printer.PrintStatusList(bookmarks, myAccountID)
} else { } else {
s.printer.PrintInfo("You have no bookmarks.\n") s.printer.PrintInfo("You have no bookmarks.\n")
} }
@ -349,7 +367,12 @@ func (s *ShowExecutor) showLiked(gtsClient *client.Client) error {
} }
if len(liked.Statuses) > 0 { if len(liked.Statuses) > 0 {
s.printer.PrintStatusList(liked) myAccountID, err := getAccountID(gtsClient, true, nil)
if err != nil {
return fmt.Errorf("unable to get your account ID: %w", err)
}
s.printer.PrintStatusList(liked, myAccountID)
} else { } else {
s.printer.PrintInfo("You have no " + s.resourceType + " statuses.\n") s.printer.PrintInfo("You have no " + s.resourceType + " statuses.\n")
} }
@ -372,21 +395,6 @@ func (s *ShowExecutor) showFollowRequests(gtsClient *client.Client) error {
return nil return nil
} }
func (s *ShowExecutor) showPoll(gtsClient *client.Client) error {
if s.pollID == "" {
return FlagNotSetError{flagText: flagPollID}
}
poll, err := gtsClient.GetPoll(s.pollID)
if err != nil {
return fmt.Errorf("unable to retrieve the poll: %w", err)
}
s.printer.PrintPoll(poll)
return nil
}
func (s *ShowExecutor) showMutedAccounts(gtsClient *client.Client) error { func (s *ShowExecutor) showMutedAccounts(gtsClient *client.Client) error {
muted, err := gtsClient.GetMutedAccounts(s.limit) muted, err := gtsClient.GetMutedAccounts(s.limit)
if err != nil { if err != nil {

View file

@ -25,7 +25,7 @@ func (b *UnblockExecutor) Execute() error {
} }
func (b *UnblockExecutor) unblockAccount(gtsClient *client.Client) error { func (b *UnblockExecutor) unblockAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, b.accountName, b.config.CredentialsFile) accountID, err := getAccountID(gtsClient, false, b.accountName)
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

@ -25,7 +25,7 @@ func (f *UnfollowExecutor) Execute() error {
} }
func (f *UnfollowExecutor) unfollowAccount(gtsClient *client.Client) error { func (f *UnfollowExecutor) unfollowAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, f.accountName, f.config.CredentialsFile) accountID, err := getAccountID(gtsClient, false, f.accountName)
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

@ -25,7 +25,7 @@ func (m *UnmuteExecutor) Execute() error {
} }
func (m *UnmuteExecutor) unmuteAccount(gtsClient *client.Client) error { func (m *UnmuteExecutor) unmuteAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, m.accountName, m.config.CredentialsFile) accountID, err := getAccountID(gtsClient, false, m.accountName)
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

@ -12,6 +12,7 @@ func (p Printer) PrintAccount(
relationship *model.AccountRelationship, relationship *model.AccountRelationship,
preferences *model.Preferences, preferences *model.Preferences,
statuses *model.StatusList, statuses *model.StatusList,
userAccountID string,
) { ) {
var builder strings.Builder var builder strings.Builder
@ -47,7 +48,7 @@ func (p Printer) PrintAccount(
} }
if statuses != nil { if statuses != nil {
builder.WriteString("\n\n" + p.statusList(*statuses)) builder.WriteString("\n\n" + p.statusList(*statuses, userAccountID))
} }
builder.WriteString("\n\n") builder.WriteString("\n\n")

View file

@ -1,91 +1,2 @@
package printer package printer
import (
"math"
"strconv"
"strings"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
)
func (p Printer) PrintPoll(poll model.Poll) {
var builder strings.Builder
builder.WriteString("\n" + p.headerFormat("POLL ID:"))
builder.WriteString("\n" + poll.ID)
builder.WriteString("\n\n" + p.headerFormat("OPTIONS:"))
builder.WriteString(p.pollOptions(poll))
builder.WriteString("\n\n" + p.headerFormat("MULTIPLE CHOICES ALLOWED:"))
builder.WriteString("\n" + strconv.FormatBool(poll.Multiple))
builder.WriteString("\n\n" + p.headerFormat("YOU VOTED:"))
builder.WriteString("\n" + strconv.FormatBool(poll.Voted))
if len(poll.OwnVotes) > 0 {
builder.WriteString("\n\n" + p.headerFormat("YOUR VOTES:"))
for _, vote := range poll.OwnVotes {
builder.WriteString("\n" + "[" + strconv.Itoa(vote) + "] " + poll.Options[vote].Title)
}
}
builder.WriteString("\n\n" + p.headerFormat("EXPIRED:"))
builder.WriteString("\n" + strconv.FormatBool(poll.Expired))
builder.WriteString("\n\n")
p.print(builder.String())
}
func (p Printer) pollOptions(poll model.Poll) string {
var builder strings.Builder
for ind, option := range poll.Options {
var (
votage float64
percentage int
)
if poll.VotesCount == 0 {
percentage = 0
} else {
votage = float64(option.VotesCount) / float64(poll.VotesCount)
percentage = int(math.Floor(100 * votage))
}
builder.WriteString("\n\n" + "[" + strconv.Itoa(ind) + "] " + option.Title)
builder.WriteString(p.pollMeter(votage))
builder.WriteString("\n" + strconv.Itoa(option.VotesCount) + " votes " + "(" + strconv.Itoa(percentage) + "%)")
}
builder.WriteString("\n\n" + p.fieldFormat("Total votes:") + " " + strconv.Itoa(poll.VotesCount))
builder.WriteString("\n" + p.fieldFormat("Poll ID:") + " " + poll.ID)
builder.WriteString("\n" + p.fieldFormat("Poll is open until:") + " " + p.formatDateTime(poll.ExpiredAt))
return builder.String()
}
func (p Printer) pollMeter(votage float64) string {
numVoteBlocks := int(math.Floor(float64(p.lineWrapCharacterLimit) * votage))
numBackgroundBlocks := p.lineWrapCharacterLimit - numVoteBlocks
voteBlockColor := p.theme.boldgreen
backgroundBlockColor := p.theme.grey
if p.noColor {
voteBlockColor = p.theme.reset
if numVoteBlocks == 0 {
numVoteBlocks = 1
}
}
meter := "\n" + voteBlockColor + strings.Repeat(symbolPollMeter, numVoteBlocks) + p.theme.reset
if !p.noColor {
meter += backgroundBlockColor + strings.Repeat(symbolPollMeter, numBackgroundBlocks) + p.theme.reset
}
return meter
}

View file

@ -13,7 +13,7 @@ const (
noMediaDescription = "This media attachment has no description." noMediaDescription = "This media attachment has no description."
symbolBullet = "\u2022" symbolBullet = "\u2022"
symbolPollMeter = "\u2501" symbolPollMeter = "\u2501"
symbolSuccess = "\u2714" symbolCheckMark = "\u2714"
symbolFailure = "\u2717" symbolFailure = "\u2717"
symbolImage = "\uf03e" symbolImage = "\uf03e"
symbolLiked = "\uf51f" symbolLiked = "\uf51f"
@ -78,9 +78,9 @@ func NewPrinter(
} }
func (p Printer) PrintSuccess(text string) { func (p Printer) PrintSuccess(text string) {
success := p.theme.boldgreen + symbolSuccess + p.theme.reset success := p.theme.boldgreen + symbolCheckMark + p.theme.reset
if p.noColor { if p.noColor {
success = symbolSuccess success = symbolCheckMark
} }
printToStdout(success + " " + text + "\n") printToStdout(success + " " + text + "\n")

View file

@ -1,13 +1,14 @@
package printer package printer
import ( import (
"math"
"strconv" "strconv"
"strings" "strings"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/model"
) )
func (p Printer) PrintStatus(status model.Status) { func (p Printer) PrintStatus(status model.Status, userAccountID string) {
var builder strings.Builder var builder strings.Builder
// The account information // The account information
@ -41,7 +42,13 @@ func (p Printer) PrintStatus(status model.Status) {
// If a poll exists in a status, write the contents to the builder. // If a poll exists in a status, write the contents to the builder.
if status.Poll != nil { if status.Poll != nil {
builder.WriteString(p.pollOptions(*status.Poll)) pollOwner := false
if status.Account.ID == userAccountID {
pollOwner = true
}
builder.WriteString("\n\n" + p.headerFormat("POLL DETAILS:"))
builder.WriteString(p.pollDetails(*status.Poll, pollOwner))
} }
// Status creation time // Status creation time
@ -72,17 +79,18 @@ func (p Printer) PrintStatus(status model.Status) {
p.print(builder.String()) p.print(builder.String())
} }
func (p Printer) PrintStatusList(list model.StatusList) { func (p Printer) PrintStatusList(list model.StatusList, userAccountID string) {
p.print(p.statusList(list)) p.print(p.statusList(list, userAccountID))
} }
func (p Printer) statusList(list model.StatusList) string { func (p Printer) statusList(list model.StatusList, userAccountID string) string {
var builder strings.Builder var builder strings.Builder
builder.WriteString(p.headerFormat(list.Name) + "\n") builder.WriteString(p.headerFormat(list.Name) + "\n")
for _, status := range list.Statuses { for _, status := range list.Statuses {
statusID := status.ID statusID := status.ID
statusOwnerID := status.Account.ID
createdAt := p.formatDateTime(status.CreatedAt) createdAt := p.formatDateTime(status.CreatedAt)
boostedAt := "" boostedAt := ""
content := status.Content content := status.Content
@ -100,6 +108,7 @@ func (p Printer) statusList(list model.StatusList) string {
)) ))
statusID = status.Reblog.ID statusID = status.Reblog.ID
statusOwnerID = status.Reblog.Account.ID
createdAt = p.formatDateTime(status.Reblog.CreatedAt) createdAt = p.formatDateTime(status.Reblog.CreatedAt)
boostedAt = p.formatDateTime(status.CreatedAt) boostedAt = p.formatDateTime(status.CreatedAt)
content = status.Reblog.Content content = status.Reblog.Content
@ -120,7 +129,12 @@ func (p Printer) statusList(list model.StatusList) string {
builder.WriteString("\n" + p.convertHTMLToText(content, true)) builder.WriteString("\n" + p.convertHTMLToText(content, true))
if poll != nil { if poll != nil {
builder.WriteString(p.pollOptions(*poll)) pollOwner := false
if statusOwnerID == userAccountID {
pollOwner = true
}
builder.WriteString(p.pollDetails(*poll, pollOwner))
} }
for _, media := range mediaAttachments { for _, media := range mediaAttachments {
@ -172,3 +186,78 @@ func (p Printer) statusList(list model.StatusList) string {
return builder.String() return builder.String()
} }
func (p Printer) pollDetails(poll model.Poll, owner bool) string {
var builder strings.Builder
for ind, option := range poll.Options {
var (
votage float64
percentage int
)
// Show the poll results under any of the following conditions:
// - the user is the owner of the poll
// - the poll has expired
// - the user has voted in the poll
if owner || poll.Expired || poll.Voted {
if poll.VotesCount == 0 {
percentage = 0
} else {
votage = float64(option.VotesCount) / float64(poll.VotesCount)
percentage = int(math.Floor(100 * votage))
}
optionTitle := "\n\n" + "[" + strconv.Itoa(ind) + "] " + option.Title
for _, vote := range poll.OwnVotes {
if ind == vote {
optionTitle += " " + symbolCheckMark
break
}
}
builder.WriteString(optionTitle)
builder.WriteString(p.pollMeter(votage))
builder.WriteString("\n" + strconv.Itoa(option.VotesCount) + " votes " + "(" + strconv.Itoa(percentage) + "%)")
} else {
builder.WriteString("\n" + "[" + strconv.Itoa(ind) + "] " + option.Title)
}
}
pollStatusField := "Poll is open until: "
if poll.Expired {
pollStatusField = "Poll was closed on: "
}
builder.WriteString("\n\n" + p.fieldFormat(pollStatusField) + p.formatDateTime(poll.ExpiredAt))
builder.WriteString("\n" + p.fieldFormat("Total votes: ") + strconv.Itoa(poll.VotesCount))
builder.WriteString("\n" + p.fieldFormat("Multiple choices allowed: ") + strconv.FormatBool(poll.Multiple))
return builder.String()
}
func (p Printer) pollMeter(votage float64) string {
numVoteBlocks := int(math.Floor(float64(p.lineWrapCharacterLimit) * votage))
numBackgroundBlocks := p.lineWrapCharacterLimit - numVoteBlocks
voteBlockColour := p.theme.boldgreen
backgroundBlockColor := p.theme.grey
if p.noColor {
voteBlockColour = p.theme.reset
if numVoteBlocks == 0 {
numVoteBlocks = 1
}
}
meter := "\n" + voteBlockColour + strings.Repeat(symbolPollMeter, numVoteBlocks) + p.theme.reset
if !p.noColor {
meter += backgroundBlockColor + strings.Repeat(symbolPollMeter, numBackgroundBlocks) + p.theme.reset
}
return meter
}

View file

@ -136,10 +136,6 @@
"type": "bool", "type": "bool",
"description": "Set to true to hide the vote count until the poll is closed" "description": "Set to true to hide the vote count until the poll is closed"
}, },
"poll-id": {
"type": "string",
"description": "The ID of the poll"
},
"poll-option": { "poll-option": {
"type": "StringSliceValue", "type": "StringSliceValue",
"description": "A poll option. Use this multiple times to set multiple options" "description": "A poll option. Use this multiple times to set multiple options"
@ -215,7 +211,6 @@
{ "flag": "account-name", "fieldName": "accountNames" }, { "flag": "account-name", "fieldName": "accountNames" },
{ "flag": "content", "default": "" }, { "flag": "content", "default": "" },
{ "flag": "list-id", "fieldName": "listID", "default": "" }, { "flag": "list-id", "fieldName": "listID", "default": "" },
{ "flag": "poll-id", "fieldName": "pollID", "default": "" },
{ "flag": "status-id", "fieldName": "statusID", "default": "" }, { "flag": "status-id", "fieldName": "statusID", "default": "" },
{ "flag": "to", "fieldName": "toResourceType", "default": "" }, { "flag": "to", "fieldName": "toResourceType", "default": "" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }, { "flag": "type", "fieldName": "resourceType", "default": "" },
@ -367,7 +362,6 @@
{ "flag": "only-media", "default": "false" }, { "flag": "only-media", "default": "false" },
{ "flag": "only-pinned", "default": "false" }, { "flag": "only-pinned", "default": "false" },
{ "flag": "only-public", "default": "false" }, { "flag": "only-public", "default": "false" },
{ "flag": "poll-id", "fieldName": "pollID", "default": "" },
{ "flag": "show-preferences", "fieldName": "showUserPreferences", "default": "false" }, { "flag": "show-preferences", "fieldName": "showUserPreferences", "default": "false" },
{ "flag": "show-statuses", "default": "false" }, { "flag": "show-statuses", "default": "false" },
{ "flag": "skip-relationship", "fieldName": "skipAccountRelationship", "default": "false" }, { "flag": "skip-relationship", "fieldName": "skipAccountRelationship", "default": "false" },