Go client library for Mastodon API with complete coverage of v1 and v2 endpoints
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:
Install with Tessl CLI
npx tessl i tessl/golang-go-mastodon