From 071eff3246005c86dae97bff5c495176e3a3910d Mon Sep 17 00:00:00 2001 From: newt Date: Fri, 25 Oct 2024 14:19:42 +0100 Subject: [PATCH] feat: google oauth + credential manager --- .gitignore | 1 + auth/auth.go | 76 ++++++++++++++++++++++++++ auth/google.go | 100 +++++++++++++++++++++++++++++++++++ auth/index.html | 16 ++++++ auth/uofg.go | 37 +++++++++++++ fetch/fetch.go | 33 ++++++------ go.mod | 30 ++++++++++- go.sum | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ images.png | Bin 0 -> 6827 bytes main.go | 5 +- tray/tray.go | 2 +- 11 files changed, 415 insertions(+), 23 deletions(-) create mode 100644 auth/auth.go create mode 100644 auth/google.go create mode 100644 auth/index.html create mode 100644 auth/uofg.go create mode 100644 images.png diff --git a/.gitignore b/.gitignore index a5deb19..68dbed5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ rs *.exe *.ics +credentials.json diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 0000000..c69a94c --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,76 @@ +package auth + +import ( + "bytes" + "context" + "encoding/gob" + "net/http" + + "github.com/danieljoos/wincred" + "golang.org/x/oauth2" +) + +type Credentials struct { + GoogleToken *oauth2.Token + GUID string + UofgPass string +} + +const credential_name = "uofgsync" + +func read_credentials() (*Credentials, error) { + // read from windows credential manager + cred, err := wincred.GetGenericCredential(credential_name) + if err != nil { + return nil, err + } + + // deserialize the credentials + var c Credentials + dec := gob.NewDecoder(bytes.NewReader(cred.CredentialBlob)) + if err := dec.Decode(&c); err != nil { + return nil, err + } + + return &c, nil +} + +func save_credentials(credentials Credentials) error { + // serialize the credentials + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + if err := enc.Encode(credentials); err != nil { + return err + } + + // save to windows credential manager + cred := wincred.NewGenericCredential(credential_name) + cred.CredentialBlob = buf.Bytes() + if err := cred.Write(); err != nil { + return err + } + + return nil +} + +func GetCredentials(config *oauth2.Config) (*Credentials, error) { + creds, err := read_credentials() + if err != nil { + google := fetch_google_token(config) + guid, uofg_pass, err := collect_uofg() + if err != nil { + return nil, err + } + creds = &Credentials{ + GoogleToken: google, + GUID: guid, + UofgPass: uofg_pass, + } + save_credentials(*creds) + } + return creds, nil +} + +func GetGoogleClient(config *oauth2.Config, token *oauth2.Token) *http.Client { + return config.Client(context.Background(), token) +} diff --git a/auth/google.go b/auth/google.go new file mode 100644 index 0000000..c841047 --- /dev/null +++ b/auth/google.go @@ -0,0 +1,100 @@ +package auth + +import ( + "context" + _ "embed" + "fmt" + "math/rand" + "net/http" + "net/url" + "sync" + + "github.com/charmbracelet/log" + "github.com/pkg/browser" + "golang.org/x/oauth2" +) + +const listen_port = 3745 + +var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +//go:embed credentials.json +var GoogleOAuthCreds []byte + +//go:embed index.html +var tab_html []byte + +func randSeq(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} + +func fetch_google_token(config *oauth2.Config) *oauth2.Token { + // request authorization + var auth_code string + config.RedirectURL = fmt.Sprintf("http://localhost:%d", listen_port) + state_token := randSeq(16) + auth_url := config.AuthCodeURL(state_token, oauth2.AccessTypeOffline) + + fmt.Printf("Go to the following link in your browser: \n%v", auth_url) + if err := browser.OpenURL(auth_url); err != nil { + log.Fatal("Unable to open browser: %v", err) + } + + // setup http server and wait for auth code + srv := &http.Server{Addr: fmt.Sprintf(":%d", listen_port)} + srv_exit_done := &sync.WaitGroup{} + srv_exit_done.Add(1) + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // parse query + query, err := url.ParseQuery(r.URL.RawQuery) + if err != nil { + log.Error("Unable to parse query: %v", err) + return + } + + // ensure the state token is correct + if query.Get("state") != state_token { + log.Error("Invalid state token") + return + } + + // get the auth code + auth_code = query.Get("code") + if auth_code == "" { + log.Error("No auth code provided") + return + } + + // send back a response that automatically closes the tab + w.Header().Set("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + w.Write(tab_html) + + // shutdown the http server + if err := srv.Shutdown(context.TODO()); err != nil { + log.Fatal(err) + } + }) + + go func() { + defer srv_exit_done.Done() + + if err := srv.ListenAndServe(); err != http.ErrServerClosed { + log.Fatal("HTTP server error: %v", err) + } + }() + + srv_exit_done.Wait() + + // exchange auth code for token + token, err := config.Exchange(context.TODO(), auth_code) + if err != nil { + log.Fatalf("Unable to retrieve token from web: %v", err) + } + return token +} diff --git a/auth/index.html b/auth/index.html new file mode 100644 index 0000000..62c7b69 --- /dev/null +++ b/auth/index.html @@ -0,0 +1,16 @@ + + + + + + UofG Sync + + +

Authenticated successfully

+

You can now safely close this tab.

+ + + + diff --git a/auth/uofg.go b/auth/uofg.go new file mode 100644 index 0000000..f66818d --- /dev/null +++ b/auth/uofg.go @@ -0,0 +1,37 @@ +package auth + +import ( + "bufio" + "encoding/base64" + "fmt" + "os" + "strings" + "syscall" + + "golang.org/x/term" +) + +func GetHeader(guid string, password string) string { + b64 := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", guid, password))) + return fmt.Sprintf("Basic %s", b64) +} + +func collect_uofg() (string, string, error) { + reader := bufio.NewReader(os.Stdin) + + fmt.Print("Enter your GUID: ") + username, err := reader.ReadString('\n') + if err != nil { + return "", "", err + } + + fmt.Print("Enter your password: ") + bytePassword, err := term.ReadPassword(int(syscall.Stdin)) + if err != nil { + return "", "", err + } + + password := string(bytePassword) + + return strings.TrimSpace(username), strings.TrimSpace(password), nil +} diff --git a/fetch/fetch.go b/fetch/fetch.go index e139769..fceeaad 100644 --- a/fetch/fetch.go +++ b/fetch/fetch.go @@ -1,36 +1,33 @@ package fetch import ( - "encoding/base64" - "fmt" "net/http" "strings" "time" + "git.newty.dev/uofgsync/auth" ics "github.com/arran4/golang-ical" "github.com/charmbracelet/log" cron "github.com/go-co-op/gocron/v2" + "golang.org/x/oauth2/google" + "google.golang.org/api/calendar/v3" ) const calendar_url = "https://frontdoor.spa.gla.ac.uk/spacett/download/uogtimetable.ics" -var authorization string var Scheduler *cron.Scheduler -func CollectAuth() { - var ( - guid string - password string - ) - fmt.Print("Enter your GUID: ") - fmt.Scan(&guid) - fmt.Print("Enter your password: ") - fmt.Scan(&password) - b64 := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", guid, password))) - authorization = fmt.Sprintf("Basic %s", b64) -} - func StartScheduler() { + // fetch credentials + config, err := google.ConfigFromJSON(auth.GoogleOAuthCreds, calendar.CalendarReadonlyScope) + if err != nil { + log.Fatal(err) + } + credentials, err := auth.GetCredentials(config) + if err != nil { + log.Fatal(err) + } + // create scheduler Scheduler, err := cron.NewScheduler() if err != nil { @@ -46,6 +43,8 @@ func StartScheduler() { // update the calendar cron.NewTask( update_calendar, + auth.GetHeader(credentials.GUID, credentials.UofgPass), + auth.GetGoogleClient(config, credentials.GoogleToken), ), ) if err != nil { @@ -57,7 +56,7 @@ func StartScheduler() { Scheduler.Start() } -func update_calendar() { +func update_calendar(authorization string, google *http.Client) { // create request req, err := http.NewRequest("GET", calendar_url, nil) if err != nil { diff --git a/go.mod b/go.mod index d1df8ea..f44d89a 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,19 @@ -module git.newty.dev/uofgcal +module git.newty.dev/uofgsync go 1.23.2 require github.com/charmbracelet/log v0.4.0 require ( + cloud.google.com/go/auth v0.9.9 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect + cloud.google.com/go/compute/metadata v0.5.2 // indirect github.com/ChannelMeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 // indirect github.com/arran4/golang-ical v0.3.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/lipgloss v0.10.0 // indirect + github.com/danieljoos/wincred v1.2.2 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect @@ -18,8 +23,14 @@ require ( github.com/getlantern/systray v1.2.2 // indirect github.com/go-co-op/gocron/v2 v2.12.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-stack/stack v1.8.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect @@ -27,9 +38,24 @@ require ( github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect go.uber.org/atomic v1.9.0 // indirect + golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/api v0.203.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect ) diff --git a/go.sum b/go.sum index 5d2b26f..909eeb0 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,14 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ= +cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/ChannelMeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 h1:N5Vqww5QISEHsWHOWDEx4PzdIay3Cg0Jp7zItq2ZAro= github.com/ChannelMeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61/go.mod h1:GnKXcK+7DYNy/8w2Ex//Uql4IgfaU82Cd5rWKb7ah00= github.com/apognu/gocal v0.9.0 h1:2lGdZprjYs9A6l1RTEmapmpE1PiDbXNX8bUVqZt3vm4= @@ -6,15 +17,26 @@ github.com/arran4/golang-ical v0.3.1 h1:v13B3eQZ9VDHTAvT6M11vVzxYgcYmjyPBE2eAZl3 github.com/arran4/golang-ical v0.3.1/go.mod h1:LZWxF8ZIu/sjBVUCV0udiVPrQAgq3V0aa0RfbO99Qkk= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61/go.mod h1:Rp8e0DCtEKwXFOC6JPJQVTz8tuGoGvw6Xfexggh/ed0= github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= +github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So= @@ -35,13 +57,45 @@ github.com/go-co-op/gocron/v2 v2.12.1 h1:dCIIBFbzhWKdgXeEifBjHPzgQ1hoWhjS4289Hjj github.com/go-co-op/gocron/v2 v2.12.1/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -66,9 +120,12 @@ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1n github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -86,20 +143,99 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= +google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -110,3 +246,5 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/images.png b/images.png new file mode 100644 index 0000000000000000000000000000000000000000..5352f8bb4813ea9f5cc6651fd94729fa2c07c917 GIT binary patch literal 6827 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 MNmH>NVj1>-02R*v6#xJL literal 0 HcmV?d00001 diff --git a/main.go b/main.go index c5686d9..6cee0a8 100644 --- a/main.go +++ b/main.go @@ -1,13 +1,12 @@ package main import ( - "git.newty.dev/uofgcal/fetch" - "git.newty.dev/uofgcal/tray" + "git.newty.dev/uofgsync/fetch" + "git.newty.dev/uofgsync/tray" "github.com/getlantern/systray" ) func main() { - fetch.CollectAuth() fetch.StartScheduler() systray.Run(tray.OnReady, tray.OnExit) } diff --git a/tray/tray.go b/tray/tray.go index 5574c82..7cbe780 100644 --- a/tray/tray.go +++ b/tray/tray.go @@ -3,7 +3,7 @@ package tray import ( _ "embed" - "git.newty.dev/uofgcal/fetch" + "git.newty.dev/uofgsync/fetch" "github.com/charmbracelet/log" "github.com/getlantern/systray" )