or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accounts.mdauthentication.mdconversations.mdfilters.mdindex.mdinstance.mdlists.mdmedia.mdnotifications.mdpolls.mdreports.mdsearch.mdstatuses.mdstreaming.mdtags.mdtimelines.mdtypes.md
tile.json

search.mddocs/

Search

Content search across accounts, statuses, and hashtags.

Overview

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.

Types

Results

type Results struct {
    Accounts []*Account
    Statuses []*Status
    Hashtags []*Tag
}

Results holds search results across all content types.

Fields:

  • Accounts - Matching account results
  • Statuses - Matching status results
  • Hashtags - Matching hashtag results

Search Operations

Search { .api }

func (c *Client) Search(ctx context.Context, q string, resolve bool) (*Results, error)

Searches content with a query string.

Parameters:

  • ctx - Context for cancellation/timeout
  • q - Search query string
  • resolve - If true, attempt to resolve remote resources via WebFinger

Returns: 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 Types

Account Search

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)
}

Hashtag Search

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)
}

Status Search

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)
}

URL Resolution

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)
}

Usage Examples

Example: Comprehensive Search

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, ""))
}

Example: Find and Follow Accounts

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
}

Example: Search and Filter

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)

Example: Resolve Remote Content

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
}

Example: Search Analytics

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
}

Example: Multi-Query Search

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 Tips

Username Search

// Search for exact username
results, _ := client.Search(ctx, "@username", false)

// Search for remote user
results, _ := client.Search(ctx, "@user@instance.com", true)

Hashtag Search

// With or without # symbol
results, _ := client.Search(ctx, "#golang", false)
results, _ := client.Search(ctx, "golang", false) // Also works

URL Resolution

// 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)

Phrase Search

// Search for phrase
results, _ := client.Search(ctx, "go programming language", false)

Best Practices

1. Use Resolve Sparingly

Only use resolve=true when needed:

// For local search
results, _ := client.Search(ctx, query, false)

// For remote URLs
results, _ := client.Search(ctx, url, true)

2. Handle Empty Results

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
}

3. Limit Result Processing

Don't process all results if there are many:

maxResults := 10
for i, account := range results.Accounts {
    if i >= maxResults {
        break
    }
    // Process account
}

4. Cache Search Results

Cache frequent searches:

type SearchCache struct {
    results   map[string]*mastodon.Results
    timestamp map[string]time.Time
    ttl       time.Duration
}

5. Handle Search Errors

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
}

Related Types

See also:

  • Accounts - For account search results
  • Statuses - For status search results
  • Tags - For hashtag search results
  • Types - For common types