commit b5b74623ac842bdc245bfa1655cfaaee9b12a5eb Author: Aaron Bieber Date: Sat Nov 23 13:06:38 2024 -0700 initial diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9e7176 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.bak +calnow +.direnv +result +tags diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5718c90 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024 Aaron Bieber + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..bbf0ac0 --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1731755305, + "narHash": "sha256-v5P3dk5JdiT+4x69ZaB18B8+Rcu3TIOrcdG4uEX7WZ8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "057f63b6dc1a2c67301286152eb5af20747a9cb4", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-24.11", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..434fa5c --- /dev/null +++ b/flake.nix @@ -0,0 +1,49 @@ +{ + description = "calnow: stuff and calnows"; + + inputs.nixpkgs.url = "nixpkgs/nixos-24.11"; + + outputs = + { self + , nixpkgs + , + }: + let + supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); + in + { + overlay = _: prev: { inherit (self.packages.${prev.system}) calnow; }; + + packages = forAllSystems (system: + let + pkgs = nixpkgsFor.${system}; + in + { + calnow = pkgs.buildGoModule { + pname = "calnow"; + version = "v0.0.0"; + src = ./.; + + vendorHash = "sha256-SWkQMF9bFAzqsmHWKNsTpx8HuXrXZe9+vIOI0mmAPrw="; + }; + }); + + defaultPackage = forAllSystems (system: self.packages.${system}.calnow); + devShells = forAllSystems (system: + let + pkgs = nixpkgsFor.${system}; + in + { + default = pkgs.mkShell { + shellHook = '' + PS1='\u@\h:\@; ' + nix run github:qbit/xin#flake-warn + echo "Go `${pkgs.go}/bin/go version`" + ''; + nativeBuildInputs = with pkgs; [ git go gopls go-tools ]; + }; + }); + }; +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..274ff41 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module suah.dev/calnow + +go 1.23.2 + +require ( + github.com/emersion/go-webdav v0.5.0 + github.com/sosodev/duration v1.3.1 +) + +require ( + github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f // indirect + github.com/teambition/rrule-go v1.8.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ba9c902 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f h1:feGUUxxvOtWVOhTko8Cbmp33a+tU0IMZxMEmnkoAISQ= +github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f/go.mod h1:2MKFUgfNMULRxqZkadG1Vh44we3y5gJAtTBlVsx1BKQ= +github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM= +github.com/emersion/go-webdav v0.5.0 h1:Ak/BQLgAihJt/UxJbCsEXDPxS5Uw4nZzgIMOq3rkKjc= +github.com/emersion/go-webdav v0.5.0/go.mod h1:ycyIzTelG5pHln4t+Y32/zBvmrM7+mV7x+V+Gx4ZQno= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/teambition/rrule-go v1.7.2/go.mod h1:mBJ1Ht5uboJ6jexKdNUJg2NcwP8uUMNvStWXlJD3MvU= +github.com/teambition/rrule-go v1.8.2 h1:lIjpjvWTj9fFUZCmuoVDrKVOtdiyzbzc93qTmRVe/J8= +github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..0e8792f --- /dev/null +++ b/main.go @@ -0,0 +1,193 @@ +package main + +import ( + "context" + "flag" + "log" + "net/http" + "os" + "slices" + "time" + + "github.com/emersion/go-webdav" + "github.com/emersion/go-webdav/caldav" + "github.com/sosodev/duration" +) + +const ( + fullTimeFmt = "20060102T150405" + fullFimeTmt = "20060102T150405" +) + +var ( + debug = false +) + +func msg(fmt string, args ...any) { + if debug { + log.Printf(fmt, args...) + } +} + +func between(now, begin, end time.Time) bool { + return now.After(begin) && now.Before(end) +} + +func fixDate(a, b time.Time) time.Time { + return time.Date( + a.Year(), + a.Month(), + a.Day(), + a.Hour(), + a.Minute(), + a.Second(), + a.Nanosecond(), + b.Location(), + ) +} + +func main() { + flag.BoolVar(&debug, "debug", false, "print debug messages") + flag.Parse() + username := os.Getenv("CALNOW_USER") + serverURL := os.Getenv("CALNOW_URL") + password := os.Getenv("CALNOW_PASS") + + if username == "" || password == "" { + log.Fatal("Please set CALNOW_USER and CALNOW_PASS environment variables") + } + + client, err := caldav.NewClient(webdav.HTTPClientWithBasicAuth( + &http.Client{}, + username, + password, + ), serverURL) + if err != nil { + log.Fatal("Failed to create client:", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + princ, err := client.FindCurrentUserPrincipal(ctx) + + homeSets, err := client.FindCalendarHomeSet(ctx, princ) + if err != nil { + log.Fatal("Failed to find home set:", err) + } + + calendars, err := client.FindCalendars(ctx, homeSets) + if err != nil { + log.Printf("Failed to find calendars for home set %s: %v", homeSets, err) + } + + now := time.Now() + start := time.Date( + now.Year(), + now.Month(), + now.Day(), + 0, 0, 1, 0, + now.Location(), + ) + + end := time.Date( + now.Year(), + now.Month(), + now.Day(), + 23, 59, 59, 999999999, + now.Location(), + ) + + for _, cal := range calendars { + if !slices.Contains(cal.SupportedComponentSet, "VEVENT") { + continue + } + + path := cal.Path + query := &caldav.CalendarQuery{ + CompRequest: caldav.CalendarCompRequest{ + Name: "VCALENDAR", + Comps: []caldav.CalendarCompRequest{{ + Name: "VEVENT", + Props: []string{ + "SUMMARY", + "UID", + "DTSTART", + "DTEND", + "DURATION", + }, + }}, + }, + + CompFilter: caldav.CompFilter{ + Name: "VCALENDAR", + Comps: []caldav.CompFilter{{ + Name: "VEVENT", + Start: start, + End: end, + }}, + }, + } + events, err := client.QueryCalendar(ctx, path, query) + if err != nil { + continue + } + for _, e := range events { + for i := range e.Data.Children { + sum := e.Data.Children[i].Props.Get("SUMMARY") + end := e.Data.Children[i].Props.Get("DTEND") + dur := e.Data.Children[i].Props.Get("DURATION") + begin := e.Data.Children[i].Props.Get("DTSTART") + + if sum != nil { + msg("%s:%s", cal.Name, sum.Value) + } + + if begin == nil && dur == nil { + continue + } + + if end != nil { + endTime, err := time.Parse(fullTimeFmt, end.Value) + if err != nil { + continue + } + beginTime, err := time.Parse(fullTimeFmt, begin.Value) + if err != nil { + continue + } + + endTime = fixDate(endTime, now) + beginTime = fixDate(beginTime, now) + + msg("%s <%s> %s, between: %t", beginTime, now, endTime, between(now, beginTime, endTime)) + if between(now, beginTime, endTime) { + os.Exit(0) + } + } + + if dur != nil && begin != nil { + beginTime, err := time.Parse(fullTimeFmt, begin.Value) + if err != nil { + continue + } + eDur, err := duration.Parse(dur.Value) + if err != nil { + continue + } + endTime := beginTime.Add(eDur.ToTimeDuration()) + + endTime = fixDate(endTime, now) + beginTime = fixDate(beginTime, now) + + msg("%s <%s> %s, between: %t", beginTime, now, endTime, between(now, beginTime, endTime)) + if between(now, beginTime, endTime) { + os.Exit(0) + } + } + } + } + } + + os.Exit(1) +}