🌷 the cutsie hackatime helper
1package main
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "os"
8 "runtime"
9 "strings"
10
11 "github.com/charmbracelet/fang"
12 "github.com/charmbracelet/lipgloss/v2"
13 "github.com/spf13/cobra"
14 "github.com/taciturnaxolotl/akami/wakatime"
15 "gopkg.in/ini.v1"
16)
17
18func main() {
19 // init our cobra command with a name and description
20 cmd := &cobra.Command{
21 Use: "akami",
22 Short: "🌷 the cutsie hackatime helper",
23 }
24
25 // add our lipgloss styles
26 fancy := lipgloss.NewStyle().Foreground(lipgloss.Magenta).Bold(true).Italic(true)
27 muted := lipgloss.NewStyle().Foreground(lipgloss.BrightBlue).Italic(true)
28 bad := lipgloss.NewStyle().Foreground(lipgloss.BrightRed).Bold(true)
29
30 // root diagnose command
31 cmd.AddCommand(&cobra.Command{
32 Use: "doc",
33 Short: "diagnose potential hackatime issues",
34 RunE: func(c *cobra.Command, _ []string) error {
35 // check our os
36 os_name := runtime.GOOS
37
38 user_dir, err := os.UserHomeDir()
39 if err != nil {
40 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")
41 }
42 hackatime_path := user_dir + "/.wakatime.cfg"
43
44 switch os_name {
45 case "linux":
46 case "darwin":
47 case "windows":
48 default:
49 return errors.New("hmm you don't seem to be running a recognized os? you are listed as running " + fancy.Render(os_name) + "; can you plz report this to @krn on slack or via email at me@dunkirk.sh?")
50 }
51
52 c.Println("Looks like you are running", fancy.Render(os_name), "so lets take a look at", muted.Render(hackatime_path), "for your config")
53
54 rawCfg, err := os.ReadFile(hackatime_path)
55 if errors.Is(err, os.ErrNotExist) {
56 return errors.New("you don't have a wakatime config file! go check https://hackatime.hackclub.com/my/wakatime_setup for the instructions and then try this again")
57 }
58
59 cfg, err := ini.Load(rawCfg)
60 if err != nil {
61 return errors.New(err.Error())
62 }
63
64 settings, err := cfg.GetSection("settings")
65 if err != nil {
66 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 https://hackatime.hackclub.com/my/wakatime_setup to regenerate it?\n\nThe raw error we got was: " + err.Error())
67 }
68
69 api_key := settings.Key("api_key").String()
70 api_url := settings.Key("api_url").String()
71 if api_key == "" {
72 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 https://hackatime.hackclub.com/my/wakatime_setup correctly?")
73 }
74 if api_url == "" {
75 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 https://hackatime.hackclub.com/my/wakatime_setup correctly?")
76 }
77
78 if api_url != "https://hackatime.hackclub.com/api/hackatime/v1" {
79 if api_url == "https://api.wakatime.com/api/v1" {
80 client := wakatime.NewClient(api_key)
81 _, err := client.GetStatusBar()
82
83 if !errors.Is(err, wakatime.ErrUnauthorized) {
84 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 " + muted.Render("https://github.com/JasonLovesDoggo/multitime") + " or you can wait for 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 https://waka.hackclub.com) and then click the import from hackatime v1 button at 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")
85 } else {
86 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 https://hackatime.hackclub.com/my/wakatime_setup to run the setup script and fix your config file")
87 }
88 }
89 c.Println("\nYour api url", muted.Render(api_url), "doesn't match the expected url of", muted.Render("https://hackatime.hackclub.com/api/hackatime/v1"), "however if you are using a custom forwarder or are sure you know what you are doing then you are probably fine")
90 }
91
92 client := wakatime.NewClientWithOptions(api_key, api_url)
93 duration, err := client.GetStatusBar()
94 if err != nil {
95 if errors.Is(err, wakatime.ErrUnauthorized) {
96 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 https://hackatime.hackclub.com/my/wakatime_setup?")
97 }
98
99 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" + bad.Render("Full error: "+err.Error()))
100 }
101
102 // Convert seconds to a formatted time string (hours, minutes, seconds)
103 totalSeconds := duration.Data.GrandTotal.TotalSeconds
104 hours := totalSeconds / 3600
105 minutes := (totalSeconds % 3600) / 60
106 seconds := totalSeconds % 60
107
108 formattedTime := ""
109 if hours > 0 {
110 formattedTime += fmt.Sprintf("%d hours, ", hours)
111 }
112 if minutes > 0 || hours > 0 {
113 formattedTime += fmt.Sprintf("%d minutes, ", minutes)
114 }
115 formattedTime += fmt.Sprintf("%d seconds", seconds)
116
117 c.Println("\nSweet!!! Looks like your hackatime is configured properly! Looks like you have coded today for", fancy.Render(formattedTime))
118
119 c.Println("\nSending one quick heartbeat to make sure everything is ship shape and then you should be good to go!")
120
121 err = client.SendHeartbeat(wakatime.Heartbeat{
122 Entity: "/home/kierank/Projects/akami/wakatime/main.go",
123 Type: "file",
124 Project: "akami",
125 Language: "Go",
126 Branch: "main",
127 Category: "coding",
128 IsWrite: true,
129 LineCount: 197,
130 ProjectRootCount: 5,
131 Dependencies: []string{"bytes", "encoding/base64", "encoding/json", "net/http", "runtime", "time"},
132 Time: 1750643351,
133 })
134 if err != nil {
135 return errors.New("oh dear; looks like something went wrong when sending that heartbeat. " + bad.Render("Full error: \""+strings.TrimSpace(err.Error())+"\""))
136 }
137
138 c.Println("\n🥳 it worked! you are good to go! Happy coding 👋")
139
140 return nil
141 },
142 })
143
144 // this is where we get the fancy fang magic ✨
145 if err := fang.Execute(
146 context.Background(),
147 cmd,
148 ); err != nil {
149 os.Exit(1)
150 }
151}