Core types and utility functions used throughout the library.
type ID stringID is a type alias for entity identifiers. Mastodon uses string IDs that can be compared numerically.
Method:
func (i ID) Compare(j ID) intCompares two Mastodon IDs. Returns:
-1 if i < j0 if i == j1 if i > jCan be used with slices.SortFunc:
slices.SortFunc([]mastodon.ID{id1, id2}, mastodon.ID.Compare)Method:
func (id *ID) UnmarshalJSON(data []byte) errorCustom JSON unmarshaling to handle both string and numeric IDs from the API.
Example:
id1 := mastodon.ID("123456")
id2 := mastodon.ID("789012")
comparison := id1.Compare(id2)
if comparison < 0 {
fmt.Println("id1 is older than id2")
} else if comparison > 0 {
fmt.Println("id1 is newer than id2")
} else {
fmt.Println("IDs are equal")
}
// Sort IDs chronologically
ids := []mastodon.ID{"999", "123", "456"}
slices.SortFunc(ids, mastodon.ID.Compare)
// Result: ["123", "456", "999"]type Pagination struct {
MaxID ID
SinceID ID
MinID ID
Limit int64
}Pagination parameters for list queries using cursor-based pagination.
Fields:
MaxID - Return results older than this ID (scroll down/backward)SinceID - Return results newer than this ID (check for updates)MinID - Return results immediately newer than this ID (scroll up/forward)Limit - Maximum number of results to return (default varies by endpoint)Example:
// Initial fetch with limit
pg := &mastodon.Pagination{
Limit: 40,
}
statuses, err := client.GetTimelineHome(ctx, pg)
// Get older statuses (scroll down)
if len(statuses) > 0 {
pg.MaxID = statuses[len(statuses)-1].ID
olderStatuses, err := client.GetTimelineHome(ctx, pg)
}
// Get newer statuses (check for updates)
pg = &mastodon.Pagination{
SinceID: statuses[0].ID,
}
newerStatuses, err := client.GetTimelineHome(ctx, pg)type Mention struct {
URL string
Username string
Acct string
ID ID
}Mention holds information for a mentioned account in a status.
Fields:
URL - Web URL for the mentioned accountUsername - Local username (without domain)Acct - Full username with domain (user@domain)ID - Account IDExample:
for _, mention := range status.Mentions {
fmt.Printf("Mentioned: @%s (ID: %s)\n", mention.Acct, mention.ID)
}type Emoji struct {
ShortCode string
StaticURL string
URL string
VisibleInPicker bool
}Emoji holds information for custom emoji.
Fields:
ShortCode - Emoji shortcode (e.g., "blobcat")StaticURL - URL for static (non-animated) versionURL - URL for animated version (if applicable)VisibleInPicker - Whether emoji appears in pickerExample:
for _, emoji := range status.Emojis {
fmt.Printf(":%s: - %s\n", emoji.ShortCode, emoji.URL)
}type Unixtime time.TimeUnixtime represents a Unix timestamp.
Method:
func (t *Unixtime) UnmarshalJSON(data []byte) errorCustom JSON unmarshaling for Unix timestamps.
Example:
week := time.Time(activity.Week)
fmt.Printf("Week of: %s\n", week.Format("2006-01-02"))type UnixTimeString struct {
time.Time
}UnixTimeString represents a Unix timestamp encoded as a string in JSON.
Method:
func (u *UnixTimeString) UnmarshalJSON(b []byte) errorCustom JSON unmarshaling for string-encoded Unix timestamps.
Example:
day := time.Time(history.Day.Time)
fmt.Printf("Day: %s\n", day.Format("2006-01-02"))type WriterResetter interface {
io.Writer
Reset()
}WriterResetter is an interface that combines io.Writer with a Reset method. This is used internally for optimizing JSON encoding by reusing buffers.
Methods:
Write(p []byte) (n int, err error) - From io.Writer interfaceReset() - Resets the writer to be empty, allowing buffer reuseNote: This is primarily an internal interface used by the Client for performance optimization. Most users will not need to interact with this type directly.
type Sbool boolSbool is a boolean type that handles JSON string booleans (e.g., "true", "false").
Method:
func (s *Sbool) UnmarshalJSON(data []byte) errorCustom JSON unmarshaling to handle string booleans from API.
Example:
trueVal := mastodon.Sbool(true)
falseVal := mastodon.Sbool(false)
alerts := &mastodon.PushAlerts{
Follow: &trueVal,
Mention: &trueVal,
Favourite: &falseVal,
Reblog: &falseVal,
}type APIError struct {
Message string
StatusCode int
// contains filtered or unexported fields
}APIError represents an error returned by the Mastodon API.
Method:
func (e *APIError) Error() stringReturns the error message as a string.
Example:
status, err := client.GetStatus(ctx, "nonexistent")
if err != nil {
if apiErr, ok := err.(*mastodon.APIError); ok {
fmt.Printf("API Error: %s (HTTP %d)\n", apiErr.Message, apiErr.StatusCode)
switch apiErr.StatusCode {
case 404:
fmt.Println("Status not found")
case 429:
fmt.Println("Rate limited")
case 401:
fmt.Println("Authentication required")
case 403:
fmt.Println("Forbidden")
case 500:
fmt.Println("Server error")
}
} else {
fmt.Printf("Error: %v\n", err)
}
}func String(v string) *stringString is a helper function to get a pointer to a string value.
Example:
// For updating profile fields where nil means "don't change"
displayName := mastodon.String("New Display Name")
note := mastodon.String("Updated bio")
profile := &mastodon.Profile{
DisplayName: displayName,
Note: note,
}
account, err := client.AccountUpdate(ctx, profile)func Base64Encode(file *os.File) (string, error)Base64Encode returns the base64 data URI format string of the file.
Example:
file, err := os.Open("avatar.jpg")
if err != nil {
log.Fatal(err)
}
defer file.Close()
encoded, err := mastodon.Base64Encode(file)
if err != nil {
log.Fatal(err)
}
profile := &mastodon.Profile{
Avatar: encoded,
}
account, err := client.AccountUpdate(ctx, profile)func Base64EncodeFileName(filename string) (string, error)Base64EncodeFileName returns the base64 data URI format string of the file with the given filename.
Example:
encoded, err := mastodon.Base64EncodeFileName("header.jpg")
if err != nil {
log.Fatal(err)
}
profile := &mastodon.Profile{
Header: encoded,
}
account, err := client.AccountUpdate(ctx, profile)func sortStatusesByID(statuses []*mastodon.Status) {
// Sort statuses by ID (chronological order)
slices.SortFunc(statuses, func(a, b *mastodon.Status) int {
return a.ID.Compare(b.ID)
})
fmt.Println("Statuses sorted from oldest to newest")
}
func findNewestStatus(statuses []*mastodon.Status) *mastodon.Status {
if len(statuses) == 0 {
return nil
}
newest := statuses[0]
for _, status := range statuses[1:] {
if status.ID.Compare(newest.ID) > 0 {
newest = status
}
}
return newest
}// Pattern 1: Load all pages
func loadAllStatuses(client *mastodon.Client) ([]*mastodon.Status, error) {
ctx := context.Background()
var allStatuses []*mastodon.Status
pg := &mastodon.Pagination{Limit: 40}
for {
statuses, err := client.GetTimelineHome(ctx, pg)
if err != nil {
return nil, err
}
if len(statuses) == 0 {
break
}
allStatuses = append(allStatuses, statuses...)
// Get next page
pg.MaxID = statuses[len(statuses)-1].ID
}
return allStatuses, nil
}
// Pattern 2: Load until condition
func loadUntilDate(client *mastodon.Client, until time.Time) ([]*mastodon.Status, error) {
ctx := context.Background()
var statuses []*mastodon.Status
pg := &mastodon.Pagination{Limit: 40}
for {
batch, err := client.GetTimelineHome(ctx, pg)
if err != nil {
return nil, err
}
if len(batch) == 0 {
break
}
for _, status := range batch {
if status.CreatedAt.Before(until) {
return statuses, nil // Reached target date
}
statuses = append(statuses, status)
}
pg.MaxID = batch[len(batch)-1].ID
}
return statuses, nil
}
// Pattern 3: Real-time updates
func pollForNewStatuses(client *mastodon.Client, lastID mastodon.ID) ([]*mastodon.Status, error) {
ctx := context.Background()
pg := &mastodon.Pagination{
SinceID: lastID,
}
return client.GetTimelineHome(ctx, pg)
}func handleAPIError(err error) {
if err == nil {
return
}
apiErr, ok := err.(*mastodon.APIError)
if !ok {
// Not an API error (network error, etc.)
log.Printf("Network or other error: %v", err)
return
}
// Handle specific HTTP status codes
switch apiErr.StatusCode {
case 400:
log.Printf("Bad request: %s", apiErr.Message)
case 401:
log.Fatal("Authentication required - check access token")
case 403:
log.Printf("Forbidden: %s", apiErr.Message)
case 404:
log.Printf("Not found: %s", apiErr.Message)
case 422:
log.Printf("Validation error: %s", apiErr.Message)
case 429:
log.Printf("Rate limited - please wait before retrying")
time.Sleep(time.Minute)
case 500, 502, 503:
log.Printf("Server error: %s", apiErr.Message)
default:
log.Printf("API error %d: %s", apiErr.StatusCode, apiErr.Message)
}
}
// Usage
status, err := client.PostStatus(ctx, toot)
handleAPIError(err)func updateProfileSelectively(client *mastodon.Client) error {
ctx := context.Background()
// Only update display name, leave other fields unchanged
profile := &mastodon.Profile{
DisplayName: mastodon.String("New Name"),
// Note is nil, so it won't be updated
// Locked is nil, so it won't be updated
}
account, err := client.AccountUpdate(ctx, profile)
if err != nil {
return err
}
fmt.Printf("Updated display name to: %s\n", account.DisplayName)
return nil
}
func clearDisplayName(client *mastodon.Client) error {
ctx := context.Background()
// Set to empty string to clear
empty := ""
profile := &mastodon.Profile{
DisplayName: &empty, // Not nil, so it will be updated to empty
}
account, err := client.AccountUpdate(ctx, profile)
if err != nil {
return err
}
fmt.Println("Cleared display name")
return nil
}func updateProfileImages(client *mastodon.Client, avatarPath, headerPath string) error {
ctx := context.Background()
// Encode avatar
avatar, err := mastodon.Base64EncodeFileName(avatarPath)
if err != nil {
return fmt.Errorf("failed to encode avatar: %w", err)
}
// Encode header
header, err := mastodon.Base64EncodeFileName(headerPath)
if err != nil {
return fmt.Errorf("failed to encode header: %w", err)
}
// Update profile
profile := &mastodon.Profile{
Avatar: avatar,
Header: header,
}
account, err := client.AccountUpdate(ctx, profile)
if err != nil {
return err
}
fmt.Println("Updated profile images")
fmt.Printf(" Avatar: %s\n", account.Avatar)
fmt.Printf(" Header: %s\n", account.Header)
return nil
}func replaceCustomEmojis(content string, emojis []mastodon.Emoji) string {
for _, emoji := range emojis {
shortcode := ":" + emoji.ShortCode + ":"
img := fmt.Sprintf(`<img src="%s" alt="%s" class="emoji">`,
emoji.URL, emoji.ShortCode)
content = strings.ReplaceAll(content, shortcode, img)
}
return content
}
// Usage
processedContent := replaceCustomEmojis(status.Content, status.Emojis)func extractMentionedUsers(status *mastodon.Status) []string {
var users []string
for _, mention := range status.Mentions {
users = append(users, mention.Acct)
}
return users
}
func isUserMentioned(status *mastodon.Status, username string) bool {
for _, mention := range status.Mentions {
if mention.Acct == username || mention.Username == username {
return true
}
}
return false
}
// Usage
mentions := extractMentionedUsers(status)
fmt.Printf("Status mentions: %v\n", mentions)
if isUserMentioned(status, "myusername") {
fmt.Println("You were mentioned in this status!")
}// Convert time.Time to string for display
func formatTime(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
}
// Convert Sbool to regular bool
func sboolToBool(sb *mastodon.Sbool) bool {
if sb == nil {
return false
}
return bool(*sb)
}
// Create Sbool pointer from bool
func boolToSbool(b bool) *mastodon.Sbool {
sb := mastodon.Sbool(b)
return &sb
}
// Convert int to string for API parameters
func intToString(i int) string {
return strconv.Itoa(i)
}Always use the ID type for identifiers:
// Good
var statusID mastodon.ID = "123456"
// Avoid
var statusID string = "123456"Check for nil before dereferencing:
if status.Poll != nil {
fmt.Printf("Poll has %d options\n", len(status.Poll.Options))
}Always use Pagination for list operations:
pg := &mastodon.Pagination{Limit: 40}
results, err := client.GetTimelineHome(ctx, pg)Always check for and handle API errors:
if err != nil {
if apiErr, ok := err.(*mastodon.APIError); ok {
// Handle API error
}
return err
}Use provided helpers for common operations:
// Use String helper for optional fields
profile.DisplayName = mastodon.String("New Name")
// Use Base64 helpers for images
avatar, _ := mastodon.Base64EncodeFileName("avatar.jpg")See also: