···113113 c.Printf("\r\033[K%s %s\n", styles.Warn.Render("[?]"), message)
114114}
115115116116-func Doctor() *cobra.Command {
117117- cmd := &cobra.Command{
118118- Use: "doc",
119119- Short: "diagnose potential hackatime issues",
120120- RunE: func(c *cobra.Command, _ []string) error {
121121- // Initialize a new context with task state
122122- c.SetContext(context.WithValue(context.Background(), "taskState", &taskState{}))
116116+var user_dir, err = os.UserHomeDir()
123117124124- // check our os
125125- printTask(c, "Checking operating system")
118118+var testHeartbeat = wakatime.Heartbeat{
119119+ Branch: "main",
120120+ Category: "coding",
121121+ CursorPos: 1,
122122+ Entity: filepath.Join(user_dir, "akami.txt"),
123123+ Type: "file",
124124+ IsWrite: true,
125125+ Language: "Go",
126126+ LineNo: 1,
127127+ LineCount: 4,
128128+ Project: "example",
129129+ ProjectRootCount: 3,
130130+ Time: float64(time.Now().Unix()),
131131+}
126132127127- os_name := runtime.GOOS
133133+func Doctor(c *cobra.Command, _ []string) error {
134134+ // Initialize a new context with task state
135135+ c.SetContext(context.WithValue(context.Background(), "taskState", &taskState{}))
128136129129- user_dir, err := os.UserHomeDir()
130130- if err != nil {
131131- errorTask(c, "Checking operating system")
132132- 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")
133133- }
134134- hackatime_path := filepath.Join(user_dir, ".wakatime.cfg")
137137+ // check our os
138138+ printTask(c, "Checking operating system")
135139136136- if os_name != "linux" && os_name != "darwin" && os_name != "windows" {
137137- errorTask(c, "Checking operating system")
138138- 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?")
139139- }
140140- completeTask(c, "Checking operating system")
140140+ os_name := runtime.GOOS
141141142142- 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))
142142+ user_dir, err := os.UserHomeDir()
143143+ if err != nil {
144144+ errorTask(c, "Checking operating system")
145145+ 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")
146146+ }
147147+ hackatime_path := filepath.Join(user_dir, ".wakatime.cfg")
143148144144- printTask(c, "Checking wakatime config file")
149149+ if os_name != "linux" && os_name != "darwin" && os_name != "windows" {
150150+ errorTask(c, "Checking operating system")
151151+ 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?")
152152+ }
153153+ completeTask(c, "Checking operating system")
145154146146- rawCfg, err := os.ReadFile(hackatime_path)
147147- if errors.Is(err, os.ErrNotExist) {
148148- errorTask(c, "Checking wakatime config file")
149149- 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")
150150- }
155155+ 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))
151156152152- cfg, err := ini.Load(rawCfg)
153153- if err != nil {
154154- errorTask(c, "Checking wakatime config file")
155155- return errors.New(err.Error())
156156- }
157157+ printTask(c, "Checking wakatime config file")
157158158158- settings, err := cfg.GetSection("settings")
159159- if err != nil {
160160- errorTask(c, "Checking wakatime config file")
161161- 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())
162162- }
163163- completeTask(c, "Checking wakatime config file")
159159+ rawCfg, err := os.ReadFile(hackatime_path)
160160+ if errors.Is(err, os.ErrNotExist) {
161161+ errorTask(c, "Checking wakatime config file")
162162+ 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")
163163+ }
164164+165165+ cfg, err := ini.Load(rawCfg)
166166+ if err != nil {
167167+ errorTask(c, "Checking wakatime config file")
168168+ return errors.New(err.Error())
169169+ }
170170+171171+ settings, err := cfg.GetSection("settings")
172172+ if err != nil {
173173+ errorTask(c, "Checking wakatime config file")
174174+ 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())
175175+ }
176176+ completeTask(c, "Checking wakatime config file")
164177165165- printTask(c, "Verifying API credentials")
178178+ printTask(c, "Verifying API credentials")
166179167167- api_key := settings.Key("api_key").String()
168168- api_url := settings.Key("api_url").String()
169169- if api_key == "" {
170170- errorTask(c, "Verifying API credentials")
171171- 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?")
172172- }
173173- if api_url == "" {
174174- errorTask(c, "Verifying API credentials")
175175- 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?")
176176- }
177177- completeTask(c, "Verifying API credentials")
180180+ api_key := settings.Key("api_key").String()
181181+ api_url := settings.Key("api_url").String()
182182+ if api_key == "" {
183183+ errorTask(c, "Verifying API credentials")
184184+ 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?")
185185+ }
186186+ if api_url == "" {
187187+ errorTask(c, "Verifying API credentials")
188188+ 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?")
189189+ }
190190+ completeTask(c, "Verifying API credentials")
178191179179- printTask(c, "Validating API URL")
192192+ printTask(c, "Validating API URL")
180193181181- correctApiUrl := "https://hackatime.hackclub.com/api/hackatime/v1"
182182- if api_url != correctApiUrl {
183183- if api_url == "https://api.wakatime.com/api/v1" {
184184- client := wakatime.NewClient(api_key)
185185- _, err := client.GetStatusBar()
194194+ correctApiUrl := "https://hackatime.hackclub.com/api/hackatime/v1"
195195+ if api_url != correctApiUrl {
196196+ if api_url == "https://api.wakatime.com/api/v1" {
197197+ client := wakatime.NewClient(api_key)
198198+ _, err := client.GetStatusBar()
186199187187- if !errors.Is(err, wakatime.ErrUnauthorized) {
188188- errorTask(c, "Validating API URL")
189189- 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")
190190- } else {
191191- errorTask(c, "Validating API URL")
192192- 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")
193193- }
194194- }
195195- warnTask(c, "Validating API URL")
196196- 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))
200200+ if !errors.Is(err, wakatime.ErrUnauthorized) {
201201+ errorTask(c, "Validating API URL")
202202+ 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")
197203 } else {
198198- completeTask(c, "Validating API URL")
204204+ errorTask(c, "Validating API URL")
205205+ 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")
199206 }
207207+ }
208208+ warnTask(c, "Validating API URL")
209209+ 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))
210210+ } else {
211211+ completeTask(c, "Validating API URL")
212212+ }
200213201201- client := wakatime.NewClientWithOptions(api_key, api_url)
202202- printTask(c, "Checking your coding stats for today")
214214+ client := wakatime.NewClientWithOptions(api_key, api_url)
215215+ printTask(c, "Checking your coding stats for today")
203216204204- duration, err := client.GetStatusBar()
205205- if err != nil {
206206- errorTask(c, "Checking your coding stats for today")
207207- if errors.Is(err, wakatime.ErrUnauthorized) {
208208- 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") + "?")
209209- }
217217+ duration, err := client.GetStatusBar()
218218+ if err != nil {
219219+ errorTask(c, "Checking your coding stats for today")
220220+ if errors.Is(err, wakatime.ErrUnauthorized) {
221221+ 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") + "?")
222222+ }
210223211211- 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()))
212212- }
213213- completeTask(c, "Checking your coding stats for today")
224224+ 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()))
225225+ }
226226+ completeTask(c, "Checking your coding stats for today")
214227215215- // Add small delay to make the spinner animation visible
228228+ // Convert seconds to a formatted time string (hours, minutes, seconds)
229229+ totalSeconds := duration.Data.GrandTotal.TotalSeconds
230230+ hours := totalSeconds / 3600
231231+ minutes := (totalSeconds % 3600) / 60
232232+ seconds := totalSeconds % 60
216233217217- // Convert seconds to a formatted time string (hours, minutes, seconds)
218218- totalSeconds := duration.Data.GrandTotal.TotalSeconds
219219- hours := totalSeconds / 3600
220220- minutes := (totalSeconds % 3600) / 60
221221- seconds := totalSeconds % 60
234234+ formattedTime := ""
235235+ if hours > 0 {
236236+ formattedTime += fmt.Sprintf("%d hours, ", hours)
237237+ }
238238+ if minutes > 0 || hours > 0 {
239239+ formattedTime += fmt.Sprintf("%d minutes, ", minutes)
240240+ }
241241+ formattedTime += fmt.Sprintf("%d seconds", seconds)
222242223223- formattedTime := ""
224224- if hours > 0 {
225225- formattedTime += fmt.Sprintf("%d hours, ", hours)
226226- }
227227- if minutes > 0 || hours > 0 {
228228- formattedTime += fmt.Sprintf("%d minutes, ", minutes)
229229- }
230230- formattedTime += fmt.Sprintf("%d seconds", seconds)
243243+ c.Printf("Sweet!!! Looks like your hackatime is configured properly! Looks like you have coded today for %s\n\n", styles.Fancy.Render(formattedTime))
231244232232- c.Printf("Sweet!!! Looks like your hackatime is configured properly! Looks like you have coded today for %s\n\n", styles.Fancy.Render(formattedTime))
233233-234234- printTask(c, "Sending test heartbeat")
245245+ printTask(c, "Sending test heartbeat")
235246236236- err = client.SendHeartbeat(wakatime.Heartbeat{
237237- Branch: "main",
238238- Category: "coding",
239239- CursorPos: 1,
240240- Entity: filepath.Join(user_dir, "akami.txt"),
241241- Type: "file",
242242- IsWrite: true,
243243- Language: "Go",
244244- LineNo: 1,
245245- LineCount: 4,
246246- Project: "example",
247247- ProjectRootCount: 3,
248248- Time: float64(time.Now().Unix()),
249249- })
250250- if err != nil {
251251- errorTask(c, "Sending test heartbeat")
252252- return errors.New("oh dear; looks like something went wrong when sending that heartbeat. " + styles.Bad.Render("Full error: \""+strings.TrimSpace(err.Error())+"\""))
253253- }
254254- completeTask(c, "Sending test heartbeat")
247247+ err = client.SendHeartbeat(testHeartbeat)
248248+ if err != nil {
249249+ errorTask(c, "Sending test heartbeat")
250250+ return errors.New("oh dear; looks like something went wrong when sending that heartbeat. " + styles.Bad.Render("Full error: \""+strings.TrimSpace(err.Error())+"\""))
251251+ }
252252+ completeTask(c, "Sending test heartbeat")
255253256256- c.Println("🥳 it worked! you are good to go! Happy coding 👋")
254254+ c.Println("🥳 it worked! you are good to go! Happy coding 👋")
257255258258- return nil
259259- },
260260- }
261261- return cmd
256256+ return nil
262257}
+5-1
main.go
···2626 }
27272828 // diagnose command
2929- cmd.AddCommand(handler.Doctor())
2929+ cmd.AddCommand(&cobra.Command{
3030+ Use: "doc",
3131+ Short: "diagnose potential hackatime issues",
3232+ RunE: handler.Doctor,
3333+ })
30343135 // this is where we get the fancy fang magic ✨
3236 if err := fang.Execute(
+10-1
wakatime/main.go
···1010 "fmt"
1111 "net/http"
1212 "runtime"
1313+ "strings"
1314 "time"
1415)
1516···3233 ErrDecodingResponse = fmt.Errorf("failed to decode API response")
3334 // ErrUnauthorized occurs when the API rejects the provided credentials
3435 ErrUnauthorized = fmt.Errorf("unauthorized: invalid API key or insufficient permissions")
3636+ // ErrNotFound occurs when the config file isn't found
3737+ ErrNotFound = fmt.Errorf("config file not found")
3838+ // ErrBrokenConfig occurs when there is no settings section in the config
3939+ ErrBrokenConfig = fmt.Errorf("invalid config file: missing settings section")
4040+ // ErrNoApiKey occurs when the api key is missing from the config
4141+ ErrNoApiKey = fmt.Errorf("no API key found in config file")
4242+ // ErrNoApiURL occurs when the api url is missing from the config
4343+ ErrNoApiURL = fmt.Errorf("no API URL found in config file")
3544)
36453746// Client represents a WakaTime API client with authentication and connection settings.
···5968func NewClientWithOptions(apiKey string, apiURL string) *Client {
6069 return &Client{
6170 APIKey: apiKey,
6262- APIURL: apiURL,
7171+ APIURL: strings.TrimSuffix(apiURL, "/"),
6372 HTTPClient: &http.Client{Timeout: 10 * time.Second},
6473 }
6574}