package main import ( "context" "flag" "fmt" "log" "net/http" "net/url" "os" "os/signal" "strings" "syscall" "time" "github.com/gen2brain/beeep" "github.com/schollz/progressbar/v3" ) func main() { ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT) defer can() fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) gcal := fs.Bool("gcal", false, "wait for google cal events") ntfy := fs.String("http", "", "https://squeaky2x3:dYbtypGkHXFtq1E00k2H42SPGUowi@ntfy.home.blapointe.com/alerts-render") if err := fs.Parse(os.Args[1:]); err != nil { panic(err) } if *gcal { if err := alertGCal(ctx, *ntfy); err != nil { panic(err) } } else { if err := alertAfter(ctx, *ntfy, fs.Args()[0]); err != nil { panic(err) } } } func alertGCal(ctx context.Context, ntfy string) error { gcal := NewGCal() if err := gcal.Login(ctx); err != nil { return err } events, err := gcal.EventsToday(ctx) if err != nil { return err } for _, event := range events { until := event.Time.Add(-1 * time.Minute) if until.Sub(time.Now()) < 1 { continue } log.Println("alerting about", event.Name, "at", until.Format("15:04"), "(", time.Until(until), ")") if err := alertAt(ctx, ntfy, until, event.Name, until.Format("15:04")); err != nil { return err } } return nil } func alertAfter(ctx context.Context, ntfy string, duration string) error { d, err := time.ParseDuration(duration) if err != nil { return err } return alertAt(ctx, ntfy, time.Now().Add(d), os.Args[0], "alerting after "+d.String()) } func alertAt(ctx context.Context, ntfy string, deadline time.Time, title, msg string) error { n := int64(time.Until(deadline) / time.Second) bar := progressbar.Default(n) c := time.NewTicker(time.Second) defer c.Stop() func() { ctx, can := context.WithDeadline(ctx, deadline) defer can() for { select { case <-c.C: case <-ctx.Done(): return } bar.Add(1) } }() bar.Finish() bar.Exit() if err := ctx.Err(); err == nil { beeep.Alert(title, msg, "/dev/null") if ntfy != "" { u, _ := url.Parse(ntfy) req, _ := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(fmt.Sprintf("%s: %s", title, msg))) req = req.WithContext(ctx) http.DefaultClient.Do(req) } } return ctx.Err() }