🌷 the cutsie hackatime helper
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: improve wakatime handling

+49 -8
+49 -8
wakatime/main.go
··· 5 5 6 6 import ( 7 7 "bytes" 8 + "encoding/base64" 8 9 "encoding/json" 9 10 "fmt" 10 11 "net/http" 12 + "runtime" 11 13 "time" 12 14 ) 13 15 ··· 79 81 IsWrite bool `json:"is_write,omitempty"` 80 82 // EditorName is the optional name of the editor or IDE being used 81 83 EditorName string `json:"editor_name,omitempty"` 84 + // Branch is the optional git branch name 85 + Branch string `json:"branch,omitempty"` 86 + // Category is the optional activity category 87 + Category string `json:"category,omitempty"` 88 + // LineCount is the optional number of lines in the file 89 + LineCount int `json:"lines,omitempty"` 90 + // UserAgent is the optional user agent string 91 + UserAgent string `json:"user_agent,omitempty"` 92 + // EntityType is the optional entity type (usually redundant with Type) 93 + EntityType string `json:"entity_type,omitempty"` 94 + // Dependencies is an optional list of project dependencies 95 + Dependencies []string `json:"dependencies,omitempty"` 96 + // ProjectRootCount is the optional number of directories in the project root path 97 + ProjectRootCount int `json:"project_root_count,omitempty"` 82 98 } 83 99 84 100 // StatusBarResponse represents the response from the WakaTime Status Bar API endpoint. ··· 101 117 // SendHeartbeat sends a coding activity heartbeat to the WakaTime API. 102 118 // It returns an error if the request fails or returns a non-success status code. 103 119 func (c *Client) SendHeartbeat(heartbeat Heartbeat) error { 120 + // Set the user agent in the heartbeat data 121 + if heartbeat.UserAgent == "" { 122 + heartbeat.UserAgent = "wakatime/unset (" + runtime.GOOS + "-" + runtime.GOARCH + ") akami-wakatime/1.0.0" 123 + } 124 + 104 125 data, err := json.Marshal(heartbeat) 105 126 if err != nil { 106 127 return fmt.Errorf("%w: %v", ErrMarshalingHeartbeat, err) ··· 112 133 } 113 134 114 135 req.Header.Set("Content-Type", "application/json") 115 - req.Header.Set("Authorization", "Basic "+c.APIKey) 136 + req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(c.APIKey))) 137 + // Set the user agent in the request header as well 138 + req.Header.Set("User-Agent", "wakatime/unset ("+runtime.GOOS+"-"+runtime.GOARCH+") akami-wakatime/1.0.0") 116 139 117 140 resp, err := c.HTTPClient.Do(req) 118 141 if err != nil { ··· 120 143 } 121 144 defer resp.Body.Close() 122 145 146 + // Read and log the response 147 + var respBody bytes.Buffer 148 + _, err = respBody.ReadFrom(resp.Body) 149 + if err != nil { 150 + return fmt.Errorf("failed to read response body: %v", err) 151 + } 152 + 153 + respContent := respBody.String() 154 + 123 155 if resp.StatusCode == http.StatusUnauthorized { 124 - return ErrUnauthorized 156 + return fmt.Errorf("%w: %s", ErrUnauthorized, respContent) 125 157 } else if resp.StatusCode < 200 || resp.StatusCode >= 300 { 126 - return fmt.Errorf("%w: status code %d", ErrInvalidStatusCode, resp.StatusCode) 158 + return fmt.Errorf("%w: status code %d, response: %s", ErrInvalidStatusCode, resp.StatusCode, respContent) 127 159 } 128 160 129 161 return nil ··· 137 169 return StatusBarResponse{}, fmt.Errorf("%w: %v", ErrCreatingRequest, err) 138 170 } 139 171 140 - req.Header.Set("Authorization", "Basic "+c.APIKey) 172 + req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(c.APIKey))) 141 173 142 174 resp, err := c.HTTPClient.Do(req) 143 175 if err != nil { ··· 145 177 } 146 178 defer resp.Body.Close() 147 179 180 + // Read the response body for potential error messages 181 + var respBody bytes.Buffer 182 + _, err = respBody.ReadFrom(resp.Body) 183 + if err != nil { 184 + return StatusBarResponse{}, fmt.Errorf("failed to read response body: %v", err) 185 + } 186 + 187 + respContent := respBody.String() 188 + 148 189 if resp.StatusCode == http.StatusUnauthorized { 149 - return StatusBarResponse{}, ErrUnauthorized 190 + return StatusBarResponse{}, fmt.Errorf("%w: %s", ErrUnauthorized, respContent) 150 191 } else if resp.StatusCode < 200 || resp.StatusCode >= 300 { 151 - return StatusBarResponse{}, fmt.Errorf("%w: status code %d", ErrInvalidStatusCode, resp.StatusCode) 192 + return StatusBarResponse{}, fmt.Errorf("%w: status code %d, response: %s", ErrInvalidStatusCode, resp.StatusCode, respContent) 152 193 } 153 194 154 195 var durationResp StatusBarResponse 155 - if err := json.NewDecoder(resp.Body).Decode(&durationResp); err != nil { 156 - return StatusBarResponse{}, fmt.Errorf("%w: %v", ErrDecodingResponse, err) 196 + if err := json.Unmarshal(respBody.Bytes(), &durationResp); err != nil { 197 + return StatusBarResponse{}, fmt.Errorf("%w: %v, response: %s", ErrDecodingResponse, err, respContent) 157 198 } 158 199 159 200 return durationResp, nil