package memos import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "strconv" ) type Client struct { baseURL string token string httpClient *http.Client } func NewClient(baseURL, token string) *Client { return &Client{ baseURL: baseURL, token: token, httpClient: &http.Client{}, } } // ListMemos fetches memos from the API with the given CEL filter. func (c *Client) ListMemos(ctx context.Context, filter string, pageSize int, pageToken string) (*ListMemosResponse, error) { u, err := url.Parse(c.baseURL + "/api/v1/memos") if err != nil { return nil, fmt.Errorf("parse url: %w", err) } q := u.Query() if filter != "" { q.Set("filter", filter) } if pageSize > 0 { q.Set("pageSize", strconv.Itoa(pageSize)) } if pageToken != "" { q.Set("pageToken", pageToken) } u.RawQuery = q.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("Authorization", "Bearer "+c.token) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("do request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("memos API returned %d: %s", resp.StatusCode, body) } var result ListMemosResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return &result, nil } // DownloadAttachment downloads the attachment data as bytes. func (c *Client) DownloadAttachment(ctx context.Context, att Attachment) ([]byte, error) { var reqURL string var needsAuth bool if att.ExternalLink != "" { reqURL = att.ExternalLink } else { reqURL = fmt.Sprintf("%s/file/%s/%s", c.baseURL, att.Name, att.Filename) needsAuth = true } req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, http.NoBody) if err != nil { return nil, fmt.Errorf("create request: %w", err) } if needsAuth { req.Header.Set("Authorization", "Bearer "+c.token) } resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("do request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("download attachment %s: status %d", att.Name, resp.StatusCode) } data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read body: %w", err) } return data, nil } // GetRandomMemo fetches a single memo without any filter (for full fallback). func (c *Client) GetRandomMemo(ctx context.Context) (*Memo, error) { resp, err := c.ListMemos(ctx, "", 1, "") if err != nil { return nil, err } if len(resp.Memos) == 0 { return nil, nil } return resp.Memos[0], nil }