Direct conversation management and operations.
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.
type Conversation struct {
ID ID
Accounts []*Account
Unread bool
LastStatus *Status
}Conversation holds information for a Mastodon direct conversation.
Fields:
ID - Unique conversation identifierAccounts - Participants in the conversationUnread - Whether conversation has unread messagesLastStatus - Most recent status in conversationfunc (c *Client) GetConversations(ctx context.Context, pg *Pagination) ([]*Conversation, error)Returns direct conversations for the current user.
Parameters:
ctx - Context for cancellation/timeoutpg - 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))
}
}func (c *Client) DeleteConversation(ctx context.Context, id ID) errorDeletes a conversation (removes from inbox, doesn't delete actual statuses).
Parameters:
ctx - Context for cancellation/timeoutid - Conversation ID to deleteReturns: Error if operation fails
Example:
err := client.DeleteConversation(ctx, "123456")
if err != nil {
log.Fatal(err)
}
fmt.Println("Conversation deleted from inbox")func (c *Client) MarkConversationAsRead(ctx context.Context, id ID) errorMarks a conversation as read.
Parameters:
ctx - Context for cancellation/timeoutid - Conversation ID to mark as readReturns: Error if operation fails
Example:
err := client.MarkConversationAsRead(ctx, "123456")
if err != nil {
log.Fatal(err)
}
fmt.Println("Conversation marked as read")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, ""))
}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
}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
}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
}
}
}
}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
}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
}Use pagination for large conversation lists:
pg := &mastodon.Pagination{Limit: 40}
conversations, err := client.GetConversations(ctx, pg)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")
}When replying to DMs, use direct visibility:
toot := &mastodon.Toot{
Status: "Reply",
Visibility: mastodon.VisibilityDirectMessage,
}Mark multiple conversations as read efficiently:
for _, conv := range unreadConversations {
go client.MarkConversationAsRead(ctx, conv.ID)
}Poll conversations periodically:
ticker := time.NewTicker(30 * time.Second)
for range ticker.C {
checkForNewMessages(client)
}Periodically remove old conversations:
// Delete conversations older than 30 days
manager.CleanupOld(ctx, 30*24*time.Hour)See also: