Go client library for Mastodon API with complete coverage of v1 and v2 endpoints
Complete hashtag information, following, and filtering operations.
Hashtags allow users to categorize and discover content. Users can follow hashtags to see related posts in their home timeline.
type Tag struct {
Name string
URL string
History []History
}Tag holds information for a hashtag.
Fields:
Name - Hashtag name (without '#' prefix)URL - Web URL for this hashtagHistory - Usage history entriestype FollowedTag struct {
Name string
URL string
History []FollowedTagHistory
Following bool
}FollowedTag represents a hashtag followed by the user.
Fields:
Name - Hashtag name (without '#' prefix)URL - Web URL for this hashtagHistory - Usage history entriesFollowing - Whether currently following this tagtype FollowedTagHistory struct {
Day UnixTimeString
Accounts int
Uses int
}FollowedTagHistory represents tag usage history for a single day.
Fields:
Day - Date (Unix timestamp string)Accounts - Number of accounts using tag that day (JSON: string)Uses - Number of times tag was used that day (JSON: string)type History struct {
Day string
Uses string
Accounts string
}History holds generic history data.
Fields:
Day - Date stringUses - Number of uses (string)Accounts - Number of accounts (string)type TagData struct {
Any []string
All []string
None []string
}TagData holds tag filter parameters for timeline queries.
Fields:
Any - Statuses containing ANY of these tagsAll - Statuses containing ALL of these tagsNone - Statuses containing NONE of these tagsfunc (c *Client) TagInfo(ctx context.Context, tag string) (*FollowedTag, error)Gets statistics and information about a hashtag.
Parameters:
ctx - Context for cancellation/timeouttag - Hashtag name (without '#' prefix)Returns: FollowedTag with statistics or error
Example:
tagInfo, err := client.TagInfo(ctx, "golang")
if err != nil {
log.Fatal(err)
}
fmt.Printf("#%s\n", tagInfo.Name)
fmt.Printf("URL: %s\n", tagInfo.URL)
fmt.Printf("Following: %v\n", tagInfo.Following)
fmt.Println("\nRecent Activity:")
for _, hist := range tagInfo.History {
fmt.Printf(" Day: %s - %d uses by %d accounts\n",
hist.Day, hist.Uses, hist.Accounts)
}func (c *Client) TagFollow(ctx context.Context, tag string) (*FollowedTag, error)Follows a hashtag (posts with this tag appear in home timeline).
Parameters:
ctx - Context for cancellation/timeouttag - Hashtag name (without '#' prefix)Returns: Updated FollowedTag or error
Example:
followedTag, err := client.TagFollow(ctx, "golang")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Now following #%s\n", followedTag.Name)func (c *Client) TagUnfollow(ctx context.Context, ID string) (*FollowedTag, error)Unfollows a hashtag.
Parameters:
ctx - Context for cancellation/timeoutID - Tag ID or name to unfollowReturns: Updated FollowedTag or error
Example:
unfollowedTag, err := client.TagUnfollow(ctx, "golang")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Unfollowed #%s\n", unfollowedTag.Name)func (c *Client) TagsFollowed(ctx context.Context, pg *Pagination) ([]*FollowedTag, error)Returns a list of hashtags the user follows.
Parameters:
ctx - Context for cancellation/timeoutpg - Pagination parameters (optional)Returns: Slice of FollowedTag objects or error
Example:
tags, err := client.TagsFollowed(ctx, nil)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Following %d hashtags:\n", len(tags))
for _, tag := range tags {
fmt.Printf(" #%s - %s\n", tag.Name, tag.URL)
}func (c *Client) GetFollowedTags(ctx context.Context, pg *Pagination) ([]*FollowedTag, error)Returns the list of hashtags followed by the user (alias for TagsFollowed).
Parameters:
ctx - Context for cancellation/timeoutpg - Pagination parameters (optional)Returns: Slice of FollowedTag objects or error
func followTags(client *mastodon.Client, tags []string) error {
ctx := context.Background()
for _, tag := range tags {
followedTag, err := client.TagFollow(ctx, tag)
if err != nil {
log.Printf("Failed to follow #%s: %v", tag, err)
continue
}
fmt.Printf("Following #%s\n", followedTag.Name)
}
return nil
}
// Usage
tags := []string{"golang", "programming", "opensource"}
err := followTags(client, tags)func displayTagStats(client *mastodon.Client, tag string) error {
ctx := context.Background()
tagInfo, err := client.TagInfo(ctx, tag)
if err != nil {
return err
}
fmt.Printf("\n#%s Statistics\n", tagInfo.Name)
fmt.Println(strings.Repeat("=", 50))
fmt.Printf("URL: %s\n", tagInfo.URL)
fmt.Printf("Following: %v\n\n", tagInfo.Following)
if len(tagInfo.History) > 0 {
fmt.Println("Recent Activity (7 days):")
var totalUses, totalAccounts int
for _, hist := range tagInfo.History {
totalUses += hist.Uses
totalAccounts += hist.Accounts
}
avgUses := float64(totalUses) / float64(len(tagInfo.History))
avgAccounts := float64(totalAccounts) / float64(len(tagInfo.History))
fmt.Printf(" Total uses: %d (avg %.1f/day)\n", totalUses, avgUses)
fmt.Printf(" Total accounts: %d (avg %.1f/day)\n", totalAccounts, avgAccounts)
fmt.Println("\nDaily Breakdown:")
for _, hist := range tagInfo.History {
fmt.Printf(" %v: %d uses by %d accounts\n",
hist.Day, hist.Uses, hist.Accounts)
}
}
return nil
}type TagManager struct {
client *mastodon.Client
}
func (tm *TagManager) ListFollowed(ctx context.Context) ([]*mastodon.FollowedTag, error) {
tags, err := tm.client.TagsFollowed(ctx, nil)
if err != nil {
return nil, err
}
fmt.Printf("You are following %d hashtags:\n\n", len(tags))
for i, tag := range tags {
fmt.Printf("%d. #%s\n", i+1, tag.Name)
}
return tags, nil
}
func (tm *TagManager) UnfollowInactive(ctx context.Context, minDailyUses int) error {
tags, err := tm.client.TagsFollowed(ctx, nil)
if err != nil {
return err
}
for _, tag := range tags {
if len(tag.History) == 0 {
continue
}
// Calculate average daily uses
var totalUses int
for _, hist := range tag.History {
totalUses += hist.Uses
}
avgUses := totalUses / len(tag.History)
if avgUses < minDailyUses {
_, err := tm.client.TagUnfollow(ctx, tag.Name)
if err != nil {
log.Printf("Failed to unfollow #%s: %v", tag.Name, err)
continue
}
fmt.Printf("Unfollowed #%s (avg %.1f uses/day)\n", tag.Name, float64(avgUses))
}
}
return nil
}
func (tm *TagManager) FollowRelated(ctx context.Context, baseTag string, limit int) error {
// Get timeline for base tag
statuses, err := tm.client.GetTimelineHashtag(ctx, baseTag, false, nil)
if err != nil {
return err
}
// Count related tags
tagCount := make(map[string]int)
for _, status := range statuses {
for _, tag := range status.Tags {
if tag.Name != baseTag {
tagCount[tag.Name]++
}
}
}
// Sort by frequency
type tagFreq struct {
name string
count int
}
var tags []tagFreq
for name, count := range tagCount {
tags = append(tags, tagFreq{name, count})
}
sort.Slice(tags, func(i, j int) bool {
return tags[i].count > tags[j].count
})
// Follow top tags
fmt.Printf("Related tags to #%s:\n", baseTag)
for i := 0; i < limit && i < len(tags); i++ {
fmt.Printf(" #%s (mentioned %d times)\n", tags[i].name, tags[i].count)
_, err := tm.client.TagFollow(ctx, tags[i].name)
if err != nil {
log.Printf("Failed to follow #%s: %v", tags[i].name, err)
}
}
return nil
}func analyzeTrending(client *mastodon.Client, tags []string) error {
ctx := context.Background()
type tagMetrics struct {
name string
totalUses int
totalAccounts int
trend string // "rising", "stable", "falling"
}
var metrics []tagMetrics
for _, tagName := range tags {
tagInfo, err := client.TagInfo(ctx, tagName)
if err != nil {
log.Printf("Error getting info for #%s: %v", tagName, err)
continue
}
if len(tagInfo.History) < 2 {
continue
}
var total int
for _, hist := range tagInfo.History {
total += hist.Uses
}
// Compare recent vs older activity
recentUses := tagInfo.History[0].Uses + tagInfo.History[1].Uses
olderUses := tagInfo.History[len(tagInfo.History)-2].Uses +
tagInfo.History[len(tagInfo.History)-1].Uses
trend := "stable"
if recentUses > olderUses*1.5 {
trend = "rising"
} else if recentUses < olderUses*0.5 {
trend = "falling"
}
metrics = append(metrics, tagMetrics{
name: tagName,
totalUses: total,
trend: trend,
})
}
// Display results
fmt.Println("Tag Trend Analysis")
fmt.Println(strings.Repeat("=", 50))
for _, m := range metrics {
indicator := "→"
if m.trend == "rising" {
indicator = "↑"
} else if m.trend == "falling" {
indicator = "↓"
}
fmt.Printf("%s #%-20s %d uses (%s)\n",
indicator, m.name, m.totalUses, m.trend)
}
return nil
}func recommendTags(client *mastodon.Client, basedOnAccount mastodon.ID) ([]string, error) {
ctx := context.Background()
// Get account's recent statuses
statuses, err := client.GetAccountStatuses(ctx, basedOnAccount, &mastodon.Pagination{Limit: 40})
if err != nil {
return nil, err
}
// Count tag usage
tagFreq := make(map[string]int)
for _, status := range statuses {
for _, tag := range status.Tags {
tagFreq[tag.Name]++
}
}
// Get currently followed tags
following, err := client.TagsFollowed(ctx, nil)
if err != nil {
return nil, err
}
followedMap := make(map[string]bool)
for _, tag := range following {
followedMap[tag.Name] = true
}
// Recommend frequent but not followed tags
var recommendations []string
for tag, freq := range tagFreq {
if !followedMap[tag] && freq >= 3 {
recommendations = append(recommendations, tag)
}
}
// Sort by frequency
sort.Slice(recommendations, func(i, j int) bool {
return tagFreq[recommendations[i]] > tagFreq[recommendations[j]]
})
return recommendations, nil
}func filterByTags(statuses []*mastodon.Status, requiredTags, excludeTags []string) []*mastodon.Status {
var filtered []*mastodon.Status
for _, status := range statuses {
statusTags := make(map[string]bool)
for _, tag := range status.Tags {
statusTags[strings.ToLower(tag.Name)] = true
}
// Check exclusions first
excluded := false
for _, excludeTag := range excludeTags {
if statusTags[strings.ToLower(excludeTag)] {
excluded = true
break
}
}
if excluded {
continue
}
// Check required tags
if len(requiredTags) == 0 {
filtered = append(filtered, status)
continue
}
hasRequired := false
for _, requiredTag := range requiredTags {
if statusTags[strings.ToLower(requiredTag)] {
hasRequired = true
break
}
}
if hasRequired {
filtered = append(filtered, status)
}
}
return filtered
}// Posts with #golang AND (#programming OR #coding) but NOT #beginner
tagData := &mastodon.TagData{
All: []string{"programming"}, // Must have this
Any: []string{"coding", "tutorial"}, // Plus one of these
None: []string{"beginner"}, // But not this
}
statuses, err := client.GetTimelineHashtagMultiple(
ctx,
"golang", // Primary tag
false, // Include federated
tagData, // Additional filters
nil, // Pagination
)Follow tags that match your interests:
interests := []string{"golang", "opensource", "webdev"}
for _, tag := range interests {
client.TagFollow(ctx, tag)
}Check tag statistics before following:
tagInfo, _ := client.TagInfo(ctx, "sometag")
if len(tagInfo.History) > 0 {
avgUses := calculateAverage(tagInfo.History)
if avgUses >= 10 { // Minimum activity threshold
client.TagFollow(ctx, "sometag")
}
}Clean up inactive or no-longer-relevant tags:
tags, _ := client.TagsFollowed(ctx, nil)
for _, tag := range tags {
// Review and unfollow if needed
if shouldUnfollow(tag) {
client.TagUnfollow(ctx, tag.Name)
}
}API expects tag names without '#':
// Correct
client.TagFollow(ctx, "golang")
// Incorrect
client.TagFollow(ctx, "#golang")Use TagData for complex filtering:
tagData := &mastodon.TagData{
All: []string{"tech"},
Any: []string{"news", "tutorial"},
None: []string{"clickbait"},
}See also:
Install with Tessl CLI
npx tessl i tessl/golang-go-mastodon