Content search across accounts, statuses, and hashtags.
The search endpoint allows you to search for accounts, statuses, and hashtags across the Mastodon network. Search can be local (this instance only) or resolve remote content via WebFinger.
type Results struct {
Accounts []*Account
Statuses []*Status
Hashtags []*Tag
}Results holds search results across all content types.
Fields:
Accounts - Matching account resultsStatuses - Matching status resultsHashtags - Matching hashtag resultsfunc (c *Client) Search(ctx context.Context, q string, resolve bool) (*Results, error)Searches content with a query string.
Parameters:
ctx - Context for cancellation/timeoutq - Search query stringresolve - If true, attempt to resolve remote resources via WebFingerReturns: Results object with matching content or error
Example:
results, err := client.Search(ctx, "golang", false)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found:\n")
fmt.Printf(" %d accounts\n", len(results.Accounts))
fmt.Printf(" %d statuses\n", len(results.Statuses))
fmt.Printf(" %d hashtags\n", len(results.Hashtags))
// Display accounts
for _, account := range results.Accounts {
fmt.Printf(" @%s - %s\n", account.Acct, account.DisplayName)
}
// Display hashtags
for _, tag := range results.Hashtags {
fmt.Printf(" #%s\n", tag.Name)
}Search for users by username or display name:
results, err := client.Search(ctx, "@username", false)
if err != nil {
log.Fatal(err)
}
for _, account := range results.Accounts {
fmt.Printf("@%s - %s\n", account.Acct, account.DisplayName)
fmt.Printf(" Followers: %d\n", account.FollowersCount)
fmt.Printf(" URL: %s\n", account.URL)
}Search for hashtags:
results, err := client.Search(ctx, "#golang", false)
if err != nil {
log.Fatal(err)
}
for _, tag := range results.Hashtags {
fmt.Printf("#%s - %s\n", tag.Name, tag.URL)
}Search for specific statuses or content:
results, err := client.Search(ctx, "mastodon client library", false)
if err != nil {
log.Fatal(err)
}
for _, status := range results.Statuses {
fmt.Printf("@%s: %s\n", status.Account.Acct, status.Content)
fmt.Printf(" URL: %s\n", status.URL)
}Resolve remote URLs (requires resolve=true):
// Resolve remote account
results, err := client.Search(ctx, "https://mastodon.social/@Gargron", true)
if err != nil {
log.Fatal(err)
}
if len(results.Accounts) > 0 {
account := results.Accounts[0]
fmt.Printf("Resolved: @%s\n", account.Acct)
}
// Resolve remote status
results, err = client.Search(ctx, "https://mastodon.social/@user/123456", true)
if err != nil {
log.Fatal(err)
}
if len(results.Statuses) > 0 {
status := results.Statuses[0]
fmt.Printf("Resolved status from @%s\n", status.Account.Acct)
}func comprehensiveSearch(client *mastodon.Client, query string) error {
ctx := context.Background()
fmt.Printf("Searching for: %s\n", query)
fmt.Println(strings.Repeat("=", 60))
results, err := client.Search(ctx, query, false)
if err != nil {
return err
}
// Display accounts
if len(results.Accounts) > 0 {
fmt.Printf("\nAccounts (%d):\n", len(results.Accounts))
for i, account := range results.Accounts {
if i >= 5 {
fmt.Printf(" ... and %d more\n", len(results.Accounts)-5)
break
}
fmt.Printf(" @%-30s %s\n", account.Acct, account.DisplayName)
if account.Note != "" {
bio := stripHTML(account.Note)
if len(bio) > 60 {
bio = bio[:57] + "..."
}
fmt.Printf(" %s\n", bio)
}
}
}
// Display statuses
if len(results.Statuses) > 0 {
fmt.Printf("\nStatuses (%d):\n", len(results.Statuses))
for i, status := range results.Statuses {
if i >= 5 {
fmt.Printf(" ... and %d more\n", len(results.Statuses)-5)
break
}
content := stripHTML(status.Content)
if len(content) > 100 {
content = content[:97] + "..."
}
fmt.Printf(" @%s: %s\n", status.Account.Acct, content)
fmt.Printf(" Posted: %s\n", status.CreatedAt.Format("2006-01-02 15:04"))
}
}
// Display hashtags
if len(results.Hashtags) > 0 {
fmt.Printf("\nHashtags (%d):\n", len(results.Hashtags))
for i, tag := range results.Hashtags {
if i >= 10 {
fmt.Printf(" ... and %d more\n", len(results.Hashtags)-10)
break
}
fmt.Printf(" #%s\n", tag.Name)
}
}
if len(results.Accounts) == 0 && len(results.Statuses) == 0 && len(results.Hashtags) == 0 {
fmt.Println("\nNo results found")
}
return nil
}
func stripHTML(html string) string {
re := regexp.MustCompile(`<[^>]*>`)
return strings.TrimSpace(re.ReplaceAllString(html, ""))
}func findAndFollowAccounts(client *mastodon.Client, query string, maxFollow int) error {
ctx := context.Background()
results, err := client.Search(ctx, query, false)
if err != nil {
return err
}
if len(results.Accounts) == 0 {
fmt.Println("No accounts found")
return nil
}
fmt.Printf("Found %d accounts matching '%s'\n", len(results.Accounts), query)
followedCount := 0
for i, account := range results.Accounts {
if i >= maxFollow {
break
}
fmt.Printf("\n%d. @%s - %s\n", i+1, account.Acct, account.DisplayName)
fmt.Printf(" Followers: %d | Following: %d | Posts: %d\n",
account.FollowersCount, account.FollowingCount, account.StatusesCount)
// Check if already following
rels, err := client.GetAccountRelationships(ctx, []string{string(account.ID)})
if err != nil || len(rels) == 0 {
continue
}
if rels[0].Following {
fmt.Println(" Already following")
continue
}
// Follow account
_, err = client.AccountFollow(ctx, account.ID)
if err != nil {
fmt.Printf(" Failed to follow: %v\n", err)
continue
}
fmt.Println(" ✓ Followed")
followedCount++
}
fmt.Printf("\nFollowed %d new accounts\n", followedCount)
return nil
}type SearchFilter struct {
Query string
MinFollowers int64
MaxFollowers int64
AccountsOnly bool
StatusesOnly bool
HashtagsOnly bool
ExcludeAccounts []string
}
func filteredSearch(client *mastodon.Client, filter SearchFilter) (*mastodon.Results, error) {
ctx := context.Background()
results, err := client.Search(ctx, filter.Query, false)
if err != nil {
return nil, err
}
// Filter accounts
if filter.AccountsOnly {
results.Statuses = nil
results.Hashtags = nil
}
if filter.MinFollowers > 0 || filter.MaxFollowers > 0 {
var filtered []*mastodon.Account
for _, account := range results.Accounts {
if filter.MinFollowers > 0 && account.FollowersCount < filter.MinFollowers {
continue
}
if filter.MaxFollowers > 0 && account.FollowersCount > filter.MaxFollowers {
continue
}
// Check exclusions
excluded := false
for _, excludeAcct := range filter.ExcludeAccounts {
if account.Acct == excludeAcct {
excluded = true
break
}
}
if !excluded {
filtered = append(filtered, account)
}
}
results.Accounts = filtered
}
if filter.StatusesOnly {
results.Accounts = nil
results.Hashtags = nil
}
if filter.HashtagsOnly {
results.Accounts = nil
results.Statuses = nil
}
return results, nil
}
// Usage
filter := SearchFilter{
Query: "golang",
MinFollowers: 100,
MaxFollowers: 10000,
AccountsOnly: true,
}
results, err := filteredSearch(client, filter)func resolveRemoteContent(client *mastodon.Client, url string) error {
ctx := context.Background()
fmt.Printf("Resolving: %s\n", url)
results, err := client.Search(ctx, url, true) // resolve=true
if err != nil {
return err
}
// Check what was resolved
if len(results.Accounts) > 0 {
account := results.Accounts[0]
fmt.Printf("Resolved Account: @%s\n", account.Acct)
fmt.Printf(" Display Name: %s\n", account.DisplayName)
fmt.Printf(" Followers: %d\n", account.FollowersCount)
return nil
}
if len(results.Statuses) > 0 {
status := results.Statuses[0]
fmt.Printf("Resolved Status:\n")
fmt.Printf(" Author: @%s\n", status.Account.Acct)
fmt.Printf(" Posted: %s\n", status.CreatedAt.Format("2006-01-02 15:04"))
fmt.Printf(" Content: %s\n", stripHTML(status.Content))
return nil
}
fmt.Println("Could not resolve URL")
return nil
}func searchAnalytics(client *mastodon.Client, query string) error {
ctx := context.Background()
results, err := client.Search(ctx, query, false)
if err != nil {
return err
}
fmt.Printf("Search Analytics for: %s\n", query)
fmt.Println(strings.Repeat("=", 60))
// Account stats
if len(results.Accounts) > 0 {
var totalFollowers, totalPosts int64
for _, account := range results.Accounts {
totalFollowers += account.FollowersCount
totalPosts += account.StatusesCount
}
avgFollowers := float64(totalFollowers) / float64(len(results.Accounts))
avgPosts := float64(totalPosts) / float64(len(results.Accounts))
fmt.Printf("\nAccount Results (%d):\n", len(results.Accounts))
fmt.Printf(" Avg followers: %.1f\n", avgFollowers)
fmt.Printf(" Avg posts: %.1f\n", avgPosts)
fmt.Printf(" Total reach: %d followers\n", totalFollowers)
}
// Status stats
if len(results.Statuses) > 0 {
var totalFavs, totalReblogs, totalReplies int64
for _, status := range results.Statuses {
totalFavs += status.FavouritesCount
totalReblogs += status.ReblogsCount
totalReplies += status.RepliesCount
}
fmt.Printf("\nStatus Results (%d):\n", len(results.Statuses))
fmt.Printf(" Total favourites: %d\n", totalFavs)
fmt.Printf(" Total reblogs: %d\n", totalReblogs)
fmt.Printf(" Total replies: %d\n", totalReplies)
if len(results.Statuses) > 0 {
fmt.Printf(" Avg engagement: %.1f per post\n",
float64(totalFavs+totalReblogs+totalReplies)/float64(len(results.Statuses)))
}
}
// Hashtag results
if len(results.Hashtags) > 0 {
fmt.Printf("\nHashtag Results (%d):\n", len(results.Hashtags))
for _, tag := range results.Hashtags {
fmt.Printf(" #%s\n", tag.Name)
}
}
return nil
}func multiSearch(client *mastodon.Client, queries []string) (map[string]*mastodon.Results, error) {
ctx := context.Background()
results := make(map[string]*mastodon.Results)
for _, query := range queries {
result, err := client.Search(ctx, query, false)
if err != nil {
log.Printf("Search failed for '%s': %v", query, err)
continue
}
results[query] = result
}
// Display comparison
fmt.Println("Multi-Query Search Results")
fmt.Println(strings.Repeat("=", 60))
for query, result := range results {
fmt.Printf("\n'%s':\n", query)
fmt.Printf(" Accounts: %d | Statuses: %d | Hashtags: %d\n",
len(result.Accounts), len(result.Statuses), len(result.Hashtags))
}
return results, nil
}
// Usage
queries := []string{"golang", "rustlang", "python"}
results, err := multiSearch(client, queries)// Search for exact username
results, _ := client.Search(ctx, "@username", false)
// Search for remote user
results, _ := client.Search(ctx, "@user@instance.com", true)// With or without # symbol
results, _ := client.Search(ctx, "#golang", false)
results, _ := client.Search(ctx, "golang", false) // Also works// Resolve remote account URL
results, _ := client.Search(ctx, "https://mastodon.social/@user", true)
// Resolve remote status URL
results, _ := client.Search(ctx, "https://instance.com/@user/123456", true)// Search for phrase
results, _ := client.Search(ctx, "go programming language", false)Only use resolve=true when needed:
// For local search
results, _ := client.Search(ctx, query, false)
// For remote URLs
results, _ := client.Search(ctx, url, true)Always check for empty results:
results, err := client.Search(ctx, query, false)
if err != nil {
return err
}
if len(results.Accounts) == 0 && len(results.Statuses) == 0 && len(results.Hashtags) == 0 {
fmt.Println("No results found")
return nil
}Don't process all results if there are many:
maxResults := 10
for i, account := range results.Accounts {
if i >= maxResults {
break
}
// Process account
}Cache frequent searches:
type SearchCache struct {
results map[string]*mastodon.Results
timestamp map[string]time.Time
ttl time.Duration
}Search may fail or timeout:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
results, err := client.Search(ctx, query, false)
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return fmt.Errorf("search timed out")
}
return err
}See also: