Compare commits
4 commits
8e59ecf697
...
d3d3936704
Author | SHA1 | Date | |
---|---|---|---|
d3d3936704 | |||
d3b541dc86 | |||
c53908f3cf | |||
b0ffd4fcec |
7 changed files with 265 additions and 44 deletions
55
internal/client/poll.go
Normal file
55
internal/client/poll.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
// SPDX-FileCopyrightText: 2024 Dan Anglin <d.n.i.anglin@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||
)
|
||||
|
||||
const (
|
||||
pollPath string = "/api/v1/polls"
|
||||
)
|
||||
|
||||
func (g *Client) GetPoll(pollID string) (model.Poll, error) {
|
||||
url := g.Authentication.Instance + pollPath + "/" + pollID
|
||||
|
||||
var poll model.Poll
|
||||
|
||||
if err := g.sendRequest(http.MethodGet, url, nil, &poll); err != nil {
|
||||
return model.Poll{}, fmt.Errorf(
|
||||
"received an error after sending the request to get the poll: %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return poll, nil
|
||||
}
|
||||
|
||||
func (g *Client) VoteInPoll(pollID string, choices []int) error {
|
||||
form := struct {
|
||||
Choices []int `json:"choices"`
|
||||
}{
|
||||
Choices: choices,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(form)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to encode the JSON form: %w", err)
|
||||
}
|
||||
|
||||
requestBody := bytes.NewBuffer(data)
|
||||
url := g.Authentication.Instance + pollPath + "/" + pollID + "/votes"
|
||||
|
||||
if err := g.sendRequest(http.MethodPost, url, requestBody, nil); err != nil {
|
||||
return fmt.Errorf("received an error after sending the request to vote in the poll: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
package executor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
|
@ -19,6 +20,8 @@ type AddExecutor struct {
|
|||
toResourceType string
|
||||
listID string
|
||||
statusID string
|
||||
pollID string
|
||||
choices PollChoices
|
||||
accountNames AccountNames
|
||||
content string
|
||||
}
|
||||
|
@ -34,10 +37,12 @@ func NewAddExecutor(tlf TopLevelFlags, name, summary string) *AddExecutor {
|
|||
|
||||
addExe.StringVar(&addExe.resourceType, flagType, "", "Specify the resource type to add (e.g. account, note)")
|
||||
addExe.StringVar(&addExe.toResourceType, flagTo, "", "Specify the target resource type to add to (e.g. list, account, etc)")
|
||||
addExe.StringVar(&addExe.listID, flagListID, "", "The ID of the list to add to")
|
||||
addExe.StringVar(&addExe.listID, flagListID, "", "The ID of the list")
|
||||
addExe.StringVar(&addExe.statusID, flagStatusID, "", "The ID of the status")
|
||||
addExe.Var(&addExe.accountNames, flagAccountName, "The name of the account")
|
||||
addExe.StringVar(&addExe.content, flagContent, "", "The content of the resource")
|
||||
addExe.StringVar(&addExe.pollID, flagPollID, "", "The ID of the poll")
|
||||
addExe.Var(&addExe.accountNames, flagAccountName, "The name of the account")
|
||||
addExe.Var(&addExe.choices, flagChoose, "Specify your choice ")
|
||||
|
||||
addExe.Usage = commandUsageFunc(name, summary, addExe.FlagSet)
|
||||
|
||||
|
@ -54,6 +59,7 @@ func (a *AddExecutor) Execute() error {
|
|||
resourceAccount: a.addToAccount,
|
||||
resourceBookmarks: a.addToBookmarks,
|
||||
resourceStatus: a.addToStatus,
|
||||
resourcePoll: a.addToPoll,
|
||||
}
|
||||
|
||||
doFunc, ok := funcMap[a.toResourceType]
|
||||
|
@ -227,3 +233,50 @@ func (a *AddExecutor) addBoostToStatus(gtsClient *client.Client) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AddExecutor) addToPoll(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 len(a.choices) == 0 {
|
||||
return errors.New("please use --" + flagChoose + " to make a choice in this poll")
|
||||
}
|
||||
|
||||
poll, err := gtsClient.GetPoll(a.pollID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to retrieve the poll: %w", err)
|
||||
}
|
||||
|
||||
if poll.Expired {
|
||||
return errors.New("this poll has expired")
|
||||
}
|
||||
|
||||
if !poll.Multiple && len(a.choices) > 1 {
|
||||
return errors.New("this poll does not allow multiple choices")
|
||||
}
|
||||
|
||||
if err := gtsClient.VoteInPoll(a.pollID, []int(a.choices)); err != nil {
|
||||
return fmt.Errorf("unable to add your vote(s) to the poll: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Successfully added your vote(s) to the poll.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -4,11 +4,16 @@
|
|||
|
||||
package executor
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
flagAccountName = "account-name"
|
||||
flagBrowser = "browser"
|
||||
flagChoose = "choose"
|
||||
flagContentType = "content-type"
|
||||
flagContent = "content"
|
||||
flagEnableFederation = "enable-federation"
|
||||
|
@ -26,6 +31,7 @@ const (
|
|||
flagListRepliesPolicy = "list-replies-policy"
|
||||
flagMyAccount = "my-account"
|
||||
flagNotify = "notify"
|
||||
flagPollID = "poll-id"
|
||||
flagSensitive = "sensitive"
|
||||
flagSkipRelationship = "skip-relationship"
|
||||
flagShowPreferences = "show-preferences"
|
||||
|
@ -58,3 +64,30 @@ type TopLevelFlags struct {
|
|||
NoColor *bool
|
||||
Pager string
|
||||
}
|
||||
|
||||
type PollChoices []int
|
||||
|
||||
func (p *PollChoices) String() string {
|
||||
value := "Choices: "
|
||||
|
||||
for ind, vote := range *p {
|
||||
if ind == len(*p)-1 {
|
||||
value += strconv.Itoa(vote)
|
||||
} else {
|
||||
value += strconv.Itoa(vote) + ", "
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func (p *PollChoices) Set(text string) error {
|
||||
value, err := strconv.Atoi(text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse the value to an integer: %w", err)
|
||||
}
|
||||
|
||||
*p = append(*p, value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
package executor
|
||||
|
||||
const(
|
||||
const (
|
||||
resourceAccount = "account"
|
||||
resourceBlocked = "blocked"
|
||||
resourceBookmarks = "bookmarks"
|
||||
|
@ -17,8 +17,10 @@ const(
|
|||
resourceLiked = "liked"
|
||||
resourceList = "list"
|
||||
resourceNote = "note"
|
||||
resourcePoll = "poll"
|
||||
resourceStatus = "status"
|
||||
resourceStar = "star"
|
||||
resourceStarred = "starred"
|
||||
resourceTimeline = "timeline"
|
||||
resourceVote = "vote"
|
||||
)
|
||||
|
|
|
@ -26,6 +26,7 @@ type ShowExecutor struct {
|
|||
timelineCategory string
|
||||
listID string
|
||||
tag string
|
||||
pollID string
|
||||
limit int
|
||||
}
|
||||
|
||||
|
@ -45,6 +46,7 @@ func NewShowExecutor(tlf TopLevelFlags, name, summary string) *ShowExecutor {
|
|||
showExe.StringVar(&showExe.timelineCategory, flagTimelineCategory, model.TimelineCategoryHome, "Specify the timeline category to view")
|
||||
showExe.StringVar(&showExe.listID, flagListID, "", "Specify the ID of the list to display")
|
||||
showExe.StringVar(&showExe.tag, flagTag, "", "Specify the name of the tag to use")
|
||||
showExe.StringVar(&showExe.pollID, flagPollID, "", "Specify the ID of the poll to display")
|
||||
showExe.IntVar(&showExe.limit, flagLimit, 20, "Specify the limit of items to display")
|
||||
|
||||
showExe.Usage = commandUsageFunc(name, summary, showExe.FlagSet)
|
||||
|
@ -70,6 +72,7 @@ func (s *ShowExecutor) Execute() error {
|
|||
resourceLiked: s.showLiked,
|
||||
resourceStarred: s.showLiked,
|
||||
resourceFollowRequest: s.showFollowRequests,
|
||||
resourcePoll: s.showPoll,
|
||||
}
|
||||
|
||||
doFunc, ok := funcMap[s.resourceType]
|
||||
|
@ -362,3 +365,18 @@ func (s *ShowExecutor) showFollowRequests(gtsClient *client.Client) error {
|
|||
|
||||
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)
|
||||
}
|
||||
|
||||
utilities.Display(poll, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
77
internal/model/poll.go
Normal file
77
internal/model/poll.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
|
||||
)
|
||||
|
||||
type Poll struct {
|
||||
Emojis []Emoji `json:"emojis"`
|
||||
Expired bool `json:"expired"`
|
||||
Voted bool `json:"voted"`
|
||||
Multiple bool `json:"multiple"`
|
||||
ExpiredAt time.Time `json:"expires_at"`
|
||||
ID string `json:"id"`
|
||||
OwnVotes []int `json:"own_votes"`
|
||||
VotersCount int `json:"voters_count"`
|
||||
VotesCount int `json:"votes_count"`
|
||||
Options []PollOption `json:"options"`
|
||||
}
|
||||
|
||||
type PollOption struct {
|
||||
Title string `json:"title"`
|
||||
VotesCount int `json:"votes_count"`
|
||||
}
|
||||
|
||||
func (p Poll) Display(noColor bool) string {
|
||||
var builder strings.Builder
|
||||
|
||||
indent := " "
|
||||
|
||||
builder.WriteString(utilities.HeaderFormat(noColor, "POLL ID:") + "\n" + indent + p.ID)
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "OPTIONS:"))
|
||||
displayPollContent(&builder, p, noColor, indent)
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "MULTIPLE CHOICES ALLOWED:") + "\n" + indent + strconv.FormatBool(p.Multiple))
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "YOU VOTED:") + "\n" + indent + strconv.FormatBool(p.Voted))
|
||||
|
||||
if len(p.OwnVotes) > 0 {
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "YOUR VOTES:"))
|
||||
|
||||
for _, vote := range p.OwnVotes {
|
||||
builder.WriteString("\n" + indent + "[" + strconv.Itoa(vote) + "] " + p.Options[vote].Title)
|
||||
}
|
||||
}
|
||||
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "EXPIRED:") + "\n" + indent + strconv.FormatBool(p.Expired))
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func displayPollContent(writer io.StringWriter, poll Poll, noColor bool, indent string) {
|
||||
for ind, option := range poll.Options {
|
||||
var percentage int
|
||||
|
||||
if poll.VotesCount == 0 {
|
||||
percentage = 0
|
||||
} else {
|
||||
calculate := math.Floor(100 * (float64(option.VotesCount) / float64(poll.VotesCount)))
|
||||
percentage = int(calculate)
|
||||
}
|
||||
|
||||
writer.WriteString(
|
||||
"\n\n" + indent + "[" + strconv.Itoa(ind) + "] " + option.Title +
|
||||
"\n" + indent + strings.Repeat("\u2584", 80) +
|
||||
"\n" + indent + strconv.Itoa(option.VotesCount) + " votes " +
|
||||
"(" + strconv.Itoa(percentage) + "%)",
|
||||
)
|
||||
}
|
||||
|
||||
writer.WriteString("\n\n" + indent + utilities.FieldFormat(noColor, "Total votes:") + " " + strconv.Itoa(poll.VotesCount))
|
||||
writer.WriteString("\n" + indent + utilities.FieldFormat(noColor, "Poll ID:") + " " + poll.ID)
|
||||
writer.WriteString("\n" + indent + utilities.FieldFormat(noColor, "Poll is open until:") + " " + utilities.FormatTime(poll.ExpiredAt))
|
||||
}
|
|
@ -68,24 +68,6 @@ type Mention struct {
|
|||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type Poll struct {
|
||||
Emojis []Emoji `json:"emojis"`
|
||||
Expired bool `json:"expired"`
|
||||
Voted bool `json:"voted"`
|
||||
Multiple bool `json:"multiple"`
|
||||
ExpiredAt time.Time `json:"expires_at"`
|
||||
ID string `json:"id"`
|
||||
OwnVotes []int `json:"own_votes"`
|
||||
VotersCount int `json:"voters_count"`
|
||||
VotesCount int `json:"votes_count"`
|
||||
Options []PollOption `json:"options"`
|
||||
}
|
||||
|
||||
type PollOption struct {
|
||||
Title string `json:"title"`
|
||||
VotesCount int `json:"votes_count"`
|
||||
}
|
||||
|
||||
type StatusReblogged struct {
|
||||
Account Account `json:"account"`
|
||||
Application Application `json:"application"`
|
||||
|
@ -158,6 +140,8 @@ type MediaDimensions struct {
|
|||
}
|
||||
|
||||
func (s Status) Display(noColor bool) string {
|
||||
indent := " "
|
||||
|
||||
var builder strings.Builder
|
||||
|
||||
// The account information
|
||||
|
@ -169,41 +153,29 @@ func (s Status) Display(noColor bool) string {
|
|||
|
||||
// If a poll exists in a status, write the contents to the builder.
|
||||
if s.Poll != nil {
|
||||
for ind, option := range s.Poll.Options {
|
||||
builder.WriteString(
|
||||
"\n\n " + strconv.Itoa(ind) + ". " + option.Title +
|
||||
"\n " + strings.Repeat("\u2580", 80) +
|
||||
"\n " + strconv.Itoa(option.VotesCount) + " votes",
|
||||
)
|
||||
}
|
||||
builder.WriteString("\n")
|
||||
displayPollContent(&builder, *s.Poll, noColor, indent)
|
||||
}
|
||||
|
||||
// The ID of the status
|
||||
builder.WriteString("\n" + utilities.HeaderFormat(noColor, "STATUS ID:") + "\n " + s.ID)
|
||||
|
||||
// The ID of the poll if it exists
|
||||
if s.Poll != nil {
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "POLL ID:") + "\n " + s.Poll.ID)
|
||||
}
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "STATUS ID:") + "\n" + indent + s.ID)
|
||||
|
||||
// Status creation time
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "CREATED AT:") + "\n " + utilities.FormatTime(s.CreatedAt))
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "CREATED AT:") + "\n" + indent + utilities.FormatTime(s.CreatedAt))
|
||||
|
||||
// Status stats
|
||||
builder.WriteString(
|
||||
"\n\n" +
|
||||
utilities.HeaderFormat(noColor, "STATS:") +
|
||||
"\n " + utilities.FieldFormat(noColor, "Boosts: ") + strconv.Itoa(s.ReblogsCount) +
|
||||
"\n " + utilities.FieldFormat(noColor, "Likes: ") + strconv.Itoa(s.FavouritesCount) +
|
||||
"\n " + utilities.FieldFormat(noColor, "Replies: ") + strconv.Itoa(s.RepliesCount),
|
||||
"\n" + indent + utilities.FieldFormat(noColor, "Boosts: ") + strconv.Itoa(s.ReblogsCount) +
|
||||
"\n" + indent + utilities.FieldFormat(noColor, "Likes: ") + strconv.Itoa(s.FavouritesCount) +
|
||||
"\n" + indent + utilities.FieldFormat(noColor, "Replies: ") + strconv.Itoa(s.RepliesCount),
|
||||
)
|
||||
|
||||
// Status visibility
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "VISIBILITY:") + "\n " + s.Visibility.String())
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "VISIBILITY:") + "\n" + indent + s.Visibility.String())
|
||||
|
||||
// Status URL
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "URL:") + "\n " + s.URL)
|
||||
builder.WriteString("\n\n" + utilities.HeaderFormat(noColor, "URL:") + "\n" + indent + s.URL)
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
@ -232,8 +204,19 @@ func (s StatusList) Display(noColor bool) string {
|
|||
createdAt = status.Reblog.CreatedAt
|
||||
}
|
||||
|
||||
builder.WriteString(utilities.WrapLines(utilities.ConvertHTMLToText(status.Content), "\n", 80) + "\n\n")
|
||||
builder.WriteString(utilities.FieldFormat(noColor, "ID:") + " " + statusID + "\t" + utilities.FieldFormat(noColor, "Created at:") + " " + utilities.FormatTime(createdAt) + "\n")
|
||||
builder.WriteString(utilities.WrapLines(utilities.ConvertHTMLToText(status.Content), "\n", 80))
|
||||
|
||||
if status.Poll != nil {
|
||||
displayPollContent(&builder, *status.Poll, noColor, "")
|
||||
}
|
||||
|
||||
builder.WriteString(
|
||||
"\n\n" +
|
||||
utilities.FieldFormat(noColor, "Status ID:") + " " + statusID + "\t" +
|
||||
utilities.FieldFormat(noColor, "Created at:") + " " + utilities.FormatTime(createdAt) +
|
||||
"\n",
|
||||
)
|
||||
|
||||
builder.WriteString(separator + "\n")
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue