client.go
1 package client 2 3 import ( 4 "fmt" 5 "io" 6 "net/http" 7 "strings" 8 "time" 9 ) 10 11 type Client struct { 12 baseURL string 13 httpClient *http.Client 14 userAgent string 15 accessToken string 16 } 17 18 type Option func(*Client) 19 20 func WithHTTPClient(hc *http.Client) Option { 21 return func(c *Client) { c.httpClient = hc } 22 } 23 24 func WithTimeout(d time.Duration) Option { 25 return func(c *Client) { c.httpClient.Timeout = d } 26 } 27 28 func WithUserAgent(ua string) Option { 29 return func(c *Client) { c.userAgent = ua } 30 } 31 32 func WithAccessToken(token string) Option { 33 return func(c *Client) { c.accessToken = token } 34 } 35 36 func New(baseURL string, opts ...Option) *Client { 37 c := &Client{ 38 baseURL: strings.TrimRight(baseURL, "/"), 39 httpClient: &http.Client{Timeout: 10 * time.Second}, 40 } 41 for _, o := range opts { 42 o(c) 43 } 44 return c 45 } 46 47 func checkStatus(resp *http.Response) error { 48 if resp.StatusCode >= 200 && resp.StatusCode < 300 { 49 return nil 50 } 51 body, _ := io.ReadAll(resp.Body) 52 msg := strings.TrimSpace(string(body)) 53 if msg == "" { 54 msg = resp.Status 55 } 56 return fmt.Errorf("invalid status code (%d): %s", resp.StatusCode, msg) 57 } 58 59 // builds an http.Request with Origin: hister:// set for CSRF bypass. 60 func (c *Client) newRequest(method, path string, body io.Reader) (*http.Request, error) { 61 req, err := http.NewRequest(method, c.baseURL+path, body) 62 if err != nil { 63 return nil, err 64 } 65 req.Header.Set("Origin", "hister://") 66 if c.userAgent != "" { 67 req.Header.Set("User-Agent", c.userAgent) 68 } 69 if c.accessToken != "" { 70 req.Header.Set("X-Access-Token", c.accessToken) 71 } 72 return req, nil 73 }