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

conversations.mddocs/

Conversations

Direct conversation management and operations.

Overview

Conversations represent direct message threads with one or more participants. This provides a more structured way to manage direct messages compared to viewing them as individual statuses.

Types

Conversation

type Conversation struct {
    ID         ID
    Accounts   []*Account
    Unread     bool
    LastStatus *Status
}

Conversation holds information for a Mastodon direct conversation.

Fields:

  • ID - Unique conversation identifier
  • Accounts - Participants in the conversation
  • Unread - Whether conversation has unread messages
  • LastStatus - Most recent status in conversation

Conversation Operations

GetConversations { .api }

func (c *Client) GetConversations(ctx context.Context, pg *Pagination) ([]*Conversation, error)

Returns direct conversations for the current user.

Parameters:

  • ctx - Context for cancellation/timeout
  • pg - Pagination parameters (optional)

Returns: Slice of Conversation objects or error

Example:

conversations, err := client.GetConversations(ctx, nil)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("You have %d conversations:\n", len(conversations))
for _, conv := range conversations {
    unreadMarker := ""
    if conv.Unread {
        unreadMarker = " [UNREAD]"
    }

    // List participants
    var participants []string
    for _, account := range conv.Accounts {
        participants = append(participants, "@"+account.Acct)
    }

    fmt.Printf("\nConversation %s%s\n", conv.ID, unreadMarker)
    fmt.Printf("  Participants: %s\n", strings.Join(participants, ", "))

    if conv.LastStatus != nil {
        fmt.Printf("  Last message: %s\n",
            conv.LastStatus.CreatedAt.Format("2006-01-02 15:04"))
        fmt.Printf("  Preview: %s\n",
            stripHTML(conv.LastStatus.Content))
    }
}

DeleteConversation { .api }

func (c *Client) DeleteConversation(ctx context.Context, id ID) error

