Complete poll creation and voting operations.
Polls allow users to create questions with multiple choice answers that can be voted on for a specified duration.
type Poll struct {
ID ID
ExpiresAt time.Time
Expired bool
Multiple bool
VotesCount int64
VotersCount int64
Options []PollOption
Voted bool
OwnVotes []int
Emojis []Emoji
}Poll holds information for a Mastodon poll.
Fields:
ID - Unique poll identifierExpiresAt - When the poll expiresExpired - Whether the poll has expiredMultiple - Whether multiple choices are allowedVotesCount - Total number of votes castVotersCount - Number of unique votersOptions - Poll options/choicesVoted - Whether current user has votedOwnVotes - Current user's vote indices (if voted)Emojis - Custom emojis used in polltype PollOption struct {
Title string
VotesCount int64
}PollOption holds information for a poll option.
Fields:
Title - Option textVotesCount - Number of votes for this optiontype TootPoll struct {
Options []string
ExpiresInSeconds int
Multiple bool
HideTotals bool
}TootPoll holds information for creating a poll in a toot.
Fields:
Options - Option texts (2-4 options)ExpiresInSeconds - Poll duration in secondsMultiple - Allow multiple choice selectionHideTotals - Hide vote counts until poll expiresfunc (c *Client) GetPoll(ctx context.Context, id ID) (*Poll, error)Returns poll by ID.
Parameters:
ctx - Context for cancellation/timeoutid - Poll ID to retrieveReturns: Poll object or error
Example:
poll, err := client.GetPoll(ctx, "123456")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Poll: expires at %s\n", poll.ExpiresAt)
fmt.Printf("Total votes: %d from %d voters\n", poll.VotesCount, poll.VotersCount)
for i, option := range poll.Options {
percentage := 0.0
if poll.VotesCount > 0 {
percentage = float64(option.VotesCount) / float64(poll.VotesCount) * 100
}
fmt.Printf("%d. %s: %d votes (%.1f%%)\n",
i+1, option.Title, option.VotesCount, percentage)
}func (c *Client) PollVote(ctx context.Context, id ID, choices ...int) (*Poll, error)Votes on a poll. Choices are Poll.Options indices (0-based).
Parameters:
ctx - Context for cancellation/timeoutid - Poll ID to vote onchoices - One or more option indices to vote forReturns: Updated Poll object or error
Example:
// Vote for option 1 (second option, 0-indexed)
poll, err := client.PollVote(ctx, "123456", 1)
if err != nil {
log.Fatal(err)
}
fmt.Println("Vote recorded!")
// Multiple choice poll - vote for options 0 and 2
poll, err = client.PollVote(ctx, "123456", 0, 2)
if err != nil {
log.Fatal(err)
}Polls are created as part of status posting using the TootPoll struct:
poll := &mastodon.TootPoll{
Options: []string{"Option A", "Option B", "Option C"},
ExpiresInSeconds: 86400, // 24 hours
Multiple: false,
HideTotals: false,
}
toot := &mastodon.Toot{
Status: "What's your favorite?",
Poll: poll,
}
status, err := client.PostStatus(ctx, toot)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created poll in status: %s\n", status.ID)func createPoll(client *mastodon.Client, question string, options []string, hours int) (*mastodon.Status, error) {
ctx := context.Background()
poll := &mastodon.TootPoll{
Options: options,
ExpiresInSeconds: hours * 3600,
Multiple: false,
HideTotals: false,
}
toot := &mastodon.Toot{
Status: question,
Poll: poll,
Visibility: mastodon.VisibilityPublic,
}
status, err := client.PostStatus(ctx, toot)
if err != nil {
return nil, err
}
fmt.Printf("Poll created! ID: %s\n", status.Poll.ID)
return status, nil
}
// Usage
options := []string{"Go", "Rust", "Python", "JavaScript"}
status, err := createPoll(client, "What's your favorite language?", options, 24)func createMultipleChoicePoll(client *mastodon.Client) (*mastodon.Status, error) {
ctx := context.Background()
poll := &mastodon.TootPoll{
Options: []string{"Morning", "Afternoon", "Evening", "Night"},
ExpiresInSeconds: 3 * 24 * 3600, // 3 days
Multiple: true, // Allow multiple choices
HideTotals: false,
}
toot := &mastodon.Toot{
Status: "When are you most productive? (Select all that apply)",
Poll: poll,
}
return client.PostStatus(ctx, toot)
}func createAnonymousPoll(client *mastodon.Client) (*mastodon.Status, error) {
ctx := context.Background()
poll := &mastodon.TootPoll{
Options: []string{"Yes", "No", "Maybe"},
ExpiresInSeconds: 7 * 24 * 3600, // 7 days
Multiple: false,
HideTotals: true, // Hide results until poll ends
}
toot := &mastodon.Toot{
Status: "Anonymous poll: Do you agree? (Results hidden until poll ends)",
Poll: poll,
}
return client.PostStatus(ctx, toot)
}func monitorPoll(client *mastodon.Client, pollID mastodon.ID, interval time.Duration) {
ctx := context.Background()
ticker := time.NewTicker(interval)
defer ticker.Stop()
var lastVoteCount int64
for range ticker.C {
poll, err := client.GetPoll(ctx, pollID)
if err != nil {
log.Printf("Error fetching poll: %v", err)
continue
}
if poll.Expired {
fmt.Println("Poll has ended!")
displayPollResults(poll)
break
}
if poll.VotesCount != lastVoteCount {
fmt.Printf("New votes! Total: %d (from %d voters)\n",
poll.VotesCount, poll.VotersCount)
lastVoteCount = poll.VotesCount
}
remaining := time.Until(poll.ExpiresAt)
fmt.Printf("Time remaining: %s\n", remaining.Round(time.Minute))
}
}
func displayPollResults(poll *mastodon.Poll) {
fmt.Println("\nFinal Results:")
fmt.Printf("Total votes: %d from %d voters\n\n", poll.VotesCount, poll.VotersCount)
// Find max votes for bar chart
maxVotes := int64(0)
for _, option := range poll.Options {
if option.VotesCount > maxVotes {
maxVotes = option.VotesCount
}
}
for i, option := range poll.Options {
percentage := 0.0
if poll.VotesCount > 0 {
percentage = float64(option.VotesCount) / float64(poll.VotesCount) * 100
}
// Simple bar chart
barLength := 20
if maxVotes > 0 {
barLength = int(float64(option.VotesCount) / float64(maxVotes) * 20)
}
bar := strings.Repeat("█", barLength)
fmt.Printf("%d. %-20s %s %.1f%% (%d votes)\n",
i+1, option.Title, bar, percentage, option.VotesCount)
}
}func autoVote(client *mastodon.Client, pollID mastodon.ID, preferredOption string) error {
ctx := context.Background()
poll, err := client.GetPoll(ctx, pollID)
if err != nil {
return err
}
if poll.Expired {
return fmt.Errorf("poll has expired")
}
if poll.Voted {
return fmt.Errorf("already voted on this poll")
}
// Find matching option
for i, option := range poll.Options {
if strings.EqualFold(option.Title, preferredOption) {
_, err := client.PollVote(ctx, pollID, i)
if err != nil {
return err
}
fmt.Printf("Voted for: %s\n", option.Title)
return nil
}
}
return fmt.Errorf("option not found: %s", preferredOption)
}type PollStats struct {
ID mastodon.ID
Question string
TotalVotes int64
TotalVoters int64
Participation float64 // VotersCount / FollowerCount
LeadingOption string
LeadingVotes int64
LeadingPercent float64
TimeRemaining time.Duration
}
func getPollStats(client *mastodon.Client, statusID mastodon.ID) (*PollStats, error) {
ctx := context.Background()
status, err := client.GetStatus(ctx, statusID)
if err != nil {
return nil, err
}
if status.Poll == nil {
return nil, fmt.Errorf("status has no poll")
}
poll := status.Poll
stats := &PollStats{
ID: poll.ID,
Question: status.Content,
TotalVotes: poll.VotesCount,
TotalVoters: poll.VotersCount,
}
// Find leading option
var maxVotes int64
var leadingIdx int
for i, option := range poll.Options {
if option.VotesCount > maxVotes {
maxVotes = option.VotesCount
leadingIdx = i
}
}
if len(poll.Options) > 0 {
stats.LeadingOption = poll.Options[leadingIdx].Title
stats.LeadingVotes = maxVotes
if poll.VotesCount > 0 {
stats.LeadingPercent = float64(maxVotes) / float64(poll.VotesCount) * 100
}
}
if !poll.Expired {
stats.TimeRemaining = time.Until(poll.ExpiresAt)
}
return stats, nil
}
func displayStats(stats *PollStats) {
fmt.Printf("Poll Statistics\n")
fmt.Printf("===============\n")
fmt.Printf("Question: %s\n", stats.Question)
fmt.Printf("Total Votes: %d\n", stats.TotalVotes)
fmt.Printf("Total Voters: %d\n", stats.TotalVoters)
fmt.Printf("Leading Option: %s (%.1f%%)\n",
stats.LeadingOption, stats.LeadingPercent)
if stats.TimeRemaining > 0 {
fmt.Printf("Time Remaining: %s\n", stats.TimeRemaining.Round(time.Minute))
} else {
fmt.Println("Status: Ended")
}
}func comparePolls(client *mastodon.Client, pollID1, pollID2 mastodon.ID) error {
ctx := context.Background()
poll1, err := client.GetPoll(ctx, pollID1)
if err != nil {
return err
}
poll2, err := client.GetPoll(ctx, pollID2)
if err != nil {
return err
}
fmt.Println("Poll Comparison")
fmt.Println("===============")
fmt.Printf("\nPoll 1: %d votes from %d voters\n", poll1.VotesCount, poll1.VotersCount)
fmt.Printf("Poll 2: %d votes from %d voters\n", poll2.VotesCount, poll2.VotersCount)
fmt.Printf("\nEngagement Rate:\n")
if poll1.VotesCount > 0 {
fmt.Printf("Poll 1: %.2f votes per voter\n",
float64(poll1.VotesCount)/float64(poll1.VotersCount))
}
if poll2.VotesCount > 0 {
fmt.Printf("Poll 2: %.2f votes per voter\n",
float64(poll2.VotesCount)/float64(poll2.VotersCount))
}
return nil
}Common poll durations:
const (
FiveMinutes = 5 * 60
ThirtyMinutes = 30 * 60
OneHour = 3600
SixHours = 6 * 3600
TwelveHours = 12 * 3600
OneDay = 24 * 3600
ThreeDays = 3 * 24 * 3600
OneWeek = 7 * 24 * 3600
)
// Quick poll
poll := &mastodon.TootPoll{
ExpiresInSeconds: OneHour,
}
// Standard poll
poll := &mastodon.TootPoll{
ExpiresInSeconds: OneDay,
}
// Extended poll
poll := &mastodon.TootPoll{
ExpiresInSeconds: OneWeek,
}Match duration to audience activity:
// Quick question for active audience
ExpiresInSeconds: 3600 // 1 hour
// Standard community poll
ExpiresInSeconds: 86400 // 24 hours
// Important decision requiring broad input
ExpiresInSeconds: 7 * 24 * 3600 // 1 weekUse clear, concise option text:
// Good
Options: []string{"Yes", "No", "Unsure"}
// Less clear
Options: []string{"Yup", "Nah", "IDK"}Keep options manageable (2-4 options is typical):
// Good: 3-4 options
Options: []string{"Go", "Rust", "Python", "Other"}
// Overwhelming: too many options
// (API limit is 4 options)Enable multiple choice when appropriate:
// Single choice: "Which is your favorite?"
Multiple: false
// Multiple choice: "Which do you use?"
Multiple: trueHide totals to prevent vote influencing:
// Anonymous voting
poll := &mastodon.TootPoll{
HideTotals: true, // Results hidden until end
}Verify poll hasn't expired:
poll, err := client.GetPoll(ctx, pollID)
if err != nil {
return err
}
if poll.Expired {
return fmt.Errorf("poll has expired")
}
if poll.Voted {
return fmt.Errorf("already voted")
}
// Now vote
client.PollVote(ctx, pollID, choiceIndex)See also: