From e11282c51960ff57d57ccbd60a7195a0a7906905 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 26 Mar 2020 21:37:12 +0000 Subject: [PATCH 01/43] added kvstore team name to args --- args.go | 7 +++++++ main.go | 1 + 2 files changed, 8 insertions(+) diff --git a/args.go b/args.go index baccdfe..6f94f74 100644 --- a/args.go +++ b/args.go @@ -20,6 +20,7 @@ func (b *bot) parseArgs(args []string) error { flags.StringVar(&cliConfig.LogConvIDStr, "log-convid", "", "sets the keybase chat1.ConvIDStr to log debugging to keybase chat.") flags.StringVar(&cliConfig.FeedbackConvIDStr, "feedback-convid", "", "sets the keybase chat1.ConvIDStr to send feedback to.") flags.StringVar(&cliConfig.FeedbackTeamAdvert, "feedback-team-advert", "", "sets the keybase team/channel to advertise feedback. @team#channel") + flags.StringVar(&cliConfig.KVStoreTeam, "kvstore-team", "", "sets the keybase team where kvstore values are stored") if err := flags.Parse(args[1:]); err != nil { return err } @@ -38,6 +39,9 @@ func (b *bot) parseArgs(args []string) error { if cliConfig.FeedbackTeamAdvert != "" { b.config.FeedbackTeamAdvert = cliConfig.FeedbackTeamAdvert } + if cliConfig.KVStoreTeam != "" { + b.config.KVStoreTeam = cliConfig.KVStoreTeam + } } // then print the running options @@ -48,6 +52,9 @@ func (b *bot) parseArgs(args []string) error { if b.config.FeedbackConvIDStr != "" { b.debug("Feedback enabled to %s and advertising %s", b.config.FeedbackConvIDStr, b.config.FeedbackTeamAdvert) } + if b.config.KVStoreTeam != "" { + b.debug("keybase kvstore enabled in @%s", b.config.KVStoreTeam) + } return nil } diff --git a/main.go b/main.go index d7c7b6f..34494e8 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ type botConfig struct { LogConvIDStr string `env:"BOT_LOG_CONVID" envDefault:""` FeedbackConvIDStr string `env:"BOT_FEEDBACK_CONVID" envDefault:""` FeedbackTeamAdvert string `env:"BOT_FEEDBACK_TEAM_ADVERT" envDefault:""` + KVStoreTeam string `env:"BOT_KVSTORE_TEAM" envDefault:""` } // hold reply information when needed From 8b2311a51b1de6fca7b7fd882ff8dae1bf2f389b Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 26 Mar 2020 23:00:00 +0000 Subject: [PATCH 02/43] updated deps --- go.mod | 1 + go.sum | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/go.mod b/go.mod index add3dc6..456b3a6 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/caarlos0/env v3.5.0+incompatible github.com/sethvargo/go-diceware v0.2.0 github.com/stretchr/testify v1.5.1 // indirect + github.com/ugorji/go v1.1.7 // indirect gopkg.in/yaml.v2 v2.2.4 // indirect samhofi.us/x/keybase v0.0.0-20200312153536-07f5168a6a29 ) diff --git a/go.sum b/go.sum index 57f6907..28f4362 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,10 @@ github.com/sethvargo/go-diceware v0.2.0/go.mod h1:II+37A5sTGAtg3zd/JqyVQ8qqAjSm/ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 473873be6c64f9fd94a8a89e584b90dde172e0ec Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 26 Mar 2020 23:00:09 +0000 Subject: [PATCH 03/43] inital kvstore get/put --- kvstore.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 kvstore.go diff --git a/kvstore.go b/kvstore.go new file mode 100644 index 0000000..9500a06 --- /dev/null +++ b/kvstore.go @@ -0,0 +1,16 @@ +package main + +// writeKV is an internal func that ensures KVStore values get written consistently +func (b *bot) writeKV(key string, value string) error { + _, err := b.k.KVPut(&b.config.KVStoreTeam, b.k.Username, key, value) + if err != nil { + return err + } + return nil +} + +// getGV is an internal function that ensures KVStore values are retreived consistently +func (b *bot) getKV(key string) (value string, revision int, err error) { + res, err := b.k.KVGet(&b.config.KVStoreTeam, b.k.Username, key) + return res.EntryValue, res.Revision, err +} From 13c971c63545312444f7cdc26da952c21c05482f Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Mon, 30 Mar 2020 22:21:25 +0000 Subject: [PATCH 04/43] initial kvstore functions --- commands.go | 54 +++++++++++++++++++++++++++++++++++++++++++++ go.mod | 6 ++++- go.sum | 15 +++++++++---- handlers.go | 12 ++++++++-- kvstore.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++------ main.go | 16 ++++++++++++++ types.go | 8 +++++++ utils.go | 33 ++++++++++++++++++++++++++++ 8 files changed, 193 insertions(+), 14 deletions(-) create mode 100644 types.go diff --git a/commands.go b/commands.go index c7b49fa..c99a73d 100644 --- a/commands.go +++ b/commands.go @@ -3,6 +3,7 @@ package main import ( "fmt" "log" + "net/url" "strings" "samhofi.us/x/keybase/types/chat1" @@ -40,3 +41,56 @@ func (b *bot) sendFeedback(convid chat1.ConvIDStr, mesgID chat1.MessageID, sende func (b *bot) sendWelcome(convid chat1.ConvIDStr) { b.k.SendMessageByConvID(convid, "Hello there!! I'm the Jitsi meeting bot, made by @haukened\nI can start Jitsi meetings right here in this chat!\nI can be activated in 2 ways:\n 1. `@jitsibot`\n 2.`!jitsi`\nYou can provide feedback to my humans using:\n 1. `@jitsibot feedback `\n 2. `!jitsibot feedback `\nYou can also join @jitsi_meet to talk about features, enhancements, or talk to live humans! Everyone is welcome!\nI also accept donations to offset hosting costs, just send some XLM to my wallet if you feel like it by typing `+5XLM@jitsibot`\nIf you ever need to see this message again, ask me for help or say hello to me!") } + +func (b *bot) setKValue(convid chat1.ConvIDStr, msgID chat1.MessageID, args []string) { + if args[0] != "set" { + return + } + switch len(args) { + case 3: + if args[1] == "url" { + // first validate the URL + u, err := url.ParseRequestURI(args[2]) + if err != nil { + b.k.ReplyByConvID(convid, msgID, "ERROR - `%s`", err) + return + } + // then make sure its HTTPS + if u.Scheme != "https" { + b.k.ReplyByConvID(convid, msgID, "ERROR - HTTPS Required") + return + } + // then get the current options + var opts ConvOptions + err = b.KVStoreGetStruct(convid, &opts) + if err != nil { + eid := b.logError(err) + b.k.ReactByConvID(convid, msgID, "Error %s", eid) + return + } + // then update the struct using only the scheme and hostname:port + if u.Port() != "" { + opts.CustomURL = fmt.Sprintf("%s://%s:%s/", u.Scheme, u.Hostname(), u.Port()) + } else { + opts.CustomURL = fmt.Sprintf("%s://%s/", u.Scheme, u.Hostname()) + } + // then write that back to kvstore, with revision + err = b.KVStorePutStruct(convid, opts) + if err != nil { + eid := b.logError(err) + b.k.ReactByConvID(convid, msgID, "ERROR %s", eid) + return + } + b.k.ReactByConvID(convid, msgID, "OK!") + return + } + default: + return + } +} + +func (b *bot) listKValue(convid chat1.ConvIDStr, msgID chat1.MessageID, args []string) { + if args[0] != "list" { + return + } +} diff --git a/go.mod b/go.mod index 456b3a6..0e1b834 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,13 @@ go 1.13 require ( github.com/caarlos0/env v3.5.0+incompatible + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.1.0 // indirect github.com/sethvargo/go-diceware v0.2.0 github.com/stretchr/testify v1.5.1 // indirect - github.com/ugorji/go v1.1.7 // indirect + github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf + github.com/ugorji/go/codec v1.1.7 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v2 v2.2.4 // indirect samhofi.us/x/keybase v0.0.0-20200312153536-07f5168a6a29 ) diff --git a/go.sum b/go.sum index 28f4362..8519edd 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,13 @@ github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yi github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/lithammer/shortuuid/v3 v3.0.4 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs= -github.com/lithammer/shortuuid/v3 v3.0.4/go.mod h1:RviRjexKqIzx/7r1peoAITm6m7gnif/h+0zmolKJjzw= +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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sethvargo/go-diceware v0.2.0 h1:3QzXGqUe0UR9y1XYSz1dxGS+fKtXOxRqqKjy+cG1yTI= @@ -13,12 +16,16 @@ github.com/sethvargo/go-diceware v0.2.0/go.mod h1:II+37A5sTGAtg3zd/JqyVQ8qqAjSm/ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 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-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/handlers.go b/handlers.go index 303cf91..7d4405c 100644 --- a/handlers.go +++ b/handlers.go @@ -49,7 +49,8 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { // Determine first if this is a command if strings.HasPrefix(m.Content.Text.Body, "!") || strings.HasPrefix(m.Content.Text.Body, "@") { // determine the root command - words := strings.Fields(m.Content.Text.Body) + body := strings.ToLower(m.Content.Text.Body) + words := strings.Fields(body) command := strings.Replace(words[0], "@", "", 1) command = strings.Replace(command, "!", "", 1) command = strings.ToLower(command) @@ -58,7 +59,14 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { nargs := len(args) switch command { case b.k.Username: - fallthrough + if nargs > 0 { + switch args[0] { + case "set": + b.setKValue(m.ConvID, m.Id, args) + case "list": + b.listKValue(m.ConvID, m.Id, args) + } + } case "jitsi": if nargs == 0 { b.setupMeeting(m.ConvID, m.Sender.Username, args, m.Channel.MembersType) diff --git a/kvstore.go b/kvstore.go index 9500a06..2c35940 100644 --- a/kvstore.go +++ b/kvstore.go @@ -1,16 +1,65 @@ package main -// writeKV is an internal func that ensures KVStore values get written consistently -func (b *bot) writeKV(key string, value string) error { - _, err := b.k.KVPut(&b.config.KVStoreTeam, b.k.Username, key, value) +import ( + "reflect" + + "samhofi.us/x/keybase/types/chat1" +) + +// mashals an interface to JSON and sends to kvstore +func (b *bot) KVStorePutStruct(convIDstr chat1.ConvIDStr, v interface{}) error { + // marshal the struct to JSON + kvstoreDataString, err := encodeStructToJSONString(v) + if err != nil { + return err + } + // put the string in kvstore + err = b.KVStorePut(string(convIDstr), getTypeName(v), kvstoreDataString) + if err != nil { + return err + } + return nil +} + +func (b *bot) KVStoreGetStruct(convIDstr chat1.ConvIDStr, v interface{}) error { + // get the string from kvstore + result, err := b.KVStoreGet(string(convIDstr), getTypeName(v)) + if err != nil { + return err + } + // if there was no result just return and the struct is unmodified + if result == "" { + return nil + } + // unmarshal the string into JSON + err = decodeJSONStringToStruct(v, result) if err != nil { return err } return nil } -// getGV is an internal function that ensures KVStore values are retreived consistently -func (b *bot) getKV(key string) (value string, revision int, err error) { - res, err := b.k.KVGet(&b.config.KVStoreTeam, b.k.Username, key) - return res.EntryValue, res.Revision, err +func (b *bot) KVStorePut(namespace string, key string, value string) error { + _, err := b.k.KVPut(&b.config.KVStoreTeam, namespace, key, value) + if err != nil { + return err + } + return nil +} + +func (b *bot) KVStoreGet(namespace string, key string) (string, error) { + kvResult, err := b.k.KVGet(&b.config.KVStoreTeam, namespace, key) + if err != nil { + return "", err + } + return kvResult.EntryValue, nil +} + +// getTypeName returns the name of a type, regardless of if its a pointer or not +func getTypeName(v interface{}) string { + t := reflect.TypeOf(v) + if t.Kind() == reflect.Ptr { + return t.Elem().Name() + } + return t.Name() } diff --git a/main.go b/main.go index 34494e8..7582da4 100644 --- a/main.go +++ b/main.go @@ -111,6 +111,22 @@ func (b *bot) run(args []string) error { b.k.ClearCommands() b.registerCommands() + // this is just for testing, and doesn't work yet + if err := b.KVStorePutStruct("test", &ConvOptions{ConvID: "test", CustomURL: "https://te.st:888"}); err != nil { + log.Printf("KV: %+v", err) + } + var vRes1 ConvOptions + if err := b.KVStoreGetStruct("test", &vRes1); err != nil { + log.Printf("KV: %+v", err) + } else { + fmt.Printf("VR: %+v\n", vRes1) + } + var vRes2 ConvOptions + if err := b.KVStoreGetStruct("test1", &vRes2); err != nil { + log.Printf("KV: %+v", err) + } else { + fmt.Printf("VR: %+v\n", vRes2) + } log.Println("Starting...") b.k.Run(b.handlers, &b.opts) return nil diff --git a/types.go b/types.go new file mode 100644 index 0000000..f14b6d4 --- /dev/null +++ b/types.go @@ -0,0 +1,8 @@ +package main + +// ConvOptions stores team specific options like custom servers +type ConvOptions struct { + ConvID string `json:"converation_id,omitempty"` + NotificationsEnabled bool `json:"notifications_enabled,omitempty"` + CustomURL string `json:"custom_url,omitempty"` +} diff --git a/utils.go b/utils.go index 8f3c80e..ef8ba5e 100644 --- a/utils.go +++ b/utils.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" + "github.com/teris-io/shortid" + "github.com/ugorji/go/codec" "samhofi.us/x/keybase/types/chat1" ) @@ -27,3 +29,34 @@ func getFeedbackExtendedDescription(bc botConfig) *chat1.UserBotExtendedDescript MobileBody: "Please note: Your feedback will be public!", } } + +func encodeStructToJSONString(v interface{}) (string, error) { + jh := codecHandle() + var bytes []byte + err := codec.NewEncoderBytes(&bytes, jh).Encode(v) + if err != nil { + return "", err + } + result := string(bytes) + return result, nil +} + +func decodeJSONStringToStruct(v interface{}, src string) error { + bytes := []byte(src) + jh := codecHandle() + return codec.NewDecoderBytes(bytes, jh).Decode(v) +} + +func codecHandle() *codec.JsonHandle { + var jh codec.JsonHandle + return &jh +} + +func (b *bot) logError(err error) string { + // generate the error id + eid := shortid.MustGenerate() + // send the error to the log + b.debug("`%s` - %s", eid, err) + // then return the error id for use + return eid +} From dd8552e733ac8aff7b136eade69979f4492d11ea Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Mon, 30 Mar 2020 22:28:00 +0000 Subject: [PATCH 05/43] updated deps --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0e1b834..4185378 100644 --- a/go.mod +++ b/go.mod @@ -12,5 +12,5 @@ require ( github.com/ugorji/go/codec v1.1.7 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v2 v2.2.4 // indirect - samhofi.us/x/keybase v0.0.0-20200312153536-07f5168a6a29 + samhofi.us/x/keybase v0.0.0-20200315012740-74fb4a152b35 ) diff --git a/go.sum b/go.sum index 8519edd..e9ad379 100644 --- a/go.sum +++ b/go.sum @@ -29,5 +29,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -samhofi.us/x/keybase v0.0.0-20200312153536-07f5168a6a29 h1:+yj8+O6C56QM8WGZYtDwBFxkdPT4UgVU+O9W+6N85kk= -samhofi.us/x/keybase v0.0.0-20200312153536-07f5168a6a29/go.mod h1:fcva80IUFyWcHtV4bBSzgKg07K6Rvuvi3GtGCLNGkyE= +samhofi.us/x/keybase v0.0.0-20200315012740-74fb4a152b35 h1:pnpx+34wna1ML9JxHEChf3lDSBOOEh91B5IDuNFUZYk= +samhofi.us/x/keybase v0.0.0-20200315012740-74fb4a152b35/go.mod h1:fcva80IUFyWcHtV4bBSzgKg07K6Rvuvi3GtGCLNGkyE= From 86217a5d6363aa797a6faed72024cee8d25861a4 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Mon, 30 Mar 2020 22:30:27 +0000 Subject: [PATCH 06/43] moved JSON to kvstore --- kvstore.go | 23 +++++++++++++++++++++++ utils.go | 23 ----------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/kvstore.go b/kvstore.go index 2c35940..4eafe7c 100644 --- a/kvstore.go +++ b/kvstore.go @@ -3,6 +3,7 @@ package main import ( "reflect" + "github.com/ugorji/go/codec" "samhofi.us/x/keybase/types/chat1" ) @@ -63,3 +64,25 @@ func getTypeName(v interface{}) string { } return t.Name() } + +func encodeStructToJSONString(v interface{}) (string, error) { + jh := codecHandle() + var bytes []byte + err := codec.NewEncoderBytes(&bytes, jh).Encode(v) + if err != nil { + return "", err + } + result := string(bytes) + return result, nil +} + +func decodeJSONStringToStruct(v interface{}, src string) error { + bytes := []byte(src) + jh := codecHandle() + return codec.NewDecoderBytes(bytes, jh).Decode(v) +} + +func codecHandle() *codec.JsonHandle { + var jh codec.JsonHandle + return &jh +} diff --git a/utils.go b/utils.go index ef8ba5e..b401642 100644 --- a/utils.go +++ b/utils.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/teris-io/shortid" - "github.com/ugorji/go/codec" "samhofi.us/x/keybase/types/chat1" ) @@ -30,28 +29,6 @@ func getFeedbackExtendedDescription(bc botConfig) *chat1.UserBotExtendedDescript } } -func encodeStructToJSONString(v interface{}) (string, error) { - jh := codecHandle() - var bytes []byte - err := codec.NewEncoderBytes(&bytes, jh).Encode(v) - if err != nil { - return "", err - } - result := string(bytes) - return result, nil -} - -func decodeJSONStringToStruct(v interface{}, src string) error { - bytes := []byte(src) - jh := codecHandle() - return codec.NewDecoderBytes(bytes, jh).Decode(v) -} - -func codecHandle() *codec.JsonHandle { - var jh codec.JsonHandle - return &jh -} - func (b *bot) logError(err error) string { // generate the error id eid := shortid.MustGenerate() From 631d5f5c0ec3b8c495e1013cb65cec18ec89d11a Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Mon, 30 Mar 2020 22:32:32 +0000 Subject: [PATCH 07/43] documenation --- kvstore.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kvstore.go b/kvstore.go index 4eafe7c..d4d3569 100644 --- a/kvstore.go +++ b/kvstore.go @@ -7,7 +7,7 @@ import ( "samhofi.us/x/keybase/types/chat1" ) -// mashals an interface to JSON and sends to kvstore +// KvStorePutStruct marshals an interface to JSON and sends to kvstore func (b *bot) KVStorePutStruct(convIDstr chat1.ConvIDStr, v interface{}) error { // marshal the struct to JSON kvstoreDataString, err := encodeStructToJSONString(v) @@ -22,6 +22,7 @@ func (b *bot) KVStorePutStruct(convIDstr chat1.ConvIDStr, v interface{}) error { return nil } +// KVStoreGetStruct gets a string from kvstore and unmarshals the JSON to a struct func (b *bot) KVStoreGetStruct(convIDstr chat1.ConvIDStr, v interface{}) error { // get the string from kvstore result, err := b.KVStoreGet(string(convIDstr), getTypeName(v)) @@ -40,6 +41,7 @@ func (b *bot) KVStoreGetStruct(convIDstr chat1.ConvIDStr, v interface{}) error { return nil } +// KVStorePut puts a string into kvstore given a key and namespace func (b *bot) KVStorePut(namespace string, key string, value string) error { _, err := b.k.KVPut(&b.config.KVStoreTeam, namespace, key, value) if err != nil { @@ -48,6 +50,7 @@ func (b *bot) KVStorePut(namespace string, key string, value string) error { return nil } +// KVStoreGet gets a string from kvstore given a key and namespace func (b *bot) KVStoreGet(namespace string, key string) (string, error) { kvResult, err := b.k.KVGet(&b.config.KVStoreTeam, namespace, key) if err != nil { From cea5d393874612fbc00a944dc0d16fc9eadfa857 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 14:35:43 +0000 Subject: [PATCH 08/43] moved botConfig to types --- main.go | 6 ------ types.go | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index 7582da4..c115322 100644 --- a/main.go +++ b/main.go @@ -32,12 +32,6 @@ type botConfig struct { KVStoreTeam string `env:"BOT_KVSTORE_TEAM" envDefault:""` } -// hold reply information when needed -type botReply struct { - convID chat1.ConvIDStr - msgID chat1.MessageID -} - // Debug provides printing only when --debug flag is set or BOT_DEBUG env var is set func (b *bot) debug(s string, a ...interface{}) { if b.config.Debug { diff --git a/types.go b/types.go index f14b6d4..0b10799 100644 --- a/types.go +++ b/types.go @@ -1,5 +1,13 @@ package main +import "samhofi.us/x/keybase/types/chat1" + +// hold reply information when needed +type botReply struct { + convID chat1.ConvIDStr + msgID chat1.MessageID +} + // ConvOptions stores team specific options like custom servers type ConvOptions struct { ConvID string `json:"converation_id,omitempty"` From 9ea12ab5f54f197ae0bdc7f3436ef4c22ce8e9af Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 14:36:46 +0000 Subject: [PATCH 09/43] removed kvstore testing code --- main.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/main.go b/main.go index c115322..563f8f5 100644 --- a/main.go +++ b/main.go @@ -105,22 +105,6 @@ func (b *bot) run(args []string) error { b.k.ClearCommands() b.registerCommands() - // this is just for testing, and doesn't work yet - if err := b.KVStorePutStruct("test", &ConvOptions{ConvID: "test", CustomURL: "https://te.st:888"}); err != nil { - log.Printf("KV: %+v", err) - } - var vRes1 ConvOptions - if err := b.KVStoreGetStruct("test", &vRes1); err != nil { - log.Printf("KV: %+v", err) - } else { - fmt.Printf("VR: %+v\n", vRes1) - } - var vRes2 ConvOptions - if err := b.KVStoreGetStruct("test1", &vRes2); err != nil { - log.Printf("KV: %+v", err) - } else { - fmt.Printf("VR: %+v\n", vRes2) - } log.Println("Starting...") b.k.Run(b.handlers, &b.opts) return nil From c08ded2a66557f9c597f77fef67f01c848cc0540 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 15:16:12 +0000 Subject: [PATCH 10/43] moved payments to commands --- commands.go | 16 ++++++++++++++++ handlers.go | 15 ++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/commands.go b/commands.go index 23b943e..b152243 100644 --- a/commands.go +++ b/commands.go @@ -9,6 +9,22 @@ import ( "samhofi.us/x/keybase/types/chat1" ) +func (b *bot) handlePayment(m chat1.MsgSummary) { + // there can be multiple payments on each message, iterate them + for _, payment := range m.Content.Text.Payments { + if strings.Contains(payment.PaymentText, b.k.Username) { + // if the payment is successful put log the payment for wallet closure + if payment.Result.ResultTyp__ == 0 && payment.Result.Error__ == nil { + var replyInfo = botReply{convID: m.ConvID, msgID: m.Id} + b.payments[*payment.Result.Sent__] = replyInfo + } else { + // if the payment fails, be sad + b.k.ReactByConvID(m.ConvID, m.Id, ":cry:") + } + } + } +} + func (b *bot) setupMeeting(convid chat1.ConvIDStr, sender string, args []string, membersType string) { b.debug("command recieved in conversation %s", convid) meeting, err := newJitsiMeetingSimple() diff --git a/handlers.go b/handlers.go index 7d4405c..6baa9e5 100644 --- a/handlers.go +++ b/handlers.go @@ -32,19 +32,8 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { } // if this chat message is a payment, add it to the bot payments if m.Content.Text.Payments != nil { - // there can be multiple payments on each message, iterate them - for _, payment := range m.Content.Text.Payments { - if strings.Contains(payment.PaymentText, b.k.Username) { - // if the payment is successful put log the payment for wallet closure - if payment.Result.ResultTyp__ == 0 && payment.Result.Error__ == nil { - var replyInfo = botReply{convID: m.ConvID, msgID: m.Id} - b.payments[*payment.Result.Sent__] = replyInfo - } else { - // if the payment fails, be sad - b.k.ReactByConvID(m.ConvID, m.Id, ":cry:") - } - } - } + b.handlePayment(m) + return } // Determine first if this is a command if strings.HasPrefix(m.Content.Text.Body, "!") || strings.HasPrefix(m.Content.Text.Body, "@") { From cbf091a900e792b9c4632aaf9d6fea53ef0a5cd1 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 15:18:12 +0000 Subject: [PATCH 11/43] documentation --- commands.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commands.go b/commands.go index b152243..b652aec 100644 --- a/commands.go +++ b/commands.go @@ -9,6 +9,7 @@ import ( "samhofi.us/x/keybase/types/chat1" ) +// handlePayment controls how the bot reacts to wallet payments in chat func (b *bot) handlePayment(m chat1.MsgSummary) { // there can be multiple payments on each message, iterate them for _, payment := range m.Content.Text.Payments { From 0c268326d7596863c62c3486e826994a55ac5b5a Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 15:19:19 +0000 Subject: [PATCH 12/43] removed unused global --- main.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/main.go b/main.go index 563f8f5..4ec2272 100644 --- a/main.go +++ b/main.go @@ -10,9 +10,6 @@ import ( "samhofi.us/x/keybase/types/stellar1" ) -// this global controls debug printing -var debug bool - // Bot holds the necessary information for the bot to work. type bot struct { k *keybase.Keybase From 26f8b2b2a62118b01e2b1d87ace563e5e8e05119 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 15:53:35 +0000 Subject: [PATCH 13/43] made logging more generic --- main.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 4ec2272..6822099 100644 --- a/main.go +++ b/main.go @@ -32,25 +32,21 @@ type botConfig struct { // Debug provides printing only when --debug flag is set or BOT_DEBUG env var is set func (b *bot) debug(s string, a ...interface{}) { if b.config.Debug { - log.Printf(s, a...) - if b.config.LogConvIDStr != "" { - b.logToChat(s, a...) - } + b.log(s, a...) } } -// logToChat will send this message to the keybase chat configured in b.logConv -func (b *bot) logToChat(s string, a ...interface{}) { - // if the ConvIdStr isn't blank try to log +// logToChat will send this message to the keybase chat configured in b.config.LogConvIDStr +func (b *bot) log(s string, a ...interface{}) { + // if the ConvIdStr isn't blank try to log to chat if b.config.LogConvIDStr != "" { // if you can't send the message, log the error to stdout if _, err := b.k.SendMessageByConvID(chat1.ConvIDStr(b.config.LogConvIDStr), s, a...); err != nil { log.Printf("Unable to log to keybase chat: %s", err) } - } else { - // otherwise (and you shouldn't be here but....) log it to stdout - log.Println("Unable to log to keybase chat, logging ConvIDStr is not set") } + // and then log it to stdout + log.Printf(s, a...) } // newBot returns a new empty bot From 04b79c7ad7b254b9f8ac9dec00c816fce9c3aec4 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 15:56:56 +0000 Subject: [PATCH 14/43] added logging to payments --- commands.go | 1 + 1 file changed, 1 insertion(+) diff --git a/commands.go b/commands.go index b652aec..452a9d9 100644 --- a/commands.go +++ b/commands.go @@ -18,6 +18,7 @@ func (b *bot) handlePayment(m chat1.MsgSummary) { if payment.Result.ResultTyp__ == 0 && payment.Result.Error__ == nil { var replyInfo = botReply{convID: m.ConvID, msgID: m.Id} b.payments[*payment.Result.Sent__] = replyInfo + b.log("payment recieved %s", payment.PaymentText) } else { // if the payment fails, be sad b.k.ReactByConvID(m.ConvID, m.Id, ":cry:") From 2c89e9510ecbca184ed56be5f06695dec1edd205 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 16:00:41 +0000 Subject: [PATCH 15/43] changed to use log instead of debug --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index b401642..c155fba 100644 --- a/utils.go +++ b/utils.go @@ -33,7 +33,7 @@ func (b *bot) logError(err error) string { // generate the error id eid := shortid.MustGenerate() // send the error to the log - b.debug("`%s` - %s", eid, err) + b.log("`%s` - %s", eid, err) // then return the error id for use return eid } From fc8aaf69d13a3acd33699f4cbf9e3b665b74d15d Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 16:01:07 +0000 Subject: [PATCH 16/43] changed setupMeeting to handleMeeting using new handler syntax --- commands.go | 14 +++++++------- handlers.go | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index 452a9d9..cedbf8e 100644 --- a/commands.go +++ b/commands.go @@ -27,17 +27,17 @@ func (b *bot) handlePayment(m chat1.MsgSummary) { } } -func (b *bot) setupMeeting(convid chat1.ConvIDStr, sender string, args []string, membersType string) { - b.debug("command recieved in conversation %s", convid) +func (b *bot) handleMeeting(m chat1.MsgSummary) { + b.debug("command recieved in conversation %s", m.ConvID) meeting, err := newJitsiMeetingSimple() if err != nil { - log.Println(err) - message := fmt.Sprintf("@%s - I'm sorry, i'm not sure what happened... I was unable to set up a new meeting.\nI've written the appropriate logs and notified my humans.", sender) - b.k.SendMessageByConvID(convid, message) + eid := b.logError(err) + message := fmt.Sprintf("@%s - I'm sorry, i'm not sure what happened... I was unable to set up a new meeting.\nI've written the appropriate logs and notified my humans. Please reference Error ID %s", m.Sender.Username, eid) + b.k.SendMessageByConvID(m.ConvID, message) return } - message := fmt.Sprintf("@%s here's your meeting: %s", sender, meeting.getURL()) - b.k.SendMessageByConvID(convid, message) + message := fmt.Sprintf("@%s here's your meeting: %s", m.Sender.Username, meeting.getURL()) + b.k.SendMessageByConvID(m.ConvID, message) } func (b *bot) sendFeedback(convid chat1.ConvIDStr, mesgID chat1.MessageID, sender string, args []string) { diff --git a/handlers.go b/handlers.go index 6baa9e5..9bb575e 100644 --- a/handlers.go +++ b/handlers.go @@ -58,13 +58,13 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { } case "jitsi": if nargs == 0 { - b.setupMeeting(m.ConvID, m.Sender.Username, args, m.Channel.MembersType) + b.handleMeeting(m) } else if nargs >= 1 { // pop the subcommand off the front of the list subcommand, args := args[0], args[1:] switch subcommand { case "meet": - b.setupMeeting(m.ConvID, m.Sender.Username, args, m.Channel.MembersType) + b.handleMeeting(m) case "feedback": b.sendFeedback(m.ConvID, m.Id, m.Sender.Username, args) case "hello": From fbb2d7e19dc21d0473a92f3a757abb309a045f49 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 16:30:06 +0000 Subject: [PATCH 17/43] changed to handleFeedback and new convention --- commands.go | 19 ++++++++++--------- handlers.go | 6 ++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/commands.go b/commands.go index cedbf8e..bff3006 100644 --- a/commands.go +++ b/commands.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "log" "net/url" "strings" @@ -40,19 +39,21 @@ func (b *bot) handleMeeting(m chat1.MsgSummary) { b.k.SendMessageByConvID(m.ConvID, message) } -func (b *bot) sendFeedback(convid chat1.ConvIDStr, mesgID chat1.MessageID, sender string, args []string) { - b.debug("feedback recieved in %s", convid) +func (b *bot) handleFeedback(m chat1.MsgSummary) { + b.log("feedback recieved in %s", m.ConvID) if b.config.FeedbackConvIDStr != "" { - feedback := strings.Join(args, " ") + args := strings.Fields(m.Content.Text.Body) + feedback := strings.Join(args[2:], " ") fcID := chat1.ConvIDStr(b.config.FeedbackConvIDStr) - if _, err := b.k.SendMessageByConvID(fcID, "Feedback from @%s:\n```%s```", sender, feedback); err != nil { - b.k.ReplyByConvID(convid, mesgID, "I'm sorry, I was unable to send your feedback because my benevolent overlords have not set a destination for feedback. :sad:") - log.Printf("Unable to send feedback: %s", err) + if _, err := b.k.SendMessageByConvID(fcID, "Feedback from @%s:\n```%s```", m.Sender.Username, feedback); err != nil { + eid := b.logError(err) + b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) } else { - b.k.ReplyByConvID(convid, mesgID, "Thanks! Your feedback has been sent to my human overlords!") + b.k.ReplyByConvID(m.ConvID, m.Id, "Thanks! Your feedback has been sent to my human overlords!") } } else { - b.debug("feedback not enabled. set --feedback-convid or BOT_FEEDBACK_CONVID") + b.k.ReplyByConvID(m.ConvID, m.Id, "I'm sorry, I was unable to send your feedback because my benevolent overlords have not set a destination for feedback. :sob:") + b.log("user tried to send feedback, but feedback is not enabled. set --feedback-convid or BOT_FEEDBACK_CONVID") } } diff --git a/handlers.go b/handlers.go index 9bb575e..65aba1f 100644 --- a/handlers.go +++ b/handlers.go @@ -60,13 +60,11 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { if nargs == 0 { b.handleMeeting(m) } else if nargs >= 1 { - // pop the subcommand off the front of the list - subcommand, args := args[0], args[1:] - switch subcommand { + switch args[0] { case "meet": b.handleMeeting(m) case "feedback": - b.sendFeedback(m.ConvID, m.Id, m.Sender.Username, args) + b.handleFeedback(m) case "hello": fallthrough case "help": From 69f59711184ce41979796a38b51e731ac5fffc58 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 16:33:19 +0000 Subject: [PATCH 18/43] documentation --- commands.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index bff3006..c3c4078 100644 --- a/commands.go +++ b/commands.go @@ -26,8 +26,11 @@ func (b *bot) handlePayment(m chat1.MsgSummary) { } } +// handleMeeting starts a new jitsi meeting func (b *bot) handleMeeting(m chat1.MsgSummary) { b.debug("command recieved in conversation %s", m.ConvID) + // currently we aren't sending dial-in information, so don't get it just generate the name + // use the simple method meeting, err := newJitsiMeetingSimple() if err != nil { eid := b.logError(err) @@ -35,10 +38,10 @@ func (b *bot) handleMeeting(m chat1.MsgSummary) { b.k.SendMessageByConvID(m.ConvID, message) return } - message := fmt.Sprintf("@%s here's your meeting: %s", m.Sender.Username, meeting.getURL()) - b.k.SendMessageByConvID(m.ConvID, message) + b.k.SendMessageByConvID(m.ConvID, "@%s here's your meeting: %s", m.Sender.Username, meeting.getURL()) } +// handleFeedback sends feedback to a keybase chat, if configured func (b *bot) handleFeedback(m chat1.MsgSummary) { b.log("feedback recieved in %s", m.ConvID) if b.config.FeedbackConvIDStr != "" { From 4c9304b8c297e8a628c2bea0fd4b2f4e86fc5693 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 16:39:03 +0000 Subject: [PATCH 19/43] updated handleWelcome() --- commands.go | 10 ++++++++-- handlers.go | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index c3c4078..7192cd3 100644 --- a/commands.go +++ b/commands.go @@ -60,8 +60,14 @@ func (b *bot) handleFeedback(m chat1.MsgSummary) { } } -func (b *bot) sendWelcome(convid chat1.ConvIDStr) { - b.k.SendMessageByConvID(convid, "Hello there!! I'm the Jitsi meeting bot, made by @haukened\nI can start Jitsi meetings right here in this chat!\nI can be activated in 2 ways:\n 1. `@jitsibot`\n 2.`!jitsi`\nYou can provide feedback to my humans using:\n 1. `@jitsibot feedback `\n 2. `!jitsibot feedback `\nYou can also join @jitsi_meet to talk about features, enhancements, or talk to live humans! Everyone is welcome!\nI also accept donations to offset hosting costs, just send some XLM to my wallet if you feel like it by typing `+5XLM@jitsibot`\nIf you ever need to see this message again, ask me for help or say hello to me!") +/* +**** this function is a special case on parameters as it must be called from 2 handlers which +**** get their information from separate types. As a result we're only passing the conversation id. +**** because of this we can't wrap handleWelcome with permissions, not that you'd want to. + */ +// handleWelcome sends the welcome message to new conversations +func (b *bot) handleWelcome(id chat1.ConvIDStr) { + b.k.SendMessageByConvID(id, "Hello there!! I'm the Jitsi meeting bot, made by @haukened\nI can start Jitsi meetings right here in this chat!\nI can be activated in 2 ways:\n 1. `@jitsibot`\n 2.`!jitsi`\nYou can provide feedback to my humans using:\n 1. `@jitsibot feedback `\n 2. `!jitsibot feedback `\nYou can also join @jitsi_meet to talk about features, enhancements, or talk to live humans! Everyone is welcome!\nI also accept donations to offset hosting costs, just send some XLM to my wallet if you feel like it by typing `+5XLM@jitsibot`\nIf you ever need to see this message again, ask me for help or say hello to me!") } func (b *bot) setKValue(convid chat1.ConvIDStr, msgID chat1.MessageID, args []string) { diff --git a/handlers.go b/handlers.go index 65aba1f..e0d995b 100644 --- a/handlers.go +++ b/handlers.go @@ -68,7 +68,7 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { case "hello": fallthrough case "help": - b.sendWelcome(m.ConvID) + b.handleWelcome(m.ConvID) default: return } @@ -90,7 +90,7 @@ func (b *bot) convHandler(m chat1.ConvSummary) { default: b.debug("New convID found %s, sending welcome message.", m.Id) } - b.sendWelcome(m.Id) + b.handleWelcome(m.Id) } // this handles wallet events, like when someone send you money in chat From 1890ee05855c937f1bfa27c84a69edee105f33da Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 16:43:15 +0000 Subject: [PATCH 20/43] re-ordered and comments --- commands.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/commands.go b/commands.go index 7192cd3..35067a3 100644 --- a/commands.go +++ b/commands.go @@ -8,6 +8,22 @@ import ( "samhofi.us/x/keybase/types/chat1" ) +/* +**** this function is a special case on parameters as it must be called from 2 handlers which +**** get their information from separate types. As a result we're only passing the conversation id. +**** because of this we can't wrap handleWelcome with permissions, not that you'd want to. + */ + +// handleWelcome sends the welcome message to new conversations +func (b *bot) handleWelcome(id chat1.ConvIDStr) { + b.k.SendMessageByConvID(id, "Hello there!! I'm the Jitsi meeting bot, made by @haukened\nI can start Jitsi meetings right here in this chat!\nI can be activated in 2 ways:\n 1. `@jitsibot`\n 2.`!jitsi`\nYou can provide feedback to my humans using:\n 1. `@jitsibot feedback `\n 2. `!jitsibot feedback `\nYou can also join @jitsi_meet to talk about features, enhancements, or talk to live humans! Everyone is welcome!\nI also accept donations to offset hosting costs, just send some XLM to my wallet if you feel like it by typing `+5XLM@jitsibot`\nIf you ever need to see this message again, ask me for help or say hello to me!") +} + +/* +**** all other commands here-below should only accept a single argument of type chat1.MsgSummary +**** in order to be compliant with the permissions wrapper. Anything not should be explicitly notated. + */ + // handlePayment controls how the bot reacts to wallet payments in chat func (b *bot) handlePayment(m chat1.MsgSummary) { // there can be multiple payments on each message, iterate them @@ -60,16 +76,6 @@ func (b *bot) handleFeedback(m chat1.MsgSummary) { } } -/* -**** this function is a special case on parameters as it must be called from 2 handlers which -**** get their information from separate types. As a result we're only passing the conversation id. -**** because of this we can't wrap handleWelcome with permissions, not that you'd want to. - */ -// handleWelcome sends the welcome message to new conversations -func (b *bot) handleWelcome(id chat1.ConvIDStr) { - b.k.SendMessageByConvID(id, "Hello there!! I'm the Jitsi meeting bot, made by @haukened\nI can start Jitsi meetings right here in this chat!\nI can be activated in 2 ways:\n 1. `@jitsibot`\n 2.`!jitsi`\nYou can provide feedback to my humans using:\n 1. `@jitsibot feedback `\n 2. `!jitsibot feedback `\nYou can also join @jitsi_meet to talk about features, enhancements, or talk to live humans! Everyone is welcome!\nI also accept donations to offset hosting costs, just send some XLM to my wallet if you feel like it by typing `+5XLM@jitsibot`\nIf you ever need to see this message again, ask me for help or say hello to me!") -} - func (b *bot) setKValue(convid chat1.ConvIDStr, msgID chat1.MessageID, args []string) { if args[0] != "set" { return From 150e0b63ab5f7004ccfbb00c7a58bf500713ad41 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 16:55:50 +0000 Subject: [PATCH 21/43] set command updated to handleSetCommand --- commands.go | 23 +++++++++++++---------- handlers.go | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/commands.go b/commands.go index 35067a3..c8d8b40 100644 --- a/commands.go +++ b/commands.go @@ -76,30 +76,33 @@ func (b *bot) handleFeedback(m chat1.MsgSummary) { } } -func (b *bot) setKValue(convid chat1.ConvIDStr, msgID chat1.MessageID, args []string) { - if args[0] != "set" { +func (b *bot) handleSetCommand(m chat1.MsgSummary) { + // first normalize the text and extract the arguments + args := strings.Fields(strings.ToLower(m.Content.Text.Body)) + if args[1] != "set" { return } switch len(args) { case 3: - if args[1] == "url" { + if args[2] == "url" { // first validate the URL u, err := url.ParseRequestURI(args[2]) if err != nil { - b.k.ReplyByConvID(convid, msgID, "ERROR - `%s`", err) + eid := b.logError(err) + b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) return } // then make sure its HTTPS if u.Scheme != "https" { - b.k.ReplyByConvID(convid, msgID, "ERROR - HTTPS Required") + b.k.ReactByConvID(m.ConvID, m.Id, "ERROR: HTTPS Required") return } // then get the current options var opts ConvOptions - err = b.KVStoreGetStruct(convid, &opts) + err = b.KVStoreGetStruct(m.ConvID, &opts) if err != nil { eid := b.logError(err) - b.k.ReactByConvID(convid, msgID, "Error %s", eid) + b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) return } // then update the struct using only the scheme and hostname:port @@ -109,13 +112,13 @@ func (b *bot) setKValue(convid chat1.ConvIDStr, msgID chat1.MessageID, args []st opts.CustomURL = fmt.Sprintf("%s://%s/", u.Scheme, u.Hostname()) } // then write that back to kvstore, with revision - err = b.KVStorePutStruct(convid, opts) + err = b.KVStorePutStruct(m.ConvID, opts) if err != nil { eid := b.logError(err) - b.k.ReactByConvID(convid, msgID, "ERROR %s", eid) + b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) return } - b.k.ReactByConvID(convid, msgID, "OK!") + b.k.ReactByConvID(m.ConvID, m.Id, "OK!") return } default: diff --git a/handlers.go b/handlers.go index e0d995b..20ded34 100644 --- a/handlers.go +++ b/handlers.go @@ -51,7 +51,7 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { if nargs > 0 { switch args[0] { case "set": - b.setKValue(m.ConvID, m.Id, args) + b.handleSetCommand(m) case "list": b.listKValue(m.ConvID, m.Id, args) } From 1806f87e401cc9b46df0d389077d84a019b59a96 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 16:57:00 +0000 Subject: [PATCH 22/43] handleListCommand updated --- commands.go | 4 +++- handlers.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index c8d8b40..5b263f8 100644 --- a/commands.go +++ b/commands.go @@ -126,7 +126,9 @@ func (b *bot) handleSetCommand(m chat1.MsgSummary) { } } -func (b *bot) listKValue(convid chat1.ConvIDStr, msgID chat1.MessageID, args []string) { +func (b *bot) handleListCommand(m chat1.MsgSummary) { + // first normalize the text and extract the arguments + args := strings.Fields(strings.ToLower(m.Content.Text.Body)) if args[0] != "list" { return } diff --git a/handlers.go b/handlers.go index 20ded34..d611d85 100644 --- a/handlers.go +++ b/handlers.go @@ -53,7 +53,7 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { case "set": b.handleSetCommand(m) case "list": - b.listKValue(m.ConvID, m.Id, args) + b.handleListCommand(m) } } case "jitsi": From eebc610720a3deaeafdbc9ab3b039a25df357d3b Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 16:58:34 +0000 Subject: [PATCH 23/43] documentation --- commands.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/commands.go b/commands.go index 5b263f8..6503d61 100644 --- a/commands.go +++ b/commands.go @@ -76,6 +76,7 @@ func (b *bot) handleFeedback(m chat1.MsgSummary) { } } +// handleSetCommand processes all settings SET calls func (b *bot) handleSetCommand(m chat1.MsgSummary) { // first normalize the text and extract the arguments args := strings.Fields(strings.ToLower(m.Content.Text.Body)) @@ -126,6 +127,7 @@ func (b *bot) handleSetCommand(m chat1.MsgSummary) { } } +// handleListCommand lists settings for the conversation func (b *bot) handleListCommand(m chat1.MsgSummary) { // first normalize the text and extract the arguments args := strings.Fields(strings.ToLower(m.Content.Text.Body)) From 0e4cf43c51db4cc17c245c1aef050ec69f93bfcd Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 17:00:36 +0000 Subject: [PATCH 24/43] added description --- utils.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils.go b/utils.go index c155fba..43a6ba4 100644 --- a/utils.go +++ b/utils.go @@ -14,6 +14,7 @@ func p(b interface{}) string { return string(s) } +// getFeedbackExtendedDescription returns the team name that feedback will be posted to, if configured func getFeedbackExtendedDescription(bc botConfig) *chat1.UserBotExtendedDescription { if bc.FeedbackTeamAdvert != "" { return &chat1.UserBotExtendedDescription{ From 755559d0bb137fddb40d51c91165dd6be0d44c4c Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 17:01:45 +0000 Subject: [PATCH 25/43] moved logging to main --- main.go | 13 ++++++++++++- utils.go | 10 ---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index 6822099..33098cb 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "log" "os" + "github.com/teris-io/shortid" "samhofi.us/x/keybase" "samhofi.us/x/keybase/types/chat1" "samhofi.us/x/keybase/types/stellar1" @@ -29,13 +30,23 @@ type botConfig struct { KVStoreTeam string `env:"BOT_KVSTORE_TEAM" envDefault:""` } -// Debug provides printing only when --debug flag is set or BOT_DEBUG env var is set +// debug provides printing only when --debug flag is set or BOT_DEBUG env var is set func (b *bot) debug(s string, a ...interface{}) { if b.config.Debug { b.log(s, a...) } } +// logError generates an error id and returns it for error reporting, and writes the error to logging locations +func (b *bot) logError(err error) string { + // generate the error id + eid := shortid.MustGenerate() + // send the error to the log + b.log("`%s` - %s", eid, err) + // then return the error id for use + return eid +} + // logToChat will send this message to the keybase chat configured in b.config.LogConvIDStr func (b *bot) log(s string, a ...interface{}) { // if the ConvIdStr isn't blank try to log to chat diff --git a/utils.go b/utils.go index 43a6ba4..f3db1ec 100644 --- a/utils.go +++ b/utils.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" - "github.com/teris-io/shortid" "samhofi.us/x/keybase/types/chat1" ) @@ -29,12 +28,3 @@ func getFeedbackExtendedDescription(bc botConfig) *chat1.UserBotExtendedDescript MobileBody: "Please note: Your feedback will be public!", } } - -func (b *bot) logError(err error) string { - // generate the error id - eid := shortid.MustGenerate() - // send the error to the log - b.log("`%s` - %s", eid, err) - // then return the error id for use - return eid -} From f30bd2a4407deea9cc47bed29675c89ab61f7a02 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 18:30:53 +0000 Subject: [PATCH 26/43] added hasCommandPrefix --- utils.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/utils.go b/utils.go index f3db1ec..c4f7771 100644 --- a/utils.go +++ b/utils.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "fmt" + "strings" "samhofi.us/x/keybase/types/chat1" ) @@ -28,3 +29,21 @@ func getFeedbackExtendedDescription(bc botConfig) *chat1.UserBotExtendedDescript MobileBody: "Please note: Your feedback will be public!", } } + +// hasCommandPrefix determines if the command matches either command or name variant +func hasCommandPrefix(s string, baseCommand string, botName string, subCommands string) bool { + // if this is actually a command + if strings.HasPrefix(s, "!") || strings.HasPrefix(s, "@") { + // generate the two possible command variants + botCommand := fmt.Sprintf("%s %s", baseCommand, subCommands) + nameCommand := fmt.Sprintf("%s %s", botName, subCommands) + // then remove the ! or @ from the string + s = strings.Replace(s, "!", "", 1) + s = strings.Replace(s, "@", "", 1) + // then check if either command variant is a match to the subCommands sent + if strings.HasPrefix(s, botCommand) || strings.HasPrefix(s, nameCommand) { + return true + } + } + return false +} From f475bddacd97b616c436a7dbb307d89299a3f550 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 20:29:48 +0000 Subject: [PATCH 27/43] added isRoot Command --- utils.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/utils.go b/utils.go index c4f7771..c593c21 100644 --- a/utils.go +++ b/utils.go @@ -47,3 +47,13 @@ func hasCommandPrefix(s string, baseCommand string, botName string, subCommands } return false } + +// isRootCommand determines if the command is the root command or name with no arguments +func isRootCommand(s string, baseCommand string, botName string) bool { + botCommand := fmt.Sprintf("!%s", baseCommand) + nameCommand := fmt.Sprintf("@%s", botName) + if s == botCommand || s == nameCommand { + return true + } + return false +} From a428d39fffec2163a0adae61a3710d9d1a9e879f Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 20:30:20 +0000 Subject: [PATCH 28/43] added bot.cmd() function to reliably return configured cmd --- main.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.go b/main.go index 33098cb..260f6f8 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,11 @@ type bot struct { config botConfig } +// this allows you to change the command the bot listens to (in additon to its username) +func (b *bot) cmd() string { + return "jitsi" +} + // botConfig hold env and cli flags and options // fields must be exported for package env (reflect) to work type botConfig struct { From 4477f62b96e60dca37df3bb674f7b135ccc58373 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 20:30:32 +0000 Subject: [PATCH 29/43] new prettier handler --- handlers.go | 73 +++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/handlers.go b/handlers.go index d611d85..334570e 100644 --- a/handlers.go +++ b/handlers.go @@ -35,45 +35,42 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { b.handlePayment(m) return } - // Determine first if this is a command + + // if its not a payment evaluate if this is a command at all if strings.HasPrefix(m.Content.Text.Body, "!") || strings.HasPrefix(m.Content.Text.Body, "@") { - // determine the root command - body := strings.ToLower(m.Content.Text.Body) - words := strings.Fields(body) - command := strings.Replace(words[0], "@", "", 1) - command = strings.Replace(command, "!", "", 1) - command = strings.ToLower(command) - // create the args - args := words[1:] - nargs := len(args) - switch command { - case b.k.Username: - if nargs > 0 { - switch args[0] { - case "set": - b.handleSetCommand(m) - case "list": - b.handleListCommand(m) - } - } - case "jitsi": - if nargs == 0 { - b.handleMeeting(m) - } else if nargs >= 1 { - switch args[0] { - case "meet": - b.handleMeeting(m) - case "feedback": - b.handleFeedback(m) - case "hello": - fallthrough - case "help": - b.handleWelcome(m.ConvID) - default: - return - } - } - default: + // first return if its not a command for me + if !strings.Contains(m.Content.Text.Body, b.cmd()) && !strings.Contains(m.Content.Text.Body, b.k.Username) { + return + } + // then check if this is the root command + if isRootCommand(m.Content.Text.Body, b.cmd(), b.k.Username) { + b.handleMeeting(m) + return + } + // then check sub-command variants + // feedback + if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "feedback") { + b.handleFeedback(m) + return + } + // help + if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "help") { + b.handleWelcome(m.ConvID) + return + } + // hello + if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "hello") { + b.handleWelcome(m.ConvID) + return + } + // set commands + if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "set") { + b.handleSetCommand(m) + return + } + // list commands + if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "list") { + b.handleSetCommand(m) return } } From a8b173036f5ea59b653c01470d4be4369a771e6c Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 20:33:33 +0000 Subject: [PATCH 30/43] added meet subcommand back --- handlers.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/handlers.go b/handlers.go index 334570e..3a60446 100644 --- a/handlers.go +++ b/handlers.go @@ -48,6 +48,11 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { return } // then check sub-command variants + // meet + if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "meet") { + b.handleMeeting(m) + return + } // feedback if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "feedback") { b.handleFeedback(m) From 61e92f3b85e21bbcb950e8f9346c2361abdccacb Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 20:46:11 +0000 Subject: [PATCH 31/43] fixed root command to have trailing space --- utils.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils.go b/utils.go index c593c21..b618b16 100644 --- a/utils.go +++ b/utils.go @@ -50,8 +50,9 @@ func hasCommandPrefix(s string, baseCommand string, botName string, subCommands // isRootCommand determines if the command is the root command or name with no arguments func isRootCommand(s string, baseCommand string, botName string) bool { - botCommand := fmt.Sprintf("!%s", baseCommand) - nameCommand := fmt.Sprintf("@%s", botName) + // the space after is important because keybase autocompletes ! and @ with a space after + botCommand := fmt.Sprintf("!%s ", baseCommand) + nameCommand := fmt.Sprintf("@%s ", botName) if s == botCommand || s == nameCommand { return true } From 984efecf7e39db04ead5a85e3201673e704fc5cc Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 20:46:32 +0000 Subject: [PATCH 32/43] added debug and fixed indexes --- commands.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 6503d61..35b5b01 100644 --- a/commands.go +++ b/commands.go @@ -78,16 +78,17 @@ func (b *bot) handleFeedback(m chat1.MsgSummary) { // handleSetCommand processes all settings SET calls func (b *bot) handleSetCommand(m chat1.MsgSummary) { + b.debug("%s called set command in %s", m.Sender.Username, m.ConvID) // first normalize the text and extract the arguments args := strings.Fields(strings.ToLower(m.Content.Text.Body)) if args[1] != "set" { return } switch len(args) { - case 3: + case 4: if args[2] == "url" { // first validate the URL - u, err := url.ParseRequestURI(args[2]) + u, err := url.ParseRequestURI(args[3]) if err != nil { eid := b.logError(err) b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) From 80587d4a1e512015304e50a81a1383dc555aeb69 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 21:34:18 +0000 Subject: [PATCH 33/43] update set/list to config commands --- commands.go | 33 +++++++++++++++++++++++++++------ handlers.go | 11 +++-------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/commands.go b/commands.go index 35b5b01..4906e24 100644 --- a/commands.go +++ b/commands.go @@ -76,19 +76,38 @@ func (b *bot) handleFeedback(m chat1.MsgSummary) { } } +// handleConfigCommand dispatches config calls +func (b *bot) handleConfigCommand(m chat1.MsgSummary) { + args := strings.Fields(strings.ToLower(m.Content.Text.Body)) + if args[1] != "config" { + return + } + if len(args) >= 3 { + switch args[2] { + case "set": + b.handleSetCommand(m) + return + case "list": + b.handleListCommand(m) + return + } + } +} + // handleSetCommand processes all settings SET calls +// this should be called from b.handleConfigCommand() func (b *bot) handleSetCommand(m chat1.MsgSummary) { - b.debug("%s called set command in %s", m.Sender.Username, m.ConvID) // first normalize the text and extract the arguments args := strings.Fields(strings.ToLower(m.Content.Text.Body)) - if args[1] != "set" { + if args[2] != "set" { return } + b.debug("config set called by @%s in %s", m.Sender.Username, m.ConvID) switch len(args) { - case 4: - if args[2] == "url" { + case 5: + if args[3] == "url" { // first validate the URL - u, err := url.ParseRequestURI(args[3]) + u, err := url.ParseRequestURI(args[4]) if err != nil { eid := b.logError(err) b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) @@ -129,10 +148,12 @@ func (b *bot) handleSetCommand(m chat1.MsgSummary) { } // handleListCommand lists settings for the conversation +// this should be called from b.handleConfigCommand() func (b *bot) handleListCommand(m chat1.MsgSummary) { // first normalize the text and extract the arguments args := strings.Fields(strings.ToLower(m.Content.Text.Body)) - if args[0] != "list" { + if args[2] != "list" { return } + b.debug("config list called by @%s in %s", m.Sender.Username, m.ConvID) } diff --git a/handlers.go b/handlers.go index 3a60446..eec82e0 100644 --- a/handlers.go +++ b/handlers.go @@ -68,14 +68,9 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { b.handleWelcome(m.ConvID) return } - // set commands - if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "set") { - b.handleSetCommand(m) - return - } - // list commands - if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "list") { - b.handleSetCommand(m) + // config commands + if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "config") { + b.handleConfigCommand(m) return } } From 8752e74bf671024b56dbd67c3a2f6243328b8ba9 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Tue, 31 Mar 2020 23:29:26 +0000 Subject: [PATCH 34/43] renamed commands to config namespace --- commands.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/commands.go b/commands.go index 4906e24..2044c4c 100644 --- a/commands.go +++ b/commands.go @@ -85,18 +85,20 @@ func (b *bot) handleConfigCommand(m chat1.MsgSummary) { if len(args) >= 3 { switch args[2] { case "set": - b.handleSetCommand(m) + b.handleConfigSet(m) return case "list": - b.handleListCommand(m) + b.handleConfigList(m) return + case "help": + b.handleConfigHelp(m) } } } -// handleSetCommand processes all settings SET calls +// handleConfigSet processes all settings SET calls // this should be called from b.handleConfigCommand() -func (b *bot) handleSetCommand(m chat1.MsgSummary) { +func (b *bot) handleConfigSet(m chat1.MsgSummary) { // first normalize the text and extract the arguments args := strings.Fields(strings.ToLower(m.Content.Text.Body)) if args[2] != "set" { @@ -147,9 +149,9 @@ func (b *bot) handleSetCommand(m chat1.MsgSummary) { } } -// handleListCommand lists settings for the conversation +// handleConfigList lists settings for the conversation // this should be called from b.handleConfigCommand() -func (b *bot) handleListCommand(m chat1.MsgSummary) { +func (b *bot) handleConfigList(m chat1.MsgSummary) { // first normalize the text and extract the arguments args := strings.Fields(strings.ToLower(m.Content.Text.Body)) if args[2] != "list" { @@ -157,3 +159,14 @@ func (b *bot) handleListCommand(m chat1.MsgSummary) { } b.debug("config list called by @%s in %s", m.Sender.Username, m.ConvID) } + +// handleConfigHelp shows config help +// this should be called from b.handleConfigCommand() +func (b *bot) handleConfigHelp(m chat1.MsgSummary) { + // first normalize the text and extract the arguments + args := strings.Fields(strings.ToLower(m.Content.Text.Body)) + if args[2] != "help" { + return + } + b.debug("config list called by @%s in %s", m.Sender.Username, m.ConvID) +} From 889d7b9e01307034724be6efd3e5f58d50cf2c57 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Wed, 1 Apr 2020 15:10:22 +0000 Subject: [PATCH 35/43] added new permissions model --- handlers.go | 29 ++++++++-------- permissions.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 13 deletions(-) create mode 100644 permissions.go diff --git a/handlers.go b/handlers.go index eec82e0..b159955 100644 --- a/handlers.go +++ b/handlers.go @@ -44,20 +44,11 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { } // then check if this is the root command if isRootCommand(m.Content.Text.Body, b.cmd(), b.k.Username) { - b.handleMeeting(m) - return - } - // then check sub-command variants - // meet - if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "meet") { - b.handleMeeting(m) - return - } - // feedback - if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "feedback") { - b.handleFeedback(m) + b.checkPermissionAndExecute("reader", m, b.handleMeeting) return } + + // then check help and welcome (non-permissions) // help if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "help") { b.handleWelcome(m.ConvID) @@ -68,9 +59,21 @@ func (b *bot) chatHandler(m chat1.MsgSummary) { b.handleWelcome(m.ConvID) return } + + // then check sub-command variants (permissions) + // meet + if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "meet") { + b.checkPermissionAndExecute("reader", m, b.handleMeeting) + return + } + // feedback + if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "feedback") { + b.checkPermissionAndExecute("reader", m, b.handleFeedback) + return + } // config commands if hasCommandPrefix(m.Content.Text.Body, b.cmd(), b.k.Username, "config") { - b.handleConfigCommand(m) + b.checkPermissionAndExecute("admin", m, b.handleConfigCommand) return } } diff --git a/permissions.go b/permissions.go new file mode 100644 index 0000000..ab107f4 --- /dev/null +++ b/permissions.go @@ -0,0 +1,90 @@ +package main + +import ( + "strings" + + "samhofi.us/x/keybase/types/chat1" +) + +// checkPermissionAndExecute will check the minimum required role for the permission and execute the handler function if allowed +func (b *bot) checkPermissionAndExecute(requiredRole string, m chat1.MsgSummary, f func(chat1.MsgSummary)) { + // get the members of the conversation + b.debug("Executing permissions check") + // currently this doesn't work due to a keybase bug + // the workaround is to check the general channel the old way + //conversation, err := b.k.ListMembersOfConversation(m.ConvID) + + // **** + channel := chat1.ChatChannel{ + Name: m.Channel.Name, + MembersType: m.Channel.MembersType, + TopicName: "general", + } + conversation, err := b.k.ListMembersOfChannel(channel) + /// **** + + if err != nil { + eid := b.logError(err) + b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) + return + } + // create a map of valid roles, according to @dxb struc + memberTypes := make(map[string]struct{}) + memberTypes["owner"] = struct{}{} + memberTypes["admin"] = struct{}{} + memberTypes["writer"] = struct{}{} + memberTypes["reader"] = struct{}{} + + // if the role is not in the map, its an invalid role + if _, ok := memberTypes[strings.ToLower(requiredRole)]; !ok { + // the role passed was not valid, so bail + b.log("ERROR: %s is not a valid permissions level", requiredRole) + return + } + + // then descend permissions from top down + for _, member := range conversation.Members.Owners { + if strings.ToLower(member.Username) == strings.ToLower(m.Sender.Username) { + f(m) + return + } + b.debug("no") + } + // if the required role was owner, return and don't evaluate the rest + if strings.ToLower(requiredRole) == "owner" { + b.debug("user does not have required permission of: owner") + return + } + // admins + for _, member := range conversation.Members.Admins { + if strings.ToLower(member.Username) == strings.ToLower(m.Sender.Username) { + f(m) + return + } + } + if strings.ToLower(requiredRole) == "admin" { + b.debug("user does not have required permission of: admin") + return + } + // writers + for _, member := range conversation.Members.Writers { + if strings.ToLower(member.Username) == strings.ToLower(m.Sender.Username) { + f(m) + return + } + } + if strings.ToLower(requiredRole) == "writer" { + b.debug("user does not have required permission of: writer") + return + } + // readers + for _, member := range conversation.Members.Readers { + if strings.ToLower(member.Username) == strings.ToLower(m.Sender.Username) { + f(m) + return + } + } + // just return - restricted bots shouldn't be able to run commands + b.debug("user does not have required permission of: reader") + return +} From 5cbd85b048ba3e2e79476f3a2b3da20ed6531ee0 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Wed, 1 Apr 2020 16:14:24 +0000 Subject: [PATCH 36/43] support for custom meeting URL --- jitsi.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/jitsi.go b/jitsi.go index 4524b91..d4ee545 100644 --- a/jitsi.go +++ b/jitsi.go @@ -19,9 +19,10 @@ type phoneNumber struct { } type jitsiMeeting struct { - Name string - ID string - Phone []phoneNumber + Name string + CustomServer string + ID string + Phone []phoneNumber } func (j *jitsiMeeting) getJitsiName() error { @@ -34,7 +35,10 @@ func (j *jitsiMeeting) getJitsiName() error { } func (j *jitsiMeeting) getURL() string { - return fmt.Sprintf("https://meet.jit.si/%s", j.Name) + if j.CustomServer == "" { + return fmt.Sprintf("https://meet.jit.si/%s", j.Name) + } + return fmt.Sprintf("%s/%s", j.CustomServer, j.Name) } func (j *jitsiMeeting) getPIN() string { From 18ebeaafe4ff04c557ac2f03fd9ce8b316199618 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 2 Apr 2020 20:38:53 +0000 Subject: [PATCH 37/43] added conversation options --- commands.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 2044c4c..f983560 100644 --- a/commands.go +++ b/commands.go @@ -45,6 +45,16 @@ func (b *bot) handlePayment(m chat1.MsgSummary) { // handleMeeting starts a new jitsi meeting func (b *bot) handleMeeting(m chat1.MsgSummary) { b.debug("command recieved in conversation %s", m.ConvID) + // check and see if this conversation has a custom URL + opts := ConvOptions{} + err := b.KVStoreGetStruct(m.ConvID, &opts) + if err != nil { + b.debug("unable to get conversation options") + eid := b.logError(err) + b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) + return + } + b.debug("%+v", opts) // currently we aren't sending dial-in information, so don't get it just generate the name // use the simple method meeting, err := newJitsiMeetingSimple() @@ -54,6 +64,10 @@ func (b *bot) handleMeeting(m chat1.MsgSummary) { b.k.SendMessageByConvID(m.ConvID, message) return } + // then set the Custom server URL, if it exists + if opts.ConvID == string(m.ConvID) && opts.CustomURL != "" { + meeting.CustomServer = opts.CustomURL + } b.k.SendMessageByConvID(m.ConvID, "@%s here's your meeting: %s", m.Sender.Username, meeting.getURL()) } @@ -130,9 +144,13 @@ func (b *bot) handleConfigSet(m chat1.MsgSummary) { } // then update the struct using only the scheme and hostname:port if u.Port() != "" { - opts.CustomURL = fmt.Sprintf("%s://%s:%s/", u.Scheme, u.Hostname(), u.Port()) + opts.CustomURL = fmt.Sprintf("%s://%s:%s", u.Scheme, u.Hostname(), u.Port()) } else { - opts.CustomURL = fmt.Sprintf("%s://%s/", u.Scheme, u.Hostname()) + opts.CustomURL = fmt.Sprintf("%s://%s", u.Scheme, u.Hostname()) + } + // ensure that the struct has convid filled out (if its new it won't) + if opts.ConvID == "" { + opts.ConvID = string(m.ConvID) } // then write that back to kvstore, with revision err = b.KVStorePutStruct(m.ConvID, opts) From 02e4f9c3986d0a2326272103f6c0d9b23b01e6a1 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 2 Apr 2020 20:39:04 +0000 Subject: [PATCH 38/43] added permissions woraround --- permissions.go | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/permissions.go b/permissions.go index ab107f4..8368a7c 100644 --- a/permissions.go +++ b/permissions.go @@ -10,24 +10,40 @@ import ( func (b *bot) checkPermissionAndExecute(requiredRole string, m chat1.MsgSummary, f func(chat1.MsgSummary)) { // get the members of the conversation b.debug("Executing permissions check") - // currently this doesn't work due to a keybase bug + // currently this doesn't work due to a keybase bug unless you're in the role of resticted bot // the workaround is to check the general channel the old way - //conversation, err := b.k.ListMembersOfConversation(m.ConvID) - - // **** - channel := chat1.ChatChannel{ - Name: m.Channel.Name, - MembersType: m.Channel.MembersType, - TopicName: "general", - } - conversation, err := b.k.ListMembersOfChannel(channel) - /// **** - + // so first check the new way + conversation, err := b.k.ListMembersOfConversation(m.ConvID) if err != nil { eid := b.logError(err) b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) return } + // **** + // check if the length of the lists are zero + // it'll look like this: + // {"owners":[],"admins":[],"writers":[],"readers":[],"bots":[],"restrictedBots":[]} + if len(conversation.Members.Owners) == 0 && + len(conversation.Members.Admins) == 0 && + len(conversation.Members.Writers) == 0 && + len(conversation.Members.Readers) == 0 && + len(conversation.Members.Bots) == 0 && + len(conversation.Members.RestrictedBots) == 0 { + channel := chat1.ChatChannel{ + Name: m.Channel.Name, + MembersType: m.Channel.MembersType, + TopicName: "general", + } + // re-map the members using the workaround, in case you're not in the restricted bot role + conversation, err = b.k.ListMembersOfChannel(channel) + if err != nil { + eid := b.logError(err) + b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) + return + } + } + /// **** + // create a map of valid roles, according to @dxb struc memberTypes := make(map[string]struct{}) memberTypes["owner"] = struct{}{} From d6499bb2c2be64ec047806d8ad85ef9428cda6dd Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 2 Apr 2020 22:43:33 +0000 Subject: [PATCH 39/43] removed notofications for now --- types.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/types.go b/types.go index 0b10799..063c9ca 100644 --- a/types.go +++ b/types.go @@ -10,7 +10,7 @@ type botReply struct { // ConvOptions stores team specific options like custom servers type ConvOptions struct { - ConvID string `json:"converation_id,omitempty"` - NotificationsEnabled bool `json:"notifications_enabled,omitempty"` - CustomURL string `json:"custom_url,omitempty"` + ConvID string `json:"converation_id,omitempty"` + //NotificationsEnabled bool `json:"notifications_enabled,omitempty"` + CustomURL string `json:"custom_url,omitempty"` } From 145239c39e958bd7204bc1476e0e3e61244edb45 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 2 Apr 2020 22:43:43 +0000 Subject: [PATCH 40/43] added reflectStruct --- types.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/types.go b/types.go index 063c9ca..d8c9a82 100644 --- a/types.go +++ b/types.go @@ -14,3 +14,9 @@ type ConvOptions struct { //NotificationsEnabled bool `json:"notifications_enabled,omitempty"` CustomURL string `json:"custom_url,omitempty"` } + +// reflectStruct holds information about reflected structs! +type reflectStruct struct { + Name string + Value interface{} +} From 95dc451ab352aa3c5cdb788bf4b05b7b8f2cfc61 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 2 Apr 2020 22:44:09 +0000 Subject: [PATCH 41/43] added structToSlice() so we can iterate structs --- utils.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/utils.go b/utils.go index b618b16..6dc02dd 100644 --- a/utils.go +++ b/utils.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "fmt" + "reflect" "strings" "samhofi.us/x/keybase/types/chat1" @@ -58,3 +59,14 @@ func isRootCommand(s string, baseCommand string, botName string) bool { } return false } + +// this converts structs to slices of (Name, Value) pairs +func structToSlice(v interface{}) []reflectStruct { + x := reflect.ValueOf(v) + values := make([]reflectStruct, x.NumField()) + for i := 0; i < x.NumField(); i++ { + values[i].Value = x.Field(i).Interface() + values[i].Name = x.Type().Field(i).Name + } + return values +} From 075dc26ed3cfb5f06f295f704e312a34a36e84e2 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Thu, 2 Apr 2020 22:44:19 +0000 Subject: [PATCH 42/43] added list command --- commands.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index f983560..3c970c2 100644 --- a/commands.go +++ b/commands.go @@ -1,9 +1,11 @@ package main import ( + "bytes" "fmt" "net/url" "strings" + "text/tabwriter" "samhofi.us/x/keybase/types/chat1" ) @@ -175,7 +177,26 @@ func (b *bot) handleConfigList(m chat1.MsgSummary) { if args[2] != "list" { return } - b.debug("config list called by @%s in %s", m.Sender.Username, m.ConvID) + // get the ConvOptions + var opts ConvOptions + err := b.KVStoreGetStruct(m.ConvID, &opts) + if err != nil { + eid := b.logError(err) + b.k.ReactByConvID(m.ConvID, m.Id, "Error ID %s", eid) + return + } + // then reflect the struct to a list + configOpts := structToSlice(opts) + // Then iterate those through a tabWriter + var buf bytes.Buffer + w := tabwriter.NewWriter(&buf, 0, 0, 3, ' ', 0) + fmt.Fprintln(w, "Config Options for this channel:\n```") + for _, opt := range configOpts { + fmt.Fprintf(w, "%s\t%v\t\n", opt.Name, opt.Value) + } + fmt.Fprintln(w, "```") + w.Flush() + b.k.ReplyByConvID(m.ConvID, m.Id, buf.String()) } // handleConfigHelp shows config help From 3badd2f2d576dcde7ce3ead155bc5aa2eb5dcea2 Mon Sep 17 00:00:00 2001 From: David Haukeness Date: Wed, 22 Apr 2020 15:30:55 +0000 Subject: [PATCH 43/43] updated cmd name --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 3c970c2..12c03ab 100644 --- a/commands.go +++ b/commands.go @@ -207,5 +207,5 @@ func (b *bot) handleConfigHelp(m chat1.MsgSummary) { if args[2] != "help" { return } - b.debug("config list called by @%s in %s", m.Sender.Username, m.ConvID) + b.debug("config help called by @%s in %s", m.Sender.Username, m.ConvID) }