fix(BREAKING): update poll interaction

Summary:

This commit updates and enhances poll interaction. From now on users
will interact with a poll via the status that contains it. Direct
interaction with the poll (via the poll's ID) is no longer supported.
This helps resolve an issue where it wasn't possible to find the owner
of the poll when interacting with it directly.

Changes:

- Users can no longer view a poll directly using the Poll ID.
  Instead polls can be viewed when viewing statuses or timelines.
- More details about a poll is shown in statuses and timelines.
- Votes are now added to polls via statuses.
- Poll results are hidden unless the following conditions are met.
    - The user is the owner of the poll.
    - The poll has expired.
    - The user has already voted in the poll.
- Enbas can now detect and stop a poll owner from voting in their own
  poll.
- When a status is created Enbas will now only print the ID of the
  created status instead of the whole thing.

PR: apollo/enbas#43

Resolves apollo/enbas#39
This commit is contained in:
Dan Anglin 2024-08-14 11:18:38 +01:00
parent a0eab3b6ae
commit eb016b96e9
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
13 changed files with 215 additions and 215 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

@ -1,7 +1,6 @@
package executor package executor
import ( import (
"errors"
"fmt" "fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/client"
@ -17,7 +16,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]
@ -164,6 +162,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 +196,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 NoVotesError{}
} }
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 NoPollInStatusError{}
}
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 PollOwnerVoteError{}
}
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

@ -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

@ -104,6 +104,24 @@ func (e NoPollOptionError) Error() string {
" flag to add options to the poll" " flag to add options to the poll"
} }
type NoVotesError struct{}
func (e NoVotesError) Error() string {
return "no votes were made, please add your vote(s) using the --vote flag"
}
type NoPollInStatusError struct{}
func (e NoPollInStatusError) Error() string {
return "this status does not have a poll"
}
type PollOwnerVoteError struct{}
func (e PollOwnerVoteError) Error() string {
return "you cannot vote in your own poll"
}
type NotFollowingError struct { type NotFollowingError struct {
Account string Account string
} }

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

@ -8,11 +8,9 @@ const (
flagInstance = "instance" flagInstance = "instance"
flagListID = "list-id" flagListID = "list-id"
flagListTitle = "list-title" flagListTitle = "list-title"
flagPollID = "poll-id"
flagPollOption = "poll-option" flagPollOption = "poll-option"
flagStatusID = "status-id" flagStatusID = "status-id"
flagTag = "tag" flagTag = "tag"
flagTo = "to" flagTo = "to"
flagType = "type" flagType = "type"
flagVote = "vote"
) )

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,
@ -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,12 +85,15 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error {
} }
} }
if s.myAccount && s.showUserPreferences { if s.myAccount {
myAccountID = account.ID
if s.showUserPreferences {
preferences, err = gtsClient.GetUserPreferences() preferences, err = gtsClient.GetUserPreferences()
if err != nil { if err != nil {
return fmt.Errorf("unable to retrieve the user preferences: %w", err) return fmt.Errorf("unable to retrieve the user preferences: %w", err)
} }
} }
}
if s.showStatuses { if s.showStatuses {
form := client.GetAccountStatusesForm{ form := client.GetAccountStatusesForm{
@ -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
} }
@ -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

@ -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 +0,0 @@
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" },