diff --git a/config.example.json b/config.example.json index c082509..a12cabd 100644 --- a/config.example.json +++ b/config.example.json @@ -2,5 +2,7 @@ "MatrixRoom": "!exampleEXAMPLEexample:cyberia.club", "MatrixToken": "syt_exampleEXAMPLE_exampleEXAMPLE_example", "MatrixUsername": "@consensus-bot:cyberia.club", - "MatrixURL": "https://matrix.cyberia.club" -} \ No newline at end of file + "MatrixURL": "https://matrix.cyberia.club", + "WarningTime": "144h", + "ActivationTimeAfterWarning":"24h" +} diff --git a/main.go b/main.go index a16a221..8469cad 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "path" "path/filepath" "reflect" + "strconv" "strings" "time" @@ -25,10 +26,12 @@ const ( var config Config type Config struct { - MatrixRoom string - MatrixURL string - MatrixUsername string - MatrixToken string + MatrixRoom string + MatrixURL string + MatrixUsername string + MatrixToken string + WarningTime string + ActivationTimeAfterWarning string } type RecordCount struct { @@ -38,11 +41,12 @@ type RecordCount struct { } type Item struct { - author string - date time.Time - number string - name string - path string + author string + date time.Time + number string + name string + path string + currentTimer *time.Timer } func (i Item) Status() string { @@ -300,6 +304,47 @@ important: let all items sit for awhile prior to closure return nil } +func createTriggerOneDayAutoDecide(item Item) func() { + //TODO: add functions to extend the timer, and to cancel the timer if the item is closed before it fires + return func() { + records := countRecords(item.path) + if records.approvals != 0 && records.concerns == 0 { + handleClose(item.number, "approved") + + } + if records.concerns > 0 { + handleClose(item.number, "cancelled") + } + if records.approvals == 0 && records.concerns == 0 { + + openDays := time.Since(item.date).Hours() / 24 + msg := fmt.Sprintf("consensus item %s is %v days old... but nobody has approved or concerned yet. Let's give it another %s, shall we?", item.number, openDays, config.WarningTime) + err := sendMessage(msg) + if err != nil { + log.Println(fmt.Sprintf("Error (from notifFunc) sending message for item %s: %v", item.name, err)) + } + notifFunc := createNotifFunc(item) + warningTime, _ := time.ParseDuration(config.WarningTime) + time.AfterFunc(warningTime, notifFunc) + } + } +} + +func createNotifFunc(item Item) func() { + //TODO: add functions to extend the timer, and to cancel the timer if the item is closed before it fires + return func() { + openDays := time.Since(item.date).Hours() / 24 + msg := fmt.Sprintf("consensus item %s is now %v days old: %s\nThe item will auto-pass after %s if there is unanimous approval, and auto-fail otherwise! So, now is a good time to weigh in if you haven't yet and have an opinion!", item.number, openDays, item.name, config.ActivationTimeAfterWarning) + err := sendMessage(msg) + if err != nil { + log.Println(fmt.Sprintf("Error (from notifFunc) sending message for item %s: %v", item.name, err)) + } + triggerOneDayAutoDecide := createTriggerOneDayAutoDecide(item) + activationTime, _ := time.ParseDuration(config.ActivationTimeAfterWarning) + time.AfterFunc(activationTime, triggerOneDayAutoDecide) + } +} + func handleNew(event *gomatrix.Event) error { body, ok := event.Body() if !ok { @@ -330,6 +375,12 @@ func handleNew(event *gomatrix.Event) error { if err != nil { return err } + //automatically check-in after time specified in configuration, then after the other time specified in configuration + //approve if it has unanimous approval or cancel if any concerns + notifFunc := createNotifFunc(item) + warningTime, _ := time.ParseDuration(config.WarningTime) + time.AfterFunc(warningTime, notifFunc) + return nil } @@ -489,6 +540,11 @@ func handleClose(itemNumber string, status string) { sendMessage("please specify an item number. see !help") return } + + if !isValidItemNumber(itemNumber) { + return + } + itemPath := recordDir + "/" + itemNumber + ".txt" if !fileExists(itemPath) { @@ -523,12 +579,28 @@ func handleClose(itemNumber string, status string) { sendMessage(msg) } +func isValidItemNumber(itemNumberCandidate string) bool { + _, err := strconv.Atoi(itemNumberCandidate) + if err != nil { + err = sendMessage("hold yer horses there pard, that ain't no item number like I ever seen before...") + if err != nil { + log.Println(fmt.Sprintf("Error (from isValidItemNumber) sending message: %v", err)) + } + return false + } + return true +} + func handleShow(itemNumber string) { if strings.TrimSpace(itemNumber) == "" { sendMessage("please specify an item number. see !help") return } + if !isValidItemNumber(itemNumber) { + return + } + itemPath := recordDir + "/" + itemNumber + ".txt" if !fileExists(itemPath) { sendMessage("item specified does not exist") @@ -546,30 +618,49 @@ func handleShow(itemNumber string) { func handleComment(event *gomatrix.Event, itemNumber string, comment string, emoji string) { if strings.TrimSpace(itemNumber) == "" { - sendMessage("please specify an item number. see !help") + err := sendMessage("please specify an item number. see !help") + if err != nil { + log.Println(fmt.Sprintf("Error (from handleComment) sending messag: %v", err)) + } return } if strings.TrimSpace(comment) == "" { - sendMessage("please specify a comment. see !help") + err := sendMessage("please specify a comment. see !help") + if err != nil { + log.Println(fmt.Sprintf("Error (from handleComment) sending messag: %v", err)) + } + return + } + + if !isValidItemNumber(itemNumber) { return } itemPath := recordDir + "/" + itemNumber + ".txt" if !fileExists(itemPath) { - sendMessage("item specified does not exist") + err := sendMessage("item specified does not exist") + if err != nil { + log.Println(fmt.Sprintf("Error (from handleComment) sending messag: %v", err)) + } return } item, err := loadItemByPath(itemPath) if err != nil { - sendMessage("item not found: " + err.Error()) + err = sendMessage("item not found: " + err.Error()) + if err != nil { + log.Println(fmt.Sprintf("Error (from handleComment) sending messag: %v", err)) + } return } if !item.isActive() { - sendMessage(fmt.Sprintf("item %s (%s) is not active and may not be commented on", item.number, item.name)) + err = sendMessage(fmt.Sprintf("item %s (%s) is not active and may not be commented on", item.number, item.name)) + if err != nil { + log.Println(fmt.Sprintf("Error (from handleComment) sending messag: %v", err)) + } return }