Go client library for Mastodon API with complete coverage of v1 and v2 endpoints
User reporting and moderation operations.
The reporting system allows users to report problematic accounts or content to instance moderators. Reports are reviewed by instance administrators.
type Report struct {
ID ID
ActionTaken bool
}Report holds information for a Mastodon report.
Fields:
ID - Unique report identifierActionTaken - Whether moderators have acted on this reportfunc (c *Client) GetReports(ctx context.Context) ([]*Report, error)Returns reports filed by the current user.
Parameters:
ctx - Context for cancellation/timeoutReturns: Slice of Report objects or error
Example:
reports, err := client.GetReports(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("You have filed %d report(s):\n", len(reports))
for _, report := range reports {
status := "Pending"
if report.ActionTaken {
status = "Action Taken"
}
fmt.Printf(" Report %s: %s\n", report.ID, status)
}func (c *Client) Report(ctx context.Context, accountID ID, ids []ID, comment string) (*Report, error)Creates a report against an account.
Parameters:
ctx - Context for cancellation/timeoutaccountID - ID of account to reportids - Slice of status IDs as evidence (can be empty)comment - Explanation of why you're reporting (required)Returns: Created Report object or error
Example:
accountID := "123456"
statusIDs := []mastodon.ID{"789012", "345678"} // Problematic statuses
comment := "This account is posting spam and harassment"
report, err := client.Report(ctx, accountID, statusIDs, comment)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Report filed successfully. Report ID: %s\n", report.ID)func reportSpammer(client *mastodon.Client, accountID mastodon.ID) error {
ctx := context.Background()
// Get recent statuses from the account as evidence
statuses, err := client.GetAccountStatuses(ctx, accountID, &mastodon.Pagination{Limit: 5})
if err != nil {
return fmt.Errorf("failed to get account statuses: %w", err)
}
// Collect status IDs
var statusIDs []mastodon.ID
for _, status := range statuses {
statusIDs = append(statusIDs, status.ID)
}
// File report
comment := "This account is posting spam links repeatedly"
report, err := client.Report(ctx, accountID, statusIDs, comment)
if err != nil {
return fmt.Errorf("failed to file report: %w", err)
}
fmt.Printf("Report filed successfully (ID: %s)\n", report.ID)
fmt.Printf("Moderators will review the account and take appropriate action.\n")
return nil
}func reportHarassment(client *mastodon.Client, accountID mastodon.ID, offendingStatusIDs []mastodon.ID) error {
ctx := context.Background()
comment := `This account has been sending harassing messages and making threatening statements.
The attached statuses contain personal attacks and violate the instance's code of conduct.`
report, err := client.Report(ctx, accountID, offendingStatusIDs, comment)
if err != nil {
return err
}
fmt.Printf("Harassment report filed (ID: %s)\n", report.ID)
return nil
}func checkReportStatus(client *mastodon.Client, reportID mastodon.ID) error {
ctx := context.Background()
reports, err := client.GetReports(ctx)
if err != nil {
return err
}
for _, report := range reports {
if report.ID == reportID {
if report.ActionTaken {
fmt.Printf("Report %s: Action has been taken by moderators\n", reportID)
} else {
fmt.Printf("Report %s: Pending review\n", reportID)
}
return nil
}
}
return fmt.Errorf("report %s not found", reportID)
}func listMyReports(client *mastodon.Client) error {
ctx := context.Background()
reports, err := client.GetReports(ctx)
if err != nil {
return err
}
if len(reports) == 0 {
fmt.Println("You have not filed any reports")
return nil
}
fmt.Printf("Your Reports (%d total)\n", len(reports))
fmt.Println(strings.Repeat("=", 50))
pending := 0
resolved := 0
for _, report := range reports {
status := "⏳ Pending"
if report.ActionTaken {
status = "✓ Action Taken"
resolved++
} else {
pending++
}
fmt.Printf("Report %s: %s\n", report.ID, status)
}
fmt.Printf("\nSummary: %d pending, %d resolved\n", pending, resolved)
return nil
}type ReportContext struct {
AccountID mastodon.ID
Reason string
StatusIDs []mastodon.ID
AdditionalInfo string
}
func fileDetailedReport(client *mastodon.Client, ctx ReportContext) error {
context := context.Background()
// Build comprehensive comment
comment := fmt.Sprintf("Reason: %s\n\n", ctx.Reason)
if ctx.AdditionalInfo != "" {
comment += fmt.Sprintf("Additional Information:\n%s\n\n", ctx.AdditionalInfo)
}
comment += fmt.Sprintf("Number of problematic posts: %d", len(ctx.StatusIDs))
report, err := client.Report(context, ctx.AccountID, ctx.StatusIDs, comment)
if err != nil {
return err
}
fmt.Printf("Detailed report filed: %s\n", report.ID)
return nil
}
// Usage
reportCtx := ReportContext{
AccountID: "123456",
Reason: "Spam and misleading information",
StatusIDs: []mastodon.ID{"789", "012", "345"},
AdditionalInfo: "Account created recently and only posts promotional content",
}
err := fileDetailedReport(client, reportCtx)type ReportManager struct {
client *mastodon.Client
}
func (rm *ReportManager) FileReport(ctx context.Context, accountID mastodon.ID, reason string, statusIDs []mastodon.ID) (*mastodon.Report, error) {
report, err := rm.client.Report(ctx, accountID, statusIDs, reason)
if err != nil {
return nil, fmt.Errorf("failed to file report: %w", err)
}
log.Printf("Report filed: %s against account %s", report.ID, accountID)
return report, nil
}
func (rm *ReportManager) GetPendingReports(ctx context.Context) ([]*mastodon.Report, error) {
reports, err := rm.client.GetReports(ctx)
if err != nil {
return nil, err
}
var pending []*mastodon.Report
for _, report := range reports {
if !report.ActionTaken {
pending = append(pending, report)
}
}
return pending, nil
}
func (rm *ReportManager) GetResolvedReports(ctx context.Context) ([]*mastodon.Report, error) {
reports, err := rm.client.GetReports(ctx)
if err != nil {
return nil, err
}
var resolved []*mastodon.Report
for _, report := range reports {
if report.ActionTaken {
resolved = append(resolved, report)
}
}
return resolved, nil
}
func (rm *ReportManager) Summary(ctx context.Context) error {
reports, err := rm.client.GetReports(ctx)
if err != nil {
return err
}
fmt.Println("Report Summary")
fmt.Println(strings.Repeat("=", 50))
fmt.Printf("Total reports filed: %d\n", len(reports))
pending := 0
resolved := 0
for _, report := range reports {
if report.ActionTaken {
resolved++
} else {
pending++
}
}
fmt.Printf("Pending review: %d\n", pending)
fmt.Printf("Action taken: %d\n", resolved)
if len(reports) > 0 {
resolvedPercent := float64(resolved) / float64(len(reports)) * 100
fmt.Printf("Resolution rate: %.1f%%\n", resolvedPercent)
}
return nil
}func interactiveReport(client *mastodon.Client, reader *bufio.Reader) error {
ctx := context.Background()
fmt.Println("File a Report")
fmt.Println(strings.Repeat("=", 50))
// Get account ID
fmt.Print("Enter account ID or username: ")
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
var accountID mastodon.ID
if strings.HasPrefix(input, "@") || strings.Contains(input, "@") {
// Lookup by username
account, err := client.AccountLookup(ctx, input)
if err != nil {
return fmt.Errorf("account not found: %w", err)
}
accountID = account.ID
fmt.Printf("Found: @%s (%s)\n", account.Acct, account.DisplayName)
} else {
accountID = mastodon.ID(input)
}
// Get status IDs
fmt.Print("Enter status IDs (comma-separated, or press Enter to skip): ")
statusInput, _ := reader.ReadString('\n')
statusInput = strings.TrimSpace(statusInput)
var statusIDs []mastodon.ID
if statusInput != "" {
ids := strings.Split(statusInput, ",")
for _, id := range ids {
statusIDs = append(statusIDs, mastodon.ID(strings.TrimSpace(id)))
}
}
// Get reason
fmt.Print("Enter reason for report: ")
reason, _ := reader.ReadString('\n')
reason = strings.TrimSpace(reason)
if reason == "" {
return fmt.Errorf("reason is required")
}
// Confirm
fmt.Printf("\nReview Report:\n")
fmt.Printf(" Account: %s\n", accountID)
fmt.Printf(" Statuses: %d\n", len(statusIDs))
fmt.Printf(" Reason: %s\n", reason)
fmt.Print("\nFile this report? (yes/no): ")
confirm, _ := reader.ReadString('\n')
confirm = strings.TrimSpace(strings.ToLower(confirm))
if confirm != "yes" && confirm != "y" {
fmt.Println("Report cancelled")
return nil
}
// File report
report, err := client.Report(ctx, accountID, statusIDs, reason)
if err != nil {
return err
}
fmt.Printf("\n✓ Report filed successfully (ID: %s)\n", report.ID)
return nil
}Common reasons for filing reports:
comment := "This account is posting unsolicited advertisements and spam links"comment := "This account is sending harassing messages and personal attacks"comment := "This account is posting hate speech and discriminatory content"comment := "This account is impersonating another user or public figure"comment := "This account is sharing private information without consent"comment := "This account is posting illegal or prohibited content"comment := "This account is deliberately spreading false information"Include specific status IDs that violate rules:
// Good: Specific problematic statuses
statusIDs := []mastodon.ID{"123", "456", "789"}
// Less helpful: No evidence
statusIDs := []mastodon.ID{}Explain the issue clearly for moderators:
// Good: Detailed explanation
comment := `This account is repeatedly posting spam links to external sites.
The pattern started 3 days ago with multiple posts per hour containing
identical links. The content appears to be commercial advertising that
violates the instance's spam policy.`
// Less helpful: Vague
comment := "spam"Only report actual violations:
// Report actual violations
if isSpam || isHarassment || isHateSpeech {
client.Report(ctx, accountID, statusIDs, reason)
}
// Don't report disagreements
// Use mute/block insteadFor personal issues, use block or mute instead of reporting:
// For annoying but not rule-breaking content
client.AccountMute(ctx, accountID)
// For harassment or rule violations
client.Report(ctx, accountID, statusIDs, "Harassment: ...")Don't file multiple reports for the same issue:
// File once with all evidence
client.Report(ctx, accountID, allStatusIDs, detailedReason)
// Don't file multiple reports for same accountCheck status but don't spam moderators:
// Check after reasonable time
time.Sleep(24 * time.Hour)
checkReportStatus(client, reportID)
// Don't repeatedly checkAfter filing a report, moderators may:
The ActionTaken field indicates whether moderators have processed the report, not necessarily that punitive action was taken.
See also:
Install with Tessl CLI
npx tessl i tessl/golang-go-mastodon