diff --git a/caldav.go b/caldav.go index cd195c1..9e7808f 100644 --- a/caldav.go +++ b/caldav.go @@ -1,36 +1,34 @@ package main import ( - // "github.com/studio-b12/gowebdav" "context" // "fmt" webdav "github.com/emersion/go-webdav" "github.com/emersion/go-webdav/caldav" - // "log" - "time" "strings" + "time" ) type TODO struct { - Name string - Description string - Time string + Name string + Desc string + Time string // Priority int //TODO // Subtasks []TODO //TODO // Repeat //TODO // Alarm //TODO } - var clientWebDAV *webdav.Client var client *caldav.Client // clientCalDAV // var calendarObjects []caldav.CalendarObject var ctx = context.Background() + // var authSession caldav.Client // clientCalDAV func (options *Options) InitDAVclients() error { - + var err error authSession := webdav.HTTPClientWithBasicAuth(nil, options.User, options.Password) @@ -47,142 +45,143 @@ func (options *Options) InitDAVclients() error { return nil } -func GetCalendars() ([]caldav.Calendar,error) { +func GetCalendars() ([]caldav.Calendar, error) { principal, err := clientWebDAV.FindCurrentUserPrincipal(ctx) if err != nil { // Handle error - return nil,err + return nil, err } // fmt.Println("principal: ",principal) TODO log - // Find the calendar home set calendarHomeSet, err := client.FindCalendarHomeSet(ctx, principal) if err != nil { - return nil,err + return nil, err } // Find calendars in the calendar home set calendars, err := client.FindCalendars(ctx, calendarHomeSet) if err != nil { - return nil,err + return nil, err } return calendars, nil } -func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject,err error) { +func GetTODOs(calendarPath string) (calendarObjects []caldav.CalendarObject, err error) { date := time.Now() - dateStart:= time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 0, date.Location()).AddDate(0, 0, -1) //TODO too complex - time.Now().Add(-92 * time.Hour), + dateStart := time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 0, date.Location()).AddDate(0, 0, -1) //TODO too complex - time.Now().Add(-92 * time.Hour), // dateEnd:= time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 0, date.Location()) //date +1 day - dateEnd:= date.AddDate(0,1,0) //TODO we hard limit pulling events only for this month, to prevent getting tooo much events + dateEnd := date.AddDate(0, 1, 0) //TODO we hard limit pulling events only for this month, to prevent getting tooo much events calQuery := caldav.CalendarQuery{ CompRequest: caldav.CalendarCompRequest{ - Name: "VCALENDAR", - Comps: []caldav.CalendarCompRequest{{ - Name: "VTODO", - Props: []string{ - "UID", - "SUMMARY", - "COMPLETED", - "DESCRIPTION", - "DUE", - "PRIORITY", - "RELATED-TO", - }, - }, - }, - }, - CompFilter: caldav.CompFilter{ - Name: "VCALENDAR", - Comps: []caldav.CompFilter{ - { - Name: "VTODO", - Start: dateStart, - End: dateEnd, - // FYI: server-side filtering is not supported for Nextcloud - }, - }, + Name: "VCALENDAR", + Comps: []caldav.CalendarCompRequest{{ + Name: "VTODO", + Props: []string{ + "UID", + "SUMMARY", + "COMPLETED", + "DESCRIPTION", + "DUE", + "PRIORITY", + "RELATED-TO", }, + }, + }, + }, + CompFilter: caldav.CompFilter{ + Name: "VCALENDAR", + Comps: []caldav.CompFilter{ + { + Name: "VTODO", + Start: dateStart, + End: dateEnd, + // FYI: server-side filtering is not supported for Nextcloud + }, + }, + }, } - calendarObjects, err = client.QueryCalendar(ctx, calendarPath, &calQuery) + calendarObjects, err = client.QueryCalendar(ctx, calendarPath, &calQuery) if err != nil { - return nil,err + return nil, err } - return calendarObjects,nil + return calendarObjects, nil } -func ParseDueDateTODOs(calObjs []caldav.CalendarObject,date time.Time) ([]TODO,error) { - var output []TODO +func ParseDueDateTODOs(calObjs []caldav.CalendarObject, date time.Time) ([]TODO, error) { + var output []TODO - for _,calObj := range calObjs { + for _, calObj := range calObjs { // fmt.Println((*(*calObj.Data).Children[0]).Name) // TODO STATUS map[] COMPLETED - for _,event := range (*calObj.Data).Children { + for _, event := range (*calObj.Data).Children { // if (*event).Name == "VTODO" { - var notCompletedTODO, withDate, fromToday bool - //TODO we can optimize there if we encounter wrong state to forcefully stop next analysis - // notCompletedTODO - if (*event).Props["COMPLETED"] == nil { - if ((*event).Props["STATUS"] == nil) { notCompletedTODO = true } else { - if ((*event).Props["STATUS"][0].Value != "COMPLETED") { notCompletedTODO = true } + var notCompletedTODO, withDate, fromToday bool + //TODO we can optimize there if we encounter wrong state to forcefully stop next analysis + // notCompletedTODO + if (*event).Props["COMPLETED"] == nil { + if (*event).Props["STATUS"] == nil { + notCompletedTODO = true + } else { + if (*event).Props["STATUS"][0].Value != "COMPLETED" { + notCompletedTODO = true } } + } - // withTodayDate - if (*event).Props["DUE"] != nil { - withDate = true - // fromToday - if strings.HasPrefix((*event).Props["DUE"][0].Value, date.Format("20060102")) { fromToday = true } + // withTodayDate + if (*event).Props["DUE"] != nil { + withDate = true + // fromToday + if strings.HasPrefix((*event).Props["DUE"][0].Value, date.Format("20060102")) { + fromToday = true } - - if notCompletedTODO && withDate && fromToday { + } - var tmpTODO TODO - - if ((*event).Props["SUMMARY"] != nil) { - name := (*event).Props["SUMMARY"][0].Value - tmpTODO.Name = name - } else { - tmpTODO.Name = "" - } - if ((*event).Props["DESCRIPTION"] != nil) { - description := (*event).Props["DESCRIPTION"][0].Value - tmpTODO.Description = description - } + if notCompletedTODO && withDate && fromToday { - var todoTime string - due := (*event).Props["DUE"][0].Value - index := strings.Index(due, "T") - if index != -1 { - str := due[index+1:] - todoTime = str[:2] + ":" + str[2:4] - - tmpTODO.Time = todoTime - } - output = append(output, tmpTODO) + var tmpTODO TODO + + if (*event).Props["SUMMARY"] != nil { + name := (*event).Props["SUMMARY"][0].Value + tmpTODO.Name = name + } else { + tmpTODO.Name = "" } + if (*event).Props["DESCRIPTION"] != nil { + description := (*event).Props["DESCRIPTION"][0].Value + tmpTODO.Desc = description + } + + var todoTime string + due := (*event).Props["DUE"][0].Value + index := strings.Index(due, "T") + if index != -1 { + str := due[index+1:] + todoTime = str[:2] + ":" + str[2:4] + + tmpTODO.Time = todoTime + } + output = append(output, tmpTODO) + } // } } } -// //TODO sort: time, priority (if no time) -// // it means, put DUE at first, other DUE;VALUE=DATE in priority order -// //TODO color: priority, add time icon if have time -// //TODO UID:7ed30f40-fce1-422c-be3b-0486dcfe8943 -// //TODO RELATED-TO:7ed30f40-fce1-422c-be3b-0486dcfe8943 # subtask -// //TODO PRIORITY:1 #1-high, 5-mid, 9-low -//TODO repeat function??? + // //TODO sort: time, priority (if no time) + // // it means, put DUE at first, other DUE;VALUE=DATE in priority order + // //TODO color: priority, add time icon if have time + // //TODO UID:7ed30f40-fce1-422c-be3b-0486dcfe8943 + // //TODO RELATED-TO:7ed30f40-fce1-422c-be3b-0486dcfe8943 # subtask + // //TODO PRIORITY:1 #1-high, 5-mid, 9-low + //TODO repeat function??? - return output,nil + return output, nil } - - - - //TODO on complete -repeat function // RRULE:FREQ=WEEKLY;INTERVAL=1 @@ -200,20 +199,3 @@ func ParseDueDateTODOs(calObjs []caldav.CalendarObject,date time.Time) ([]TODO,e // DESCRIPTION:Default Tasks.org description // END:VALARM // END:VTODO - - - - - - - - - - - - - - - - - diff --git a/go.mod b/go.mod index 5cc852c..2b74a27 100644 --- a/go.mod +++ b/go.mod @@ -3,32 +3,61 @@ module git.sual.in/casual/tempus go 1.22.3 require ( + github.com/charmbracelet/bubbles v0.18.0 + github.com/charmbracelet/bubbletea v0.26.4 + github.com/charmbracelet/lipgloss v0.11.0 github.com/emersion/go-webdav v0.5.0 github.com/projectdiscovery/goflags v0.1.56 + github.com/zalando/go-keyring v0.2.5 + golang.org/x/term v0.18.0 ) require ( + github.com/alessio/shellescape v1.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/charmbracelet/x/ansi v0.1.2 // indirect + github.com/charmbracelet/x/input v0.1.0 // indirect + github.com/charmbracelet/x/term v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.1.0 // indirect github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect + github.com/danieljoos/wincred v1.2.0 // indirect github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/css v1.0.0 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/microcosm-cc/bluemonday v1.0.25 // indirect github.com/miekg/dns v1.1.56 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pkg/errors v0.9.1 // indirect github.com/projectdiscovery/blackrock v0.0.1 // indirect github.com/projectdiscovery/utils v0.1.3 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect github.com/teambition/rrule-go v1.8.2 // indirect github.com/tidwall/gjson v1.14.3 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect + gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b000b7c..14affad 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,31 @@ +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +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/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= +github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= +github.com/charmbracelet/bubbletea v0.26.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkimep7FC9c40= +github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0= +github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g= +github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8= +github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY= +github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ= +github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28= +github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI= +github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw= +github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4= +github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= 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/emersion/go-ical v0.0.0-20220601085725-0864dccc089f h1:feGUUxxvOtWVOhTko8Cbmp33a+tU0IMZxMEmnkoAISQ= @@ -11,16 +33,42 @@ github.com/emersion/go-ical v0.0.0-20220601085725-0864dccc089f/go.mod h1:2MKFUgf 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/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -31,8 +79,16 @@ github.com/projectdiscovery/goflags v0.1.56 h1:tJYiZN7s9Jk9DxfYOUiqOoybaIDlXyX4Z github.com/projectdiscovery/goflags v0.1.56/go.mod h1:DsGF0NPpM5hGg75N3MTSvWJ4MIT7HFEAOEeWZ074+Fg= github.com/projectdiscovery/utils v0.1.3 h1:yhHkrbYZA1eOO8e+fPDUvRMS5aUIalyM3Nab7rK4tpg= github.com/projectdiscovery/utils v0.1.3/go.mod h1:gny8RbNYXE55IoamF6thRDQ8tcJEw+r0FOGAvncz/oQ= +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= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= +github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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= github.com/teambition/rrule-go v1.7.2/go.mod h1:mBJ1Ht5uboJ6jexKdNUJg2NcwP8uUMNvStWXlJD3MvU= @@ -44,20 +100,30 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= +github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/keyring.go b/keyring.go new file mode 100644 index 0000000..1fa35f1 --- /dev/null +++ b/keyring.go @@ -0,0 +1,121 @@ +package main + +import ( + // "log" + + "github.com/zalando/go-keyring" + // "github.com/99designs/keyring" + "encoding/base64" + // "fmt" + "strings" +) + + +const ( + service = "Tempus" + user = "login" +) + +// func debugKeyring() { +// service := "Tempus" +// user := "login" +// password := "secr123123et" +// encoded := base64.StdEncoding.EncodeToString([]byte(password)) +// // set password +// err := keyring.Set(service, user, encoded) +// if err != nil { +// log.Fatal(err) +// } +// +// // get password +// secret, err := keyring.Get(service, user) +// if err != nil { +// log.Fatal(err) +// } +// +// log.Println(secret) +// +// decodedByte, err := base64.StdEncoding.DecodeString(secret) +// decoded := string(decodedByte) +// if err != nil { +// fmt.Println("decode error:", err) +// return +// } +// log.Println(decoded) +// } + +func storeCredentialsToKeyring(url,login,password string) error { + url = base64.StdEncoding.EncodeToString([]byte(url)) + login = base64.StdEncoding.EncodeToString([]byte(login)) + password = base64.StdEncoding.EncodeToString([]byte(password)) + + credentials := url + " " + login + " " + password + + err := keyring.Set(service, user, credentials) + if err != nil { + return err + } + return nil +} + +func getCredentialsFromKeyring() (url,login,password string, err error) { + secret, err := keyring.Get(service, user) + if err != nil { + return "","","",err + } + + v := strings.Split(secret," ") + + urlByte, err := base64.StdEncoding.DecodeString(v[0]) + if err != nil { + return "","","",err + } + loginByte, err := base64.StdEncoding.DecodeString(v[1]) + if err != nil { + return "","","",err + } + passwordByte, err := base64.StdEncoding.DecodeString(v[2]) + if err != nil { + return "","","",err + } + + url = string(urlByte) + login = string(loginByte) + password = string(passwordByte) + + + return +} + + +// func debugKeyring() { +// +// kr, err := keyring.Open(keyring.Config{ +// AllowedBackends: []keyring.BackendType{ +// keyring.SecretServiceBackend, +// }, +// LibSecretCollectionName: "Defaultkeyring", +// ServiceName: "myapp", +// }) +// if err != nil { +// log.Fatal(err) +// } +// +// err = kr.Set(keyring.Item{ +// Key: "foo", +// // Data: []byte("secret-bar"), +// }) +// if err != nil { +// log.Fatal(err) +// } +// +// v, err := kr.Get("llamas") +// if err != nil { +// log.Fatal(err) +// } +// +// log.Printf("llamas was %v", v) +// +// +// +// } diff --git a/main.go b/main.go index 62642d5..fcfe7d0 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,12 @@ import ( "os" "sync" "time" - ) + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/list" + + // "strconv" +) var waitGroup sync.WaitGroup @@ -20,25 +25,82 @@ func main() { options, err := ParseOptions() errHandler(err, "Error parsing options") + + + // debugKeyring() + + err = storeCredentialsToKeyring("https://pkg.go.dev/encoding/base64#Encoding.EncodeToString","casual","h>ÕdzPlÇqQ+çCQ{ð±;Kм7¸Âhð~Ümy)v") + errHandler(err, "Error parsing options") + +// url1,login1,password1,err := getCredentialsFromKeyring() +// errHandler(err, "Error parsing options") +// m.loginInputs[url].Value() +// m.loginInputs[login].Value() +// m.loginInputs[pass].Value() + + // if true {os.Exit(1)} + + + + + + + + + + + + + + + + + options.InitDAVclients() calendars, err := GetCalendars() errHandler(err, "Error getting calendars") - for _,calendar := range calendars { + for _, calendar := range calendars { fmt.Println(calendar.Name, "-", calendar.Path) } - calendarObjects, err := GetTODOs(calendars[1].Path) - errHandler(err, "Error getting TODOs") + errHandler(err, "Error getting TODOs") today := time.Now() - todos,err := ParseDueDateTODOs(calendarObjects ,today) + todayTodos, err := ParseDueDateTODOs(calendarObjects, today) + tomorrow := time.Now().AddDate(0,0,1) + tomorrowTodos, err := ParseDueDateTODOs(calendarObjects, tomorrow) + + // fmt.Println(todos) + + fmt.Println("In total we have", len(calendarObjects), "todos") + + var itemsToday []list.Item + var itemsTomorrow []list.Item + + for _,todo := range todayTodos { + itemsToday = append(itemsToday,todo) + } + + for _,todo := range tomorrowTodos { + itemsTomorrow = append(itemsTomorrow,todo) + } + + m := InitModel() + m.TodayTab = list.New(itemsToday, list.NewDefaultDelegate(), 0, 0) + m.TodayTab.Title = "Today" + m.TomorrowTab = list.New(itemsTomorrow, list.NewDefaultDelegate(), 0, 0) + m.TomorrowTab.Title = "Tomorrow" + // m.LoggedIn = true + m.ActiveWindow = "login" - fmt.Println(todos) + p := tea.NewProgram(m, tea.WithAltScreen()) + if _, err := p.Run(); err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } - fmt.Println("In total we have",len(calendarObjects), "todos") + fmt.Println(m.loginInputs[login].Value()) } - - diff --git a/options.go b/options.go index f561a5b..1799703 100644 --- a/options.go +++ b/options.go @@ -36,7 +36,7 @@ func ParseOptions() (*Options, error) { flagSet.StringVarP(&options.User, "l", "login", "", "WebDAV login"), flagSet.StringVarP(&options.Password, "p", "password", "", "WebDAV password (forbid filesystem access!!!)"), ) - + // flagSet.CreateGroup("debug", "Debug", // flagSet.BoolVarP(&options.Verbose, "v", "verbose", false, "verbose output with debugging information"), // ) diff --git a/tui.go b/tui.go new file mode 100644 index 0000000..31257d6 --- /dev/null +++ b/tui.go @@ -0,0 +1,270 @@ +package main + + +import ( + "fmt" + // "os" + // "strings" + + "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/textinput" + // textblink "github.com/charmbracelet/bubbles/textinput" + // "github.com/erikgeiser/promptkit/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "golang.org/x/term" +) + +var docStyle = lipgloss.NewStyle().Margin(1, 2) + +var loginStyle = lipgloss.NewStyle().Width(40).Align(lipgloss.Center).BorderStyle(lipgloss.NormalBorder()) + +var inputStyle = lipgloss.NewStyle() + +func (i TODO) Title() string { return i.Name } +func (i TODO) Description() string { return i.Desc } +func (i TODO) FilterValue() string { return i.Name } + +type model struct { + Tabs []string + // TabContent []string + LoggedIn bool + ActiveWindow string + + TodayTab list.Model + TomorrowTab list.Model + + loginInputs []textinput.Model + focused int + err error +} + +func (m model) Init() tea.Cmd { + return textinput.Blink + // return nil +} + +const ( + url = iota + login + pass +) +//TODO add changing calendar + +func InitModel() model { + var inputs []textinput.Model = make([]textinput.Model, 3) + inputs[url] = textinput.New() + inputs[url].Placeholder = "https://nextcloud.example/remote.php/dav" + inputs[url].Focus() + // inputs[url].CharLimit = 20 + inputs[url].Width = 30 + inputs[url].Prompt = "" + // inputs[url].Validate = urlValidator + + inputs[login] = textinput.New() + inputs[login].Placeholder = "username" + // inputs[login].CharLimit = 5 + inputs[login].Width = 30 + inputs[login].Prompt = "" + // inputs[login].Validate = loginValidator + + inputs[pass] = textinput.New() + inputs[pass].Placeholder = "MySecurePassword" + // inputs[pass].CharLimit = 3 + inputs[pass].Width = 30 + inputs[pass].Prompt = "" + // inputs[pass].Validate = passValidator + + output := model{ + Tabs: []string{"Today", "Tomorrow", "Add"}, + loginInputs: inputs, + focused: 0, + err: nil, + // TabContent: []string{"ERROR?", "Mascara Tab", "Foundation Tab"}, + } + return output +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + + + + switch msg := msg.(type) { + case tea.KeyMsg: + if m.ActiveWindow == "login" { + switch keypress := msg.String(); keypress { + case "ctrl+c", "q": + return m, tea.Quit + case "enter": + if m.focused == len(m.loginInputs)-1 { + //TODO submit + + // try login -> store -> move to getting stuff + return m, tea.Quit + } + m.nextInput() + case "shift+tab", "up": + m.prevInput() + case "tab", "down": + m.nextInput() + } + for i := range m.loginInputs { + m.loginInputs[i].Blur() + } + m.loginInputs[m.focused].Focus() + + } + + switch keypress := msg.String(); keypress { + case "ctrl+c", "q": + return m, tea.Quit + // case "right", "l", "n", "tab": + //TODO add to help + case "tab": + if m.LoggedIn { + h, v := docStyle.GetFrameSize() + width, height, _ := term.GetSize(0) + switch m.ActiveWindow { + case "today": + m.ActiveWindow = "tomorrow" + m.TomorrowTab.SetSize(width-h, height-v) + case "tomorrow": + m.ActiveWindow = "today" + m.TodayTab.SetSize(width-h, height-v) + } + return m, nil + } + + // case "a" { + // TODO add new element + // return m, tea.Quit + // } + case "t": + // TODO add new element + return m, tea.Quit + } + + case tea.WindowSizeMsg: + h, v := docStyle.GetFrameSize() + switch m.ActiveWindow { + case "login": + m.TodayTab.SetSize(msg.Width-h, msg.Height-v) + case "today": + m.TodayTab.SetSize(msg.Width-h, msg.Height-v) + case "tomorrow": + m.TomorrowTab.SetSize(msg.Width-h, msg.Height-v) + } + + } + + + var cmd tea.Cmd + // text input + switch m.ActiveWindow { + case "login": + for i := range m.loginInputs { + m.loginInputs[i], cmd = m.loginInputs[i].Update(msg) + } + case "today": + m.TodayTab, cmd = m.TodayTab.Update(msg) + case "tomorrow": + m.TomorrowTab, cmd = m.TomorrowTab.Update(msg) + } + + + + // m.TodayTab, cmd = m.TodayTab.Update(msg) + // m.TomorrowTab, cmd = m.TomorrowTab.Update(msg) + + return m, cmd +} + +func (m model) View() string { + + var tabOutput string + + switch m.ActiveWindow { + case "login": + width, height, _ := term.GetSize(0) + width -=2 + height -=2 + loginStyle = loginStyle. + // Width(30). + // Height(height/5). + MarginTop(height/5). + MarginLeft(width/2-20) + // MarginRight(width/3) + tabOutput = loginStyle.Render(m.RenderLogin()) + // w, h := lipgloss.Size(tabOutput) + + + case "today": + tabOutput = docStyle.Render(m.TodayTab.View()) + case "tomorrow": + tabOutput = docStyle.Render(m.TomorrowTab.View()) + case "": + width, height, _ := term.GetSize(0) + width -=2 + height -=2 + loginStyle = loginStyle. + Width(width/3). + Height(1). + MarginTop(height/2). + MarginLeft(width/3+2). + MarginRight(width/3) + tabOutput = loginStyle.Render("ERROR") + } + // if m.activeTab == 0 { + // tabOutput = docStyle.Render(m.TodayTab.View()) + // } + // if m.activeTab == 1 { + // tabOutput = docStyle.Render(m.TomorrowTab.View()) + // } + + return tabOutput +} + +func (m model) RenderLogin() string { + + return fmt.Sprintf( + `%s + + +%s +%s + +%s +%s + +%s +%s + + +%s + `, + + inputStyle.Width(30).Align(lipgloss.Center).Render("Login"), + inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("WebDAV server URL"), + m.loginInputs[url].View(), + inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("Login"), + m.loginInputs[login].View(), + inputStyle.Width(30).Foreground(lipgloss.AdaptiveColor{Dark: "50"}).Render("Password"), + m.loginInputs[pass].View(), //TODO hide + inputStyle.Render("Continue ->"), + ) + // .Align(lipgloss.Center).BorderStyle(lipgloss.NormalBorder()) + +} + +func (m *model) nextInput() { + m.focused = (m.focused + 1) % len(m.loginInputs) +} + +// prevInput focuses the previous input field +func (m *model) prevInput() { + m.focused-- + // Wrap around + if m.focused < 0 { + m.focused = len(m.loginInputs) - 1 + } +}