🌷 the cutsie hackatime helper

feat: move to a different command structure

dunkirk.sh c859cbcc 6cd4382d

verified
+130 -122
+115 -120
handler/main.go
··· 113 113 c.Printf("\r\033[K%s %s\n", styles.Warn.Render("[?]"), message) 114 114 } 115 115 116 - func Doctor() *cobra.Command { 117 - cmd := &cobra.Command{ 118 - Use: "doc", 119 - Short: "diagnose potential hackatime issues", 120 - RunE: func(c *cobra.Command, _ []string) error { 121 - // Initialize a new context with task state 122 - c.SetContext(context.WithValue(context.Background(), "taskState", &taskState{})) 116 + var user_dir, err = os.UserHomeDir() 123 117 124 - // check our os 125 - printTask(c, "Checking operating system") 118 + var testHeartbeat = wakatime.Heartbeat{ 119 + Branch: "main", 120 + Category: "coding", 121 + CursorPos: 1, 122 + Entity: filepath.Join(user_dir, "akami.txt"), 123 + Type: "file", 124 + IsWrite: true, 125 + Language: "Go", 126 + LineNo: 1, 127 + LineCount: 4, 128 + Project: "example", 129 + ProjectRootCount: 3, 130 + Time: float64(time.Now().Unix()), 131 + } 126 132 127 - os_name := runtime.GOOS 133 + func Doctor(c *cobra.Command, _ []string) error { 134 + // Initialize a new context with task state 135 + c.SetContext(context.WithValue(context.Background(), "taskState", &taskState{})) 128 136 129 - user_dir, err := os.UserHomeDir() 130 - if err != nil { 131 - errorTask(c, "Checking operating system") 132 - return errors.New("somehow your user doesn't exist? fairly sure this should never happen; plz report this to @krn on slack or via email at me@dunkirk.sh") 133 - } 134 - hackatime_path := filepath.Join(user_dir, ".wakatime.cfg") 137 + // check our os 138 + printTask(c, "Checking operating system") 135 139 136 - if os_name != "linux" && os_name != "darwin" && os_name != "windows" { 137 - errorTask(c, "Checking operating system") 138 - return errors.New("hmm you don't seem to be running a recognized os? you are listed as running " + styles.Fancy.Render(os_name) + "; can you plz report this to @krn on slack or via email at me@dunkirk.sh?") 139 - } 140 - completeTask(c, "Checking operating system") 140 + os_name := runtime.GOOS 141 141 142 - c.Printf("Looks like you are running %s so lets take a look at %s for your config\n\n", styles.Fancy.Render(os_name), styles.Muted.Render(hackatime_path)) 142 + user_dir, err := os.UserHomeDir() 143 + if err != nil { 144 + errorTask(c, "Checking operating system") 145 + return errors.New("somehow your user doesn't exist? fairly sure this should never happen; plz report this to @krn on slack or via email at me@dunkirk.sh") 146 + } 147 + hackatime_path := filepath.Join(user_dir, ".wakatime.cfg") 143 148 144 - printTask(c, "Checking wakatime config file") 149 + if os_name != "linux" && os_name != "darwin" && os_name != "windows" { 150 + errorTask(c, "Checking operating system") 151 + return errors.New("hmm you don't seem to be running a recognized os? you are listed as running " + styles.Fancy.Render(os_name) + "; can you plz report this to @krn on slack or via email at me@dunkirk.sh?") 152 + } 153 + completeTask(c, "Checking operating system") 145 154 146 - rawCfg, err := os.ReadFile(hackatime_path) 147 - if errors.Is(err, os.ErrNotExist) { 148 - errorTask(c, "Checking wakatime config file") 149 - return errors.New("you don't have a wakatime config file! go check " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + " for the instructions and then try this again") 150 - } 155 + c.Printf("Looks like you are running %s so lets take a look at %s for your config\n\n", styles.Fancy.Render(os_name), styles.Muted.Render(hackatime_path)) 151 156 152 - cfg, err := ini.Load(rawCfg) 153 - if err != nil { 154 - errorTask(c, "Checking wakatime config file") 155 - return errors.New(err.Error()) 156 - } 157 + printTask(c, "Checking wakatime config file") 157 158 158 - settings, err := cfg.GetSection("settings") 159 - if err != nil { 160 - errorTask(c, "Checking wakatime config file") 161 - return errors.New("wow! your config file seems to be messed up and doesn't have a settings heading; can you follow the instructions at " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + " to regenerate it?\n\nThe raw error we got was: " + err.Error()) 162 - } 163 - completeTask(c, "Checking wakatime config file") 159 + rawCfg, err := os.ReadFile(hackatime_path) 160 + if errors.Is(err, os.ErrNotExist) { 161 + errorTask(c, "Checking wakatime config file") 162 + return errors.New("you don't have a wakatime config file! go check " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + " for the instructions and then try this again") 163 + } 164 + 165 + cfg, err := ini.Load(rawCfg) 166 + if err != nil { 167 + errorTask(c, "Checking wakatime config file") 168 + return errors.New(err.Error()) 169 + } 170 + 171 + settings, err := cfg.GetSection("settings") 172 + if err != nil { 173 + errorTask(c, "Checking wakatime config file") 174 + return errors.New("wow! your config file seems to be messed up and doesn't have a settings heading; can you follow the instructions at " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + " to regenerate it?\n\nThe raw error we got was: " + err.Error()) 175 + } 176 + completeTask(c, "Checking wakatime config file") 164 177 165 - printTask(c, "Verifying API credentials") 178 + printTask(c, "Verifying API credentials") 166 179 167 - api_key := settings.Key("api_key").String() 168 - api_url := settings.Key("api_url").String() 169 - if api_key == "" { 170 - errorTask(c, "Verifying API credentials") 171 - return errors.New("hmm 🤔 looks like you don't have an api_key in your config file? are you sure you have followed the setup instructions at " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + " correctly?") 172 - } 173 - if api_url == "" { 174 - errorTask(c, "Verifying API credentials") 175 - return errors.New("hmm 🤔 looks like you don't have an api_url in your config file? are you sure you have followed the setup instructions at " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + " correctly?") 176 - } 177 - completeTask(c, "Verifying API credentials") 180 + api_key := settings.Key("api_key").String() 181 + api_url := settings.Key("api_url").String() 182 + if api_key == "" { 183 + errorTask(c, "Verifying API credentials") 184 + return errors.New("hmm 🤔 looks like you don't have an api_key in your config file? are you sure you have followed the setup instructions at " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + " correctly?") 185 + } 186 + if api_url == "" { 187 + errorTask(c, "Verifying API credentials") 188 + return errors.New("hmm 🤔 looks like you don't have an api_url in your config file? are you sure you have followed the setup instructions at " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + " correctly?") 189 + } 190 + completeTask(c, "Verifying API credentials") 178 191 179 - printTask(c, "Validating API URL") 192 + printTask(c, "Validating API URL") 180 193 181 - correctApiUrl := "https://hackatime.hackclub.com/api/hackatime/v1" 182 - if api_url != correctApiUrl { 183 - if api_url == "https://api.wakatime.com/api/v1" { 184 - client := wakatime.NewClient(api_key) 185 - _, err := client.GetStatusBar() 194 + correctApiUrl := "https://hackatime.hackclub.com/api/hackatime/v1" 195 + if api_url != correctApiUrl { 196 + if api_url == "https://api.wakatime.com/api/v1" { 197 + client := wakatime.NewClient(api_key) 198 + _, err := client.GetStatusBar() 186 199 187 - if !errors.Is(err, wakatime.ErrUnauthorized) { 188 - errorTask(c, "Validating API URL") 189 - return errors.New("turns out you were connected to wakatime.com instead of hackatime; since your key seems to work if you would like to keep syncing data to wakatime.com as well as to hackatime you can either setup a realy serve like " + styles.Muted.Render("https://github.com/JasonLovesDoggo/multitime") + " or you can wait for " + styles.Muted.Render("https://github.com/hackclub/hackatime/issues/85") + " to get merged in hackatime and have it synced there :)\n\nIf you want to import your wakatime.com data into hackatime then you can use hackatime v1 temporarily to connect your wakatime account and import (in settings under integrations at " + styles.Muted.Render("https://waka.hackclub.com") + ") and then click the import from hackatime v1 button at " + styles.Muted.Render("https://hackatime.hackclub.com/my/settings") + ".\n\n If you have more questions feel free to reach out to me (hackatime v1 creator) on slack (at @krn) or via email at me@dunkirk.sh") 190 - } else { 191 - errorTask(c, "Validating API URL") 192 - return errors.New("turns out your config is connected to the wrong api url and is trying to use wakatime.com to sync time but you don't have a working api key from them. Go to " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + " to run the setup script and fix your config file") 193 - } 194 - } 195 - warnTask(c, "Validating API URL") 196 - c.Printf("\nYour api url %s doesn't match the expected url of %s however if you are using a custom forwarder or are sure you know what you are doing then you are probably fine\n\n", styles.Muted.Render(api_url), styles.Muted.Render(correctApiUrl)) 200 + if !errors.Is(err, wakatime.ErrUnauthorized) { 201 + errorTask(c, "Validating API URL") 202 + return errors.New("turns out you were connected to wakatime.com instead of hackatime; since your key seems to work if you would like to keep syncing data to wakatime.com as well as to hackatime you can either setup a realy serve like " + styles.Muted.Render("https://github.com/JasonLovesDoggo/multitime") + " or you can wait for " + styles.Muted.Render("https://github.com/hackclub/hackatime/issues/85") + " to get merged in hackatime and have it synced there :)\n\nIf you want to import your wakatime.com data into hackatime then you can use hackatime v1 temporarily to connect your wakatime account and import (in settings under integrations at " + styles.Muted.Render("https://waka.hackclub.com") + ") and then click the import from hackatime v1 button at " + styles.Muted.Render("https://hackatime.hackclub.com/my/settings") + ".\n\n If you have more questions feel free to reach out to me (hackatime v1 creator) on slack (at @krn) or via email at me@dunkirk.sh") 197 203 } else { 198 - completeTask(c, "Validating API URL") 204 + errorTask(c, "Validating API URL") 205 + return errors.New("turns out your config is connected to the wrong api url and is trying to use wakatime.com to sync time but you don't have a working api key from them. Go to " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + " to run the setup script and fix your config file") 199 206 } 207 + } 208 + warnTask(c, "Validating API URL") 209 + c.Printf("\nYour api url %s doesn't match the expected url of %s however if you are using a custom forwarder or are sure you know what you are doing then you are probably fine\n\n", styles.Muted.Render(api_url), styles.Muted.Render(correctApiUrl)) 210 + } else { 211 + completeTask(c, "Validating API URL") 212 + } 200 213 201 - client := wakatime.NewClientWithOptions(api_key, api_url) 202 - printTask(c, "Checking your coding stats for today") 214 + client := wakatime.NewClientWithOptions(api_key, api_url) 215 + printTask(c, "Checking your coding stats for today") 203 216 204 - duration, err := client.GetStatusBar() 205 - if err != nil { 206 - errorTask(c, "Checking your coding stats for today") 207 - if errors.Is(err, wakatime.ErrUnauthorized) { 208 - return errors.New("Your config file looks mostly correct and you have the correct api url but when we tested your api_key it looks like it is invalid? Can you double check if the key in your config file is the same as at " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + "?") 209 - } 217 + duration, err := client.GetStatusBar() 218 + if err != nil { 219 + errorTask(c, "Checking your coding stats for today") 220 + if errors.Is(err, wakatime.ErrUnauthorized) { 221 + return errors.New("Your config file looks mostly correct and you have the correct api url but when we tested your api_key it looks like it is invalid? Can you double check if the key in your config file is the same as at " + styles.Muted.Render("https://hackatime.hackclub.com/my/wakatime_setup") + "?") 222 + } 210 223 211 - return errors.New("Something weird happened with the hackatime api; if the error doesn't make sense then please contact @krn on slack or via email at me@dunkirk.sh\n\n" + styles.Bad.Render("Full error: "+err.Error())) 212 - } 213 - completeTask(c, "Checking your coding stats for today") 224 + return errors.New("Something weird happened with the hackatime api; if the error doesn't make sense then please contact @krn on slack or via email at me@dunkirk.sh\n\n" + styles.Bad.Render("Full error: "+err.Error())) 225 + } 226 + completeTask(c, "Checking your coding stats for today") 214 227 215 - // Add small delay to make the spinner animation visible 228 + // Convert seconds to a formatted time string (hours, minutes, seconds) 229 + totalSeconds := duration.Data.GrandTotal.TotalSeconds 230 + hours := totalSeconds / 3600 231 + minutes := (totalSeconds % 3600) / 60 232 + seconds := totalSeconds % 60 216 233 217 - // Convert seconds to a formatted time string (hours, minutes, seconds) 218 - totalSeconds := duration.Data.GrandTotal.TotalSeconds 219 - hours := totalSeconds / 3600 220 - minutes := (totalSeconds % 3600) / 60 221 - seconds := totalSeconds % 60 234 + formattedTime := "" 235 + if hours > 0 { 236 + formattedTime += fmt.Sprintf("%d hours, ", hours) 237 + } 238 + if minutes > 0 || hours > 0 { 239 + formattedTime += fmt.Sprintf("%d minutes, ", minutes) 240 + } 241 + formattedTime += fmt.Sprintf("%d seconds", seconds) 222 242 223 - formattedTime := "" 224 - if hours > 0 { 225 - formattedTime += fmt.Sprintf("%d hours, ", hours) 226 - } 227 - if minutes > 0 || hours > 0 { 228 - formattedTime += fmt.Sprintf("%d minutes, ", minutes) 229 - } 230 - formattedTime += fmt.Sprintf("%d seconds", seconds) 243 + c.Printf("Sweet!!! Looks like your hackatime is configured properly! Looks like you have coded today for %s\n\n", styles.Fancy.Render(formattedTime)) 231 244 232 - c.Printf("Sweet!!! Looks like your hackatime is configured properly! Looks like you have coded today for %s\n\n", styles.Fancy.Render(formattedTime)) 233 - 234 - printTask(c, "Sending test heartbeat") 245 + printTask(c, "Sending test heartbeat") 235 246 236 - err = client.SendHeartbeat(wakatime.Heartbeat{ 237 - Branch: "main", 238 - Category: "coding", 239 - CursorPos: 1, 240 - Entity: filepath.Join(user_dir, "akami.txt"), 241 - Type: "file", 242 - IsWrite: true, 243 - Language: "Go", 244 - LineNo: 1, 245 - LineCount: 4, 246 - Project: "example", 247 - ProjectRootCount: 3, 248 - Time: float64(time.Now().Unix()), 249 - }) 250 - if err != nil { 251 - errorTask(c, "Sending test heartbeat") 252 - return errors.New("oh dear; looks like something went wrong when sending that heartbeat. " + styles.Bad.Render("Full error: \""+strings.TrimSpace(err.Error())+"\"")) 253 - } 254 - completeTask(c, "Sending test heartbeat") 247 + err = client.SendHeartbeat(testHeartbeat) 248 + if err != nil { 249 + errorTask(c, "Sending test heartbeat") 250 + return errors.New("oh dear; looks like something went wrong when sending that heartbeat. " + styles.Bad.Render("Full error: \""+strings.TrimSpace(err.Error())+"\"")) 251 + } 252 + completeTask(c, "Sending test heartbeat") 255 253 256 - c.Println("🥳 it worked! you are good to go! Happy coding 👋") 254 + c.Println("🥳 it worked! you are good to go! Happy coding 👋") 257 255 258 - return nil 259 - }, 260 - } 261 - return cmd 256 + return nil 262 257 }
+5 -1
main.go
··· 26 26 } 27 27 28 28 // diagnose command 29 - cmd.AddCommand(handler.Doctor()) 29 + cmd.AddCommand(&cobra.Command{ 30 + Use: "doc", 31 + Short: "diagnose potential hackatime issues", 32 + RunE: handler.Doctor, 33 + }) 30 34 31 35 // this is where we get the fancy fang magic ✨ 32 36 if err := fang.Execute(
+10 -1
wakatime/main.go
··· 10 10 "fmt" 11 11 "net/http" 12 12 "runtime" 13 + "strings" 13 14 "time" 14 15 ) 15 16 ··· 32 33 ErrDecodingResponse = fmt.Errorf("failed to decode API response") 33 34 // ErrUnauthorized occurs when the API rejects the provided credentials 34 35 ErrUnauthorized = fmt.Errorf("unauthorized: invalid API key or insufficient permissions") 36 + // ErrNotFound occurs when the config file isn't found 37 + ErrNotFound = fmt.Errorf("config file not found") 38 + // ErrBrokenConfig occurs when there is no settings section in the config 39 + ErrBrokenConfig = fmt.Errorf("invalid config file: missing settings section") 40 + // ErrNoApiKey occurs when the api key is missing from the config 41 + ErrNoApiKey = fmt.Errorf("no API key found in config file") 42 + // ErrNoApiURL occurs when the api url is missing from the config 43 + ErrNoApiURL = fmt.Errorf("no API URL found in config file") 35 44 ) 36 45 37 46 // Client represents a WakaTime API client with authentication and connection settings. ··· 59 68 func NewClientWithOptions(apiKey string, apiURL string) *Client { 60 69 return &Client{ 61 70 APIKey: apiKey, 62 - APIURL: apiURL, 71 + APIURL: strings.TrimSuffix(apiURL, "/"), 63 72 HTTPClient: &http.Client{Timeout: 10 * time.Second}, 64 73 } 65 74 }