Deletes a conversation (removes from inbox, doesn't delete actual statuses).

Parameters:

  • ctx - Context for cancellation/timeout
  • id - Conversation ID to delete

Returns: Error if operation fails

Example:

err := client.DeleteConversation(ctx, "123456")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Conversation deleted from inbox")

MarkConversationAsRead { .api }

func (c *Client) MarkConversationAsRead(ctx context.Context, id ID) error

Marks a conversation as read.

Parameters:

  • ctx - Context for cancellation/timeout
  • id - Conversation ID to mark as read

Returns: Error if operation fails

Example:

err := client.MarkConversationAsRead(ctx, "123456")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Conversation marked as read")

Usage Examples

Example: List Unread Conversations

func listUnreadConversations(client *mastodon.Client) error {
    ctx := context.Background()

    conversations, err := client.GetConversations(ctx, nil)
    if err != nil {
        return err
    }

    unreadCount := 0
    for _, conv := range conversations {
        if conv.Unread {
            unreadCount++
        }
    }

    if unreadCount == 0 {
        fmt.Println("No unread conversations")
        return nil
    }

    fmt.Printf("You have %d unread conversation(s):\n\n", unreadCount)

    for _, conv := range conversations {
        if !conv.Unread {
            continue
        }

        var participants []string
        for _, account := range conv.Accounts {
            participants = append(participants, "@"+account.Acct)
        }

        fmt.Printf("From: %s\n", strings.Join(participants, ", "))

        if conv.LastStatus != nil {
            fmt.Printf("Time: %s\n", conv.LastStatus.CreatedAt.Format("2006-01-02 15:04"))
            content := stripHTML(conv.LastStatus.Content)
            if len(content) > 100 {
                content = content[:97] + "..."
            }
            fmt.Printf("Message: %s\n", content)
        }

        fmt.Println()
    }

    return nil
}

func stripHTML(html string) string {
    // Simple HTML tag removal (use proper HTML parser for production)
    re := regexp.MustCompile(`<[^>]*>`)
    return strings.TrimSpace(re.ReplaceAllString(html, ""))
}

Example: Mark All Conversations as Read

func markAllConversationsRead(client *mastodon.Client) error {
    ctx := context.Background()

    conversations, err := client.GetConversations(ctx, nil)
    if err != nil {
        return err
    }

    markedCount := 0
    for _, conv := range conversations {
        if conv.Unread {
            err := client.MarkConversationAsRead(ctx, conv.ID)
            if err != nil {
                log.Printf("Failed to mark conversation %s as read: %v", conv.ID, err)
                continue
            }
            markedCount++
        }
    }

    fmt.Printf("Marked %d conversation(s) as read\n", markedCount)
    return nil
}

Example: Conversation Management

type ConversationManager struct {
    client *mastodon.Client
}

func (cm *ConversationManager) GetByParticipant(ctx context.Context, username string) ([]*mastodon.Conversation, error) {
    conversations, err := cm.client.GetConversations(ctx, nil)
    if err != nil {
        return nil, err
    }

    var matches []*mastodon.Conversation
    for _, conv := range conversations {
        for _, account := range conv.Accounts {
            if account.Acct == username || account.Username == username {
                matches = append(matches, conv)
                break
            }
        }
    }

    return matches, nil
}

func (cm *ConversationManager) CleanupOld(ctx context.Context, olderThan time.Duration) error {
    conversations, err := cm.client.GetConversations(ctx, nil)
    if err != nil {
        return err
    }

    cutoff := time.Now().Add(-olderThan)
    deletedCount := 0

    for _, conv := range conversations {
        if conv.LastStatus == nil {
            continue
        }

        if conv.LastStatus.CreatedAt.Before(cutoff) {
            err := cm.client.DeleteConversation(ctx, conv.ID)
            if err != nil {
                log.Printf("Failed to delete conversation %s: %v", conv.ID, err)
                continue
            }
            deletedCount++
        }
    }

    fmt.Printf("Deleted %d old conversation(s)\n", deletedCount)
    return nil
}

func (cm *ConversationManager) ExportConversation(ctx context.Context, convID mastodon.ID) error {
    // Get conversation details
    conversations, err := cm.client.GetConversations(ctx, nil)
    if err != nil {
        return err
    }

    var targetConv *mastodon.Conversation
    for _, conv := range conversations {
        if conv.ID == convID {
            targetConv = conv
            break
        }
    }

    if targetConv == nil {
        return fmt.Errorf("conversation not found")
    }

    // Export to file
    filename := fmt.Sprintf("conversation_%s.txt", convID)
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    fmt.Fprintf(file, "Conversation ID: %s\n", targetConv.ID)
    fmt.Fprintf(file, "Participants: ")
    for i, account := range targetConv.Accounts {
        if i > 0 {
            fmt.Fprintf(file, ", ")
        }
        fmt.Fprintf(file, "@%s", account.Acct)
    }
    fmt.Fprintf(file, "\n\n")

    if targetConv.LastStatus != nil {
        fmt.Fprintf(file, "Last message at: %s\n",
            targetConv.LastStatus.CreatedAt.Format("2006-01-02 15:04:05"))
        fmt.Fprintf(file, "Content:\n%s\n",
            stripHTML(targetConv.LastStatus.Content))
    }

    fmt.Printf("Exported conversation to: %s\n", filename)
    return nil
}

Example: Conversation Monitoring

func monitorConversations(client *mastodon.Client, interval time.Duration) {
    ctx := context.Background()
    ticker := time.NewTicker(interval)
    defer ticker.Stop()

    lastCheck := make(map[mastodon.ID]time.Time)

    for range ticker.C {
        conversations, err := client.GetConversations(ctx, nil)
        if err != nil {
            log.Printf("Error fetching conversations: %v", err)
            continue
        }

        for _, conv := range conversations {
            if !conv.Unread {
                continue
            }

            if conv.LastStatus == nil {
                continue
            }

            // Check if this is a new message
            lastTime, exists := lastCheck[conv.ID]
            if !exists || conv.LastStatus.CreatedAt.After(lastTime) {
                // New message
                var from string
                if len(conv.Accounts) > 0 {
                    from = "@" + conv.Accounts[0].Acct
                }

                fmt.Printf("\n[NEW MESSAGE] From %s:\n", from)
                fmt.Printf("%s\n", stripHTML(conv.LastStatus.Content))

                lastCheck[conv.ID] = conv.LastStatus.CreatedAt
            }
        }
    }
}

Example: Conversation Statistics

func getConversationStats(client *mastodon.Client) error {
    ctx := context.Background()

    conversations, err := client.GetConversations(ctx, nil)
    if err != nil {
        return err
    }

    fmt.Println("Conversation Statistics")
    fmt.Println(strings.Repeat("=", 50))

    unreadCount := 0
    participantCount := make(map[string]int)
    var oldestConv, newestConv *mastodon.Conversation

    for _, conv := range conversations {
        if conv.Unread {
            unreadCount++
        }

        for _, account := range conv.Accounts {
            participantCount[account.Acct]++
        }

        if conv.LastStatus != nil {
            if oldestConv == nil || conv.LastStatus.CreatedAt.Before(oldestConv.LastStatus.CreatedAt) {
                oldestConv = conv
            }
            if newestConv == nil || conv.LastStatus.CreatedAt.After(newestConv.LastStatus.CreatedAt) {
                newestConv = conv
            }
        }
    }

    fmt.Printf("Total conversations: %d\n", len(conversations))
    fmt.Printf("Unread: %d\n", unreadCount)
    fmt.Printf("Unique participants: %d\n\n", len(participantCount))

    // Top conversationalists
    type partInfo struct {
        acct  string
        count int
    }
    var parts []partInfo
    for acct, count := range participantCount {
        parts = append(parts, partInfo{acct, count})
    }
    sort.Slice(parts, func(i, j int) bool {
        return parts[i].count > parts[j].count
    })

    fmt.Println("Most frequent participants:")
    for i := 0; i < 5 && i < len(parts); i++ {
        fmt.Printf("  @%-30s %d conversation(s)\n", parts[i].acct, parts[i].count)
    }

    if oldestConv != nil && oldestConv.LastStatus != nil {
        fmt.Printf("\nOldest conversation: %s\n",
            oldestConv.LastStatus.CreatedAt.Format("2006-01-02"))
    }

    if newestConv != nil && newestConv.LastStatus != nil {
        fmt.Printf("Newest conversation: %s\n",
            newestConv.LastStatus.CreatedAt.Format("2006-01-02"))
    }

    return nil
}

Example: Reply to Conversation

func replyToConversation(client *mastodon.Client, convID mastodon.ID, message string) error {
    ctx := context.Background()

    // Get conversation details
    conversations, err := client.GetConversations(ctx, nil)
    if err != nil {
        return err
    }

    var targetConv *mastodon.Conversation
    for _, conv := range conversations {
        if conv.ID == convID {
            targetConv = conv
            break
        }
    }

    if targetConv == nil {
        return fmt.Errorf("conversation not found")
    }

    if targetConv.LastStatus == nil {
        return fmt.Errorf("no status to reply to")
    }

    // Create reply
    toot := &mastodon.Toot{
        Status:      message,
        InReplyToID: targetConv.LastStatus.ID,
        Visibility:  mastodon.VisibilityDirectMessage,
    }

    status, err := client.PostStatus(ctx, toot)
    if err != nil {
        return err
    }

    fmt.Printf("Replied to conversation %s\n", convID)
    fmt.Printf("Message ID: %s\n", status.ID)

    // Mark as read
    err = client.MarkConversationAsRead(ctx, convID)
    if err != nil {
        log.Printf("Warning: Failed to mark as read: %v", err)
    }

    return nil
}

Best Practices

1. Handle Pagination

Use pagination for large conversation lists:

pg := &mastodon.Pagination{Limit: 40}
conversations, err := client.GetConversations(ctx, pg)

2. Check for Nil LastStatus

Not all conversations may have a last status:

if conv.LastStatus != nil {
    fmt.Printf("Last: %s\n", conv.LastStatus.Content)
} else {
    fmt.Println("No messages in conversation")
}

3. Use Appropriate Visibility

When replying to DMs, use direct visibility:

toot := &mastodon.Toot{
    Status:     "Reply",
    Visibility: mastodon.VisibilityDirectMessage,
}

4. Batch Read Operations

Mark multiple conversations as read efficiently:

for _, conv := range unreadConversations {
    go client.MarkConversationAsRead(ctx, conv.ID)
}

5. Monitor for New Messages

Poll conversations periodically:

ticker := time.NewTicker(30 * time.Second)
for range ticker.C {
    checkForNewMessages(client)
}

6. Clean Up Old Conversations

Periodically remove old conversations:

// Delete conversations older than 30 days
manager.CleanupOld(ctx, 30*24*time.Hour)

Related Types

See also:

  • Statuses - For LastStatus details and replying
  • Accounts - For participant account information
  • Timelines - For direct timeline access
  • Streaming - For real-time direct message events
  • Types - For ID, Pagination, and other common types