// Package clients provides third-party printer integrations. package clients import ( "context" "encoding/json" "fmt" "log/slog" "net/http" "time" ) // MoonrakerPrinterInfo represents the response from /api/printer/info. type MoonrakerPrinterInfo struct { State string `json:"state"` Hostname string `json:"hostname,omitempty"` SoftwareVersion string `json:"software_version,omitempty"` } // MoonrakerPrintStats represents the response from /api/printer/print_stats. type MoonrakerPrintStats struct { State string `json:"state"` Filename string `json:"filename,omitempty"` FilamentUsedMm float64 `json:"filament_used,omitempty"` TotalDuration float64 `json:"total_duration,omitempty"` PrintDuration float64 `json:"print_duration,omitempty"` Message string `json:"message,omitempty"` } // MoonrakerPrintJob represents a single job from the history API. type MoonrakerPrintJob struct { JobID string `json:"job_id,omitempty"` Filename string `json:"filename"` Status string `json:"status"` StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time,omitempty"` FilamentUsedMm float64 `json:"filament_used,omitempty"` TotalDuration float64 `json:"total_duration,omitempty"` } // MoonrakerHistoryResponse represents the response from /api/server/history/job. type MoonrakerHistoryResponse struct { Items []MoonrakerPrintJob `json:"jobs"` } // MoonrakerClient is an HTTP client for the Moonraker API. type MoonrakerClient struct { HTTPClient *http.Client } // NewMoonrakerClient creates a MoonrakerClient with the given request timeout. func NewMoonrakerClient(timeout time.Duration) *MoonrakerClient { return &MoonrakerClient{ HTTPClient: &http.Client{Timeout: timeout}, } } // baseURL builds the Moonraker base URL from host and port. func (c *MoonrakerClient) baseURL(host string, port int) string { if port == 0 { port = 80 } return fmt.Sprintf("http://%s:%d", host, port) } // GetPrinterInfo fetches printer info from Moonraker. func (c *MoonrakerClient) GetPrinterInfo(ctx context.Context, host string, port int, apiKey string) (*MoonrakerPrinterInfo, error) { url := c.baseURL(host, port) + "/api/printer/info" req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } if apiKey != "" { req.Header.Set("X-Api-Key", apiKey) } resp, err := c.HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("moonraker getPrinterInfo request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("moonraker getPrinterInfo returned status %d", resp.StatusCode) } var body struct { Result MoonrakerPrinterInfo `json:"result"` } if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { return nil, fmt.Errorf("moonraker getPrinterInfo decode failed: %w", err) } slog.Debug("moonraker printer info", "host", host, "state", body.Result.State) return &body.Result, nil } // GetPrintStats fetches current print statistics from Moonraker. func (c *MoonrakerClient) GetPrintStats(ctx context.Context, host string, port int, apiKey string) (*MoonrakerPrintStats, error) { url := c.baseURL(host, port) + "/api/printer/print_stats" req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } if apiKey != "" { req.Header.Set("X-Api-Key", apiKey) } resp, err := c.HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("moonraker getPrintStats request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("moonraker getPrintStats returned status %d", resp.StatusCode) } var body struct { Result MoonrakerPrintStats `json:"result"` } if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { return nil, fmt.Errorf("moonraker getPrintStats decode failed: %w", err) } slog.Debug("moonraker print stats", "host", host, "state", body.Result.State, "filename", body.Result.Filename) return &body.Result, nil } // GetPrintHistory fetches completed print job history from Moonraker. func (c *MoonrakerClient) GetPrintHistory(ctx context.Context, host string, port int, apiKey string, limit int) (*MoonrakerHistoryResponse, error) { if limit <= 0 { limit = 25 } url := fmt.Sprintf("%s/api/server/history/job?limit=%d", c.baseURL(host, port), limit) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } if apiKey != "" { req.Header.Set("X-Api-Key", apiKey) } resp, err := c.HTTPClient.Do(req) if err != nil { return nil, fmt.Errorf("moonraker getPrintHistory request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("moonraker getPrintHistory returned status %d", resp.StatusCode) } var body MoonrakerHistoryResponse if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { return nil, fmt.Errorf("moonraker getPrintHistory decode failed: %w", err) } slog.Debug("moonraker print history", "host", host, "count", len(body.Items)) return &body, nil }