This commit is contained in:
Aaron Bieber 2024-11-23 13:06:38 -07:00
commit b5b74623ac
No known key found for this signature in database
8 changed files with 312 additions and 0 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.bak
calnow
.direnv
result
tags

15
LICENSE Normal file
View File

@ -0,0 +1,15 @@
/*
* Copyright (c) 2024 Aaron Bieber <aaron@bolddaemon.com>
*
* 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.
*/

26
flake.lock generated Normal file
View File

@ -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
}

49
flake.nix Normal file
View File

@ -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 ];
};
});
};
}

13
go.mod Normal file
View File

@ -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
)

10
go.sum Normal file
View File

@ -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=

193
main.go Normal file
View File

@ -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)
}