diff --git a/go.mod b/go.mod index db39459..6179b00 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,9 @@ require ( github.com/fogleman/gg v1.1.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/gorilla/websocket v1.4.0 // indirect - github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect - github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect github.com/mattn/go-sqlite3 v1.9.0 - github.com/nlopes/slack v0.5.0 + github.com/nlopes/slack v0.6.0 github.com/notnil/chess v0.0.0-20181214160432-429595102215 - github.com/pkg/errors v0.8.0 // indirect github.com/stretchr/testify v1.3.0 // indirect golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 // indirect ) diff --git a/go.sum b/go.sum index 2beac7b..538ccc3 100644 --- a/go.sum +++ b/go.sum @@ -2,24 +2,22 @@ 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/cjsaylor/chessimage v0.0.0-20190107020940-8abad33612f4 h1:U70Ohwln5iU8lFp1hUaCJG2MgHsnwsAkMWF4swb8z6g= github.com/cjsaylor/chessimage v0.0.0-20190107020940-8abad33612f4/go.mod h1:P2rvcEmV7UwQCr7hSwCSt4fRBPaa1jO0Usm7dtWtHOE= -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/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/flopp/go-findfont v0.0.0-20180308170802-e788239e52bc h1:cqzZoaYMsDUGa4J2OP6UiJqRxXHarhT8tKkO/WpFL5Y= github.com/flopp/go-findfont v0.0.0-20180308170802-e788239e52bc/go.mod h1:IOE5a/919uJLUrsF48h4y96LcOEjPWbZkMEAMPQDEnQ= github.com/fogleman/gg v1.1.0 h1:wVTfU9tB/LDr2eI5HILatkzBQLD3yl0KtPFt8KlcSIY= github.com/fogleman/gg v1.1.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU= -github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0= -github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 h1:iOAVXzZyXtW408TMYejlUPo6BIn92HmOacWtIfNyYns= -github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0= -github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA= +github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk= github.com/notnil/chess v0.0.0-20181214160432-429595102215 h1:goyOz2Piqvtv4vbLOA4GckV3tiLXtY7iYmS4K5F8yNc= github.com/notnil/chess v0.0.0-20181214160432-429595102215/go.mod h1:Yu0kMeugIBDf7tmefiwvk+/DabQ5AzQwKUM5Kjt26iQ= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= @@ -27,6 +25,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI= diff --git a/vendor/github.com/caarlos0/env/.travis.yml b/vendor/github.com/caarlos0/env/.travis.yml index 4ad7e69..5a5a2c8 100644 --- a/vendor/github.com/caarlos0/env/.travis.yml +++ b/vendor/github.com/caarlos0/env/.travis.yml @@ -4,6 +4,9 @@ go: - 1.6 - 1.7 - 1.8 + - 1.9 + - '1.10.x' + - '1.11.x' - tip before_install: - go get github.com/axw/gocov/gocov diff --git a/vendor/github.com/caarlos0/env/README.md b/vendor/github.com/caarlos0/env/README.md index cf46c54..ef50e91 100644 --- a/vendor/github.com/caarlos0/env/README.md +++ b/vendor/github.com/caarlos0/env/README.md @@ -36,6 +36,7 @@ type config struct { IsProduction bool `env:"PRODUCTION"` Hosts []string `env:"HOSTS" envSeparator:":"` Duration time.Duration `env:"DURATION"` + TempFolder string `env:"TEMP_FOLDER" envDefault:"${HOME}/tmp" envExpand:"true"` } func main() { @@ -83,6 +84,9 @@ and `0` for `int`s. By default, slice types will split the environment value on `,`; you can change this behavior by setting the `envSeparator` tag. +If you set the `envExpand` tag, environment variables (either in `${var}` or `$var` format) +in the string will be replaced according with the actual value of the variable. + ## Custom Parser Funcs If you have a type that is not supported out of the box by the lib, you are able diff --git a/vendor/github.com/caarlos0/env/env.go b/vendor/github.com/caarlos0/env/env.go index 15f0e81..9e29045 100644 --- a/vendor/github.com/caarlos0/env/env.go +++ b/vendor/github.com/caarlos0/env/env.go @@ -1,6 +1,7 @@ package env import ( + "encoding" "errors" "fmt" "os" @@ -18,6 +19,9 @@ var ( ErrUnsupportedType = errors.New("Type is not supported") // ErrUnsupportedSliceType if the slice element type is not supported by env ErrUnsupportedSliceType = errors.New("Unsupported slice type") + // OnEnvVarSet is an optional convenience callback, such as for logging purposes. + // If not nil, it's called after successfully setting the given field from the given value. + OnEnvVarSet func(reflect.StructField, string) // Friendly names for reflect types sliceOfInts = reflect.TypeOf([]int(nil)) sliceOfInt64s = reflect.TypeOf([]int64(nil)) @@ -68,14 +72,16 @@ func doParse(ref reflect.Value, funcMap CustomParsers) error { var errorList []string for i := 0; i < refType.NumField(); i++ { - if reflect.Ptr == ref.Field(i).Kind() && !ref.Field(i).IsNil() && ref.Field(i).CanSet() { - err := Parse(ref.Field(i).Interface()) + refField := ref.Field(i) + if reflect.Ptr == refField.Kind() && !refField.IsNil() && refField.CanSet() { + err := Parse(refField.Interface()) if nil != err { return err } continue } - value, err := get(refType.Field(i)) + refTypeField := refType.Field(i) + value, err := get(refTypeField) if err != nil { errorList = append(errorList, err.Error()) continue @@ -83,10 +89,13 @@ func doParse(ref reflect.Value, funcMap CustomParsers) error { if value == "" { continue } - if err := set(ref.Field(i), refType.Field(i), value, funcMap); err != nil { + if err := set(refField, refTypeField, value, funcMap); err != nil { errorList = append(errorList, err.Error()) continue } + if OnEnvVarSet != nil { + OnEnvVarSet(refTypeField, value) + } } if len(errorList) == 0 { return nil @@ -105,6 +114,11 @@ func get(field reflect.StructField) (string, error) { defaultValue := field.Tag.Get("envDefault") val = getOr(key, defaultValue) + expandVar := field.Tag.Get("envExpand") + if strings.ToLower(expandVar) == "true" { + val = os.ExpandEnv(val) + } + if len(opts) > 0 { for _, opt := range opts { // The only option supported is "required". @@ -114,7 +128,7 @@ func get(field reflect.StructField) (string, error) { case "required": val, err = getRequired(key) default: - err = errors.New("Env tag option " + opt + " not supported.") + err = fmt.Errorf("env tag option %q not supported", opt) } } } @@ -132,8 +146,7 @@ func getRequired(key string) (string, error) { if value, ok := os.LookupEnv(key); ok { return value, nil } - // We do not use fmt.Errorf to avoid another import. - return "", errors.New("Required environment variable " + key + " is not set") + return "", fmt.Errorf("required environment variable %q is not set", key) } func getOr(key, defaultValue string) string { @@ -145,6 +158,18 @@ func getOr(key, defaultValue string) string { } func set(field reflect.Value, refType reflect.StructField, value string, funcMap CustomParsers) error { + // use custom parser if configured for this type + parserFunc, ok := funcMap[refType.Type] + if ok { + val, err := parserFunc(value) + if err != nil { + return fmt.Errorf("Custom parser error: %v", err) + } + field.Set(reflect.ValueOf(val)) + return nil + } + + // fall back to built-in parsers switch field.Kind() { case reflect.Slice: separator := refType.Tag.Get("envSeparator") @@ -201,40 +226,9 @@ func set(field reflect.Value, refType reflect.StructField, value string, funcMap return err } field.SetUint(uintValue) - case reflect.Struct: - return handleStruct(field, refType, value, funcMap) default: - parserFunc, ok := funcMap[refType.Type] - if !ok { - return ErrUnsupportedType - } - val, err := parserFunc(value) - if err != nil { - return err - } - field.Set(reflect.ValueOf(val)) - } - return nil -} - -func handleStruct(field reflect.Value, refType reflect.StructField, value string, funcMap CustomParsers) error { - // Does the custom parser func map contain this type? - parserFunc, ok := funcMap[field.Type()] - if !ok { - // Map does not contain a custom parser for this type - return ErrUnsupportedType - } - - // Call on the custom parser func - data, err := parserFunc(value) - if err != nil { - return fmt.Errorf("Custom parser error: %v", err) + return handleTextUnmarshaler(field, value) } - - // Set the field to the data returned by the customer parser func - rv := reflect.ValueOf(data) - field.Set(rv) - return nil } @@ -291,11 +285,37 @@ func handleSlice(field reflect.Value, value, separator string) error { } field.Set(reflect.ValueOf(durationData)) default: - return ErrUnsupportedSliceType + elemType := field.Type().Elem() + // Ensure we test *type as we can always address elements in a slice. + if elemType.Kind() == reflect.Ptr { + elemType = elemType.Elem() + } + if _, ok := reflect.New(elemType).Interface().(encoding.TextUnmarshaler); !ok { + return ErrUnsupportedSliceType + } + return parseTextUnmarshalers(field, splitData) + } return nil } +func handleTextUnmarshaler(field reflect.Value, value string) error { + if reflect.Ptr == field.Kind() { + if field.IsNil() { + field.Set(reflect.New(field.Type().Elem())) + } + } else if field.CanAddr() { + field = field.Addr() + } + + tm, ok := field.Interface().(encoding.TextUnmarshaler) + if !ok { + return ErrUnsupportedType + } + + return tm.UnmarshalText([]byte(value)) +} + func parseInts(data []string) ([]int, error) { intSlice := make([]int, 0, len(data)) @@ -388,3 +408,29 @@ func parseDurations(data []string) ([]time.Duration, error) { } return durationSlice, nil } + +func parseTextUnmarshalers(field reflect.Value, data []string) error { + s := len(data) + elemType := field.Type().Elem() + slice := reflect.MakeSlice(reflect.SliceOf(elemType), s, s) + for i, v := range data { + sv := slice.Index(i) + kind := sv.Kind() + if kind == reflect.Ptr { + sv = reflect.New(elemType.Elem()) + } else { + sv = sv.Addr() + } + tm := sv.Interface().(encoding.TextUnmarshaler) + if err := tm.UnmarshalText([]byte(v)); err != nil { + return err + } + if kind == reflect.Ptr { + slice.Index(i).Set(sv) + } + } + + field.Set(slice) + + return nil +} diff --git a/vendor/github.com/nlopes/slack/.travis.yml b/vendor/github.com/nlopes/slack/.travis.yml index e4b9c75..ed99d9e 100644 --- a/vendor/github.com/nlopes/slack/.travis.yml +++ b/vendor/github.com/nlopes/slack/.travis.yml @@ -1,12 +1,9 @@ language: go -go: - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - - 1.11.x - - tip +env: + - GO111MODULE=on + +install: true before_install: - export PATH=$HOME/gopath/bin:$PATH @@ -20,6 +17,19 @@ script: matrix: allow_failures: - go: tip + include: + - go: "1.7.x" + script: go test -v ./... + - go: "1.8.x" + script: go test -v ./... + - go: "1.9.x" + script: go test -v ./... + - go: "1.10.x" + script: go test -v ./... + - go: "1.11.x" + script: go test -v -mod=vendor ./... + - go: "tip" + script: go test -v -mod=vendor ./... git: depth: 10 diff --git a/vendor/github.com/nlopes/slack/CHANGELOG.md b/vendor/github.com/nlopes/slack/CHANGELOG.md index 63309f2..48bcce5 100644 --- a/vendor/github.com/nlopes/slack/CHANGELOG.md +++ b/vendor/github.com/nlopes/slack/CHANGELOG.md @@ -1,3 +1,20 @@ +### v0.6.0 - August 31, 2019 +full differences can be viewed using `git log --oneline --decorate --color v0.5.0..v0.6.0` +thanks to everyone who has contributed since January! + + +#### Breaking Changes: +- Info struct has had fields removed related to deprecated functionality by slack. +- minor adjustments to some structs. +- some internal default values have changed, usually to be more inline with slack defaults or to correct inability to set a particular value. (Message Parse for example.) + +##### Highlights: +- new slacktest package easy mocking for slack client. use, enjoy, please submit PRs for improvements and default behaviours! shamelessly taken from the [slack-test repo](https://github.com/lusis/slack-test) thank you lusis for letting us use it and bring it into the slack repo. +- blocks, blocks, blocks. +- RTM ManagedConnection has undergone a significant cleanup. +in particular handles backoffs gracefully, removed many deadlocks, +and Disconnect is now much more responsive. + ### v0.5.0 - January 20, 2019 full differences can be viewed using `git log --oneline --decorate --color v0.4.0..v0.5.0` - Breaking changes: various old struct fields have been removed or updated to match slack's api. diff --git a/vendor/github.com/nlopes/slack/Gopkg.lock b/vendor/github.com/nlopes/slack/Gopkg.lock deleted file mode 100644 index 9c33d0d..0000000 --- a/vendor/github.com/nlopes/slack/Gopkg.lock +++ /dev/null @@ -1,39 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - name = "github.com/davecgh/go-spew" - packages = ["spew"] - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" - -[[projects]] - name = "github.com/gorilla/websocket" - packages = ["."] - revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" - version = "v1.2.0" - -[[projects]] - name = "github.com/pkg/errors" - packages = ["."] - revision = "645ef00459ed84a119197bfb8d8205042c6df63d" - version = "v0.8.0" - -[[projects]] - name = "github.com/pmezard/go-difflib" - packages = ["difflib"] - revision = "792786c7400a136282c1664665ae0a8db921c6c2" - version = "v1.0.0" - -[[projects]] - name = "github.com/stretchr/testify" - packages = ["assert"] - revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" - version = "v1.2.2" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "596fa546322c2a1e9708a10c9f39aca2e04792b477fab86fb2899fbaab776070" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/vendor/github.com/nlopes/slack/Gopkg.toml b/vendor/github.com/nlopes/slack/Gopkg.toml deleted file mode 100644 index 257870d..0000000 --- a/vendor/github.com/nlopes/slack/Gopkg.toml +++ /dev/null @@ -1,17 +0,0 @@ -ignored = ["github.com/lusis/slack-test"] - -[[constraint]] - name = "github.com/gorilla/websocket" - version = "1.2.0" - -[[constraint]] - name = "github.com/stretchr/testify" - version = "1.2.1" - -[[constraint]] - name = "github.com/pkg/errors" - version = "0.8.0" - -[prune] - go-tests = true - unused-packages = true diff --git a/vendor/github.com/nlopes/slack/README.md b/vendor/github.com/nlopes/slack/README.md index b414823..a5e8e5e 100644 --- a/vendor/github.com/nlopes/slack/README.md +++ b/vendor/github.com/nlopes/slack/README.md @@ -35,7 +35,7 @@ func main() { api := slack.New("YOUR_TOKEN_HERE") // If you set debugging, it will log all requests to the console // Useful when encountering issues - // api.SetDebug(true) + // slack.New("YOUR_TOKEN_HERE", slack.OptionDebug(true)) groups, err := api.GetGroups(false) if err != nil { fmt.Printf("%s\n", err) diff --git a/vendor/github.com/nlopes/slack/admin.go b/vendor/github.com/nlopes/slack/admin.go index db44aa3..d51426b 100644 --- a/vendor/github.com/nlopes/slack/admin.go +++ b/vendor/github.com/nlopes/slack/admin.go @@ -2,28 +2,19 @@ package slack import ( "context" - "errors" "fmt" "net/url" + "strings" ) -type adminResponse struct { - OK bool `json:"ok"` - Error string `json:"error"` -} - -func adminRequest(ctx context.Context, client httpClient, method string, teamName string, values url.Values, d debug) (*adminResponse, error) { - adminResponse := &adminResponse{} - err := parseAdminResponse(ctx, client, method, teamName, values, adminResponse, d) +func (api *Client) adminRequest(ctx context.Context, method string, teamName string, values url.Values) error { + resp := &SlackResponse{} + err := parseAdminResponse(ctx, api.httpclient, method, teamName, values, resp, api) if err != nil { - return nil, err + return err } - if !adminResponse.OK { - return nil, errors.New(adminResponse.Error) - } - - return adminResponse, nil + return resp.Err() } // DisableUser disabled a user account, given a user ID @@ -40,9 +31,8 @@ func (api *Client) DisableUserContext(ctx context.Context, teamName string, uid "_attempts": {"1"}, } - _, err := adminRequest(ctx, api.httpclient, "setInactive", teamName, values, api) - if err != nil { - return fmt.Errorf("Failed to disable user with id '%s': %s", uid, err) + if err := api.adminRequest(ctx, "setInactive", teamName, values); err != nil { + return fmt.Errorf("failed to disable user with id '%s': %s", uid, err) } return nil @@ -67,7 +57,7 @@ func (api *Client) InviteGuestContext(ctx context.Context, teamName, channel, fi "_attempts": {"1"}, } - _, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api) + err := api.adminRequest(ctx, "invite", teamName, values) if err != nil { return fmt.Errorf("Failed to invite single-channel guest: %s", err) } @@ -94,7 +84,7 @@ func (api *Client) InviteRestrictedContext(ctx context.Context, teamName, channe "_attempts": {"1"}, } - _, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api) + err := api.adminRequest(ctx, "invite", teamName, values) if err != nil { return fmt.Errorf("Failed to restricted account: %s", err) } @@ -118,7 +108,7 @@ func (api *Client) InviteToTeamContext(ctx context.Context, teamName, firstName, "_attempts": {"1"}, } - _, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api) + err := api.adminRequest(ctx, "invite", teamName, values) if err != nil { return fmt.Errorf("Failed to invite to team: %s", err) } @@ -140,7 +130,7 @@ func (api *Client) SetRegularContext(ctx context.Context, teamName, user string) "_attempts": {"1"}, } - _, err := adminRequest(ctx, api.httpclient, "setRegular", teamName, values, api) + err := api.adminRequest(ctx, "setRegular", teamName, values) if err != nil { return fmt.Errorf("Failed to change the user (%s) to a regular user: %s", user, err) } @@ -162,7 +152,7 @@ func (api *Client) SendSSOBindingEmailContext(ctx context.Context, teamName, use "_attempts": {"1"}, } - _, err := adminRequest(ctx, api.httpclient, "sendSSOBind", teamName, values, api) + err := api.adminRequest(ctx, "sendSSOBind", teamName, values) if err != nil { return fmt.Errorf("Failed to send SSO binding email for user (%s): %s", user, err) } @@ -185,7 +175,7 @@ func (api *Client) SetUltraRestrictedContext(ctx context.Context, teamName, uid, "_attempts": {"1"}, } - _, err := adminRequest(ctx, api.httpclient, "setUltraRestricted", teamName, values, api) + err := api.adminRequest(ctx, "setUltraRestricted", teamName, values) if err != nil { return fmt.Errorf("Failed to ultra-restrict account: %s", err) } @@ -194,22 +184,23 @@ func (api *Client) SetUltraRestrictedContext(ctx context.Context, teamName, uid, } // SetRestricted converts a user into a restricted account -func (api *Client) SetRestricted(teamName, uid string) error { - return api.SetRestrictedContext(context.Background(), teamName, uid) +func (api *Client) SetRestricted(teamName, uid string, channelIds ...string) error { + return api.SetRestrictedContext(context.Background(), teamName, uid, channelIds...) } // SetRestrictedContext converts a user into a restricted account with a custom context -func (api *Client) SetRestrictedContext(ctx context.Context, teamName, uid string) error { +func (api *Client) SetRestrictedContext(ctx context.Context, teamName, uid string, channelIds ...string) error { values := url.Values{ "user": {uid}, "token": {api.token}, "set_active": {"true"}, "_attempts": {"1"}, + "channels": {strings.Join(channelIds, ",")}, } - _, err := adminRequest(ctx, api.httpclient, "setRestricted", teamName, values, api) + err := api.adminRequest(ctx, "setRestricted", teamName, values) if err != nil { - return fmt.Errorf("Failed to restrict account: %s", err) + return fmt.Errorf("failed to restrict account: %s", err) } return nil diff --git a/vendor/github.com/nlopes/slack/attachments.go b/vendor/github.com/nlopes/slack/attachments.go index 06f59fa..cf8b5c6 100644 --- a/vendor/github.com/nlopes/slack/attachments.go +++ b/vendor/github.com/nlopes/slack/attachments.go @@ -17,7 +17,7 @@ type AttachmentAction struct { Name string `json:"name"` // Required. Text string `json:"text"` // Required. Style string `json:"style,omitempty"` // Optional. Allowed values: "default", "primary", "danger". - Type string `json:"type"` // Required. Must be set to "button" or "select". + Type actionType `json:"type"` // Required. Must be set to "button" or "select". Value string `json:"value,omitempty"` // Optional. DataSource string `json:"data_source,omitempty"` // Optional. MinQueryLength int `json:"min_query_length,omitempty"` // Optional. Default value is 1. @@ -28,6 +28,11 @@ type AttachmentAction struct { URL string `json:"url,omitempty"` // Optional. } +// actionType returns the type of the action +func (a AttachmentAction) actionType() actionType { + return a.Type +} + // AttachmentActionOption the individual option to appear in action menu. type AttachmentActionOption struct { Text string `json:"text"` // Required. @@ -45,13 +50,6 @@ type AttachmentActionOptionGroup struct { // DEPRECATED: use InteractionCallback type AttachmentActionCallback InteractionCallback -// ActionCallback specific fields for the action callback. -type ActionCallback struct { - MessageTs string `json:"message_ts"` - AttachmentID string `json:"attachment_id"` - Actions []AttachmentAction `json:"actions"` -} - // ConfirmationField are used to ask users to confirm actions type ConfirmationField struct { Title string `json:"title,omitempty"` // Optional. diff --git a/vendor/github.com/nlopes/slack/auth.go b/vendor/github.com/nlopes/slack/auth.go index f8fe1f9..dc1dbcd 100644 --- a/vendor/github.com/nlopes/slack/auth.go +++ b/vendor/github.com/nlopes/slack/auth.go @@ -12,9 +12,9 @@ type AuthRevokeResponse struct { } // authRequest sends the actual request, and unmarshals the response -func authRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*AuthRevokeResponse, error) { +func (api *Client) authRequest(ctx context.Context, path string, values url.Values) (*AuthRevokeResponse, error) { response := &AuthRevokeResponse{} - err := postSlackMethod(ctx, client, path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } @@ -36,5 +36,5 @@ func (api *Client) SendAuthRevokeContext(ctx context.Context, token string) (*Au "token": {token}, } - return authRequest(ctx, api.httpclient, "auth.revoke", values, api) + return api.authRequest(ctx, "auth.revoke", values) } diff --git a/vendor/github.com/nlopes/slack/backoff.go b/vendor/github.com/nlopes/slack/backoff.go index 197bce2..2ba697e 100644 --- a/vendor/github.com/nlopes/slack/backoff.go +++ b/vendor/github.com/nlopes/slack/backoff.go @@ -1,7 +1,6 @@ package slack import ( - "math" "math/rand" "time" ) @@ -14,41 +13,42 @@ import ( // conjunction with the time package. type backoff struct { attempts int - //Factor is the multiplying factor for each increment step - Factor float64 - //Jitter eases contention by randomizing backoff steps - Jitter bool - //Min and Max are the minimum and maximum values of the counter - Min, Max time.Duration + // Initial value to scale out + Initial time.Duration + // Jitter value randomizes an additional delay between 0 and Jitter + Jitter time.Duration + // Max maximum values of the backoff + Max time.Duration } // Returns the current value of the counter and then multiplies it // Factor -func (b *backoff) Duration() time.Duration { - //Zero-values are nonsensical, so we use - //them to apply defaults - if b.Min == 0 { - b.Min = 100 * time.Millisecond - } +func (b *backoff) Duration() (dur time.Duration) { + // Zero-values are nonsensical, so we use + // them to apply defaults if b.Max == 0 { b.Max = 10 * time.Second } - if b.Factor == 0 { - b.Factor = 2 + + if b.Initial == 0 { + b.Initial = 100 * time.Millisecond } - //calculate this duration - dur := float64(b.Min) * math.Pow(b.Factor, float64(b.attempts)) - if b.Jitter { - dur = rand.Float64()*(dur-float64(b.Min)) + float64(b.Min) + + // calculate this duration + if dur = time.Duration(1 << uint(b.attempts)); dur > 0 { + dur = dur * b.Initial + } else { + dur = b.Max } - //cap! - if dur > float64(b.Max) { - return b.Max + + if b.Jitter > 0 { + dur = dur + time.Duration(rand.Intn(int(b.Jitter))) } - //bump attempts count + + // bump attempts count b.attempts++ - //return as a time.Duration - return time.Duration(dur) + + return dur } //Resets the current value of the counter back to Min diff --git a/vendor/github.com/nlopes/slack/block.go b/vendor/github.com/nlopes/slack/block.go new file mode 100644 index 0000000..1fc7fec --- /dev/null +++ b/vendor/github.com/nlopes/slack/block.go @@ -0,0 +1,71 @@ +package slack + +// @NOTE: Blocks are in beta and subject to change. + +// More Information: https://api.slack.com/block-kit + +// MessageBlockType defines a named string type to define each block type +// as a constant for use within the package. +type MessageBlockType string + +const ( + MBTSection MessageBlockType = "section" + MBTDivider MessageBlockType = "divider" + MBTImage MessageBlockType = "image" + MBTAction MessageBlockType = "actions" + MBTContext MessageBlockType = "context" +) + +// Block defines an interface all block types should implement +// to ensure consistency between blocks. +type Block interface { + BlockType() MessageBlockType +} + +// Blocks is a convenience struct defined to allow dynamic unmarshalling of +// the "blocks" value in Slack's JSON response, which varies depending on block type +type Blocks struct { + BlockSet []Block `json:"blocks,omitempty"` +} + +// BlockAction is the action callback sent when a block is interacted with +type BlockAction struct { + ActionID string `json:"action_id"` + BlockID string `json:"block_id"` + Type actionType `json:"type"` + Text TextBlockObject `json:"text"` + Value string `json:"value"` + ActionTs string `json:"action_ts"` + SelectedOption OptionBlockObject `json:"selected_option"` + SelectedUser string `json:"selected_user"` + SelectedChannel string `json:"selected_channel"` + SelectedConversation string `json:"selected_conversation"` + SelectedDate string `json:"selected_date"` + InitialOption OptionBlockObject `json:"initial_option"` + InitialUser string `json:"initial_user"` + InitialChannel string `json:"initial_channel"` + InitialConversation string `json:"initial_conversation"` + InitialDate string `json:"initial_date"` +} + +// actionType returns the type of the action +func (b BlockAction) actionType() actionType { + return b.Type +} + +// NewBlockMessage creates a new Message that contains one or more blocks to be displayed +func NewBlockMessage(blocks ...Block) Message { + return Message{ + Msg: Msg{ + Blocks: Blocks{ + BlockSet: blocks, + }, + }, + } +} + +// AddBlockMessage appends a block to the end of the existing list of blocks +func AddBlockMessage(message Message, newBlk Block) Message { + message.Msg.Blocks.BlockSet = append(message.Msg.Blocks.BlockSet, newBlk) + return message +} diff --git a/vendor/github.com/nlopes/slack/block_action.go b/vendor/github.com/nlopes/slack/block_action.go new file mode 100644 index 0000000..fe46a95 --- /dev/null +++ b/vendor/github.com/nlopes/slack/block_action.go @@ -0,0 +1,26 @@ +package slack + +// ActionBlock defines data that is used to hold interactive elements. +// +// More Information: https://api.slack.com/reference/messaging/blocks#actions +type ActionBlock struct { + Type MessageBlockType `json:"type"` + BlockID string `json:"block_id,omitempty"` + Elements BlockElements `json:"elements"` +} + +// BlockType returns the type of the block +func (s ActionBlock) BlockType() MessageBlockType { + return s.Type +} + +// NewActionBlock returns a new instance of an Action Block +func NewActionBlock(blockID string, elements ...BlockElement) *ActionBlock { + return &ActionBlock{ + Type: MBTAction, + BlockID: blockID, + Elements: BlockElements{ + ElementSet: elements, + }, + } +} diff --git a/vendor/github.com/nlopes/slack/block_context.go b/vendor/github.com/nlopes/slack/block_context.go new file mode 100644 index 0000000..c37bf27 --- /dev/null +++ b/vendor/github.com/nlopes/slack/block_context.go @@ -0,0 +1,32 @@ +package slack + +// ContextBlock defines data that is used to display message context, which can +// include both images and text. +// +// More Information: https://api.slack.com/reference/messaging/blocks#actions +type ContextBlock struct { + Type MessageBlockType `json:"type"` + BlockID string `json:"block_id,omitempty"` + ContextElements ContextElements `json:"elements"` +} + +// BlockType returns the type of the block +func (s ContextBlock) BlockType() MessageBlockType { + return s.Type +} + +type ContextElements struct { + Elements []MixedElement +} + +// NewContextBlock returns a new instance of a context block +func NewContextBlock(blockID string, mixedElements ...MixedElement) *ContextBlock { + elements := ContextElements{ + Elements: mixedElements, + } + return &ContextBlock{ + Type: MBTContext, + BlockID: blockID, + ContextElements: elements, + } +} diff --git a/vendor/github.com/nlopes/slack/block_conv.go b/vendor/github.com/nlopes/slack/block_conv.go new file mode 100644 index 0000000..619867e --- /dev/null +++ b/vendor/github.com/nlopes/slack/block_conv.go @@ -0,0 +1,303 @@ +package slack + +import ( + "encoding/json" + + "github.com/pkg/errors" +) + +type sumtype struct { + TypeVal string `json:"type"` +} + +// MarshalJSON implements the Marshaller interface for Blocks so that any JSON +// marshalling is delegated and proper type determination can be made before marshal +func (b Blocks) MarshalJSON() ([]byte, error) { + bytes, err := json.Marshal(b.BlockSet) + if err != nil { + return nil, err + } + + return bytes, nil +} + +// UnmarshalJSON implements the Unmarshaller interface for Blocks, so that any JSON +// unmarshalling is delegated and proper type determination can be made before unmarshal +func (b *Blocks) UnmarshalJSON(data []byte) error { + var raw []json.RawMessage + + if string(data) == "{}" { + return nil + } + + err := json.Unmarshal(data, &raw) + if err != nil { + return err + } + + var blocks Blocks + for _, r := range raw { + s := sumtype{} + err := json.Unmarshal(r, &s) + if err != nil { + return err + } + + var blockType string + if s.TypeVal != "" { + blockType = s.TypeVal + } + + var block Block + switch blockType { + case "actions": + block = &ActionBlock{} + case "context": + block = &ContextBlock{} + case "divider": + block = &DividerBlock{} + case "image": + block = &ImageBlock{} + case "section": + block = &SectionBlock{} + default: + return errors.New("unsupported block type") + } + + err = json.Unmarshal(r, block) + if err != nil { + return err + } + + blocks.BlockSet = append(blocks.BlockSet, block) + } + + *b = blocks + return nil +} + +// MarshalJSON implements the Marshaller interface for BlockElements so that any JSON +// marshalling is delegated and proper type determination can be made before marshal +func (b *BlockElements) MarshalJSON() ([]byte, error) { + bytes, err := json.Marshal(b.ElementSet) + if err != nil { + return nil, err + } + + return bytes, nil +} + +// UnmarshalJSON implements the Unmarshaller interface for BlockElements, so that any JSON +// unmarshalling is delegated and proper type determination can be made before unmarshal +func (b *BlockElements) UnmarshalJSON(data []byte) error { + var raw []json.RawMessage + + if string(data) == "{}" { + return nil + } + + err := json.Unmarshal(data, &raw) + if err != nil { + return err + } + + var blockElements BlockElements + for _, r := range raw { + s := sumtype{} + err := json.Unmarshal(r, &s) + if err != nil { + return err + } + + var blockElementType string + if s.TypeVal != "" { + blockElementType = s.TypeVal + } + + var blockElement BlockElement + switch blockElementType { + case "image": + blockElement = &ImageBlockElement{} + case "button": + blockElement = &ButtonBlockElement{} + case "overflow": + blockElement = &OverflowBlockElement{} + case "datepicker": + blockElement = &DatePickerBlockElement{} + case "static_select", "external_select", "users_select", "conversations_select", "channels_select": + blockElement = &SelectBlockElement{} + default: + return errors.New("unsupported block element type") + } + + err = json.Unmarshal(r, blockElement) + if err != nil { + return err + } + + blockElements.ElementSet = append(blockElements.ElementSet, blockElement) + } + + *b = blockElements + return nil +} + +// MarshalJSON implements the Marshaller interface for Accessory so that any JSON +// marshalling is delegated and proper type determination can be made before marshal +func (a *Accessory) MarshalJSON() ([]byte, error) { + bytes, err := json.Marshal(toBlockElement(a)) + if err != nil { + return nil, err + } + + return bytes, nil +} + +// UnmarshalJSON implements the Unmarshaller interface for Accessory, so that any JSON +// unmarshalling is delegated and proper type determination can be made before unmarshal +func (a *Accessory) UnmarshalJSON(data []byte) error { + var r json.RawMessage + + if string(data) == "{\"accessory\":null}" { + return nil + } + + err := json.Unmarshal(data, &r) + if err != nil { + return err + } + + s := sumtype{} + err = json.Unmarshal(r, &s) + if err != nil { + return err + } + + var blockElementType string + if s.TypeVal != "" { + blockElementType = s.TypeVal + } + + switch blockElementType { + case "image": + element, err := unmarshalBlockElement(r, &ImageBlockElement{}) + if err != nil { + return err + } + a.ImageElement = element.(*ImageBlockElement) + case "button": + element, err := unmarshalBlockElement(r, &ButtonBlockElement{}) + if err != nil { + return err + } + a.ButtonElement = element.(*ButtonBlockElement) + case "overflow": + element, err := unmarshalBlockElement(r, &OverflowBlockElement{}) + if err != nil { + return err + } + a.OverflowElement = element.(*OverflowBlockElement) + case "datepicker": + element, err := unmarshalBlockElement(r, &DatePickerBlockElement{}) + if err != nil { + return err + } + a.DatePickerElement = element.(*DatePickerBlockElement) + case "static_select": + element, err := unmarshalBlockElement(r, &SelectBlockElement{}) + if err != nil { + return err + } + a.SelectElement = element.(*SelectBlockElement) + } + + return nil +} + +func unmarshalBlockElement(r json.RawMessage, element BlockElement) (BlockElement, error) { + err := json.Unmarshal(r, element) + if err != nil { + return nil, err + } + return element, nil +} + +func toBlockElement(element *Accessory) BlockElement { + if element.ImageElement != nil { + return element.ImageElement + } + if element.ButtonElement != nil { + return element.ButtonElement + } + if element.OverflowElement != nil { + return element.OverflowElement + } + if element.DatePickerElement != nil { + return element.DatePickerElement + } + if element.SelectElement != nil { + return element.SelectElement + } + + return nil +} + +// MarshalJSON implements the Marshaller interface for ContextElements so that any JSON +// marshalling is delegated and proper type determination can be made before marshal +func (e *ContextElements) MarshalJSON() ([]byte, error) { + bytes, err := json.Marshal(e.Elements) + if err != nil { + return nil, err + } + + return bytes, nil +} + +// UnmarshalJSON implements the Unmarshaller interface for ContextElements, so that any JSON +// unmarshalling is delegated and proper type determination can be made before unmarshal +func (e *ContextElements) UnmarshalJSON(data []byte) error { + var raw []json.RawMessage + + if string(data) == "{\"elements\":null}" { + return nil + } + + err := json.Unmarshal(data, &raw) + if err != nil { + return err + } + + for _, r := range raw { + s := sumtype{} + err := json.Unmarshal(r, &s) + if err != nil { + return err + } + + var contextElementType string + if s.TypeVal != "" { + contextElementType = s.TypeVal + } + + switch contextElementType { + case PlainTextType, MarkdownType: + elem, err := unmarshalBlockObject(r, &TextBlockObject{}) + if err != nil { + return err + } + + e.Elements = append(e.Elements, elem.(*TextBlockObject)) + case "image": + elem, err := unmarshalBlockElement(r, &ImageBlockElement{}) + if err != nil { + return err + } + + e.Elements = append(e.Elements, elem.(*ImageBlockElement)) + default: + return errors.New("unsupported context element type") + } + } + + return nil +} diff --git a/vendor/github.com/nlopes/slack/block_divider.go b/vendor/github.com/nlopes/slack/block_divider.go new file mode 100644 index 0000000..2d442ba --- /dev/null +++ b/vendor/github.com/nlopes/slack/block_divider.go @@ -0,0 +1,22 @@ +package slack + +// DividerBlock for displaying a divider line between blocks (similar to
tag in html) +// +// More Information: https://api.slack.com/reference/messaging/blocks#divider +type DividerBlock struct { + Type MessageBlockType `json:"type"` + BlockID string `json:"block_id,omitempty"` +} + +// BlockType returns the type of the block +func (s DividerBlock) BlockType() MessageBlockType { + return s.Type +} + +// NewDividerBlock returns a new instance of a divider block +func NewDividerBlock() *DividerBlock { + return &DividerBlock{ + Type: MBTDivider, + } + +} diff --git a/vendor/github.com/nlopes/slack/block_element.go b/vendor/github.com/nlopes/slack/block_element.go new file mode 100644 index 0000000..c62ba99 --- /dev/null +++ b/vendor/github.com/nlopes/slack/block_element.go @@ -0,0 +1,238 @@ +package slack + +// https://api.slack.com/reference/messaging/block-elements + +const ( + METImage MessageElementType = "image" + METButton MessageElementType = "button" + METOverflow MessageElementType = "overflow" + METDatepicker MessageElementType = "datepicker" + + MixedElementImage MixedElementType = "mixed_image" + MixedElementText MixedElementType = "mixed_text" + + OptTypeStatic string = "static_select" + OptTypeExternal string = "external_select" + OptTypeUser string = "users_select" + OptTypeConversations string = "conversations_select" + OptTypeChannels string = "channels_select" +) + +type MessageElementType string +type MixedElementType string + +// BlockElement defines an interface that all block element types should implement. +type BlockElement interface { + ElementType() MessageElementType +} + +type MixedElement interface { + MixedElementType() MixedElementType +} + +type Accessory struct { + ImageElement *ImageBlockElement + ButtonElement *ButtonBlockElement + OverflowElement *OverflowBlockElement + DatePickerElement *DatePickerBlockElement + SelectElement *SelectBlockElement +} + +// NewAccessory returns a new Accessory for a given block element +func NewAccessory(element BlockElement) *Accessory { + switch element.(type) { + case *ImageBlockElement: + return &Accessory{ImageElement: element.(*ImageBlockElement)} + case *ButtonBlockElement: + return &Accessory{ButtonElement: element.(*ButtonBlockElement)} + case *OverflowBlockElement: + return &Accessory{OverflowElement: element.(*OverflowBlockElement)} + case *DatePickerBlockElement: + return &Accessory{DatePickerElement: element.(*DatePickerBlockElement)} + case *SelectBlockElement: + return &Accessory{SelectElement: element.(*SelectBlockElement)} + } + + return nil +} + +// BlockElements is a convenience struct defined to allow dynamic unmarshalling of +// the "elements" value in Slack's JSON response, which varies depending on BlockElement type +type BlockElements struct { + ElementSet []BlockElement `json:"elements,omitempty"` +} + +// ImageBlockElement An element to insert an image - this element can be used +// in section and context blocks only. If you want a block with only an image +// in it, you're looking for the image block. +// +// More Information: https://api.slack.com/reference/messaging/block-elements#image +type ImageBlockElement struct { + Type MessageElementType `json:"type"` + ImageURL string `json:"image_url"` + AltText string `json:"alt_text"` +} + +// ElementType returns the type of the Element +func (s ImageBlockElement) ElementType() MessageElementType { + return s.Type +} + +func (s ImageBlockElement) MixedElementType() MixedElementType { + return MixedElementImage +} + +// NewImageBlockElement returns a new instance of an image block element +func NewImageBlockElement(imageURL, altText string) *ImageBlockElement { + return &ImageBlockElement{ + Type: METImage, + ImageURL: imageURL, + AltText: altText, + } +} + +type Style string + +const ( + StyleDefault Style = "default" + StylePrimary Style = "primary" + StyleDanger Style = "danger" +) + +// ButtonBlockElement defines an interactive element that inserts a button. The +// button can be a trigger for anything from opening a simple link to starting +// a complex workflow. +// +// More Information: https://api.slack.com/reference/messaging/block-elements#button +type ButtonBlockElement struct { + Type MessageElementType `json:"type,omitempty"` + Text *TextBlockObject `json:"text"` + ActionID string `json:"action_id,omitempty"` + URL string `json:"url,omitempty"` + Value string `json:"value,omitempty"` + Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` + Style Style `json:"style,omitempty"` +} + +// ElementType returns the type of the element +func (s ButtonBlockElement) ElementType() MessageElementType { + return s.Type +} + +// add styling to button object +func (s *ButtonBlockElement) WithStyle(style Style) { + s.Style = style +} + +// NewButtonBlockElement returns an instance of a new button element to be used within a block +func NewButtonBlockElement(actionID, value string, text *TextBlockObject) *ButtonBlockElement { + return &ButtonBlockElement{ + Type: METButton, + ActionID: actionID, + Text: text, + Value: value, + } +} + +// SelectBlockElement defines the simplest form of select menu, with a static list +// of options passed in when defining the element. +// +// More Information: https://api.slack.com/reference/messaging/block-elements#select +type SelectBlockElement struct { + Type string `json:"type,omitempty"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + ActionID string `json:"action_id,omitempty"` + Options []*OptionBlockObject `json:"options,omitempty"` + OptionGroups []*OptionGroupBlockObject `json:"option_groups,omitempty"` + InitialOption *OptionBlockObject `json:"initial_option,omitempty"` + InitialUser string `json:"initial_user,omitempty"` + InitialConversation string `json:"initial_conversation,omitempty"` + InitialChannel string `json:"initial_channel,omitempty"` + MinQueryLength int `json:"min_query_length,omitempty"` + Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` +} + +// ElementType returns the type of the Element +func (s SelectBlockElement) ElementType() MessageElementType { + return MessageElementType(s.Type) +} + +// NewOptionsSelectBlockElement returns a new instance of SelectBlockElement for use with +// the Options object only. +func NewOptionsSelectBlockElement(optType string, placeholder *TextBlockObject, actionID string, options ...*OptionBlockObject) *SelectBlockElement { + return &SelectBlockElement{ + Type: optType, + Placeholder: placeholder, + ActionID: actionID, + Options: options, + } +} + +// NewOptionsGroupSelectBlockElement returns a new instance of SelectBlockElement for use with +// the Options object only. +func NewOptionsGroupSelectBlockElement( + optType string, + placeholder *TextBlockObject, + actionID string, + optGroups ...*OptionGroupBlockObject, +) *SelectBlockElement { + return &SelectBlockElement{ + Type: optType, + Placeholder: placeholder, + ActionID: actionID, + OptionGroups: optGroups, + } +} + +// OverflowBlockElement defines the fields needed to use an overflow element. +// And Overflow Element is like a cross between a button and a select menu - +// when a user clicks on this overflow button, they will be presented with a +// list of options to choose from. +// +// More Information: https://api.slack.com/reference/messaging/block-elements#overflow +type OverflowBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id,omitempty"` + Options []*OptionBlockObject `json:"options"` + Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` +} + +// ElementType returns the type of the Element +func (s OverflowBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewOverflowBlockElement returns an instance of a new Overflow Block Element +func NewOverflowBlockElement(actionID string, options ...*OptionBlockObject) *OverflowBlockElement { + return &OverflowBlockElement{ + Type: METOverflow, + ActionID: actionID, + Options: options, + } +} + +// DatePickerBlockElement defines an element which lets users easily select a +// date from a calendar style UI. Date picker elements can be used inside of +// section and actions blocks. +// +// More Information: https://api.slack.com/reference/messaging/block-elements#datepicker +type DatePickerBlockElement struct { + Type MessageElementType `json:"type"` + ActionID string `json:"action_id"` + Placeholder *TextBlockObject `json:"placeholder,omitempty"` + InitialDate string `json:"initial_date,omitempty"` + Confirm *ConfirmationBlockObject `json:"confirm,omitempty"` +} + +// ElementType returns the type of the Element +func (s DatePickerBlockElement) ElementType() MessageElementType { + return s.Type +} + +// NewDatePickerBlockElement returns an instance of a date picker element +func NewDatePickerBlockElement(actionID string) *DatePickerBlockElement { + return &DatePickerBlockElement{ + Type: METDatepicker, + ActionID: actionID, + } +} diff --git a/vendor/github.com/nlopes/slack/block_image.go b/vendor/github.com/nlopes/slack/block_image.go new file mode 100644 index 0000000..6de3f63 --- /dev/null +++ b/vendor/github.com/nlopes/slack/block_image.go @@ -0,0 +1,28 @@ +package slack + +// ImageBlock defines data required to display an image as a block element +// +// More Information: https://api.slack.com/reference/messaging/blocks#image +type ImageBlock struct { + Type MessageBlockType `json:"type"` + ImageURL string `json:"image_url"` + AltText string `json:"alt_text"` + BlockID string `json:"block_id,omitempty"` + Title *TextBlockObject `json:"title"` +} + +// BlockType returns the type of the block +func (s ImageBlock) BlockType() MessageBlockType { + return s.Type +} + +// NewImageBlock returns an instance of a new Image Block type +func NewImageBlock(imageURL, altText, blockID string, title *TextBlockObject) *ImageBlock { + return &ImageBlock{ + Type: MBTImage, + ImageURL: imageURL, + AltText: altText, + BlockID: blockID, + Title: title, + } +} diff --git a/vendor/github.com/nlopes/slack/block_object.go b/vendor/github.com/nlopes/slack/block_object.go new file mode 100644 index 0000000..9e77e6c --- /dev/null +++ b/vendor/github.com/nlopes/slack/block_object.go @@ -0,0 +1,216 @@ +package slack + +import ( + "encoding/json" +) + +// Block Objects are also known as Composition Objects +// +// For more information: https://api.slack.com/reference/messaging/composition-objects + +// BlockObject defines an interface that all block object types should +// implement. +// @TODO: Is this interface needed? + +// blockObject object types +const ( + MarkdownType = "mrkdwn" + PlainTextType = "plain_text" + // The following objects don't actually have types and their corresponding + // const values are just for internal use + motConfirmation = "confirm" + motOption = "option" + motOptionGroup = "option_group" +) + +type MessageObjectType string + +type blockObject interface { + validateType() MessageObjectType +} + +type BlockObjects struct { + TextObjects []*TextBlockObject + ConfirmationObjects []*ConfirmationBlockObject + OptionObjects []*OptionBlockObject + OptionGroupObjects []*OptionGroupBlockObject +} + +// UnmarshalJSON implements the Unmarshaller interface for BlockObjects, so that any JSON +// unmarshalling is delegated and proper type determination can be made before unmarshal +func (b *BlockObjects) UnmarshalJSON(data []byte) error { + var raw []json.RawMessage + err := json.Unmarshal(data, &raw) + if err != nil { + return err + } + + for _, r := range raw { + var obj map[string]interface{} + err := json.Unmarshal(r, &obj) + if err != nil { + return err + } + + blockObjectType := getBlockObjectType(obj) + + switch blockObjectType { + case PlainTextType, MarkdownType: + object, err := unmarshalBlockObject(r, &TextBlockObject{}) + if err != nil { + return err + } + b.TextObjects = append(b.TextObjects, object.(*TextBlockObject)) + case motConfirmation: + object, err := unmarshalBlockObject(r, &ConfirmationBlockObject{}) + if err != nil { + return err + } + b.ConfirmationObjects = append(b.ConfirmationObjects, object.(*ConfirmationBlockObject)) + case motOption: + object, err := unmarshalBlockObject(r, &OptionBlockObject{}) + if err != nil { + return err + } + b.OptionObjects = append(b.OptionObjects, object.(*OptionBlockObject)) + case motOptionGroup: + object, err := unmarshalBlockObject(r, &OptionGroupBlockObject{}) + if err != nil { + return err + } + b.OptionGroupObjects = append(b.OptionGroupObjects, object.(*OptionGroupBlockObject)) + + } + } + + return nil +} + +// Ideally would have a better way to identify the block objects for +// type casting at time of unmarshalling, should be adapted if possible +// to accomplish in a more reliable manner. +func getBlockObjectType(obj map[string]interface{}) string { + if t, ok := obj["type"].(string); ok { + return t + } + if _, ok := obj["confirm"].(string); ok { + return "confirm" + } + if _, ok := obj["options"].(string); ok { + return "option_group" + } + if _, ok := obj["text"].(string); ok { + if _, ok := obj["value"].(string); ok { + return "option" + } + } + return "" +} + +func unmarshalBlockObject(r json.RawMessage, object blockObject) (blockObject, error) { + err := json.Unmarshal(r, object) + if err != nil { + return nil, err + } + return object, nil +} + +// TextBlockObject defines a text element object to be used with blocks +// +// More Information: https://api.slack.com/reference/messaging/composition-objects#text +type TextBlockObject struct { + Type string `json:"type"` + Text string `json:"text"` + Emoji bool `json:"emoji,omitempty"` + Verbatim bool `json:"verbatim,omitempty"` +} + +// validateType enforces block objects for element and block parameters +func (s TextBlockObject) validateType() MessageObjectType { + return MessageObjectType(s.Type) +} + +// validateType enforces block objects for element and block parameters +func (s TextBlockObject) MixedElementType() MixedElementType { + return MixedElementText +} + +// NewTextBlockObject returns an instance of a new Text Block Object +func NewTextBlockObject(elementType, text string, emoji, verbatim bool) *TextBlockObject { + return &TextBlockObject{ + Type: elementType, + Text: text, + Emoji: emoji, + Verbatim: verbatim, + } +} + +// ConfirmationBlockObject defines a dialog that provides a confirmation step to +// any interactive element. This dialog will ask the user to confirm their action by +// offering a confirm and deny buttons. +// +// More Information: https://api.slack.com/reference/messaging/composition-objects#confirm +type ConfirmationBlockObject struct { + Title *TextBlockObject `json:"title"` + Text *TextBlockObject `json:"text"` + Confirm *TextBlockObject `json:"confirm"` + Deny *TextBlockObject `json:"deny"` +} + +// validateType enforces block objects for element and block parameters +func (s ConfirmationBlockObject) validateType() MessageObjectType { + return motConfirmation +} + +// NewConfirmationBlockObject returns an instance of a new Confirmation Block Object +func NewConfirmationBlockObject(title, text, confirm, deny *TextBlockObject) *ConfirmationBlockObject { + return &ConfirmationBlockObject{ + Title: title, + Text: text, + Confirm: confirm, + Deny: deny, + } +} + +// OptionBlockObject represents a single selectable item in a select menu +// +// More Information: https://api.slack.com/reference/messaging/composition-objects#option +type OptionBlockObject struct { + Text *TextBlockObject `json:"text"` + Value string `json:"value"` + URL string `json:"url"` +} + +// NewOptionBlockObject returns an instance of a new Option Block Element +func NewOptionBlockObject(value string, text *TextBlockObject) *OptionBlockObject { + return &OptionBlockObject{ + Text: text, + Value: value, + } +} + +// validateType enforces block objects for element and block parameters +func (s OptionBlockObject) validateType() MessageObjectType { + return motOption +} + +// OptionGroupBlockObject Provides a way to group options in a select menu. +// +// More Information: https://api.slack.com/reference/messaging/composition-objects#option-group +type OptionGroupBlockObject struct { + Label *TextBlockObject `json:"label,omitempty"` + Options []*OptionBlockObject `json:"options"` +} + +// validateType enforces block objects for element and block parameters +func (s OptionGroupBlockObject) validateType() MessageObjectType { + return motOptionGroup +} + +// NewOptionGroupBlockElement returns an instance of a new option group block element +func NewOptionGroupBlockElement(label *TextBlockObject, options ...*OptionBlockObject) *OptionGroupBlockObject { + return &OptionGroupBlockObject{ + Label: label, + Options: options, + } +} diff --git a/vendor/github.com/nlopes/slack/block_section.go b/vendor/github.com/nlopes/slack/block_section.go new file mode 100644 index 0000000..01ffd5a --- /dev/null +++ b/vendor/github.com/nlopes/slack/block_section.go @@ -0,0 +1,42 @@ +package slack + +// SectionBlock defines a new block of type section +// +// More Information: https://api.slack.com/reference/messaging/blocks#section +type SectionBlock struct { + Type MessageBlockType `json:"type"` + Text *TextBlockObject `json:"text,omitempty"` + BlockID string `json:"block_id,omitempty"` + Fields []*TextBlockObject `json:"fields,omitempty"` + Accessory *Accessory `json:"accessory,omitempty"` +} + +// BlockType returns the type of the block +func (s SectionBlock) BlockType() MessageBlockType { + return s.Type +} + +// SectionBlockOption allows configuration of options for a new section block +type SectionBlockOption func(*SectionBlock) + +func SectionBlockOptionBlockID(blockID string) SectionBlockOption { + return func(block *SectionBlock) { + block.BlockID = blockID + } +} + +// NewSectionBlock returns a new instance of a section block to be rendered +func NewSectionBlock(textObj *TextBlockObject, fields []*TextBlockObject, accessory *Accessory, options ...SectionBlockOption) *SectionBlock { + block := SectionBlock{ + Type: MBTSection, + Text: textObj, + Fields: fields, + Accessory: accessory, + } + + for _, option := range options { + option(&block) + } + + return &block +} diff --git a/vendor/github.com/nlopes/slack/bots.go b/vendor/github.com/nlopes/slack/bots.go index e27e76a..5d5a2ad 100644 --- a/vendor/github.com/nlopes/slack/bots.go +++ b/vendor/github.com/nlopes/slack/bots.go @@ -2,7 +2,6 @@ package slack import ( "context" - "errors" "net/url" ) @@ -19,15 +18,17 @@ type botResponseFull struct { SlackResponse } -func botRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*botResponseFull, error) { +func (api *Client) botRequest(ctx context.Context, path string, values url.Values) (*botResponseFull, error) { response := &botResponseFull{} - err := postSlackMethod(ctx, client, path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) + + if err := response.Err(); err != nil { + return nil, err } + return response, nil } @@ -40,10 +41,13 @@ func (api *Client) GetBotInfo(bot string) (*Bot, error) { func (api *Client) GetBotInfoContext(ctx context.Context, bot string) (*Bot, error) { values := url.Values{ "token": {api.token}, - "bot": {bot}, } - response, err := botRequest(ctx, api.httpclient, "bots.info", values, api) + if bot != "" { + values.Add("bot", bot) + } + + response, err := api.botRequest(ctx, "bots.info", values) if err != nil { return nil, err } diff --git a/vendor/github.com/nlopes/slack/channels.go b/vendor/github.com/nlopes/slack/channels.go index 711ae7c..c99e665 100644 --- a/vendor/github.com/nlopes/slack/channels.go +++ b/vendor/github.com/nlopes/slack/channels.go @@ -2,7 +2,6 @@ package slack import ( "context" - "errors" "net/url" "strconv" ) @@ -19,23 +18,21 @@ type channelResponseFull struct { // Channel contains information about the channel type Channel struct { - groupConversation + GroupConversation IsChannel bool `json:"is_channel"` IsGeneral bool `json:"is_general"` IsMember bool `json:"is_member"` Locale string `json:"locale"` } -func channelRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*channelResponseFull, error) { +func (api *Client) channelRequest(ctx context.Context, path string, values url.Values) (*channelResponseFull, error) { response := &channelResponseFull{} - err := postForm(ctx, client, APIURL+path, values, response, d) + err := postForm(ctx, api.httpclient, api.endpoint+path, values, response, api) if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) - } - return response, nil + + return response, response.Err() } type channelsConfig struct { @@ -75,7 +72,7 @@ func (api *Client) ArchiveChannelContext(ctx context.Context, channelID string) "channel": {channelID}, } - _, err = channelRequest(ctx, api.httpclient, "channels.archive", values, api) + _, err = api.channelRequest(ctx, "channels.archive", values) return err } @@ -93,7 +90,7 @@ func (api *Client) UnarchiveChannelContext(ctx context.Context, channelID string "channel": {channelID}, } - _, err = channelRequest(ctx, api.httpclient, "channels.unarchive", values, api) + _, err = api.channelRequest(ctx, "channels.unarchive", values) return err } @@ -111,7 +108,7 @@ func (api *Client) CreateChannelContext(ctx context.Context, channelName string) "name": {channelName}, } - response, err := channelRequest(ctx, api.httpclient, "channels.create", values, api) + response, err := api.channelRequest(ctx, "channels.create", values) if err != nil { return nil, err } @@ -156,7 +153,7 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channelID strin } } - response, err := channelRequest(ctx, api.httpclient, "channels.history", values, api) + response, err := api.channelRequest(ctx, "channels.history", values) if err != nil { return nil, err } @@ -178,7 +175,7 @@ func (api *Client) GetChannelInfoContext(ctx context.Context, channelID string) "include_locale": {strconv.FormatBool(true)}, } - response, err := channelRequest(ctx, api.httpclient, "channels.info", values, api) + response, err := api.channelRequest(ctx, "channels.info", values) if err != nil { return nil, err } @@ -200,7 +197,7 @@ func (api *Client) InviteUserToChannelContext(ctx context.Context, channelID, us "user": {user}, } - response, err := channelRequest(ctx, api.httpclient, "channels.invite", values, api) + response, err := api.channelRequest(ctx, "channels.invite", values) if err != nil { return nil, err } @@ -221,7 +218,7 @@ func (api *Client) JoinChannelContext(ctx context.Context, channelName string) ( "name": {channelName}, } - response, err := channelRequest(ctx, api.httpclient, "channels.join", values, api) + response, err := api.channelRequest(ctx, "channels.join", values) if err != nil { return nil, err } @@ -242,7 +239,7 @@ func (api *Client) LeaveChannelContext(ctx context.Context, channelID string) (b "channel": {channelID}, } - response, err := channelRequest(ctx, api.httpclient, "channels.leave", values, api) + response, err := api.channelRequest(ctx, "channels.leave", values) if err != nil { return false, err } @@ -265,7 +262,7 @@ func (api *Client) KickUserFromChannelContext(ctx context.Context, channelID, us "user": {user}, } - _, err = channelRequest(ctx, api.httpclient, "channels.kick", values, api) + _, err = api.channelRequest(ctx, "channels.kick", values) return err } @@ -283,6 +280,7 @@ func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool, "token": {api.token}, }, } + if excludeArchived { options = append(options, GetChannelsOptionExcludeArchived()) } @@ -293,7 +291,7 @@ func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool, } } - response, err := channelRequest(ctx, api.httpclient, "channels.list", config.values, api) + response, err := api.channelRequest(ctx, "channels.list", config.values) if err != nil { return nil, err } @@ -320,7 +318,7 @@ func (api *Client) SetChannelReadMarkContext(ctx context.Context, channelID, ts "ts": {ts}, } - _, err = channelRequest(ctx, api.httpclient, "channels.mark", values, api) + _, err = api.channelRequest(ctx, "channels.mark", values) return err } @@ -341,7 +339,7 @@ func (api *Client) RenameChannelContext(ctx context.Context, channelID, name str // XXX: the created entry in this call returns a string instead of a number // so I may have to do some workaround to solve it. - response, err := channelRequest(ctx, api.httpclient, "channels.rename", values, api) + response, err := api.channelRequest(ctx, "channels.rename", values) if err != nil { return nil, err } @@ -363,7 +361,7 @@ func (api *Client) SetChannelPurposeContext(ctx context.Context, channelID, purp "purpose": {purpose}, } - response, err := channelRequest(ctx, api.httpclient, "channels.setPurpose", values, api) + response, err := api.channelRequest(ctx, "channels.setPurpose", values) if err != nil { return "", err } @@ -385,7 +383,7 @@ func (api *Client) SetChannelTopicContext(ctx context.Context, channelID, topic "topic": {topic}, } - response, err := channelRequest(ctx, api.httpclient, "channels.setTopic", values, api) + response, err := api.channelRequest(ctx, "channels.setTopic", values) if err != nil { return "", err } @@ -406,7 +404,7 @@ func (api *Client) GetChannelRepliesContext(ctx context.Context, channelID, thre "channel": {channelID}, "thread_ts": {thread_ts}, } - response, err := channelRequest(ctx, api.httpclient, "channels.replies", values, api) + response, err := api.channelRequest(ctx, "channels.replies", values) if err != nil { return nil, err } diff --git a/vendor/github.com/nlopes/slack/chat.go b/vendor/github.com/nlopes/slack/chat.go index c08c180..a480e5a 100644 --- a/vendor/github.com/nlopes/slack/chat.go +++ b/vendor/github.com/nlopes/slack/chat.go @@ -3,6 +3,7 @@ package slack import ( "context" "encoding/json" + "net/http" "net/url" "github.com/nlopes/slack/slackutilsx" @@ -25,7 +26,7 @@ const ( type chatResponseFull struct { Channel string `json:"channel"` - Timestamp string `json:"ts"` //Regualr message timestamp + Timestamp string `json:"ts"` //Regular message timestamp MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp Text string `json:"text"` SlackResponse @@ -156,17 +157,18 @@ func (api *Client) SendMessage(channel string, options ...MsgOption) (string, st } // SendMessageContext more flexible method for configuring messages with a custom context. -func (api *Client) SendMessageContext(ctx context.Context, channelID string, options ...MsgOption) (channel string, timestamp string, text string, err error) { +func (api *Client) SendMessageContext(ctx context.Context, channelID string, options ...MsgOption) (_channel string, _timestamp string, _text string, err error) { var ( - config sendConfig + req *http.Request + parser func(*chatResponseFull) responseParser response chatResponseFull ) - if config, err = applyMsgOptions(api.token, channelID, options...); err != nil { + if req, parser, err = buildSender(api.endpoint, options...).BuildRequest(api.token, channelID); err != nil { return "", "", "", err } - if err = postForm(ctx, api.httpclient, config.endpoint, config.values, &response, api); err != nil { + if err = doPost(ctx, api.httpclient, req, parser(&response), api); err != nil { return "", "", "", err } @@ -176,14 +178,15 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt // UnsafeApplyMsgOptions utility function for debugging/testing chat requests. // NOTE: USE AT YOUR OWN RISK: No issues relating to the use of this function // will be supported by the library. -func UnsafeApplyMsgOptions(token, channel string, options ...MsgOption) (string, url.Values, error) { - config, err := applyMsgOptions(token, channel, options...) +func UnsafeApplyMsgOptions(token, channel, apiurl string, options ...MsgOption) (string, url.Values, error) { + config, err := applyMsgOptions(token, channel, apiurl, options...) return config.endpoint, config.values, err } -func applyMsgOptions(token, channel string, options ...MsgOption) (sendConfig, error) { +func applyMsgOptions(token, channel, apiurl string, options ...MsgOption) (sendConfig, error) { config := sendConfig{ - endpoint: APIURL + string(chatPostMessage), + apiurl: apiurl, + endpoint: apiurl + string(chatPostMessage), values: url.Values{ "token": {token}, "channel": {channel}, @@ -199,6 +202,13 @@ func applyMsgOptions(token, channel string, options ...MsgOption) (sendConfig, e return config, nil } +func buildSender(apiurl string, options ...MsgOption) sendConfig { + return sendConfig{ + apiurl: apiurl, + options: options, + } +} + type sendMode string const ( @@ -206,22 +216,77 @@ const ( chatPostMessage sendMode = "chat.postMessage" chatDelete sendMode = "chat.delete" chatPostEphemeral sendMode = "chat.postEphemeral" + chatResponse sendMode = "chat.responseURL" chatMeMessage sendMode = "chat.meMessage" chatUnfurl sendMode = "chat.unfurl" ) type sendConfig struct { + apiurl string + options []MsgOption + mode sendMode + endpoint string + values url.Values + attachments []Attachment + responseType string +} + +func (t sendConfig) BuildRequest(token, channelID string) (req *http.Request, _ func(*chatResponseFull) responseParser, err error) { + if t, err = applyMsgOptions(token, channelID, t.apiurl, t.options...); err != nil { + return nil, nil, err + } + + switch t.mode { + case chatResponse: + return responseURLSender{ + endpoint: t.endpoint, + values: t.values, + attachments: t.attachments, + responseType: t.responseType, + }.BuildRequest() + default: + return formSender{endpoint: t.endpoint, values: t.values}.BuildRequest() + } +} + +type formSender struct { endpoint string values url.Values } +func (t formSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) { + req, err := formReq(t.endpoint, t.values) + return req, func(resp *chatResponseFull) responseParser { + return newJSONParser(resp) + }, err +} + +type responseURLSender struct { + endpoint string + values url.Values + attachments []Attachment + responseType string +} + +func (t responseURLSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) { + req, err := jsonReq(t.endpoint, Msg{ + Text: t.values.Get("text"), + Timestamp: t.values.Get("ts"), + Attachments: t.attachments, + ResponseType: t.responseType, + }) + return req, func(resp *chatResponseFull) responseParser { + return newContentTypeParser(resp) + }, err +} + // MsgOption option provided when sending a message. type MsgOption func(*sendConfig) error // MsgOptionPost posts a messages, this is the default. func MsgOptionPost() MsgOption { return func(config *sendConfig) error { - config.endpoint = APIURL + string(chatPostMessage) + config.endpoint = config.apiurl + string(chatPostMessage) config.values.Del("ts") return nil } @@ -230,7 +295,7 @@ func MsgOptionPost() MsgOption { // MsgOptionPostEphemeral - posts an ephemeral message to the provided user. func MsgOptionPostEphemeral(userID string) MsgOption { return func(config *sendConfig) error { - config.endpoint = APIURL + string(chatPostEphemeral) + config.endpoint = config.apiurl + string(chatPostEphemeral) MsgOptionUser(userID)(config) config.values.Del("ts") @@ -241,7 +306,7 @@ func MsgOptionPostEphemeral(userID string) MsgOption { // MsgOptionMeMessage posts a "me message" type from the calling user func MsgOptionMeMessage() MsgOption { return func(config *sendConfig) error { - config.endpoint = APIURL + string(chatMeMessage) + config.endpoint = config.apiurl + string(chatMeMessage) return nil } } @@ -249,7 +314,7 @@ func MsgOptionMeMessage() MsgOption { // MsgOptionUpdate updates a message based on the timestamp. func MsgOptionUpdate(timestamp string) MsgOption { return func(config *sendConfig) error { - config.endpoint = APIURL + string(chatUpdate) + config.endpoint = config.apiurl + string(chatUpdate) config.values.Add("ts", timestamp) return nil } @@ -258,7 +323,7 @@ func MsgOptionUpdate(timestamp string) MsgOption { // MsgOptionDelete deletes a message based on the timestamp. func MsgOptionDelete(timestamp string) MsgOption { return func(config *sendConfig) error { - config.endpoint = APIURL + string(chatDelete) + config.endpoint = config.apiurl + string(chatDelete) config.values.Add("ts", timestamp) return nil } @@ -267,7 +332,7 @@ func MsgOptionDelete(timestamp string) MsgOption { // MsgOptionUnfurl unfurls a message based on the timestamp. func MsgOptionUnfurl(timestamp string, unfurls map[string]Attachment) MsgOption { return func(config *sendConfig) error { - config.endpoint = APIURL + string(chatUnfurl) + config.endpoint = config.apiurl + string(chatUnfurl) config.values.Add("ts", timestamp) unfurlsStr, err := json.Marshal(unfurls) if err == nil { @@ -277,6 +342,17 @@ func MsgOptionUnfurl(timestamp string, unfurls map[string]Attachment) MsgOption } } +// MsgOptionResponseURL supplies a url to use as the endpoint. +func MsgOptionResponseURL(url string, rt string) MsgOption { + return func(config *sendConfig) error { + config.mode = chatResponse + config.endpoint = url + config.responseType = rt + config.values.Del("ts") + return nil + } +} + // MsgOptionAsUser whether or not to send the message as the user. func MsgOptionAsUser(b bool) MsgOption { return func(config *sendConfig) error { @@ -322,9 +398,31 @@ func MsgOptionAttachments(attachments ...Attachment) MsgOption { return nil } - attachments, err := json.Marshal(attachments) + config.attachments = attachments + + // FIXME: We are setting the attachments on the message twice: above for + // the json version, and below for the html version. The marshalled bytes + // we put into config.values below don't work directly in the Msg version. + + attachmentBytes, err := json.Marshal(attachments) if err == nil { - config.values.Set("attachments", string(attachments)) + config.values.Set("attachments", string(attachmentBytes)) + } + + return err + } +} + +// MsgOptionBlocks sets blocks for the message +func MsgOptionBlocks(blocks ...Block) MsgOption { + return func(config *sendConfig) error { + if blocks == nil { + return nil + } + + blocks, err := json.Marshal(blocks) + if err == nil { + config.values.Set("blocks", string(blocks)) } return err } @@ -395,15 +493,31 @@ func MsgOptionParse(b bool) MsgOption { return func(c *sendConfig) error { var v string if b { - v = "1" + v = "full" } else { - v = "0" + v = "none" } c.values.Set("parse", v) return nil } } +// MsgOptionIconURL sets an icon URL +func MsgOptionIconURL(iconURL string) MsgOption { + return func(c *sendConfig) error { + c.values.Set("icon_url", iconURL) + return nil + } +} + +// MsgOptionIconEmoji sets an icon emoji +func MsgOptionIconEmoji(iconEmoji string) MsgOption { + return func(c *sendConfig) error { + c.values.Set("icon_emoji", iconEmoji) + return nil + } +} + // UnsafeMsgOptionEndpoint deliver the message to the specified endpoint. // NOTE: USE AT YOUR OWN RISK: No issues relating to the use of this Option // will be supported by the library, it is subject to change without notice that @@ -499,7 +613,7 @@ func (api *Client) GetPermalinkContext(ctx context.Context, params *PermalinkPar Permalink string `json:"permalink"` SlackResponse }{} - err := getSlackMethod(ctx, api.httpclient, "chat.getPermalink", values, &response, api) + err := api.getMethod(ctx, "chat.getPermalink", values, &response) if err != nil { return "", err } diff --git a/vendor/github.com/nlopes/slack/conversation.go b/vendor/github.com/nlopes/slack/conversation.go index ccd38f8..1e4a61f 100644 --- a/vendor/github.com/nlopes/slack/conversation.go +++ b/vendor/github.com/nlopes/slack/conversation.go @@ -2,14 +2,13 @@ package slack import ( "context" - "errors" "net/url" "strconv" "strings" ) // Conversation is the foundation for IM and BaseGroupConversation -type conversation struct { +type Conversation struct { ID string `json:"id"` Created JSONTime `json:"created"` IsOpen bool `json:"is_open"` @@ -36,8 +35,8 @@ type conversation struct { } // GroupConversation is the foundation for Group and Channel -type groupConversation struct { - conversation +type GroupConversation struct { + Conversation Name string `json:"name"` Creator string `json:"creator"` IsArchived bool `json:"is_archived"` @@ -67,10 +66,11 @@ type GetUsersInConversationParameters struct { } type GetConversationsForUserParameters struct { - UserID string - Cursor string - Types []string - Limit int + UserID string + Cursor string + Types []string + Limit int + ExcludeArchived bool } type responseMetaData struct { @@ -99,13 +99,16 @@ func (api *Client) GetUsersInConversationContext(ctx context.Context, params *Ge ResponseMetaData responseMetaData `json:"response_metadata"` SlackResponse }{} - err := postSlackMethod(ctx, api.httpclient, "conversations.members", values, &response, api) + + err := api.postMethod(ctx, "conversations.members", values, &response) if err != nil { return nil, "", err } - if !response.Ok { - return nil, "", errors.New(response.Error) + + if err := response.Err(); err != nil { + return nil, "", err } + return response.Members, response.ResponseMetaData.NextCursor, nil } @@ -131,12 +134,15 @@ func (api *Client) GetConversationsForUserContext(ctx context.Context, params *G if params.Types != nil { values.Add("types", strings.Join(params.Types, ",")) } + if params.ExcludeArchived { + values.Add("exclude_archived", "true") + } response := struct { Channels []Channel `json:"channels"` ResponseMetaData responseMetaData `json:"response_metadata"` SlackResponse }{} - err = postSlackMethod(ctx, api.httpclient, "users.conversations", values, &response, api) + err = api.postMethod(ctx, "users.conversations", values, &response) if err != nil { return nil, "", err } @@ -155,8 +161,9 @@ func (api *Client) ArchiveConversationContext(ctx context.Context, channelID str "token": {api.token}, "channel": {channelID}, } + response := SlackResponse{} - err := postSlackMethod(ctx, api.httpclient, "conversations.archive", values, &response, api) + err := api.postMethod(ctx, "conversations.archive", values, &response) if err != nil { return err } @@ -176,7 +183,7 @@ func (api *Client) UnArchiveConversationContext(ctx context.Context, channelID s "channel": {channelID}, } response := SlackResponse{} - err := postSlackMethod(ctx, api.httpclient, "conversations.unarchive", values, &response, api) + err := api.postMethod(ctx, "conversations.unarchive", values, &response) if err != nil { return err } @@ -200,7 +207,7 @@ func (api *Client) SetTopicOfConversationContext(ctx context.Context, channelID, SlackResponse Channel *Channel `json:"channel"` }{} - err := postSlackMethod(ctx, api.httpclient, "conversations.setTopic", values, &response, api) + err := api.postMethod(ctx, "conversations.setTopic", values, &response) if err != nil { return nil, err } @@ -224,7 +231,8 @@ func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelI SlackResponse Channel *Channel `json:"channel"` }{} - err := postSlackMethod(ctx, api.httpclient, "conversations.setPurpose", values, &response, api) + + err := api.postMethod(ctx, "conversations.setPurpose", values, &response) if err != nil { return nil, err } @@ -248,7 +256,8 @@ func (api *Client) RenameConversationContext(ctx context.Context, channelID, cha SlackResponse Channel *Channel `json:"channel"` }{} - err := postSlackMethod(ctx, api.httpclient, "conversations.rename", values, &response, api) + + err := api.postMethod(ctx, "conversations.rename", values, &response) if err != nil { return nil, err } @@ -272,7 +281,8 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel SlackResponse Channel *Channel `json:"channel"` }{} - err := postSlackMethod(ctx, api.httpclient, "conversations.invite", values, &response, api) + + err := api.postMethod(ctx, "conversations.invite", values, &response) if err != nil { return nil, err } @@ -292,8 +302,9 @@ func (api *Client) KickUserFromConversationContext(ctx context.Context, channelI "channel": {channelID}, "user": {user}, } + response := SlackResponse{} - err := postSlackMethod(ctx, api.httpclient, "conversations.kick", values, &response, api) + err := api.postMethod(ctx, "conversations.kick", values, &response) if err != nil { return err } @@ -318,7 +329,7 @@ func (api *Client) CloseConversationContext(ctx context.Context, channelID strin AlreadyClosed bool `json:"already_closed"` }{} - err = postSlackMethod(ctx, api.httpclient, "conversations.close", values, &response, api) + err = api.postMethod(ctx, "conversations.close", values, &response) if err != nil { return false, false, err } @@ -338,13 +349,12 @@ func (api *Client) CreateConversationContext(ctx context.Context, channelName st "name": {channelName}, "is_private": {strconv.FormatBool(isPrivate)}, } - response, err := channelRequest( - ctx, api.httpclient, "conversations.create", values, api) + response, err := api.channelRequest(ctx, "conversations.create", values) if err != nil { return nil, err } - return &response.Channel, response.Err() + return &response.Channel, nil } // GetConversationInfo retrieves information about a conversation @@ -359,8 +369,7 @@ func (api *Client) GetConversationInfoContext(ctx context.Context, channelID str "channel": {channelID}, "include_locale": {strconv.FormatBool(includeLocale)}, } - response, err := channelRequest( - ctx, api.httpclient, "conversations.info", values, api) + response, err := api.channelRequest(ctx, "conversations.info", values) if err != nil { return nil, err } @@ -380,7 +389,7 @@ func (api *Client) LeaveConversationContext(ctx context.Context, channelID strin "channel": {channelID}, } - response, err := channelRequest(ctx, api.httpclient, "conversations.leave", values, api) + response, err := api.channelRequest(ctx, "conversations.leave", values) if err != nil { return false, err } @@ -436,7 +445,7 @@ func (api *Client) GetConversationRepliesContext(ctx context.Context, params *Ge Messages []Message `json:"messages"` }{} - err = postSlackMethod(ctx, api.httpclient, "conversations.replies", values, &response, api) + err = api.postMethod(ctx, "conversations.replies", values, &response) if err != nil { return nil, false, "", err } @@ -476,7 +485,8 @@ func (api *Client) GetConversationsContext(ctx context.Context, params *GetConve ResponseMetaData responseMetaData `json:"response_metadata"` SlackResponse }{} - err = postSlackMethod(ctx, api.httpclient, "conversations.list", values, &response, api) + + err = api.postMethod(ctx, "conversations.list", values, &response) if err != nil { return nil, "", err } @@ -513,7 +523,8 @@ func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConv AlreadyOpen bool `json:"already_open"` SlackResponse }{} - err := postSlackMethod(ctx, api.httpclient, "conversations.open", values, &response, api) + + err := api.postMethod(ctx, "conversations.open", values, &response) if err != nil { return nil, false, false, err } @@ -537,7 +548,8 @@ func (api *Client) JoinConversationContext(ctx context.Context, channelID string } `json:"response_metadata"` SlackResponse }{} - err := postSlackMethod(ctx, api.httpclient, "conversations.join", values, &response, api) + + err := api.postMethod(ctx, "conversations.join", values, &response) if err != nil { return nil, "", nil, err } @@ -599,7 +611,7 @@ func (api *Client) GetConversationHistoryContext(ctx context.Context, params *Ge response := GetConversationHistoryResponse{} - err := postSlackMethod(ctx, api.httpclient, "conversations.history", values, &response, api) + err := api.postMethod(ctx, "conversations.history", values, &response) if err != nil { return nil, err } diff --git a/vendor/github.com/nlopes/slack/dialog.go b/vendor/github.com/nlopes/slack/dialog.go index 7b9e381..376cd9e 100644 --- a/vendor/github.com/nlopes/slack/dialog.go +++ b/vendor/github.com/nlopes/slack/dialog.go @@ -3,7 +3,7 @@ package slack import ( "context" "encoding/json" - "errors" + "strings" ) // InputType is the type of the dialog input type @@ -25,6 +25,7 @@ type DialogInput struct { Name string `json:"name"` Placeholder string `json:"placeholder"` Optional bool `json:"optional"` + Hint string `json:"hint"` } // DialogTrigger ... @@ -89,7 +90,7 @@ func (api *Client) OpenDialog(triggerID string, dialog Dialog) (err error) { // EXPERIMENTAL: dialog functionality is currently experimental, api is not considered stable. func (api *Client) OpenDialogContext(ctx context.Context, triggerID string, dialog Dialog) (err error) { if triggerID == "" { - return errors.New("received empty parameters") + return ErrParametersMissing } req := DialogTrigger{ @@ -103,10 +104,15 @@ func (api *Client) OpenDialogContext(ctx context.Context, triggerID string, dial } response := &DialogOpenResponse{} - endpoint := APIURL + "dialog.open" + endpoint := api.endpoint + "dialog.open" if err := postJSON(ctx, api.httpclient, endpoint, api.token, encoded, response, api); err != nil { return err } + if len(response.DialogResponseMetadata.Messages) > 0 { + response.Ok = false + response.Error += "\n" + strings.Join(response.DialogResponseMetadata.Messages, "\n") + } + return response.Err() } diff --git a/vendor/github.com/nlopes/slack/dialog_select.go b/vendor/github.com/nlopes/slack/dialog_select.go index ea95ccf..385cef6 100644 --- a/vendor/github.com/nlopes/slack/dialog_select.go +++ b/vendor/github.com/nlopes/slack/dialog_select.go @@ -21,10 +21,11 @@ type DialogInputSelect struct { DialogInput Value string `json:"value,omitempty"` //Optional. DataSource SelectDataSource `json:"data_source,omitempty"` //Optional. Allowed values: "users", "channels", "conversations", "external". - SelectedOptions string `json:"selected_options,omitempty"` //Optional. Default value for "external" only + SelectedOptions []DialogSelectOption `json:"selected_options,omitempty"` //Optional. May hold at most one element, for use with "external" only. Options []DialogSelectOption `json:"options,omitempty"` //One of options or option_groups is required. OptionGroups []DialogOptionGroup `json:"option_groups,omitempty"` //Provide up to 100 options. MinQueryLength int `json:"min_query_length,omitempty"` //Optional. minimum characters before query is sent. + Hint string `json:"hint,omitempty"` //Optional. Additional hint text. } // DialogSelectOption is an option for the user to select from the menu @@ -54,14 +55,7 @@ func NewStaticSelectDialogInput(name, label string, options []DialogSelectOption } // NewGroupedSelectDialogInput creates grouped options select input for Dialogs. -func NewGroupedSelectDialogInput(name, label string, groups map[string]map[string]string) *DialogInputSelect { - optionGroups := []DialogOptionGroup{} - for groupName, options := range groups { - optionGroups = append(optionGroups, DialogOptionGroup{ - Label: groupName, - Options: optionsFromMap(options), - }) - } +func NewGroupedSelectDialogInput(name, label string, options []DialogOptionGroup) *DialogInputSelect { return &DialogInputSelect{ DialogInput: DialogInput{ Type: InputTypeSelect, @@ -69,34 +63,15 @@ func NewGroupedSelectDialogInput(name, label string, groups map[string]map[strin Label: label, }, DataSource: DialogDataSourceStatic, - OptionGroups: optionGroups, - } -} - -func optionsFromArray(options []string) []DialogSelectOption { - selectOptions := make([]DialogSelectOption, len(options)) - for idx, value := range options { - selectOptions[idx] = DialogSelectOption{ - Label: value, - Value: value, - } - } - return selectOptions + OptionGroups: options} } -func optionsFromMap(options map[string]string) []DialogSelectOption { - selectOptions := make([]DialogSelectOption, len(options)) - idx := 0 - var option DialogSelectOption - for key, value := range options { - option = DialogSelectOption{ - Label: key, - Value: value, - } - selectOptions[idx] = option - idx++ +// NewDialogOptionGroup creates a DialogOptionGroup from several select options +func NewDialogOptionGroup(label string, options ...DialogSelectOption) DialogOptionGroup { + return DialogOptionGroup{ + Label: label, + Options: options, } - return selectOptions } // NewConversationsSelect returns a `Conversations` select diff --git a/vendor/github.com/nlopes/slack/dialog_text.go b/vendor/github.com/nlopes/slack/dialog_text.go index bf9602c..da06bd6 100644 --- a/vendor/github.com/nlopes/slack/dialog_text.go +++ b/vendor/github.com/nlopes/slack/dialog_text.go @@ -3,6 +3,9 @@ package slack // TextInputSubtype Accepts email, number, tel, or url. In some form factors, optimized input is provided for this subtype. type TextInputSubtype string +// TextInputOption handle to extra inputs options. +type TextInputOption func(*TextInputElement) + const ( // InputSubtypeEmail email keyboard InputSubtypeEmail TextInputSubtype = "email" @@ -26,8 +29,8 @@ type TextInputElement struct { } // NewTextInput constructor for a `text` input -func NewTextInput(name, label, text string) *TextInputElement { - return &TextInputElement{ +func NewTextInput(name, label, text string, options ...TextInputOption) *TextInputElement { + t := &TextInputElement{ DialogInput: DialogInput{ Type: InputTypeText, Name: name, @@ -35,6 +38,12 @@ func NewTextInput(name, label, text string) *TextInputElement { }, Value: text, } + + for _, opt := range options { + opt(t) + } + + return t } // NewTextAreaInput constructor for a `textarea` input diff --git a/vendor/github.com/nlopes/slack/dnd.go b/vendor/github.com/nlopes/slack/dnd.go index da6e4a1..a3aa680 100644 --- a/vendor/github.com/nlopes/slack/dnd.go +++ b/vendor/github.com/nlopes/slack/dnd.go @@ -2,7 +2,6 @@ package slack import ( "context" - "errors" "net/url" "strconv" "strings" @@ -36,16 +35,14 @@ type dndTeamInfoResponse struct { SlackResponse } -func dndRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*dndResponseFull, error) { +func (api *Client) dndRequest(ctx context.Context, path string, values url.Values) (*dndResponseFull, error) { response := &dndResponseFull{} - err := postSlackMethod(ctx, client, path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) - } - return response, nil + + return response, response.Err() } // EndDND ends the user's scheduled Do Not Disturb session @@ -61,7 +58,7 @@ func (api *Client) EndDNDContext(ctx context.Context) error { response := &SlackResponse{} - if err := postSlackMethod(ctx, api.httpclient, "dnd.endDnd", values, response, api); err != nil { + if err := api.postMethod(ctx, "dnd.endDnd", values, response); err != nil { return err } @@ -79,7 +76,7 @@ func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) { "token": {api.token}, } - response, err := dndRequest(ctx, api.httpclient, "dnd.endSnooze", values, api) + response, err := api.dndRequest(ctx, "dnd.endSnooze", values) if err != nil { return nil, err } @@ -100,7 +97,7 @@ func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDSta values.Set("user", *user) } - response, err := dndRequest(ctx, api.httpclient, "dnd.info", values, api) + response, err := api.dndRequest(ctx, "dnd.info", values) if err != nil { return nil, err } @@ -120,13 +117,14 @@ func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (m } response := &dndTeamInfoResponse{} - if err := postSlackMethod(ctx, api.httpclient, "dnd.teamInfo", values, response, api); err != nil { + if err := api.postMethod(ctx, "dnd.teamInfo", values, response); err != nil { return nil, err } if response.Err() != nil { return nil, response.Err() } + return response.Users, nil } @@ -137,7 +135,7 @@ func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) { return api.SetSnoozeContext(context.Background(), minutes) } -// SetSnooze adjusts the snooze duration for a user's Do Not Disturb settings with a custom context. +// SetSnoozeContext adjusts the snooze duration for a user's Do Not Disturb settings with a custom context. // For more information see the SetSnooze docs func (api *Client) SetSnoozeContext(ctx context.Context, minutes int) (*DNDStatus, error) { values := url.Values{ @@ -145,7 +143,7 @@ func (api *Client) SetSnoozeContext(ctx context.Context, minutes int) (*DNDStatu "num_minutes": {strconv.Itoa(minutes)}, } - response, err := dndRequest(ctx, api.httpclient, "dnd.setSnooze", values, api) + response, err := api.dndRequest(ctx, "dnd.setSnooze", values) if err != nil { return nil, err } diff --git a/vendor/github.com/nlopes/slack/emoji.go b/vendor/github.com/nlopes/slack/emoji.go index aed2129..b2b0c6c 100644 --- a/vendor/github.com/nlopes/slack/emoji.go +++ b/vendor/github.com/nlopes/slack/emoji.go @@ -2,7 +2,6 @@ package slack import ( "context" - "errors" "net/url" ) @@ -23,12 +22,14 @@ func (api *Client) GetEmojiContext(ctx context.Context) (map[string]string, erro } response := &emojiResponseFull{} - err := postSlackMethod(ctx, api.httpclient, "emoji.list", values, response, api) + err := api.postMethod(ctx, "emoji.list", values, response) if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) + + if response.Err() != nil { + return nil, response.Err() } + return response.Emoji, nil } diff --git a/vendor/github.com/nlopes/slack/errors.go b/vendor/github.com/nlopes/slack/errors.go new file mode 100644 index 0000000..09113ff --- /dev/null +++ b/vendor/github.com/nlopes/slack/errors.go @@ -0,0 +1,18 @@ +package slack + +import "github.com/nlopes/slack/internal/errorsx" + +// Errors returned by various methods. +const ( + ErrAlreadyDisconnected = errorsx.String("Invalid call to Disconnect - Slack API is already disconnected") + ErrRTMDisconnected = errorsx.String("disconnect received while trying to connect") + ErrParametersMissing = errorsx.String("received empty parameters") + ErrInvalidConfiguration = errorsx.String("invalid configuration") + ErrMissingHeaders = errorsx.String("missing headers") + ErrExpiredTimestamp = errorsx.String("timestamp is too old") +) + +// internal errors +const ( + errPaginationComplete = errorsx.String("pagination complete") +) diff --git a/vendor/github.com/nlopes/slack/files.go b/vendor/github.com/nlopes/slack/files.go index 136ea26..3a7363d 100644 --- a/vendor/github.com/nlopes/slack/files.go +++ b/vendor/github.com/nlopes/slack/files.go @@ -2,7 +2,6 @@ package slack import ( "context" - "errors" "fmt" "io" "net/url" @@ -91,7 +90,8 @@ type File struct { } type Share struct { - Public map[string][]ShareFileInfo `json:"public"` + Public map[string][]ShareFileInfo `json:"public"` + Private map[string][]ShareFileInfo `json:"private"` } type ShareFileInfo struct { @@ -134,11 +134,21 @@ type GetFilesParameters struct { Page int } +// ListFilesParameters contains all the parameters necessary (including the optional ones) for a ListFiles() request +type ListFilesParameters struct { + Limit int + User string + Channel string + Types string + Cursor string +} + type fileResponseFull struct { File `json:"file"` Paging `json:"paging"` - Comments []Comment `json:"comments"` - Files []File `json:"files"` + Comments []Comment `json:"comments"` + Files []File `json:"files"` + Metadata ResponseMetadata `json:"response_metadata"` SlackResponse } @@ -156,9 +166,9 @@ func NewGetFilesParameters() GetFilesParameters { } } -func fileRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*fileResponseFull, error) { +func (api *Client) fileRequest(ctx context.Context, path string, values url.Values) (*fileResponseFull, error) { response := &fileResponseFull{} - err := postForm(ctx, client, APIURL+path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } @@ -180,18 +190,57 @@ func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count, "page": {strconv.Itoa(page)}, } - response, err := fileRequest(ctx, api.httpclient, "files.info", values, api) + response, err := api.fileRequest(ctx, "files.info", values) if err != nil { return nil, nil, nil, err } return &response.File, response.Comments, &response.Paging, nil } +// GetFile retreives a given file from its private download URL +func (api *Client) GetFile(downloadURL string, writer io.Writer) error { + return downloadFile(api.httpclient, api.token, downloadURL, writer, api) +} + // GetFiles retrieves all files according to the parameters given func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) { return api.GetFilesContext(context.Background(), params) } +// ListFiles retrieves all files according to the parameters given. Uses cursor based pagination. +func (api *Client) ListFiles(params ListFilesParameters) ([]File, *ListFilesParameters, error) { + return api.ListFilesContext(context.Background(), params) +} + +// ListFilesContext retrieves all files according to the parameters given with a custom context. Uses cursor based pagination. +func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParameters) ([]File, *ListFilesParameters, error) { + values := url.Values{ + "token": {api.token}, + } + + if params.User != DEFAULT_FILES_USER { + values.Add("user", params.User) + } + if params.Channel != DEFAULT_FILES_CHANNEL { + values.Add("channel", params.Channel) + } + if params.Limit != DEFAULT_FILES_COUNT { + values.Add("limit", strconv.Itoa(params.Limit)) + } + if params.Cursor != "" { + values.Add("cursor", params.Cursor) + } + + response, err := api.fileRequest(ctx, "files.list", values) + if err != nil { + return nil, nil, err + } + + params.Cursor = response.Metadata.Cursor + + return response.Files, ¶ms, nil +} + // GetFilesContext retrieves all files according to the parameters given with a custom context func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) { values := url.Values{ @@ -219,7 +268,7 @@ func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameter values.Add("page", strconv.Itoa(params.Page)) } - response, err := fileRequest(ctx, api.httpclient, "files.list", values, api) + response, err := api.fileRequest(ctx, "files.list", values) if err != nil { return nil, nil, err } @@ -239,9 +288,6 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam if err != nil { return nil, err } - if params.Filename == "" { - return nil, fmt.Errorf("files.upload: FileUploadParameters.Filename is mandatory") - } response := &fileResponseFull{} values := url.Values{ "token": {api.token}, @@ -266,12 +312,16 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam } if params.Content != "" { values.Add("content", params.Content) - err = postForm(ctx, api.httpclient, APIURL+"files.upload", values, response, api) + err = api.postMethod(ctx, "files.upload", values, response) } else if params.File != "" { - err = postLocalWithMultipartResponse(ctx, api.httpclient, "files.upload", params.File, "file", values, response, api) + err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.File, "file", values, response, api) } else if params.Reader != nil { - err = postWithMultipartResponse(ctx, api.httpclient, "files.upload", params.Filename, "file", values, params.Reader, response, api) + if params.Filename == "" { + return nil, fmt.Errorf("files.upload: FileUploadParameters.Filename is mandatory when using FileUploadParameters.Reader") + } + err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.Filename, "file", values, params.Reader, response, api) } + if err != nil { return nil, err } @@ -287,7 +337,7 @@ func (api *Client) DeleteFileComment(commentID, fileID string) error { // DeleteFileCommentContext deletes a file's comment with a custom context func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, commentID string) (err error) { if fileID == "" || commentID == "" { - return errors.New("received empty parameters") + return ErrParametersMissing } values := url.Values{ @@ -295,7 +345,7 @@ func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, comment "file": {fileID}, "id": {commentID}, } - _, err = fileRequest(ctx, api.httpclient, "files.comments.delete", values, api) + _, err = api.fileRequest(ctx, "files.comments.delete", values) return err } @@ -311,7 +361,7 @@ func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err er "file": {fileID}, } - _, err = fileRequest(ctx, api.httpclient, "files.delete", values, api) + _, err = api.fileRequest(ctx, "files.delete", values) return err } @@ -327,7 +377,7 @@ func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string "file": {fileID}, } - response, err := fileRequest(ctx, api.httpclient, "files.revokePublicURL", values, api) + response, err := api.fileRequest(ctx, "files.revokePublicURL", values) if err != nil { return nil, err } @@ -346,7 +396,7 @@ func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) "file": {fileID}, } - response, err := fileRequest(ctx, api.httpclient, "files.sharedPublicURL", values, api) + response, err := api.fileRequest(ctx, "files.sharedPublicURL", values) if err != nil { return nil, nil, nil, err } diff --git a/vendor/github.com/nlopes/slack/go.mod b/vendor/github.com/nlopes/slack/go.mod new file mode 100644 index 0000000..87256eb --- /dev/null +++ b/vendor/github.com/nlopes/slack/go.mod @@ -0,0 +1,9 @@ +module github.com/nlopes/slack + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gorilla/websocket v1.2.0 + github.com/pkg/errors v0.8.0 + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.2 +) diff --git a/vendor/github.com/nlopes/slack/go.sum b/vendor/github.com/nlopes/slack/go.sum new file mode 100644 index 0000000..3bb45c1 --- /dev/null +++ b/vendor/github.com/nlopes/slack/go.sum @@ -0,0 +1,22 @@ +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/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/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/nlopes/slack v0.1.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0= +github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/victorcoder/slack-test v0.0.0-20190131110821-6f9a569c10af h1:JFxr+No3ZWgCtxnnTWCybnB/z0Iy3qLmdj3u2NV5o48= +github.com/victorcoder/slack-test v0.0.0-20190131110821-6f9a569c10af/go.mod h1:dStM4ShMus8J3hiq66ExbbzGLkwyZ+RQJePwFhWCCvQ= +github.com/victorcoder/slack-test v0.0.0-20190131113129-a43b3bb77f43 h1:wtFekkaAAQibpy3iE4Hhx2Gi9pZAbITOSfVP7GXk5eM= +github.com/victorcoder/slack-test v0.0.0-20190131113129-a43b3bb77f43/go.mod h1:dStM4ShMus8J3hiq66ExbbzGLkwyZ+RQJePwFhWCCvQ= +golang.org/x/net v0.0.0-20180108090419-434ec0c7fe37 h1:BkNcmLtAVeWe9h5k0jt24CQgaG5vb4x/doFbAiEC/Ho= +golang.org/x/net v0.0.0-20180108090419-434ec0c7fe37/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/vendor/github.com/nlopes/slack/groups.go b/vendor/github.com/nlopes/slack/groups.go index 560faee..2337486 100644 --- a/vendor/github.com/nlopes/slack/groups.go +++ b/vendor/github.com/nlopes/slack/groups.go @@ -8,7 +8,7 @@ import ( // Group contains all the information for a group type Group struct { - groupConversation + GroupConversation IsGroup bool `json:"is_group"` } @@ -27,9 +27,9 @@ type groupResponseFull struct { SlackResponse } -func groupRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*groupResponseFull, error) { +func (api *Client) groupRequest(ctx context.Context, path string, values url.Values) (*groupResponseFull, error) { response := &groupResponseFull{} - err := postForm(ctx, client, APIURL+path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func (api *Client) ArchiveGroupContext(ctx context.Context, group string) error "channel": {group}, } - _, err := groupRequest(ctx, api.httpclient, "groups.archive", values, api) + _, err := api.groupRequest(ctx, "groups.archive", values) return err } @@ -65,7 +65,7 @@ func (api *Client) UnarchiveGroupContext(ctx context.Context, group string) erro "channel": {group}, } - _, err := groupRequest(ctx, api.httpclient, "groups.unarchive", values, api) + _, err := api.groupRequest(ctx, "groups.unarchive", values) return err } @@ -81,7 +81,7 @@ func (api *Client) CreateGroupContext(ctx context.Context, group string) (*Group "name": {group}, } - response, err := groupRequest(ctx, api.httpclient, "groups.create", values, api) + response, err := api.groupRequest(ctx, "groups.create", values) if err != nil { return nil, err } @@ -106,7 +106,7 @@ func (api *Client) CreateChildGroupContext(ctx context.Context, group string) (* "channel": {group}, } - response, err := groupRequest(ctx, api.httpclient, "groups.createChild", values, api) + response, err := api.groupRequest(ctx, "groups.createChild", values) if err != nil { return nil, err } @@ -148,7 +148,7 @@ func (api *Client) GetGroupHistoryContext(ctx context.Context, group string, par } } - response, err := groupRequest(ctx, api.httpclient, "groups.history", values, api) + response, err := api.groupRequest(ctx, "groups.history", values) if err != nil { return nil, err } @@ -168,7 +168,7 @@ func (api *Client) InviteUserToGroupContext(ctx context.Context, group, user str "user": {user}, } - response, err := groupRequest(ctx, api.httpclient, "groups.invite", values, api) + response, err := api.groupRequest(ctx, "groups.invite", values) if err != nil { return nil, false, err } @@ -187,7 +187,7 @@ func (api *Client) LeaveGroupContext(ctx context.Context, group string) (err err "channel": {group}, } - _, err = groupRequest(ctx, api.httpclient, "groups.leave", values, api) + _, err = api.groupRequest(ctx, "groups.leave", values) return err } @@ -204,7 +204,7 @@ func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user str "user": {user}, } - _, err = groupRequest(ctx, api.httpclient, "groups.kick", values, api) + _, err = api.groupRequest(ctx, "groups.kick", values) return err } @@ -222,7 +222,7 @@ func (api *Client) GetGroupsContext(ctx context.Context, excludeArchived bool) ( values.Add("exclude_archived", "1") } - response, err := groupRequest(ctx, api.httpclient, "groups.list", values, api) + response, err := api.groupRequest(ctx, "groups.list", values) if err != nil { return nil, err } @@ -242,7 +242,7 @@ func (api *Client) GetGroupInfoContext(ctx context.Context, group string) (*Grou "include_locale": {strconv.FormatBool(true)}, } - response, err := groupRequest(ctx, api.httpclient, "groups.info", values, api) + response, err := api.groupRequest(ctx, "groups.info", values) if err != nil { return nil, err } @@ -267,7 +267,7 @@ func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string "ts": {ts}, } - _, err = groupRequest(ctx, api.httpclient, "groups.mark", values, api) + _, err = api.groupRequest(ctx, "groups.mark", values) return err } @@ -283,7 +283,7 @@ func (api *Client) OpenGroupContext(ctx context.Context, group string) (bool, bo "channel": {group}, } - response, err := groupRequest(ctx, api.httpclient, "groups.open", values, api) + response, err := api.groupRequest(ctx, "groups.open", values) if err != nil { return false, false, err } @@ -307,7 +307,7 @@ func (api *Client) RenameGroupContext(ctx context.Context, group, name string) ( // XXX: the created entry in this call returns a string instead of a number // so I may have to do some workaround to solve it. - response, err := groupRequest(ctx, api.httpclient, "groups.rename", values, api) + response, err := api.groupRequest(ctx, "groups.rename", values) if err != nil { return nil, err } @@ -327,7 +327,7 @@ func (api *Client) SetGroupPurposeContext(ctx context.Context, group, purpose st "purpose": {purpose}, } - response, err := groupRequest(ctx, api.httpclient, "groups.setPurpose", values, api) + response, err := api.groupRequest(ctx, "groups.setPurpose", values) if err != nil { return "", err } @@ -347,7 +347,7 @@ func (api *Client) SetGroupTopicContext(ctx context.Context, group, topic string "topic": {topic}, } - response, err := groupRequest(ctx, api.httpclient, "groups.setTopic", values, api) + response, err := api.groupRequest(ctx, "groups.setTopic", values) if err != nil { return "", err } diff --git a/vendor/github.com/nlopes/slack/im.go b/vendor/github.com/nlopes/slack/im.go index 10563d9..ee784fe 100644 --- a/vendor/github.com/nlopes/slack/im.go +++ b/vendor/github.com/nlopes/slack/im.go @@ -22,15 +22,13 @@ type imResponseFull struct { // IM contains information related to the Direct Message channel type IM struct { - conversation - IsIM bool `json:"is_im"` - User string `json:"user"` - IsUserDeleted bool `json:"is_user_deleted"` + Conversation + IsUserDeleted bool `json:"is_user_deleted"` } -func imRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*imResponseFull, error) { +func (api *Client) imRequest(ctx context.Context, path string, values url.Values) (*imResponseFull, error) { response := &imResponseFull{} - err := postSlackMethod(ctx, client, path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } @@ -50,7 +48,7 @@ func (api *Client) CloseIMChannelContext(ctx context.Context, channel string) (b "channel": {channel}, } - response, err := imRequest(ctx, api.httpclient, "im.close", values, api) + response, err := api.imRequest(ctx, "im.close", values) if err != nil { return false, false, err } @@ -71,7 +69,7 @@ func (api *Client) OpenIMChannelContext(ctx context.Context, user string) (bool, "user": {user}, } - response, err := imRequest(ctx, api.httpclient, "im.open", values, api) + response, err := api.imRequest(ctx, "im.open", values) if err != nil { return false, false, "", err } @@ -91,7 +89,7 @@ func (api *Client) MarkIMChannelContext(ctx context.Context, channel, ts string) "ts": {ts}, } - _, err := imRequest(ctx, api.httpclient, "im.mark", values, api) + _, err := api.imRequest(ctx, "im.mark", values) return err } @@ -130,7 +128,7 @@ func (api *Client) GetIMHistoryContext(ctx context.Context, channel string, para } } - response, err := imRequest(ctx, api.httpclient, "im.history", values, api) + response, err := api.imRequest(ctx, "im.history", values) if err != nil { return nil, err } @@ -148,7 +146,7 @@ func (api *Client) GetIMChannelsContext(ctx context.Context) ([]IM, error) { "token": {api.token}, } - response, err := imRequest(ctx, api.httpclient, "im.list", values, api) + response, err := api.imRequest(ctx, "im.list", values) if err != nil { return nil, err } diff --git a/vendor/github.com/nlopes/slack/info.go b/vendor/github.com/nlopes/slack/info.go index db8534c..31f459f 100644 --- a/vendor/github.com/nlopes/slack/info.go +++ b/vendor/github.com/nlopes/slack/info.go @@ -156,17 +156,12 @@ type Icons struct { Image72 string `json:"image_72,omitempty"` } -// Info contains various details about Users, Channels, Bots and the authenticated user. +// Info contains various details about the authenticated user and team. // It is returned by StartRTM or included in the "ConnectedEvent" RTM event. type Info struct { - URL string `json:"url,omitempty"` - User *UserDetails `json:"self,omitempty"` - Team *Team `json:"team,omitempty"` - Users []User `json:"users,omitempty"` - Channels []Channel `json:"channels,omitempty"` - Groups []Group `json:"groups,omitempty"` - Bots []Bot `json:"bots,omitempty"` - IMs []IM `json:"ims,omitempty"` + URL string `json:"url,omitempty"` + User *UserDetails `json:"self,omitempty"` + Team *Team `json:"team,omitempty"` } type infoResponseFull struct { @@ -174,52 +169,27 @@ type infoResponseFull struct { SlackResponse } -// GetBotByID returns a bot given a bot id +// GetBotByID is deprecated and returns nil func (info Info) GetBotByID(botID string) *Bot { - for _, bot := range info.Bots { - if bot.ID == botID { - return &bot - } - } return nil } -// GetUserByID returns a user given a user id +// GetUserByID is deprecated and returns nil func (info Info) GetUserByID(userID string) *User { - for _, user := range info.Users { - if user.ID == userID { - return &user - } - } return nil } -// GetChannelByID returns a channel given a channel id +// GetChannelByID is deprecated and returns nil func (info Info) GetChannelByID(channelID string) *Channel { - for _, channel := range info.Channels { - if channel.ID == channelID { - return &channel - } - } return nil } -// GetGroupByID returns a group given a group id +// GetGroupByID is deprecated and returns nil func (info Info) GetGroupByID(groupID string) *Group { - for _, group := range info.Groups { - if group.ID == groupID { - return &group - } - } return nil } -// GetIMByID returns an IM given an IM id +// GetIMByID is deprecated and returns nil func (info Info) GetIMByID(imID string) *IM { - for _, im := range info.IMs { - if im.ID == imID { - return &im - } - } return nil } diff --git a/vendor/github.com/nlopes/slack/interactions.go b/vendor/github.com/nlopes/slack/interactions.go index addc286..5433463 100644 --- a/vendor/github.com/nlopes/slack/interactions.go +++ b/vendor/github.com/nlopes/slack/interactions.go @@ -1,8 +1,20 @@ package slack +import ( + "encoding/json" +) + // InteractionType type of interactions type InteractionType string +// ActionType type represents the type of action (attachment, block, etc.) +type actionType string + +// action is an interface that should be implemented by all callback action types +type action interface { + actionType() actionType +} + // Types of interactions that can be received. const ( InteractionTypeDialogCancellation = InteractionType("dialog_cancellation") @@ -10,6 +22,7 @@ const ( InteractionTypeDialogSuggestion = InteractionType("dialog_suggestion") InteractionTypeInteractionMessage = InteractionType("interactive_message") InteractionTypeMessageAction = InteractionType("message_action") + InteractionTypeBlockActions = InteractionType("block_actions") ) // InteractionCallback is sent from slack when a user interactions with a button or dialog. @@ -27,6 +40,59 @@ type InteractionCallback struct { Message Message `json:"message"` Name string `json:"name"` Value string `json:"value"` - ActionCallback + MessageTs string `json:"message_ts"` + AttachmentID string `json:"attachment_id"` + ActionCallback ActionCallbacks `json:"actions"` DialogSubmissionCallback } + +// ActionCallback is a convenience struct defined to allow dynamic unmarshalling of +// the "actions" value in Slack's JSON response, which varies depending on block type +type ActionCallbacks struct { + AttachmentActions []*AttachmentAction + BlockActions []*BlockAction +} + +// UnmarshalJSON implements the Marshaller interface in order to delegate +// marshalling and allow for proper type assertion when decoding the response +func (a *ActionCallbacks) UnmarshalJSON(data []byte) error { + var raw []json.RawMessage + err := json.Unmarshal(data, &raw) + if err != nil { + return err + } + + for _, r := range raw { + var obj map[string]interface{} + err := json.Unmarshal(r, &obj) + if err != nil { + return err + } + + if _, ok := obj["block_id"].(string); ok { + action, err := unmarshalAction(r, &BlockAction{}) + if err != nil { + return err + } + + a.BlockActions = append(a.BlockActions, action.(*BlockAction)) + return nil + } + + action, err := unmarshalAction(r, &AttachmentAction{}) + if err != nil { + return err + } + a.AttachmentActions = append(a.AttachmentActions, action.(*AttachmentAction)) + } + + return nil +} + +func unmarshalAction(r json.RawMessage, callbackAction action) (action, error) { + err := json.Unmarshal(r, callbackAction) + if err != nil { + return nil, err + } + return callbackAction, nil +} diff --git a/vendor/github.com/nlopes/slack/internal/errorsx/errorsx.go b/vendor/github.com/nlopes/slack/internal/errorsx/errorsx.go new file mode 100644 index 0000000..cb85057 --- /dev/null +++ b/vendor/github.com/nlopes/slack/internal/errorsx/errorsx.go @@ -0,0 +1,8 @@ +package errorsx + +// String representing an error, useful for declaring string constants as errors. +type String string + +func (t String) Error() string { + return string(t) +} diff --git a/vendor/github.com/nlopes/slack/internal/timex/timex.go b/vendor/github.com/nlopes/slack/internal/timex/timex.go new file mode 100644 index 0000000..40063f7 --- /dev/null +++ b/vendor/github.com/nlopes/slack/internal/timex/timex.go @@ -0,0 +1,18 @@ +package timex + +import "time" + +// Max returns the maximum duration +func Max(values ...time.Duration) time.Duration { + var ( + max time.Duration + ) + + for _, v := range values { + if v > max { + max = v + } + } + + return max +} diff --git a/vendor/github.com/nlopes/slack/messages.go b/vendor/github.com/nlopes/slack/messages.go index bde9a37..37a2633 100644 --- a/vendor/github.com/nlopes/slack/messages.go +++ b/vendor/github.com/nlopes/slack/messages.go @@ -16,6 +16,7 @@ type OutgoingMessage struct { type Message struct { Msg SubMessage *Msg `json:"message,omitempty"` + PreviousMessage *Msg `json:"previous_message,omitempty"` } // Msg contains information about a slack message @@ -92,8 +93,18 @@ type Msg struct { ResponseType string `json:"response_type,omitempty"` ReplaceOriginal bool `json:"replace_original"` DeleteOriginal bool `json:"delete_original"` + + // Block type Message + Blocks Blocks `json:"blocks,omitempty"` } +const ( + // ResponseTypeInChannel in channel response for slash commands. + ResponseTypeInChannel = "in_channel" + // ResponseTypeEphemeral ephemeral respone for slash commands. + ResponseTypeEphemeral = "ephemeral" +) + // Icon is used for bot messages type Icon struct { IconURL string `json:"icon_url,omitempty"` diff --git a/vendor/github.com/nlopes/slack/misc.go b/vendor/github.com/nlopes/slack/misc.go index 30ae462..0dcee95 100644 --- a/vendor/github.com/nlopes/slack/misc.go +++ b/vendor/github.com/nlopes/slack/misc.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "io/ioutil" + "mime" "mime/multipart" "net/http" "net/http/httputil" @@ -48,31 +49,95 @@ type statusCodeError struct { } func (t statusCodeError) Error() string { - // TODO: this is a bad error string, should clean it up with a breaking changes - // merger. - return fmt.Sprintf("Slack server error: %s.", t.Status) + return fmt.Sprintf("slack server error: %s", t.Status) } func (t statusCodeError) HTTPStatusCode() int { return t.Code } +func (t statusCodeError) Retryable() bool { + if t.Code >= 500 || t.Code == http.StatusTooManyRequests { + return true + } + return false +} + +// RateLimitedError represents the rate limit respond from slack type RateLimitedError struct { RetryAfter time.Duration } func (e *RateLimitedError) Error() string { - return fmt.Sprintf("Slack rate limit exceeded, retry after %s", e.RetryAfter) + return fmt.Sprintf("slack rate limit exceeded, retry after %s", e.RetryAfter) +} + +func (e *RateLimitedError) Retryable() bool { + return true } func fileUploadReq(ctx context.Context, path string, values url.Values, r io.Reader) (*http.Request, error) { req, err := http.NewRequest("POST", path, r) + if err != nil { + return nil, err + } req = req.WithContext(ctx) + req.URL.RawQuery = (values).Encode() + return req, nil +} + +func downloadFile(client httpClient, token string, downloadURL string, writer io.Writer, d debug) error { + if downloadURL == "" { + return fmt.Errorf("received empty download URL") + } + + req, err := http.NewRequest("GET", downloadURL, &bytes.Buffer{}) + if err != nil { + return err + } + + var bearer = "Bearer " + token + req.Header.Add("Authorization", bearer) + req.WithContext(context.Background()) + + resp, err := client.Do(req) if err != nil { + return err + } + + defer resp.Body.Close() + + err = checkStatusCode(resp, d) + if err != nil { + return err + } + + _, err = io.Copy(writer, resp.Body) + + return err +} + +func formReq(endpoint string, values url.Values) (req *http.Request, err error) { + if req, err = http.NewRequest("POST", endpoint, strings.NewReader(values.Encode())); err != nil { return nil, err } - req.URL.RawQuery = (values).Encode() + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + return req, nil +} + +func jsonReq(endpoint string, body interface{}) (req *http.Request, err error) { + buffer := bytes.NewBuffer([]byte{}) + if err = json.NewEncoder(buffer).Encode(body); err != nil { + return nil, err + } + + if req, err = http.NewRequest("POST", endpoint, buffer); err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json; charset=utf-8") return req, nil } @@ -89,7 +154,7 @@ func parseResponseBody(body io.ReadCloser, intf interface{}, d debug) error { return json.Unmarshal(response, intf) } -func postLocalWithMultipartResponse(ctx context.Context, client httpClient, path, fpath, fieldname string, values url.Values, intf interface{}, d debug) error { +func postLocalWithMultipartResponse(ctx context.Context, client httpClient, method, fpath, fieldname string, values url.Values, intf interface{}, d debug) error { fullpath, err := filepath.Abs(fpath) if err != nil { return err @@ -99,7 +164,8 @@ func postLocalWithMultipartResponse(ctx context.Context, client httpClient, path return err } defer file.Close() - return postWithMultipartResponse(ctx, client, path, filepath.Base(fpath), fieldname, values, file, intf, d) + + return postWithMultipartResponse(ctx, client, method, filepath.Base(fpath), fieldname, values, file, intf, d) } func postWithMultipartResponse(ctx context.Context, client httpClient, path, name, fieldname string, values url.Values, r io.Reader, intf interface{}, d debug) error { @@ -123,7 +189,7 @@ func postWithMultipartResponse(ctx context.Context, client httpClient, path, nam return } }() - req, err := fileUploadReq(ctx, APIURL+path, values, pipeReader) + req, err := fileUploadReq(ctx, path, values, pipeReader) if err != nil { return err } @@ -136,28 +202,20 @@ func postWithMultipartResponse(ctx context.Context, client httpClient, path, nam } defer resp.Body.Close() - if resp.StatusCode == http.StatusTooManyRequests { - retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64) - if err != nil { - return err - } - return &RateLimitedError{time.Duration(retry) * time.Second} + err = checkStatusCode(resp, d) + if err != nil { + return err } - // Slack seems to send an HTML body along with 5xx error codes. Don't parse it. - if resp.StatusCode != http.StatusOK { - logResponse(resp, d) - return statusCodeError{Code: resp.StatusCode, Status: resp.Status} - } select { case err = <-errc: return err default: - return parseResponseBody(resp.Body, intf, d) + return newJSONParser(intf)(resp) } } -func doPost(ctx context.Context, client httpClient, req *http.Request, intf interface{}, d debug) error { +func doPost(ctx context.Context, client httpClient, req *http.Request, parser responseParser, d debug) error { req = req.WithContext(ctx) resp, err := client.Do(req) if err != nil { @@ -165,21 +223,12 @@ func doPost(ctx context.Context, client httpClient, req *http.Request, intf inte } defer resp.Body.Close() - if resp.StatusCode == http.StatusTooManyRequests { - retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64) - if err != nil { - return err - } - return &RateLimitedError{time.Duration(retry) * time.Second} - } - - // Slack seems to send an HTML body along with 5xx error codes. Don't parse it. - if resp.StatusCode != http.StatusOK { - logResponse(resp, d) - return statusCodeError{Code: resp.StatusCode, Status: resp.Status} + err = checkStatusCode(resp, d) + if err != nil { + return err } - return parseResponseBody(resp.Body, intf, d) + return parser(resp) } // post JSON. @@ -191,7 +240,8 @@ func postJSON(ctx context.Context, client httpClient, endpoint, token string, js } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - return doPost(ctx, client, req, intf, d) + + return doPost(ctx, client, req, newJSONParser(intf), d) } // post a url encoded form. @@ -202,17 +252,7 @@ func postForm(ctx context.Context, client httpClient, endpoint string, values ur return err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - return doPost(ctx, client, req, intf, d) -} - -// post to a slack web method. -func postSlackMethod(ctx context.Context, client httpClient, path string, values url.Values, intf interface{}, d debug) error { - return postForm(ctx, client, APIURL+path, values, intf, d) -} - -// get a slack web method. -func getSlackMethod(ctx context.Context, client httpClient, path string, values url.Values, intf interface{}, d debug) error { - return getResource(ctx, client, APIURL+path, values, intf, d) + return doPost(ctx, client, req, newJSONParser(intf), d) } func getResource(ctx context.Context, client httpClient, endpoint string, values url.Values, intf interface{}, d debug) error { @@ -223,7 +263,7 @@ func getResource(ctx context.Context, client httpClient, endpoint string, values req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.URL.RawQuery = values.Encode() - return doPost(ctx, client, req, intf, d) + return doPost(ctx, client, req, newJSONParser(intf), d) } func parseAdminResponse(ctx context.Context, client httpClient, method string, teamName string, values url.Values, intf interface{}, d debug) error { @@ -251,12 +291,6 @@ func okJSONHandler(rw http.ResponseWriter, r *http.Request) { rw.Write(response) } -type errorString string - -func (t errorString) Error() string { - return string(t) -} - // timerReset safely reset a timer, see time.Timer.Reset for details. func timerReset(t *time.Timer, d time.Duration) { if !t.Stop() { @@ -264,3 +298,63 @@ func timerReset(t *time.Timer, d time.Duration) { } t.Reset(d) } + +func checkStatusCode(resp *http.Response, d debug) error { + if resp.StatusCode == http.StatusTooManyRequests { + retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64) + if err != nil { + return err + } + return &RateLimitedError{time.Duration(retry) * time.Second} + } + + // Slack seems to send an HTML body along with 5xx error codes. Don't parse it. + if resp.StatusCode != http.StatusOK { + logResponse(resp, d) + return statusCodeError{Code: resp.StatusCode, Status: resp.Status} + } + + return nil +} + +type responseParser func(*http.Response) error + +func newJSONParser(dst interface{}) responseParser { + return func(resp *http.Response) error { + return json.NewDecoder(resp.Body).Decode(dst) + } +} + +func newTextParser(dst interface{}) responseParser { + return func(resp *http.Response) error { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + if !bytes.Equal(b, []byte("ok")) { + return errors.New(string(b)) + } + + return nil + } +} + +func newContentTypeParser(dst interface{}) responseParser { + return func(req *http.Response) (err error) { + var ( + ctype string + ) + + if ctype, _, err = mime.ParseMediaType(req.Header.Get("Content-Type")); err != nil { + return err + } + + switch ctype { + case "application/json": + return newJSONParser(dst)(req) + default: + return newTextParser(dst)(req) + } + } +} diff --git a/vendor/github.com/nlopes/slack/oauth.go b/vendor/github.com/nlopes/slack/oauth.go index 8a8194c..29d6dce 100644 --- a/vendor/github.com/nlopes/slack/oauth.go +++ b/vendor/github.com/nlopes/slack/oauth.go @@ -57,7 +57,7 @@ func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, c "redirect_uri": {redirectURI}, } response := &OAuthResponse{} - if err = postSlackMethod(ctx, client, "oauth.access", values, response, discard{}); err != nil { + if err = postForm(ctx, client, APIURL+"oauth.access", values, response, discard{}); err != nil { return nil, err } return response, response.Err() diff --git a/vendor/github.com/nlopes/slack/pins.go b/vendor/github.com/nlopes/slack/pins.go index c1d525d..ef97c8d 100644 --- a/vendor/github.com/nlopes/slack/pins.go +++ b/vendor/github.com/nlopes/slack/pins.go @@ -34,7 +34,7 @@ func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemR } response := &SlackResponse{} - if err := postSlackMethod(ctx, api.httpclient, "pins.add", values, response, api); err != nil { + if err := api.postMethod(ctx, "pins.add", values, response); err != nil { return err } @@ -63,7 +63,7 @@ func (api *Client) RemovePinContext(ctx context.Context, channel string, item It } response := &SlackResponse{} - if err := postSlackMethod(ctx, api.httpclient, "pins.remove", values, response, api); err != nil { + if err := api.postMethod(ctx, "pins.remove", values, response); err != nil { return err } @@ -83,7 +83,7 @@ func (api *Client) ListPinsContext(ctx context.Context, channel string) ([]Item, } response := &listPinsResponseFull{} - err := postSlackMethod(ctx, api.httpclient, "pins.list", values, response, api) + err := api.postMethod(ctx, "pins.list", values, response) if err != nil { return nil, nil, err } diff --git a/vendor/github.com/nlopes/slack/reactions.go b/vendor/github.com/nlopes/slack/reactions.go index abe1e72..2a9bd42 100644 --- a/vendor/github.com/nlopes/slack/reactions.go +++ b/vendor/github.com/nlopes/slack/reactions.go @@ -2,7 +2,6 @@ package slack import ( "context" - "errors" "net/url" "strconv" ) @@ -155,7 +154,7 @@ func (api *Client) AddReactionContext(ctx context.Context, name string, item Ite } response := &SlackResponse{} - if err := postSlackMethod(ctx, api.httpclient, "reactions.add", values, response, api); err != nil { + if err := api.postMethod(ctx, "reactions.add", values, response); err != nil { return err } @@ -189,7 +188,7 @@ func (api *Client) RemoveReactionContext(ctx context.Context, name string, item } response := &SlackResponse{} - if err := postSlackMethod(ctx, api.httpclient, "reactions.remove", values, response, api); err != nil { + if err := api.postMethod(ctx, "reactions.remove", values, response); err != nil { return err } @@ -223,12 +222,14 @@ func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params } response := &getReactionsResponseFull{} - if err := postSlackMethod(ctx, api.httpclient, "reactions.get", values, response, api); err != nil { + if err := api.postMethod(ctx, "reactions.get", values, response); err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) + + if err := response.Err(); err != nil { + return nil, err } + return response.extractReactions(), nil } @@ -256,12 +257,14 @@ func (api *Client) ListReactionsContext(ctx context.Context, params ListReaction } response := &listReactionsResponseFull{} - err := postSlackMethod(ctx, api.httpclient, "reactions.list", values, response, api) + err := api.postMethod(ctx, "reactions.list", values, response) if err != nil { return nil, nil, err } - if !response.Ok { - return nil, nil, errors.New(response.Error) + + if err := response.Err(); err != nil { + return nil, nil, err } + return response.extractReactedItems(), &response.Paging, nil } diff --git a/vendor/github.com/nlopes/slack/reminders.go b/vendor/github.com/nlopes/slack/reminders.go index 54c9178..9b90538 100644 --- a/vendor/github.com/nlopes/slack/reminders.go +++ b/vendor/github.com/nlopes/slack/reminders.go @@ -23,7 +23,7 @@ type reminderResp struct { func (api *Client) doReminder(ctx context.Context, path string, values url.Values) (*Reminder, error) { response := &reminderResp{} - if err := postSlackMethod(ctx, api.httpclient, path, values, response, api); err != nil { + if err := api.postMethod(ctx, path, values, response); err != nil { return nil, err } return &response.Reminder, response.Err() @@ -68,7 +68,7 @@ func (api *Client) DeleteReminder(id string) error { "reminder": {id}, } response := &SlackResponse{} - if err := postSlackMethod(context.Background(), api.httpclient, "reminders.delete", values, response, api); err != nil { + if err := api.postMethod(context.Background(), "reminders.delete", values, response); err != nil { return err } return response.Err() diff --git a/vendor/github.com/nlopes/slack/rtm.go b/vendor/github.com/nlopes/slack/rtm.go index e7fa83f..09cb51c 100644 --- a/vendor/github.com/nlopes/slack/rtm.go +++ b/vendor/github.com/nlopes/slack/rtm.go @@ -38,7 +38,7 @@ func (api *Client) StartRTM() (info *Info, websocketURL string, err error) { // To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it. func (api *Client) StartRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) { response := &infoResponseFull{} - err = postSlackMethod(ctx, api.httpclient, "rtm.start", url.Values{"token": {api.token}}, response, api) + err = api.postMethod(ctx, "rtm.start", url.Values{"token": {api.token}}, response) if err != nil { return nil, "", err } @@ -63,7 +63,7 @@ func (api *Client) ConnectRTM() (info *Info, websocketURL string, err error) { // To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it. func (api *Client) ConnectRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) { response := &infoResponseFull{} - err = postSlackMethod(ctx, api.httpclient, "rtm.connect", url.Values{"token": {api.token}}, response, api) + err = api.postMethod(ctx, "rtm.connect", url.Values{"token": {api.token}}, response) if err != nil { api.Debugf("Failed to connect to RTM: %s", err) return nil, "", err @@ -112,14 +112,13 @@ func RTMOptionConnParams(connParams url.Values) RTMOption { func (api *Client) NewRTM(options ...RTMOption) *RTM { result := &RTM{ Client: *api, - wasIntentional: true, - isConnected: false, IncomingEvents: make(chan RTMEvent, 50), outgoingMessages: make(chan OutgoingMessage, 20), pingInterval: defaultPingInterval, pingDeadman: time.NewTimer(deadmanDuration(defaultPingInterval)), killChannel: make(chan bool), - disconnected: make(chan struct{}, 1), + disconnected: make(chan struct{}), + disconnectedm: &sync.Once{}, forcePing: make(chan bool), rawEvents: make(chan json.RawMessage), idGen: NewSafeID(1), diff --git a/vendor/github.com/nlopes/slack/search.go b/vendor/github.com/nlopes/slack/search.go index 2d018fc..67e3b1d 100644 --- a/vendor/github.com/nlopes/slack/search.go +++ b/vendor/github.com/nlopes/slack/search.go @@ -41,6 +41,7 @@ type SearchMessage struct { User string `json:"user"` Username string `json:"username"` Timestamp string `json:"ts"` + Blocks Blocks `json:"blocks,omitempty"` Text string `json:"text"` Permalink string `json:"permalink"` Attachments []Attachment `json:"attachments"` @@ -103,7 +104,7 @@ func (api *Client) _search(ctx context.Context, path, query string, params Searc } response = &searchResponseFull{} - err := postSlackMethod(ctx, api.httpclient, path, values, response, api) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } diff --git a/vendor/github.com/nlopes/slack/security.go b/vendor/github.com/nlopes/slack/security.go index 3572702..dbe8fb2 100644 --- a/vendor/github.com/nlopes/slack/security.go +++ b/vendor/github.com/nlopes/slack/security.go @@ -4,7 +4,6 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" - "errors" "fmt" "hash" "net/http" @@ -34,7 +33,7 @@ func unsafeSignatureVerifier(header http.Header, secret string) (_ SecretsVerifi stimestamp := header.Get(hTimestamp) if signature == "" || stimestamp == "" { - return SecretsVerifier{}, errors.New("missing headers") + return SecretsVerifier{}, ErrMissingHeaders } if bsignature, err = hex.DecodeString(strings.TrimPrefix(signature, "v0=")); err != nil { @@ -70,7 +69,7 @@ func NewSecretsVerifier(header http.Header, secret string) (sv SecretsVerifier, diff := absDuration(time.Since(time.Unix(timestamp, 0))) if diff > 5*time.Minute { - return SecretsVerifier{}, fmt.Errorf("timestamp is too old") + return SecretsVerifier{}, ErrExpiredTimestamp } return sv, err @@ -88,7 +87,7 @@ func (v SecretsVerifier) Ensure() error { return nil } - return fmt.Errorf("Expected signing signature: %s, but computed: %s", v.signature, computed) + return fmt.Errorf("Expected signing signature: %s, but computed: %s", hex.EncodeToString(v.signature), hex.EncodeToString(computed)) } func abs64(n int64) int64 { diff --git a/vendor/github.com/nlopes/slack/slack.go b/vendor/github.com/nlopes/slack/slack.go index c1ba0fc..9423052 100644 --- a/vendor/github.com/nlopes/slack/slack.go +++ b/vendor/github.com/nlopes/slack/slack.go @@ -9,11 +9,12 @@ import ( "os" ) -// APIURL added as a var so that we can change this for testing purposes -var APIURL = "https://slack.com/api/" - -// WEBAPIURLFormat ... -const WEBAPIURLFormat = "https://%s.slack.com/api/users.admin.%s?t=%d" +const ( + // APIURL of the slack api. + APIURL = "https://slack.com/api/" + // WEBAPIURLFormat ... + WEBAPIURLFormat = "https://%s.slack.com/api/users.admin.%s?t=%d" +) // httpClient defines the minimal interface needed for an http.Client to be implemented. type httpClient interface { @@ -40,6 +41,8 @@ type AuthTestResponse struct { User string `json:"user"` TeamID string `json:"team_id"` UserID string `json:"user_id"` + // EnterpriseID is only returned when an enterprise id present + EnterpriseID string `json:"enterprise_id,omitempty"` } type authTestResponseFull struct { @@ -48,8 +51,11 @@ type authTestResponseFull struct { } // Client for the slack api. +type ParamOption func(*url.Values) + type Client struct { token string + endpoint string debug bool log ilogger httpclient httpClient @@ -79,10 +85,16 @@ func OptionLog(l logger) func(*Client) { } } +// OptionAPIURL set the url for the client. only useful for testing. +func OptionAPIURL(u string) func(*Client) { + return func(c *Client) { c.endpoint = u } +} + // New builds a slack client from the provided token and options. func New(token string, options ...Option) *Client { s := &Client{ token: token, + endpoint: APIURL, httpclient: &http.Client{}, log: log.New(os.Stderr, "nlopes/slack", log.LstdFlags|log.Lshortfile), } @@ -103,7 +115,7 @@ func (api *Client) AuthTest() (response *AuthTestResponse, error error) { func (api *Client) AuthTestContext(ctx context.Context) (response *AuthTestResponse, err error) { api.Debugf("Challenging auth...") responseFull := &authTestResponseFull{} - err = postSlackMethod(ctx, api.httpclient, "auth.test", url.Values{"token": {api.token}}, responseFull, api) + err = api.postMethod(ctx, "auth.test", url.Values{"token": {api.token}}, responseFull) if err != nil { return nil, err } @@ -129,3 +141,13 @@ func (api *Client) Debugln(v ...interface{}) { func (api *Client) Debug() bool { return api.debug } + +// post to a slack web method. +func (api *Client) postMethod(ctx context.Context, path string, values url.Values, intf interface{}) error { + return postForm(ctx, api.httpclient, api.endpoint+path, values, intf, api) +} + +// get a slack web method. +func (api *Client) getMethod(ctx context.Context, path string, values url.Values, intf interface{}) error { + return getResource(ctx, api.httpclient, api.endpoint+path, values, intf, api) +} diff --git a/vendor/github.com/nlopes/slack/slackevents/inner_events.go b/vendor/github.com/nlopes/slack/slackevents/inner_events.go index 52d6722..1ab48ce 100644 --- a/vendor/github.com/nlopes/slack/slackevents/inner_events.go +++ b/vendor/github.com/nlopes/slack/slackevents/inner_events.go @@ -21,6 +21,14 @@ type AppMentionEvent struct { EventTimeStamp json.Number `json:"event_ts"` } +// AppHomeOpenedEvent Your Slack app home was opened. +type AppHomeOpenedEvent struct { + Type string `json:"type"` + User string `json:"user"` + Channel string `json:"channel"` + EventTimeStamp json.Number `json:"event_ts"` +} + // AppUninstalledEvent Your Slack app was uninstalled. type AppUninstalledEvent struct { Type string `json:"type"` @@ -113,6 +121,17 @@ type PinAddedEvent pinEvent // PinRemovedEvent An item was unpinned from a channel - https://api.slack.com/events/pin_removed type PinRemovedEvent pinEvent +type tokens struct { + Oauth []string `json:"oauth"` + Bot []string `json:"bot"` +} + +// TokensRevokedEvent APP's API tokes are revoked - https://api.slack.com/events/tokens_revoked +type TokensRevokedEvent struct { + Type string `json:"type"` + Tokens tokens `json:"tokens"` +} + // JSONTime exists so that we can have a String method converting the date type JSONTime int64 @@ -217,6 +236,8 @@ func (e MessageEvent) IsEdited() bool { const ( // AppMention is an Events API subscribable event AppMention = "app_mention" + // AppHomeOpened Your Slack app home was opened + AppHomeOpened = "app_home_opened" // AppUninstalled Your Slack app was uninstalled. AppUninstalled = "app_uninstalled" // GridMigrationFinished An enterprise grid migration has finished on this workspace. @@ -233,6 +254,8 @@ const ( PinAdded = "pin_added" // PinRemoved An item was unpinned from a channel PinRemoved = "pin_removed" + // TokensRevoked APP's API tokes are revoked + TokensRevoked = "tokens_revoked" ) // EventsAPIInnerEventMapping maps INNER Event API events to their corresponding struct @@ -240,6 +263,7 @@ const ( // target for the matching event type. var EventsAPIInnerEventMapping = map[string]interface{}{ AppMention: AppMentionEvent{}, + AppHomeOpened: AppHomeOpenedEvent{}, AppUninstalled: AppUninstalledEvent{}, GridMigrationFinished: GridMigrationFinishedEvent{}, GridMigrationStarted: GridMigrationStartedEvent{}, @@ -248,4 +272,5 @@ var EventsAPIInnerEventMapping = map[string]interface{}{ MemberJoinedChannel: MemberJoinedChannelEvent{}, PinAdded: PinAddedEvent{}, PinRemoved: PinRemovedEvent{}, + TokensRevoked: TokensRevokedEvent{}, } diff --git a/vendor/github.com/nlopes/slack/slackevents/outer_events.go b/vendor/github.com/nlopes/slack/slackevents/outer_events.go index 59f0b0a..e925241 100644 --- a/vendor/github.com/nlopes/slack/slackevents/outer_events.go +++ b/vendor/github.com/nlopes/slack/slackevents/outer_events.go @@ -35,6 +35,7 @@ type EventsAPICallbackEvent struct { APIAppID string `json:"api_app_id"` InnerEvent *json.RawMessage `json:"event"` AuthedUsers []string `json:"authed_users"` + AuthedTeams []string `json:"authed_teams"` EventID string `json:"event_id"` EventTime int `json:"event_time"` } diff --git a/vendor/github.com/nlopes/slack/slackutilsx/slackutilsx.go b/vendor/github.com/nlopes/slack/slackutilsx/slackutilsx.go index ccf5372..1f7b2b8 100644 --- a/vendor/github.com/nlopes/slack/slackutilsx/slackutilsx.go +++ b/vendor/github.com/nlopes/slack/slackutilsx/slackutilsx.go @@ -55,3 +55,8 @@ func EscapeMessage(message string) string { replacer := strings.NewReplacer("&", "&", "<", "<", ">", ">") return replacer.Replace(message) } + +// Retryable errors return true. +type Retryable interface { + Retryable() bool +} diff --git a/vendor/github.com/nlopes/slack/stars.go b/vendor/github.com/nlopes/slack/stars.go index 7e1e621..e84d044 100644 --- a/vendor/github.com/nlopes/slack/stars.go +++ b/vendor/github.com/nlopes/slack/stars.go @@ -2,7 +2,6 @@ package slack import ( "context" - "errors" "net/url" "strconv" ) @@ -58,7 +57,7 @@ func (api *Client) AddStarContext(ctx context.Context, channel string, item Item } response := &SlackResponse{} - if err := postSlackMethod(ctx, api.httpclient, "stars.add", values, response, api); err != nil { + if err := api.postMethod(ctx, "stars.add", values, response); err != nil { return err } @@ -87,7 +86,7 @@ func (api *Client) RemoveStarContext(ctx context.Context, channel string, item I } response := &SlackResponse{} - if err := postSlackMethod(ctx, api.httpclient, "stars.remove", values, response, api); err != nil { + if err := api.postMethod(ctx, "stars.remove", values, response); err != nil { return err } @@ -115,13 +114,15 @@ func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) } response := &listResponseFull{} - err := postSlackMethod(ctx, api.httpclient, "stars.list", values, response, api) + err := api.postMethod(ctx, "stars.list", values, response) if err != nil { return nil, nil, err } - if !response.Ok { - return nil, nil, errors.New(response.Error) + + if err := response.Err(); err != nil { + return nil, nil, err } + return response.Items, &response.Paging, nil } diff --git a/vendor/github.com/nlopes/slack/team.go b/vendor/github.com/nlopes/slack/team.go index 1892cf5..029e2b5 100644 --- a/vendor/github.com/nlopes/slack/team.go +++ b/vendor/github.com/nlopes/slack/team.go @@ -66,9 +66,9 @@ func NewAccessLogParameters() AccessLogParameters { } } -func teamRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*TeamResponse, error) { +func (api *Client) teamRequest(ctx context.Context, path string, values url.Values) (*TeamResponse, error) { response := &TeamResponse{} - err := postSlackMethod(ctx, client, path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } @@ -76,9 +76,9 @@ func teamRequest(ctx context.Context, client httpClient, path string, values url return response, response.Err() } -func billableInfoRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (map[string]BillingActive, error) { +func (api *Client) billableInfoRequest(ctx context.Context, path string, values url.Values) (map[string]BillingActive, error) { response := &BillableInfoResponse{} - err := postSlackMethod(ctx, client, path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } @@ -86,9 +86,9 @@ func billableInfoRequest(ctx context.Context, client httpClient, path string, va return response.BillableInfo, response.Err() } -func accessLogsRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*LoginResponse, error) { +func (api *Client) accessLogsRequest(ctx context.Context, path string, values url.Values) (*LoginResponse, error) { response := &LoginResponse{} - err := postSlackMethod(ctx, client, path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } @@ -106,7 +106,7 @@ func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) { "token": {api.token}, } - response, err := teamRequest(ctx, api.httpclient, "team.info", values, api) + response, err := api.teamRequest(ctx, "team.info", values) if err != nil { return nil, err } @@ -130,24 +130,26 @@ func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogPar values.Add("page", strconv.Itoa(params.Page)) } - response, err := accessLogsRequest(ctx, api.httpclient, "team.accessLogs", values, api) + response, err := api.accessLogsRequest(ctx, "team.accessLogs", values) if err != nil { return nil, nil, err } return response.Logins, &response.Paging, nil } +// GetBillableInfo ... func (api *Client) GetBillableInfo(user string) (map[string]BillingActive, error) { return api.GetBillableInfoContext(context.Background(), user) } +// GetBillableInfoContext ... func (api *Client) GetBillableInfoContext(ctx context.Context, user string) (map[string]BillingActive, error) { values := url.Values{ "token": {api.token}, "user": {user}, } - return billableInfoRequest(ctx, api.httpclient, "team.billableInfo", values, api) + return api.billableInfoRequest(ctx, "team.billableInfo", values) } // GetBillableInfoForTeam returns the billing_active status of all users on the team. @@ -161,5 +163,5 @@ func (api *Client) GetBillableInfoForTeamContext(ctx context.Context) (map[strin "token": {api.token}, } - return billableInfoRequest(ctx, api.httpclient, "team.billableInfo", values, api) + return api.billableInfoRequest(ctx, "team.billableInfo", values) } diff --git a/vendor/github.com/nlopes/slack/usergroups.go b/vendor/github.com/nlopes/slack/usergroups.go index 9e14527..f320659 100644 --- a/vendor/github.com/nlopes/slack/usergroups.go +++ b/vendor/github.com/nlopes/slack/usergroups.go @@ -40,9 +40,9 @@ type userGroupResponseFull struct { SlackResponse } -func userGroupRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*userGroupResponseFull, error) { +func (api *Client) userGroupRequest(ctx context.Context, path string, values url.Values) (*userGroupResponseFull, error) { response := &userGroupResponseFull{} - err := postSlackMethod(ctx, client, path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } @@ -74,7 +74,7 @@ func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGro values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")} } - response, err := userGroupRequest(ctx, api.httpclient, "usergroups.create", values, api) + response, err := api.userGroupRequest(ctx, "usergroups.create", values) if err != nil { return UserGroup{}, err } @@ -93,7 +93,7 @@ func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string "usergroup": {userGroup}, } - response, err := userGroupRequest(ctx, api.httpclient, "usergroups.disable", values, api) + response, err := api.userGroupRequest(ctx, "usergroups.disable", values) if err != nil { return UserGroup{}, err } @@ -112,7 +112,7 @@ func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string) "usergroup": {userGroup}, } - response, err := userGroupRequest(ctx, api.httpclient, "usergroups.enable", values, api) + response, err := api.userGroupRequest(ctx, "usergroups.enable", values) if err != nil { return UserGroup{}, err } @@ -176,7 +176,7 @@ func (api *Client) GetUserGroupsContext(ctx context.Context, options ...GetUserG values.Add("include_users", "true") } - response, err := userGroupRequest(ctx, api.httpclient, "usergroups.list", values, api) + response, err := api.userGroupRequest(ctx, "usergroups.list", values) if err != nil { return nil, err } @@ -206,8 +206,12 @@ func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGro if userGroup.Description != "" { values["description"] = []string{userGroup.Description} } + + if len(userGroup.Prefs.Channels) > 0 { + values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")} + } - response, err := userGroupRequest(ctx, api.httpclient, "usergroups.update", values, api) + response, err := api.userGroupRequest(ctx, "usergroups.update", values) if err != nil { return UserGroup{}, err } @@ -226,7 +230,7 @@ func (api *Client) GetUserGroupMembersContext(ctx context.Context, userGroup str "usergroup": {userGroup}, } - response, err := userGroupRequest(ctx, api.httpclient, "usergroups.users.list", values, api) + response, err := api.userGroupRequest(ctx, "usergroups.users.list", values) if err != nil { return []string{}, err } @@ -246,7 +250,7 @@ func (api *Client) UpdateUserGroupMembersContext(ctx context.Context, userGroup "users": {members}, } - response, err := userGroupRequest(ctx, api.httpclient, "usergroups.users.update", values, api) + response, err := api.userGroupRequest(ctx, "usergroups.users.update", values) if err != nil { return UserGroup{}, err } diff --git a/vendor/github.com/nlopes/slack/users.go b/vendor/github.com/nlopes/slack/users.go index 74b7937..4da8e4c 100644 --- a/vendor/github.com/nlopes/slack/users.go +++ b/vendor/github.com/nlopes/slack/users.go @@ -3,16 +3,15 @@ package slack import ( "context" "encoding/json" - "errors" "net/url" "strconv" + "time" ) const ( DEFAULT_USER_PHOTO_CROP_X = -1 DEFAULT_USER_PHOTO_CROP_Y = -1 DEFAULT_USER_PHOTO_CROP_W = -1 - errPaginationComplete = errorString("pagination complete") ) // UserProfile contains all the information details of a given user @@ -37,6 +36,7 @@ type UserProfile struct { ApiAppID string `json:"api_app_id,omitempty"` StatusText string `json:"status_text,omitempty"` StatusEmoji string `json:"status_emoji,omitempty"` + StatusExpiration int `json:"status_expiration"` Team string `json:"team"` Fields UserProfileCustomFields `json:"fields"` } @@ -100,28 +100,31 @@ type UserProfileCustomField struct { // User contains all the information of a user type User struct { - ID string `json:"id"` - TeamID string `json:"team_id"` - Name string `json:"name"` - Deleted bool `json:"deleted"` - Color string `json:"color"` - RealName string `json:"real_name"` - TZ string `json:"tz,omitempty"` - TZLabel string `json:"tz_label"` - TZOffset int `json:"tz_offset"` - Profile UserProfile `json:"profile"` - IsBot bool `json:"is_bot"` - IsAdmin bool `json:"is_admin"` - IsOwner bool `json:"is_owner"` - IsPrimaryOwner bool `json:"is_primary_owner"` - IsRestricted bool `json:"is_restricted"` - IsUltraRestricted bool `json:"is_ultra_restricted"` - IsStranger bool `json:"is_stranger"` - IsAppUser bool `json:"is_app_user"` - Has2FA bool `json:"has_2fa"` - HasFiles bool `json:"has_files"` - Presence string `json:"presence"` - Locale string `json:"locale"` + ID string `json:"id"` + TeamID string `json:"team_id"` + Name string `json:"name"` + Deleted bool `json:"deleted"` + Color string `json:"color"` + RealName string `json:"real_name"` + TZ string `json:"tz,omitempty"` + TZLabel string `json:"tz_label"` + TZOffset int `json:"tz_offset"` + Profile UserProfile `json:"profile"` + IsBot bool `json:"is_bot"` + IsAdmin bool `json:"is_admin"` + IsOwner bool `json:"is_owner"` + IsPrimaryOwner bool `json:"is_primary_owner"` + IsRestricted bool `json:"is_restricted"` + IsUltraRestricted bool `json:"is_ultra_restricted"` + IsStranger bool `json:"is_stranger"` + IsAppUser bool `json:"is_app_user"` + IsInvitedUser bool `json:"is_invited_user"` + Has2FA bool `json:"has_2fa"` + HasFiles bool `json:"has_files"` + Presence string `json:"presence"` + Locale string `json:"locale"` + Updated JSONTime `json:"updated"` + Enterprise EnterpriseUser `json:"enterprise_user,omitempty"` } // UserPresence contains details about a user online status @@ -152,6 +155,17 @@ type UserIdentity struct { Image512 string `json:"image_512"` } +// EnterpriseUser is present when a user is part of Slack Enterprise Grid +// https://api.slack.com/types/user#enterprise_grid_user_objects +type EnterpriseUser struct { + ID string `json:"id"` + EnterpriseID string `json:"enterprise_id"` + EnterpriseName string `json:"enterprise_name"` + IsAdmin bool `json:"is_admin"` + IsOwner bool `json:"is_owner"` + Teams []string `json:"teams"` +} + type TeamIdentity struct { ID string `json:"id"` Name string `json:"name"` @@ -189,9 +203,9 @@ func NewUserSetPhotoParams() UserSetPhotoParams { } } -func userRequest(ctx context.Context, client httpClient, path string, values url.Values, d debug) (*userResponseFull, error) { +func (api *Client) userRequest(ctx context.Context, path string, values url.Values) (*userResponseFull, error) { response := &userResponseFull{} - err := postForm(ctx, client, APIURL+path, values, response, d) + err := api.postMethod(ctx, path, values, response) if err != nil { return nil, err } @@ -211,7 +225,7 @@ func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*Us "user": {user}, } - response, err := userRequest(ctx, api.httpclient, "users.getPresence", values, api) + response, err := api.userRequest(ctx, "users.getPresence", values) if err != nil { return nil, err } @@ -231,7 +245,7 @@ func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, "include_locale": {strconv.FormatBool(true)}, } - response, err := userRequest(ctx, api.httpclient, "users.info", values, api) + response, err := api.userRequest(ctx, "users.info", values) if err != nil { return nil, err } @@ -310,7 +324,7 @@ func (t UserPagination) Next(ctx context.Context) (_ UserPagination, err error) "include_locale": {strconv.FormatBool(true)}, } - if resp, err = userRequest(ctx, t.c.httpclient, "users.list", values, t.c); err != nil { + if resp, err = t.c.userRequest(ctx, "users.list", values); err != nil { return t, err } @@ -333,12 +347,19 @@ func (api *Client) GetUsers() ([]User, error) { // GetUsersContext returns the list of users (with their detailed information) with a custom context func (api *Client) GetUsersContext(ctx context.Context) (results []User, err error) { - var ( - p UserPagination - ) - - for p = api.GetUsersPaginated(); !p.Done(err); p, err = p.Next(ctx) { - results = append(results, p.Users...) + p := api.GetUsersPaginated() + for err == nil { + p, err = p.Next(ctx) + if err == nil { + results = append(results, p.Users...) + } else if rateLimitedError, ok := err.(*RateLimitedError); ok { + select { + case <-ctx.Done(): + err = ctx.Err() + case <-time.After(rateLimitedError.RetryAfter): + err = nil + } + } } return results, p.Failure(err) @@ -355,7 +376,7 @@ func (api *Client) GetUserByEmailContext(ctx context.Context, email string) (*Us "token": {api.token}, "email": {email}, } - response, err := userRequest(ctx, api.httpclient, "users.lookupByEmail", values, api) + response, err := api.userRequest(ctx, "users.lookupByEmail", values) if err != nil { return nil, err } @@ -373,7 +394,7 @@ func (api *Client) SetUserAsActiveContext(ctx context.Context) (err error) { "token": {api.token}, } - _, err = userRequest(ctx, api.httpclient, "users.setActive", values, api) + _, err = api.userRequest(ctx, "users.setActive", values) return err } @@ -389,7 +410,7 @@ func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) "presence": {presence}, } - _, err := userRequest(ctx, api.httpclient, "users.setPresence", values, api) + _, err := api.userRequest(ctx, "users.setPresence", values) return err } @@ -399,19 +420,21 @@ func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) { } // GetUserIdentityContext will retrieve user info available per identity scopes with a custom context -func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityResponse, error) { +func (api *Client) GetUserIdentityContext(ctx context.Context) (response *UserIdentityResponse, err error) { values := url.Values{ "token": {api.token}, } - response := &UserIdentityResponse{} + response = &UserIdentityResponse{} - err := postForm(ctx, api.httpclient, APIURL+"users.identity", values, response, api) + err = api.postMethod(ctx, "users.identity", values, response) if err != nil { return nil, err } - if !response.Ok { - return nil, errors.New(response.Error) + + if err := response.Err(); err != nil { + return nil, err } + return response, nil } @@ -421,7 +444,7 @@ func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error { } // SetUserPhotoContext changes the currently authenticated user's profile image using a custom context -func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) error { +func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) (err error) { response := &SlackResponse{} values := url.Values{ "token": {api.token}, @@ -436,7 +459,7 @@ func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params values.Add("crop_w", strconv.Itoa(params.CropW)) } - err := postLocalWithMultipartResponse(ctx, api.httpclient, "users.setPhoto", image, "image", values, response, api) + err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"users.setPhoto", image, "image", values, response, api) if err != nil { return err } @@ -450,13 +473,13 @@ func (api *Client) DeleteUserPhoto() error { } // DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context -func (api *Client) DeleteUserPhotoContext(ctx context.Context) error { +func (api *Client) DeleteUserPhotoContext(ctx context.Context) (err error) { response := &SlackResponse{} values := url.Values{ "token": {api.token}, } - err := postForm(ctx, api.httpclient, APIURL+"users.deletePhoto", values, response, api) + err = api.postMethod(ctx, "users.deletePhoto", values, response) if err != nil { return err } @@ -467,15 +490,30 @@ func (api *Client) DeleteUserPhotoContext(ctx context.Context) error { // SetUserCustomStatus will set a custom status and emoji for the currently // authenticated user. If statusEmoji is "" and statusText is not, the Slack API // will automatically set it to ":speech_balloon:". Otherwise, if both are "" -// the Slack API will unset the custom status/emoji. -func (api *Client) SetUserCustomStatus(statusText, statusEmoji string) error { - return api.SetUserCustomStatusContext(context.Background(), statusText, statusEmoji) +// the Slack API will unset the custom status/emoji. If statusExpiration is set to 0 +// the status will not expire. +func (api *Client) SetUserCustomStatus(statusText, statusEmoji string, statusExpiration int64) error { + return api.SetUserCustomStatusContextWithUser(context.Background(), "", statusText, statusEmoji, statusExpiration) } // SetUserCustomStatusContext will set a custom status and emoji for the currently authenticated user with a custom context // // For more information see SetUserCustomStatus -func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, statusEmoji string) error { +func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, statusEmoji string, statusExpiration int64) error { + return api.SetUserCustomStatusContextWithUser(context.Background(), "", statusText, statusEmoji, statusExpiration) +} + +// SetUserCustomStatusWithUser will set a custom status and emoji for the provided user. +// +// For more information see SetUserCustomStatus +func (api *Client) SetUserCustomStatusWithUser(user, statusText, statusEmoji string, statusExpiration int64) error { + return api.SetUserCustomStatusContextWithUser(context.Background(), user, statusText, statusEmoji, statusExpiration) +} + +// SetUserCustomStatusContextWithUser will set a custom status and emoji for the provided user with a custom context +// +// For more information see SetUserCustomStatus +func (api *Client) SetUserCustomStatusContextWithUser(ctx context.Context, user, statusText, statusEmoji string, statusExpiration int64) error { // XXX(theckman): this anonymous struct is for making requests to the Slack // API for setting and unsetting a User's Custom Status/Emoji. To change // these values we must provide a JSON document as the profile POST field. @@ -488,11 +526,13 @@ func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, s // - https://api.slack.com/docs/presence-and-status#custom_status profile, err := json.Marshal( &struct { - StatusText string `json:"status_text"` - StatusEmoji string `json:"status_emoji"` + StatusText string `json:"status_text"` + StatusEmoji string `json:"status_emoji"` + StatusExpiration int64 `json:"status_expiration"` }{ - StatusText: statusText, - StatusEmoji: statusEmoji, + StatusText: statusText, + StatusEmoji: statusEmoji, + StatusExpiration: statusExpiration, }, ) @@ -501,20 +541,17 @@ func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, s } values := url.Values{ + "user": {user}, "token": {api.token}, "profile": {string(profile)}, } response := &userResponseFull{} - if err = postForm(ctx, api.httpclient, APIURL+"users.profile.set", values, response, api); err != nil { + if err = api.postMethod(ctx, "users.profile.set", values, response); err != nil { return err } - if !response.Ok { - return errors.New(response.Error) - } - - return nil + return response.Err() } // UnsetUserCustomStatus removes the custom status message for the currently @@ -526,7 +563,7 @@ func (api *Client) UnsetUserCustomStatus() error { // UnsetUserCustomStatusContext removes the custom status message for the currently authenticated user // with a custom context. This is a convenience method that wraps (*Client).SetUserCustomStatus(). func (api *Client) UnsetUserCustomStatusContext(ctx context.Context) error { - return api.SetUserCustomStatusContext(ctx, "", "") + return api.SetUserCustomStatusContext(ctx, "", "", 0) } // GetUserProfile retrieves a user's profile information. @@ -547,12 +584,14 @@ func (api *Client) GetUserProfileContext(ctx context.Context, userID string, inc } resp := &getUserProfileResponse{} - err := postSlackMethod(ctx, api.httpclient, "users.profile.get", values, &resp, api) + err := api.postMethod(ctx, "users.profile.get", values, &resp) if err != nil { return nil, err } - if !resp.Ok { - return nil, errors.New(resp.Error) + + if err := resp.Err(); err != nil { + return nil, err } + return resp.Profile, nil } diff --git a/vendor/github.com/nlopes/slack/webhooks.go b/vendor/github.com/nlopes/slack/webhooks.go index 3ea69ff..14e1b8d 100644 --- a/vendor/github.com/nlopes/slack/webhooks.go +++ b/vendor/github.com/nlopes/slack/webhooks.go @@ -9,26 +9,32 @@ import ( ) type WebhookMessage struct { - Text string `json:"text,omitempty"` - Attachments []Attachment `json:"attachments,omitempty"` + Username string `json:"username,omitempty"` + IconEmoji string `json:"icon_emoji,omitempty"` + IconURL string `json:"icon_url,omitempty"` + Channel string `json:"channel,omitempty"` + ThreadTimestamp string `json:"thread_ts,omitempty"` + Text string `json:"text,omitempty"` + Attachments []Attachment `json:"attachments,omitempty"` + Parse string `json:"parse,omitempty"` } func PostWebhook(url string, msg *WebhookMessage) error { + return PostWebhookCustomHTTP(url, http.DefaultClient, msg) +} + +func PostWebhookCustomHTTP(url string, httpClient *http.Client, msg *WebhookMessage) error { raw, err := json.Marshal(msg) if err != nil { return errors.Wrap(err, "marshal failed") } - response, err := http.Post(url, "application/json", bytes.NewReader(raw)) + response, err := httpClient.Post(url, "application/json", bytes.NewReader(raw)) if err != nil { return errors.Wrap(err, "failed to post webhook") } - if response.StatusCode != http.StatusOK { - return statusCodeError{Code: response.StatusCode, Status: response.Status} - } - - return nil + return checkStatusCode(response, discard{}) } diff --git a/vendor/github.com/nlopes/slack/websocket.go b/vendor/github.com/nlopes/slack/websocket.go index e5dee68..122807b 100644 --- a/vendor/github.com/nlopes/slack/websocket.go +++ b/vendor/github.com/nlopes/slack/websocket.go @@ -2,7 +2,6 @@ package slack import ( "encoding/json" - "errors" "net/url" "sync" "time" @@ -33,11 +32,10 @@ type RTM struct { IncomingEvents chan RTMEvent outgoingMessages chan OutgoingMessage killChannel chan bool - disconnected chan struct{} // disconnected is closed when Disconnect is invoked, regardless of connection state. Allows for ManagedConnection to not leak. + disconnected chan struct{} + disconnectedm *sync.Once forcePing chan bool rawEvents chan json.RawMessage - wasIntentional bool - isConnected bool // UserDetails upon connection info *Info @@ -58,32 +56,30 @@ type RTM struct { connParams url.Values } +// signal that we are disconnected by closing the channel. +// protect it with a mutex to ensure it only happens once. +func (rtm *RTM) disconnect() { + rtm.disconnectedm.Do(func() { + close(rtm.disconnected) + }) +} + // Disconnect and wait, blocking until a successful disconnection. func (rtm *RTM) Disconnect() error { - // avoid RTM disconnect race conditions - rtm.mu.Lock() - defer rtm.mu.Unlock() - - // always push into the disconnected channel when invoked, + // always push into the kill channel when invoked, // this lets the ManagedConnection() function properly clean up. // if the buffer is full then just continue on. select { - case rtm.disconnected <- struct{}{}: - default: + case rtm.killChannel <- true: + return nil + case <-rtm.disconnected: + return ErrAlreadyDisconnected } - - if !rtm.isConnected { - return errors.New("Invalid call to Disconnect - Slack API is already disconnected") - } - - rtm.killChannel <- true - return nil } // GetInfo returns the info structure received when calling -// "startrtm", holding all channels, groups and other metadata needed -// to implement a full chat client. It will be non-nil after a call to -// StartRTM(). +// "startrtm", holding metadata needed to implement a full +// chat client. It will be non-nil after a call to StartRTM(). func (rtm *RTM) GetInfo() *Info { return rtm.info } diff --git a/vendor/github.com/nlopes/slack/websocket_internals.go b/vendor/github.com/nlopes/slack/websocket_internals.go index e8374b0..3e1906e 100644 --- a/vendor/github.com/nlopes/slack/websocket_internals.go +++ b/vendor/github.com/nlopes/slack/websocket_internals.go @@ -18,6 +18,7 @@ type ConnectedEvent struct { // ConnectionErrorEvent contains information about a connection error type ConnectionErrorEvent struct { Attempt int + Backoff time.Duration // how long we'll wait before the next attempt ErrorObj error } @@ -34,6 +35,7 @@ type ConnectingEvent struct { // DisconnectedEvent contains information about how we disconnected type DisconnectedEvent struct { Intentional bool + Cause error } // LatencyReport contains information about connection latency diff --git a/vendor/github.com/nlopes/slack/websocket_managed_conn.go b/vendor/github.com/nlopes/slack/websocket_managed_conn.go index 6215791..8b3b383 100644 --- a/vendor/github.com/nlopes/slack/websocket_managed_conn.go +++ b/vendor/github.com/nlopes/slack/websocket_managed_conn.go @@ -10,6 +10,8 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/nlopes/slack/internal/errorsx" + "github.com/nlopes/slack/internal/timex" ) // ManageConnection can be called on a Slack RTM instance returned by the @@ -38,6 +40,7 @@ func (rtm *RTM) ManageConnection() { if info, conn, err = rtm.connect(connectionCount, rtm.useRTMStart); err != nil { // when the connection is unsuccessful its fatal, and we need to bail out. rtm.Debugf("Failed to connect with RTM on try %d: %s", connectionCount, err) + rtm.disconnect() return } @@ -45,7 +48,6 @@ func (rtm *RTM) ManageConnection() { // and conn. rtm.mu.Lock() rtm.conn = conn - rtm.isConnected = true rtm.info = info rtm.mu.Unlock() @@ -56,20 +58,19 @@ func (rtm *RTM) ManageConnection() { rtm.Debugf("RTM connection succeeded on try %d", connectionCount) - keepRunning := make(chan bool) - // we're now connected (or have failed fatally) so we can set up - // listeners - go rtm.handleIncomingEvents(keepRunning) + // we're now connected so we can set up listeners + go rtm.handleIncomingEvents() // this should be a blocking call until the connection has ended - rtm.handleEvents(keepRunning) + rtm.handleEvents() - // after being disconnected we need to check if it was intentional - // if not then we should try to reconnect - if rtm.wasIntentional { + select { + case <-rtm.disconnected: + // after handle events returns we need to check if we're disconnected return + default: + // otherwise continue and run the loop again to reconnect } - // else continue and run the loop again to connect } } @@ -88,18 +89,20 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke // used to provide exponential backoff wait time with jitter before trying // to connect to slack again boff := &backoff{ - Min: 100 * time.Millisecond, - Max: 5 * time.Minute, - Factor: 2, - Jitter: true, + Max: 5 * time.Minute, } for { + var ( + backoff time.Duration + ) + // send connecting event rtm.IncomingEvents <- RTMEvent{"connecting", &ConnectingEvent{ Attempt: boff.attempts + 1, ConnectionCount: connectionCount, }} + // attempt to start the connection info, conn, err := rtm.startRTMAndDial(useRTMStart) if err == nil { @@ -109,32 +112,49 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke // check for fatal errors switch err.Error() { case errInvalidAuth, errInactiveAccount, errMissingAuthToken: - rtm.Debugf("Invalid auth when connecting with RTM: %s", err) + rtm.Debugf("invalid auth when connecting with RTM: %s", err) rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}} return nil, nil, err default: } + switch actual := err.(type) { + case statusCodeError: + if actual.Code == http.StatusNotFound { + rtm.Debugf("invalid auth when connecting with RTM: %s", err) + rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}} + return nil, nil, err + } + case *RateLimitedError: + backoff = actual.RetryAfter + default: + } + + backoff = timex.Max(backoff, boff.Duration()) // any other errors are treated as recoverable and we try again after // sending the event along the IncomingEvents channel rtm.IncomingEvents <- RTMEvent{"connection_error", &ConnectionErrorEvent{ Attempt: boff.attempts, + Backoff: backoff, ErrorObj: err, }} - // check if Disconnect() has been invoked. + // get time we should wait before attempting to connect again + rtm.Debugf("reconnection %d failed: %s reconnecting in %v\n", boff.attempts, err, backoff) + + // wait for one of the following to occur, + // backoff duration has elapsed, killChannel is signalled, or + // the rtm finishes disconnecting. select { + case <-time.After(backoff): // retry after the backoff. + case intentional := <-rtm.killChannel: + if intentional { + rtm.killConnection(intentional, ErrRTMDisconnected) + return nil, nil, ErrRTMDisconnected + } case <-rtm.disconnected: - rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{Intentional: true}} - return nil, nil, fmt.Errorf("disconnect received while trying to connect") - default: + return nil, nil, ErrRTMDisconnected } - - // get time we should wait before attempting to connect again - dur := boff.Duration() - rtm.Debugf("reconnection %d failed: %s", boff.attempts+1, err) - rtm.Debugln(" -> reconnecting in", dur) - time.Sleep(dur) } } @@ -187,15 +207,19 @@ func (rtm *RTM) startRTMAndDial(useRTMStart bool) (info *Info, _ *websocket.Conn // // This should not be called directly! Instead a boolean value (true for // intentional, false otherwise) should be sent to the killChannel on the RTM. -func (rtm *RTM) killConnection(keepRunning chan bool, intentional bool) error { +func (rtm *RTM) killConnection(intentional bool, cause error) (err error) { rtm.Debugln("killing connection") - if rtm.isConnected { - close(keepRunning) + + if rtm.conn != nil { + err = rtm.conn.Close() + } + + rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{Intentional: intentional, Cause: cause}} + + if intentional { + rtm.disconnect() } - rtm.isConnected = false - rtm.wasIntentional = intentional - err := rtm.conn.Close() - rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{intentional}} + return err } @@ -204,31 +228,28 @@ func (rtm *RTM) killConnection(keepRunning chan bool, intentional bool) error { // interval. This also sends outgoing messages that are received from the RTM's // outgoingMessages channel. This also handles incoming raw events from the RTM // rawEvents channel. -func (rtm *RTM) handleEvents(keepRunning chan bool) { +func (rtm *RTM) handleEvents() { ticker := time.NewTicker(rtm.pingInterval) defer ticker.Stop() for { select { // catch "stop" signal on channel close case intentional := <-rtm.killChannel: - _ = rtm.killConnection(keepRunning, intentional) + _ = rtm.killConnection(intentional, errorsx.String("signaled")) return - // detect when the connection is dead. case <-rtm.pingDeadman.C: - rtm.Debugln("deadman switch trigger disconnecting") - _ = rtm.killConnection(keepRunning, false) + _ = rtm.killConnection(false, errorsx.String("deadman switch triggered")) + return // send pings on ticker interval case <-ticker.C: - err := rtm.ping() - if err != nil { - _ = rtm.killConnection(keepRunning, false) + if err := rtm.ping(); err != nil { + _ = rtm.killConnection(false, err) return } case <-rtm.forcePing: - err := rtm.ping() - if err != nil { - _ = rtm.killConnection(keepRunning, false) + if err := rtm.ping(); err != nil { + _ = rtm.killConnection(false, err) return } // listen for messages that need to be sent @@ -238,7 +259,8 @@ func (rtm *RTM) handleEvents(keepRunning chan bool) { case rawEvent := <-rtm.rawEvents: switch rtm.handleRawEvent(rawEvent) { case rtmEventTypeGoodbye: - _ = rtm.killConnection(keepRunning, false) + _ = rtm.killConnection(false, errorsx.String("goodbye detected")) + return default: } } @@ -250,17 +272,10 @@ func (rtm *RTM) handleEvents(keepRunning chan bool) { // // This will stop executing once the RTM's keepRunning channel has been closed // or has anything sent to it. -func (rtm *RTM) handleIncomingEvents(keepRunning <-chan bool) { +func (rtm *RTM) handleIncomingEvents() { for { - // non-blocking listen to see if channel is closed - select { - // catch "stop" signal on channel close - case <-keepRunning: + if err := rtm.receiveIncomingEvent(); err != nil { return - default: - if err := rtm.receiveIncomingEvent(); err != nil { - return - } } } } @@ -296,7 +311,6 @@ func (rtm *RTM) sendOutgoingMessage(msg OutgoingMessage) { Message: msg, ErrorObj: err, }} - // TODO force ping? } } @@ -332,20 +346,32 @@ func (rtm *RTM) receiveIncomingEvent() error { // 'PING' message // trigger a 'PING' to detect potential websocket disconnect - rtm.forcePing <- true + select { + case rtm.forcePing <- true: + case <-rtm.disconnected: + } case err != nil: // All other errors from ReadJSON come from NextReader, and should // kill the read loop and force a reconnect. rtm.IncomingEvents <- RTMEvent{"incoming_error", &IncomingEventError{ ErrorObj: err, }} - rtm.killChannel <- false + + select { + case rtm.killChannel <- false: + case <-rtm.disconnected: + } + return err case len(event) == 0: rtm.Debugln("Received empty event") default: - rtm.Debugln("Incoming Event:", string(event[:])) - rtm.rawEvents <- event + rtm.Debugln("Incoming Event:", string(event)) + select { + case rtm.rawEvents <- event: + case <-rtm.disconnected: + rtm.Debugln("disonnected while attempting to send raw event") + } } return nil } diff --git a/vendor/golang.org/x/image/draw/gen.go b/vendor/golang.org/x/image/draw/gen.go deleted file mode 100644 index 822bb6a..0000000 --- a/vendor/golang.org/x/image/draw/gen.go +++ /dev/null @@ -1,1404 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build ignore - -package main - -import ( - "bytes" - "flag" - "fmt" - "go/format" - "io/ioutil" - "log" - "os" - "strings" -) - -var debug = flag.Bool("debug", false, "") - -func main() { - flag.Parse() - - w := new(bytes.Buffer) - w.WriteString("// generated by \"go run gen.go\". DO NOT EDIT.\n\n" + - "package draw\n\nimport (\n" + - "\"image\"\n" + - "\"image/color\"\n" + - "\"math\"\n" + - "\n" + - "\"golang.org/x/image/math/f64\"\n" + - ")\n") - - gen(w, "nnInterpolator", codeNNScaleLeaf, codeNNTransformLeaf) - gen(w, "ablInterpolator", codeABLScaleLeaf, codeABLTransformLeaf) - genKernel(w) - - if *debug { - os.Stdout.Write(w.Bytes()) - return - } - out, err := format.Source(w.Bytes()) - if err != nil { - log.Fatal(err) - } - if err := ioutil.WriteFile("impl.go", out, 0660); err != nil { - log.Fatal(err) - } -} - -var ( - // dsTypes are the (dst image type, src image type) pairs to generate - // scale_DType_SType implementations for. The last element in the slice - // should be the fallback pair ("Image", "image.Image"). - // - // TODO: add *image.CMYK src type after Go 1.5 is released. - // An *image.CMYK is also alwaysOpaque. - dsTypes = []struct{ dType, sType string }{ - {"*image.RGBA", "*image.Gray"}, - {"*image.RGBA", "*image.NRGBA"}, - {"*image.RGBA", "*image.RGBA"}, - {"*image.RGBA", "*image.YCbCr"}, - {"*image.RGBA", "image.Image"}, - {"Image", "image.Image"}, - } - dTypes, sTypes []string - sTypesForDType = map[string][]string{} - subsampleRatios = []string{ - "444", - "422", - "420", - "440", - } - ops = []string{"Over", "Src"} - // alwaysOpaque are those image.Image implementations that are always - // opaque. For these types, Over is equivalent to the faster Src, in the - // absence of a source mask. - alwaysOpaque = map[string]bool{ - "*image.Gray": true, - "*image.YCbCr": true, - } -) - -func init() { - dTypesSeen := map[string]bool{} - sTypesSeen := map[string]bool{} - for _, t := range dsTypes { - if !sTypesSeen[t.sType] { - sTypesSeen[t.sType] = true - sTypes = append(sTypes, t.sType) - } - if !dTypesSeen[t.dType] { - dTypesSeen[t.dType] = true - dTypes = append(dTypes, t.dType) - } - sTypesForDType[t.dType] = append(sTypesForDType[t.dType], t.sType) - } - sTypesForDType["anyDType"] = sTypes -} - -type data struct { - dType string - sType string - sratio string - receiver string - op string -} - -func gen(w *bytes.Buffer, receiver string, codes ...string) { - expn(w, codeRoot, &data{receiver: receiver}) - for _, code := range codes { - for _, t := range dsTypes { - for _, op := range ops { - if op == "Over" && alwaysOpaque[t.sType] { - continue - } - expn(w, code, &data{ - dType: t.dType, - sType: t.sType, - receiver: receiver, - op: op, - }) - } - } - } -} - -func genKernel(w *bytes.Buffer) { - expn(w, codeKernelRoot, &data{}) - for _, sType := range sTypes { - expn(w, codeKernelScaleLeafX, &data{ - sType: sType, - }) - } - for _, dType := range dTypes { - for _, op := range ops { - expn(w, codeKernelScaleLeafY, &data{ - dType: dType, - op: op, - }) - } - } - for _, t := range dsTypes { - for _, op := range ops { - if op == "Over" && alwaysOpaque[t.sType] { - continue - } - expn(w, codeKernelTransformLeaf, &data{ - dType: t.dType, - sType: t.sType, - op: op, - }) - } - } -} - -func expn(w *bytes.Buffer, code string, d *data) { - if d.sType == "*image.YCbCr" && d.sratio == "" { - for _, sratio := range subsampleRatios { - e := *d - e.sratio = sratio - expn(w, code, &e) - } - return - } - - for _, line := range strings.Split(code, "\n") { - line = expnLine(line, d) - if line == ";" { - continue - } - fmt.Fprintln(w, line) - } -} - -func expnLine(line string, d *data) string { - for { - i := strings.IndexByte(line, '$') - if i < 0 { - break - } - prefix, s := line[:i], line[i+1:] - - i = len(s) - for j, c := range s { - if !('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z') { - i = j - break - } - } - dollar, suffix := s[:i], s[i:] - - e := expnDollar(prefix, dollar, suffix, d) - if e == "" { - log.Fatalf("couldn't expand %q", line) - } - line = e - } - return line -} - -// expnDollar expands a "$foo" fragment in a line of generated code. It returns -// the empty string if there was a problem. It returns ";" if the generated -// code is a no-op. -func expnDollar(prefix, dollar, suffix string, d *data) string { - switch dollar { - case "dType": - return prefix + d.dType + suffix - case "dTypeRN": - return prefix + relName(d.dType) + suffix - case "sratio": - return prefix + d.sratio + suffix - case "sType": - return prefix + d.sType + suffix - case "sTypeRN": - return prefix + relName(d.sType) + suffix - case "receiver": - return prefix + d.receiver + suffix - case "op": - return prefix + d.op + suffix - - case "switch": - return expnSwitch("", "", true, suffix) - case "switchD": - return expnSwitch("", "", false, suffix) - case "switchS": - return expnSwitch("", "anyDType", false, suffix) - - case "preOuter": - switch d.dType { - default: - return ";" - case "Image": - s := "" - if d.sType == "image.Image" { - s = "srcMask, smp := opts.SrcMask, opts.SrcMaskP\n" - } - return s + - "dstMask, dmp := opts.DstMask, opts.DstMaskP\n" + - "dstColorRGBA64 := &color.RGBA64{}\n" + - "dstColor := color.Color(dstColorRGBA64)" - } - - case "preInner": - switch d.dType { - default: - return ";" - case "*image.RGBA": - return "d := " + pixOffset("dst", "dr.Min.X+adr.Min.X", "dr.Min.Y+int(dy)", "*4", "*dst.Stride") - } - - case "preKernelOuter": - switch d.sType { - default: - return ";" - case "image.Image": - return "srcMask, smp := opts.SrcMask, opts.SrcMaskP" - } - - case "preKernelInner": - switch d.dType { - default: - return ";" - case "*image.RGBA": - return "d := " + pixOffset("dst", "dr.Min.X+int(dx)", "dr.Min.Y+adr.Min.Y", "*4", "*dst.Stride") - } - - case "blend": - args, _ := splitArgs(suffix) - if len(args) != 4 { - return "" - } - switch d.sType { - default: - return argf(args, ""+ - "$3r = $0*$1r + $2*$3r\n"+ - "$3g = $0*$1g + $2*$3g\n"+ - "$3b = $0*$1b + $2*$3b\n"+ - "$3a = $0*$1a + $2*$3a", - ) - case "*image.Gray": - return argf(args, ""+ - "$3r = $0*$1r + $2*$3r", - ) - case "*image.YCbCr": - return argf(args, ""+ - "$3r = $0*$1r + $2*$3r\n"+ - "$3g = $0*$1g + $2*$3g\n"+ - "$3b = $0*$1b + $2*$3b", - ) - } - - case "clampToAlpha": - if alwaysOpaque[d.sType] { - return ";" - } - // Go uses alpha-premultiplied color. The naive computation can lead to - // invalid colors, e.g. red > alpha, when some weights are negative. - return ` - if pr > pa { - pr = pa - } - if pg > pa { - pg = pa - } - if pb > pa { - pb = pa - } - ` - - case "convFtou": - args, _ := splitArgs(suffix) - if len(args) != 2 { - return "" - } - - switch d.sType { - default: - return argf(args, ""+ - "$0r := uint32($1r)\n"+ - "$0g := uint32($1g)\n"+ - "$0b := uint32($1b)\n"+ - "$0a := uint32($1a)", - ) - case "*image.Gray": - return argf(args, ""+ - "$0r := uint32($1r)", - ) - case "*image.YCbCr": - return argf(args, ""+ - "$0r := uint32($1r)\n"+ - "$0g := uint32($1g)\n"+ - "$0b := uint32($1b)", - ) - } - - case "outputu": - args, _ := splitArgs(suffix) - if len(args) != 3 { - return "" - } - - switch d.op { - case "Over": - switch d.dType { - default: - log.Fatalf("bad dType %q", d.dType) - case "Image": - return argf(args, ""+ - "qr, qg, qb, qa := dst.At($0, $1).RGBA()\n"+ - "if dstMask != nil {\n"+ - " _, _, _, ma := dstMask.At(dmp.X + $0, dmp.Y + $1).RGBA()\n"+ - " $2r = $2r * ma / 0xffff\n"+ - " $2g = $2g * ma / 0xffff\n"+ - " $2b = $2b * ma / 0xffff\n"+ - " $2a = $2a * ma / 0xffff\n"+ - "}\n"+ - "$2a1 := 0xffff - $2a\n"+ - "dstColorRGBA64.R = uint16(qr*$2a1/0xffff + $2r)\n"+ - "dstColorRGBA64.G = uint16(qg*$2a1/0xffff + $2g)\n"+ - "dstColorRGBA64.B = uint16(qb*$2a1/0xffff + $2b)\n"+ - "dstColorRGBA64.A = uint16(qa*$2a1/0xffff + $2a)\n"+ - "dst.Set($0, $1, dstColor)", - ) - case "*image.RGBA": - return argf(args, ""+ - "$2a1 := (0xffff - $2a) * 0x101\n"+ - "dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*$2a1/0xffff + $2r) >> 8)\n"+ - "dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*$2a1/0xffff + $2g) >> 8)\n"+ - "dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*$2a1/0xffff + $2b) >> 8)\n"+ - "dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*$2a1/0xffff + $2a) >> 8)", - ) - } - - case "Src": - switch d.dType { - default: - log.Fatalf("bad dType %q", d.dType) - case "Image": - return argf(args, ""+ - "if dstMask != nil {\n"+ - " qr, qg, qb, qa := dst.At($0, $1).RGBA()\n"+ - " _, _, _, ma := dstMask.At(dmp.X + $0, dmp.Y + $1).RGBA()\n"+ - " pr = pr * ma / 0xffff\n"+ - " pg = pg * ma / 0xffff\n"+ - " pb = pb * ma / 0xffff\n"+ - " pa = pa * ma / 0xffff\n"+ - " $2a1 := 0xffff - ma\n"+ // Note that this is ma, not $2a. - " dstColorRGBA64.R = uint16(qr*$2a1/0xffff + $2r)\n"+ - " dstColorRGBA64.G = uint16(qg*$2a1/0xffff + $2g)\n"+ - " dstColorRGBA64.B = uint16(qb*$2a1/0xffff + $2b)\n"+ - " dstColorRGBA64.A = uint16(qa*$2a1/0xffff + $2a)\n"+ - " dst.Set($0, $1, dstColor)\n"+ - "} else {\n"+ - " dstColorRGBA64.R = uint16($2r)\n"+ - " dstColorRGBA64.G = uint16($2g)\n"+ - " dstColorRGBA64.B = uint16($2b)\n"+ - " dstColorRGBA64.A = uint16($2a)\n"+ - " dst.Set($0, $1, dstColor)\n"+ - "}", - ) - case "*image.RGBA": - switch d.sType { - default: - return argf(args, ""+ - "dst.Pix[d+0] = uint8($2r >> 8)\n"+ - "dst.Pix[d+1] = uint8($2g >> 8)\n"+ - "dst.Pix[d+2] = uint8($2b >> 8)\n"+ - "dst.Pix[d+3] = uint8($2a >> 8)", - ) - case "*image.Gray": - return argf(args, ""+ - "out := uint8($2r >> 8)\n"+ - "dst.Pix[d+0] = out\n"+ - "dst.Pix[d+1] = out\n"+ - "dst.Pix[d+2] = out\n"+ - "dst.Pix[d+3] = 0xff", - ) - case "*image.YCbCr": - return argf(args, ""+ - "dst.Pix[d+0] = uint8($2r >> 8)\n"+ - "dst.Pix[d+1] = uint8($2g >> 8)\n"+ - "dst.Pix[d+2] = uint8($2b >> 8)\n"+ - "dst.Pix[d+3] = 0xff", - ) - } - } - } - - case "outputf": - args, _ := splitArgs(suffix) - if len(args) != 5 { - return "" - } - ret := "" - - switch d.op { - case "Over": - switch d.dType { - default: - log.Fatalf("bad dType %q", d.dType) - case "Image": - ret = argf(args, ""+ - "qr, qg, qb, qa := dst.At($0, $1).RGBA()\n"+ - "$3r0 := uint32($2($3r * $4))\n"+ - "$3g0 := uint32($2($3g * $4))\n"+ - "$3b0 := uint32($2($3b * $4))\n"+ - "$3a0 := uint32($2($3a * $4))\n"+ - "if dstMask != nil {\n"+ - " _, _, _, ma := dstMask.At(dmp.X + $0, dmp.Y + $1).RGBA()\n"+ - " $3r0 = $3r0 * ma / 0xffff\n"+ - " $3g0 = $3g0 * ma / 0xffff\n"+ - " $3b0 = $3b0 * ma / 0xffff\n"+ - " $3a0 = $3a0 * ma / 0xffff\n"+ - "}\n"+ - "$3a1 := 0xffff - $3a0\n"+ - "dstColorRGBA64.R = uint16(qr*$3a1/0xffff + $3r0)\n"+ - "dstColorRGBA64.G = uint16(qg*$3a1/0xffff + $3g0)\n"+ - "dstColorRGBA64.B = uint16(qb*$3a1/0xffff + $3b0)\n"+ - "dstColorRGBA64.A = uint16(qa*$3a1/0xffff + $3a0)\n"+ - "dst.Set($0, $1, dstColor)", - ) - case "*image.RGBA": - ret = argf(args, ""+ - "$3r0 := uint32($2($3r * $4))\n"+ - "$3g0 := uint32($2($3g * $4))\n"+ - "$3b0 := uint32($2($3b * $4))\n"+ - "$3a0 := uint32($2($3a * $4))\n"+ - "$3a1 := (0xffff - uint32($3a0)) * 0x101\n"+ - "dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*$3a1/0xffff + $3r0) >> 8)\n"+ - "dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*$3a1/0xffff + $3g0) >> 8)\n"+ - "dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*$3a1/0xffff + $3b0) >> 8)\n"+ - "dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*$3a1/0xffff + $3a0) >> 8)", - ) - } - - case "Src": - switch d.dType { - default: - log.Fatalf("bad dType %q", d.dType) - case "Image": - ret = argf(args, ""+ - "if dstMask != nil {\n"+ - " qr, qg, qb, qa := dst.At($0, $1).RGBA()\n"+ - " _, _, _, ma := dstMask.At(dmp.X + $0, dmp.Y + $1).RGBA()\n"+ - " pr := uint32($2($3r * $4)) * ma / 0xffff\n"+ - " pg := uint32($2($3g * $4)) * ma / 0xffff\n"+ - " pb := uint32($2($3b * $4)) * ma / 0xffff\n"+ - " pa := uint32($2($3a * $4)) * ma / 0xffff\n"+ - " pa1 := 0xffff - ma\n"+ // Note that this is ma, not pa. - " dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr)\n"+ - " dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg)\n"+ - " dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb)\n"+ - " dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa)\n"+ - " dst.Set($0, $1, dstColor)\n"+ - "} else {\n"+ - " dstColorRGBA64.R = $2($3r * $4)\n"+ - " dstColorRGBA64.G = $2($3g * $4)\n"+ - " dstColorRGBA64.B = $2($3b * $4)\n"+ - " dstColorRGBA64.A = $2($3a * $4)\n"+ - " dst.Set($0, $1, dstColor)\n"+ - "}", - ) - case "*image.RGBA": - switch d.sType { - default: - ret = argf(args, ""+ - "dst.Pix[d+0] = uint8($2($3r * $4) >> 8)\n"+ - "dst.Pix[d+1] = uint8($2($3g * $4) >> 8)\n"+ - "dst.Pix[d+2] = uint8($2($3b * $4) >> 8)\n"+ - "dst.Pix[d+3] = uint8($2($3a * $4) >> 8)", - ) - case "*image.Gray": - ret = argf(args, ""+ - "out := uint8($2($3r * $4) >> 8)\n"+ - "dst.Pix[d+0] = out\n"+ - "dst.Pix[d+1] = out\n"+ - "dst.Pix[d+2] = out\n"+ - "dst.Pix[d+3] = 0xff", - ) - case "*image.YCbCr": - ret = argf(args, ""+ - "dst.Pix[d+0] = uint8($2($3r * $4) >> 8)\n"+ - "dst.Pix[d+1] = uint8($2($3g * $4) >> 8)\n"+ - "dst.Pix[d+2] = uint8($2($3b * $4) >> 8)\n"+ - "dst.Pix[d+3] = 0xff", - ) - } - } - } - - return strings.Replace(ret, " * 1)", ")", -1) - - case "srcf", "srcu": - lhs, eqOp := splitEq(prefix) - if lhs == "" { - return "" - } - args, extra := splitArgs(suffix) - if len(args) != 2 { - return "" - } - - tmp := "" - if dollar == "srcf" { - tmp = "u" - } - - // TODO: there's no need to multiply by 0x101 in the switch below if - // the next thing we're going to do is shift right by 8. - - buf := new(bytes.Buffer) - switch d.sType { - default: - log.Fatalf("bad sType %q", d.sType) - case "image.Image": - fmt.Fprintf(buf, ""+ - "%sr%s, %sg%s, %sb%s, %sa%s := src.At(%s, %s).RGBA()\n", - lhs, tmp, lhs, tmp, lhs, tmp, lhs, tmp, args[0], args[1], - ) - if d.dType == "" || d.dType == "Image" { - fmt.Fprintf(buf, ""+ - "if srcMask != nil {\n"+ - " _, _, _, ma := srcMask.At(smp.X+%s, smp.Y+%s).RGBA()\n"+ - " %sr%s = %sr%s * ma / 0xffff\n"+ - " %sg%s = %sg%s * ma / 0xffff\n"+ - " %sb%s = %sb%s * ma / 0xffff\n"+ - " %sa%s = %sa%s * ma / 0xffff\n"+ - "}\n", - args[0], args[1], - lhs, tmp, lhs, tmp, - lhs, tmp, lhs, tmp, - lhs, tmp, lhs, tmp, - lhs, tmp, lhs, tmp, - ) - } - case "*image.Gray": - fmt.Fprintf(buf, ""+ - "%si := %s\n"+ - "%sr%s := uint32(src.Pix[%si]) * 0x101\n", - lhs, pixOffset("src", args[0], args[1], "", "*src.Stride"), - lhs, tmp, lhs, - ) - case "*image.NRGBA": - fmt.Fprintf(buf, ""+ - "%si := %s\n"+ - "%sa%s := uint32(src.Pix[%si+3]) * 0x101\n"+ - "%sr%s := uint32(src.Pix[%si+0]) * %sa%s / 0xff\n"+ - "%sg%s := uint32(src.Pix[%si+1]) * %sa%s / 0xff\n"+ - "%sb%s := uint32(src.Pix[%si+2]) * %sa%s / 0xff\n", - lhs, pixOffset("src", args[0], args[1], "*4", "*src.Stride"), - lhs, tmp, lhs, - lhs, tmp, lhs, lhs, tmp, - lhs, tmp, lhs, lhs, tmp, - lhs, tmp, lhs, lhs, tmp, - ) - case "*image.RGBA": - fmt.Fprintf(buf, ""+ - "%si := %s\n"+ - "%sr%s := uint32(src.Pix[%si+0]) * 0x101\n"+ - "%sg%s := uint32(src.Pix[%si+1]) * 0x101\n"+ - "%sb%s := uint32(src.Pix[%si+2]) * 0x101\n"+ - "%sa%s := uint32(src.Pix[%si+3]) * 0x101\n", - lhs, pixOffset("src", args[0], args[1], "*4", "*src.Stride"), - lhs, tmp, lhs, - lhs, tmp, lhs, - lhs, tmp, lhs, - lhs, tmp, lhs, - ) - case "*image.YCbCr": - fmt.Fprintf(buf, ""+ - "%si := %s\n"+ - "%sj := %s\n"+ - "%s\n", - lhs, pixOffset("src", args[0], args[1], "", "*src.YStride"), - lhs, cOffset(args[0], args[1], d.sratio), - ycbcrToRGB(lhs, tmp), - ) - } - - if dollar == "srcf" { - switch d.sType { - default: - fmt.Fprintf(buf, ""+ - "%sr %s float64(%sru)%s\n"+ - "%sg %s float64(%sgu)%s\n"+ - "%sb %s float64(%sbu)%s\n"+ - "%sa %s float64(%sau)%s\n", - lhs, eqOp, lhs, extra, - lhs, eqOp, lhs, extra, - lhs, eqOp, lhs, extra, - lhs, eqOp, lhs, extra, - ) - case "*image.Gray": - fmt.Fprintf(buf, ""+ - "%sr %s float64(%sru)%s\n", - lhs, eqOp, lhs, extra, - ) - case "*image.YCbCr": - fmt.Fprintf(buf, ""+ - "%sr %s float64(%sru)%s\n"+ - "%sg %s float64(%sgu)%s\n"+ - "%sb %s float64(%sbu)%s\n", - lhs, eqOp, lhs, extra, - lhs, eqOp, lhs, extra, - lhs, eqOp, lhs, extra, - ) - } - } - - return strings.TrimSpace(buf.String()) - - case "tweakD": - if d.dType == "*image.RGBA" { - return "d += dst.Stride" - } - return ";" - - case "tweakDx": - if d.dType == "*image.RGBA" { - return strings.Replace(prefix, "dx++", "dx, d = dx+1, d+4", 1) - } - return prefix - - case "tweakDy": - if d.dType == "*image.RGBA" { - return strings.Replace(prefix, "for dy, s", "for _, s", 1) - } - return prefix - - case "tweakP": - switch d.sType { - case "*image.Gray": - if strings.HasPrefix(strings.TrimSpace(prefix), "pa * ") { - return "1," - } - return "pr," - case "*image.YCbCr": - if strings.HasPrefix(strings.TrimSpace(prefix), "pa * ") { - return "1," - } - } - return prefix - - case "tweakPr": - if d.sType == "*image.Gray" { - return "pr *= s.invTotalWeightFFFF" - } - return ";" - - case "tweakVarP": - switch d.sType { - case "*image.Gray": - return strings.Replace(prefix, "var pr, pg, pb, pa", "var pr", 1) - case "*image.YCbCr": - return strings.Replace(prefix, "var pr, pg, pb, pa", "var pr, pg, pb", 1) - } - return prefix - } - return "" -} - -func expnSwitch(op, dType string, expandBoth bool, template string) string { - if op == "" && dType != "anyDType" { - lines := []string{"switch op {"} - for _, op = range ops { - lines = append(lines, - fmt.Sprintf("case %s:", op), - expnSwitch(op, dType, expandBoth, template), - ) - } - lines = append(lines, "}") - return strings.Join(lines, "\n") - } - - switchVar := "dst" - if dType != "" { - switchVar = "src" - } - lines := []string{fmt.Sprintf("switch %s := %s.(type) {", switchVar, switchVar)} - - fallback, values := "Image", dTypes - if dType != "" { - fallback, values = "image.Image", sTypesForDType[dType] - } - for _, v := range values { - if dType != "" { - // v is the sType. Skip those always-opaque sTypes, where Over is - // equivalent to Src. - if op == "Over" && alwaysOpaque[v] { - continue - } - } - - if v == fallback { - lines = append(lines, "default:") - } else { - lines = append(lines, fmt.Sprintf("case %s:", v)) - } - - if dType != "" { - if v == "*image.YCbCr" { - lines = append(lines, expnSwitchYCbCr(op, dType, template)) - } else { - lines = append(lines, expnLine(template, &data{dType: dType, sType: v, op: op})) - } - } else if !expandBoth { - lines = append(lines, expnLine(template, &data{dType: v, op: op})) - } else { - lines = append(lines, expnSwitch(op, v, false, template)) - } - } - - lines = append(lines, "}") - return strings.Join(lines, "\n") -} - -func expnSwitchYCbCr(op, dType, template string) string { - lines := []string{ - "switch src.SubsampleRatio {", - "default:", - expnLine(template, &data{dType: dType, sType: "image.Image", op: op}), - } - for _, sratio := range subsampleRatios { - lines = append(lines, - fmt.Sprintf("case image.YCbCrSubsampleRatio%s:", sratio), - expnLine(template, &data{dType: dType, sType: "*image.YCbCr", sratio: sratio, op: op}), - ) - } - lines = append(lines, "}") - return strings.Join(lines, "\n") -} - -func argf(args []string, s string) string { - if len(args) > 9 { - panic("too many args") - } - for i, a := range args { - old := fmt.Sprintf("$%d", i) - s = strings.Replace(s, old, a, -1) - } - return s -} - -func pixOffset(m, x, y, xstride, ystride string) string { - return fmt.Sprintf("(%s-%s.Rect.Min.Y)%s + (%s-%s.Rect.Min.X)%s", y, m, ystride, x, m, xstride) -} - -func cOffset(x, y, sratio string) string { - switch sratio { - case "444": - return fmt.Sprintf("( %s - src.Rect.Min.Y )*src.CStride + ( %s - src.Rect.Min.X )", y, x) - case "422": - return fmt.Sprintf("( %s - src.Rect.Min.Y )*src.CStride + ((%s)/2 - src.Rect.Min.X/2)", y, x) - case "420": - return fmt.Sprintf("((%s)/2 - src.Rect.Min.Y/2)*src.CStride + ((%s)/2 - src.Rect.Min.X/2)", y, x) - case "440": - return fmt.Sprintf("((%s)/2 - src.Rect.Min.Y/2)*src.CStride + ( %s - src.Rect.Min.X )", y, x) - } - return fmt.Sprintf("unsupported sratio %q", sratio) -} - -func ycbcrToRGB(lhs, tmp string) string { - s := ` - // This is an inline version of image/color/ycbcr.go's YCbCr.RGBA method. - $yy1 := int(src.Y[$i]) * 0x10101 - $cb1 := int(src.Cb[$j]) - 128 - $cr1 := int(src.Cr[$j]) - 128 - $r@ := ($yy1 + 91881*$cr1) >> 8 - $g@ := ($yy1 - 22554*$cb1 - 46802*$cr1) >> 8 - $b@ := ($yy1 + 116130*$cb1) >> 8 - if $r@ < 0 { - $r@ = 0 - } else if $r@ > 0xffff { - $r@ = 0xffff - } - if $g@ < 0 { - $g@ = 0 - } else if $g@ > 0xffff { - $g@ = 0xffff - } - if $b@ < 0 { - $b@ = 0 - } else if $b@ > 0xffff { - $b@ = 0xffff - } - ` - s = strings.Replace(s, "$", lhs, -1) - s = strings.Replace(s, "@", tmp, -1) - return s -} - -func split(s, sep string) (string, string) { - if i := strings.Index(s, sep); i >= 0 { - return strings.TrimSpace(s[:i]), strings.TrimSpace(s[i+len(sep):]) - } - return "", "" -} - -func splitEq(s string) (lhs, eqOp string) { - s = strings.TrimSpace(s) - if lhs, _ = split(s, ":="); lhs != "" { - return lhs, ":=" - } - if lhs, _ = split(s, "+="); lhs != "" { - return lhs, "+=" - } - return "", "" -} - -func splitArgs(s string) (args []string, extra string) { - s = strings.TrimSpace(s) - if s == "" || s[0] != '[' { - return nil, "" - } - s = s[1:] - - i := strings.IndexByte(s, ']') - if i < 0 { - return nil, "" - } - args, extra = strings.Split(s[:i], ","), s[i+1:] - for i := range args { - args[i] = strings.TrimSpace(args[i]) - } - return args, extra -} - -func relName(s string) string { - if i := strings.LastIndex(s, "."); i >= 0 { - return s[i+1:] - } - return s -} - -const ( - codeRoot = ` - func (z $receiver) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { - // Try to simplify a Scale to a Copy when DstMask is not specified. - // If DstMask is not nil, Copy will call Scale back with same dr and sr, and cause stack overflow. - if dr.Size() == sr.Size() && (opts == nil || opts.DstMask == nil) { - Copy(dst, dr.Min, src, sr, op, opts) - return - } - - var o Options - if opts != nil { - o = *opts - } - - // adr is the affected destination pixels. - adr := dst.Bounds().Intersect(dr) - adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) - if adr.Empty() || sr.Empty() { - return - } - // Make adr relative to dr.Min. - adr = adr.Sub(dr.Min) - if op == Over && o.SrcMask == nil && opaque(src) { - op = Src - } - - // sr is the source pixels. If it extends beyond the src bounds, - // we cannot use the type-specific fast paths, as they access - // the Pix fields directly without bounds checking. - // - // Similarly, the fast paths assume that the masks are nil. - if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { - switch op { - case Over: - z.scale_Image_Image_Over(dst, dr, adr, src, sr, &o) - case Src: - z.scale_Image_Image_Src(dst, dr, adr, src, sr, &o) - } - } else if _, ok := src.(*image.Uniform); ok { - Draw(dst, dr, src, src.Bounds().Min, op) - } else { - $switch z.scale_$dTypeRN_$sTypeRN$sratio_$op(dst, dr, adr, src, sr, &o) - } - } - - func (z $receiver) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) { - // Try to simplify a Transform to a Copy. - if s2d[0] == 1 && s2d[1] == 0 && s2d[3] == 0 && s2d[4] == 1 { - dx := int(s2d[2]) - dy := int(s2d[5]) - if float64(dx) == s2d[2] && float64(dy) == s2d[5] { - Copy(dst, image.Point{X: sr.Min.X + dx, Y: sr.Min.X + dy}, src, sr, op, opts) - return - } - } - - var o Options - if opts != nil { - o = *opts - } - - dr := transformRect(&s2d, &sr) - // adr is the affected destination pixels. - adr := dst.Bounds().Intersect(dr) - adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) - if adr.Empty() || sr.Empty() { - return - } - if op == Over && o.SrcMask == nil && opaque(src) { - op = Src - } - - d2s := invert(&s2d) - // bias is a translation of the mapping from dst coordinates to src - // coordinates such that the latter temporarily have non-negative X - // and Y coordinates. This allows us to write int(f) instead of - // int(math.Floor(f)), since "round to zero" and "round down" are - // equivalent when f >= 0, but the former is much cheaper. The X-- - // and Y-- are because the TransformLeaf methods have a "sx -= 0.5" - // adjustment. - bias := transformRect(&d2s, &adr).Min - bias.X-- - bias.Y-- - d2s[2] -= float64(bias.X) - d2s[5] -= float64(bias.Y) - // Make adr relative to dr.Min. - adr = adr.Sub(dr.Min) - // sr is the source pixels. If it extends beyond the src bounds, - // we cannot use the type-specific fast paths, as they access - // the Pix fields directly without bounds checking. - // - // Similarly, the fast paths assume that the masks are nil. - if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { - switch op { - case Over: - z.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, &o) - case Src: - z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, &o) - } - } else if u, ok := src.(*image.Uniform); ok { - transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, op) - } else { - $switch z.transform_$dTypeRN_$sTypeRN$sratio_$op(dst, dr, adr, &d2s, src, sr, bias, &o) - } - } - ` - - codeNNScaleLeaf = ` - func (nnInterpolator) scale_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, src $sType, sr image.Rectangle, opts *Options) { - dw2 := uint64(dr.Dx()) * 2 - dh2 := uint64(dr.Dy()) * 2 - sw := uint64(sr.Dx()) - sh := uint64(sr.Dy()) - $preOuter - for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { - sy := (2*uint64(dy) + 1) * sh / dh2 - $preInner - for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { $tweakDx - sx := (2*uint64(dx) + 1) * sw / dw2 - p := $srcu[sr.Min.X + int(sx), sr.Min.Y + int(sy)] - $outputu[dr.Min.X + int(dx), dr.Min.Y + int(dy), p] - } - } - } - ` - - codeNNTransformLeaf = ` - func (nnInterpolator) transform_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point, opts *Options) { - $preOuter - for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { - dyf := float64(dr.Min.Y + int(dy)) + 0.5 - $preInner - for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { $tweakDx - dxf := float64(dr.Min.X + int(dx)) + 0.5 - sx0 := int(d2s[0]*dxf + d2s[1]*dyf + d2s[2]) + bias.X - sy0 := int(d2s[3]*dxf + d2s[4]*dyf + d2s[5]) + bias.Y - if !(image.Point{sx0, sy0}).In(sr) { - continue - } - p := $srcu[sx0, sy0] - $outputu[dr.Min.X + int(dx), dr.Min.Y + int(dy), p] - } - } - } - ` - - codeABLScaleLeaf = ` - func (ablInterpolator) scale_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, src $sType, sr image.Rectangle, opts *Options) { - sw := int32(sr.Dx()) - sh := int32(sr.Dy()) - yscale := float64(sh) / float64(dr.Dy()) - xscale := float64(sw) / float64(dr.Dx()) - swMinus1, shMinus1 := sw - 1, sh - 1 - $preOuter - - for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { - sy := (float64(dy)+0.5)*yscale - 0.5 - // If sy < 0, we will clamp sy0 to 0 anyway, so it doesn't matter if - // we say int32(sy) instead of int32(math.Floor(sy)). Similarly for - // sx, below. - sy0 := int32(sy) - yFrac0 := sy - float64(sy0) - yFrac1 := 1 - yFrac0 - sy1 := sy0 + 1 - if sy < 0 { - sy0, sy1 = 0, 0 - yFrac0, yFrac1 = 0, 1 - } else if sy1 > shMinus1 { - sy0, sy1 = shMinus1, shMinus1 - yFrac0, yFrac1 = 1, 0 - } - $preInner - - for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { $tweakDx - sx := (float64(dx)+0.5)*xscale - 0.5 - sx0 := int32(sx) - xFrac0 := sx - float64(sx0) - xFrac1 := 1 - xFrac0 - sx1 := sx0 + 1 - if sx < 0 { - sx0, sx1 = 0, 0 - xFrac0, xFrac1 = 0, 1 - } else if sx1 > swMinus1 { - sx0, sx1 = swMinus1, swMinus1 - xFrac0, xFrac1 = 1, 0 - } - - s00 := $srcf[sr.Min.X + int(sx0), sr.Min.Y + int(sy0)] - s10 := $srcf[sr.Min.X + int(sx1), sr.Min.Y + int(sy0)] - $blend[xFrac1, s00, xFrac0, s10] - s01 := $srcf[sr.Min.X + int(sx0), sr.Min.Y + int(sy1)] - s11 := $srcf[sr.Min.X + int(sx1), sr.Min.Y + int(sy1)] - $blend[xFrac1, s01, xFrac0, s11] - $blend[yFrac1, s10, yFrac0, s11] - $convFtou[p, s11] - $outputu[dr.Min.X + int(dx), dr.Min.Y + int(dy), p] - } - } - } - ` - - codeABLTransformLeaf = ` - func (ablInterpolator) transform_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point, opts *Options) { - $preOuter - for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { - dyf := float64(dr.Min.Y + int(dy)) + 0.5 - $preInner - for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { $tweakDx - dxf := float64(dr.Min.X + int(dx)) + 0.5 - sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] - sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] - if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { - continue - } - - sx -= 0.5 - sx0 := int(sx) - xFrac0 := sx - float64(sx0) - xFrac1 := 1 - xFrac0 - sx0 += bias.X - sx1 := sx0 + 1 - if sx0 < sr.Min.X { - sx0, sx1 = sr.Min.X, sr.Min.X - xFrac0, xFrac1 = 0, 1 - } else if sx1 >= sr.Max.X { - sx0, sx1 = sr.Max.X-1, sr.Max.X-1 - xFrac0, xFrac1 = 1, 0 - } - - sy -= 0.5 - sy0 := int(sy) - yFrac0 := sy - float64(sy0) - yFrac1 := 1 - yFrac0 - sy0 += bias.Y - sy1 := sy0 + 1 - if sy0 < sr.Min.Y { - sy0, sy1 = sr.Min.Y, sr.Min.Y - yFrac0, yFrac1 = 0, 1 - } else if sy1 >= sr.Max.Y { - sy0, sy1 = sr.Max.Y-1, sr.Max.Y-1 - yFrac0, yFrac1 = 1, 0 - } - - s00 := $srcf[sx0, sy0] - s10 := $srcf[sx1, sy0] - $blend[xFrac1, s00, xFrac0, s10] - s01 := $srcf[sx0, sy1] - s11 := $srcf[sx1, sy1] - $blend[xFrac1, s01, xFrac0, s11] - $blend[yFrac1, s10, yFrac0, s11] - $convFtou[p, s11] - $outputu[dr.Min.X + int(dx), dr.Min.Y + int(dy), p] - } - } - } - ` - - codeKernelRoot = ` - func (z *kernelScaler) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { - if z.dw != int32(dr.Dx()) || z.dh != int32(dr.Dy()) || z.sw != int32(sr.Dx()) || z.sh != int32(sr.Dy()) { - z.kernel.Scale(dst, dr, src, sr, op, opts) - return - } - - var o Options - if opts != nil { - o = *opts - } - - // adr is the affected destination pixels. - adr := dst.Bounds().Intersect(dr) - adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) - if adr.Empty() || sr.Empty() { - return - } - // Make adr relative to dr.Min. - adr = adr.Sub(dr.Min) - if op == Over && o.SrcMask == nil && opaque(src) { - op = Src - } - - if _, ok := src.(*image.Uniform); ok && o.DstMask == nil && o.SrcMask == nil && sr.In(src.Bounds()) { - Draw(dst, dr, src, src.Bounds().Min, op) - return - } - - // Create a temporary buffer: - // scaleX distributes the source image's columns over the temporary image. - // scaleY distributes the temporary image's rows over the destination image. - var tmp [][4]float64 - if z.pool.New != nil { - tmpp := z.pool.Get().(*[][4]float64) - defer z.pool.Put(tmpp) - tmp = *tmpp - } else { - tmp = z.makeTmpBuf() - } - - // sr is the source pixels. If it extends beyond the src bounds, - // we cannot use the type-specific fast paths, as they access - // the Pix fields directly without bounds checking. - // - // Similarly, the fast paths assume that the masks are nil. - if o.SrcMask != nil || !sr.In(src.Bounds()) { - z.scaleX_Image(tmp, src, sr, &o) - } else { - $switchS z.scaleX_$sTypeRN$sratio(tmp, src, sr, &o) - } - - if o.DstMask != nil { - switch op { - case Over: - z.scaleY_Image_Over(dst, dr, adr, tmp, &o) - case Src: - z.scaleY_Image_Src(dst, dr, adr, tmp, &o) - } - } else { - $switchD z.scaleY_$dTypeRN_$op(dst, dr, adr, tmp, &o) - } - } - - func (q *Kernel) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) { - var o Options - if opts != nil { - o = *opts - } - - dr := transformRect(&s2d, &sr) - // adr is the affected destination pixels. - adr := dst.Bounds().Intersect(dr) - adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) - if adr.Empty() || sr.Empty() { - return - } - if op == Over && o.SrcMask == nil && opaque(src) { - op = Src - } - d2s := invert(&s2d) - // bias is a translation of the mapping from dst coordinates to src - // coordinates such that the latter temporarily have non-negative X - // and Y coordinates. This allows us to write int(f) instead of - // int(math.Floor(f)), since "round to zero" and "round down" are - // equivalent when f >= 0, but the former is much cheaper. The X-- - // and Y-- are because the TransformLeaf methods have a "sx -= 0.5" - // adjustment. - bias := transformRect(&d2s, &adr).Min - bias.X-- - bias.Y-- - d2s[2] -= float64(bias.X) - d2s[5] -= float64(bias.Y) - // Make adr relative to dr.Min. - adr = adr.Sub(dr.Min) - - if u, ok := src.(*image.Uniform); ok && o.DstMask != nil && o.SrcMask != nil && sr.In(src.Bounds()) { - transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, op) - return - } - - xscale := abs(d2s[0]) - if s := abs(d2s[1]); xscale < s { - xscale = s - } - yscale := abs(d2s[3]) - if s := abs(d2s[4]); yscale < s { - yscale = s - } - - // sr is the source pixels. If it extends beyond the src bounds, - // we cannot use the type-specific fast paths, as they access - // the Pix fields directly without bounds checking. - // - // Similarly, the fast paths assume that the masks are nil. - if o.DstMask != nil || o.SrcMask != nil || !sr.In(src.Bounds()) { - switch op { - case Over: - q.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) - case Src: - q.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) - } - } else { - $switch q.transform_$dTypeRN_$sTypeRN$sratio_$op(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale, &o) - } - } - ` - - codeKernelScaleLeafX = ` - func (z *kernelScaler) scaleX_$sTypeRN$sratio(tmp [][4]float64, src $sType, sr image.Rectangle, opts *Options) { - t := 0 - $preKernelOuter - for y := int32(0); y < z.sh; y++ { - for _, s := range z.horizontal.sources { - var pr, pg, pb, pa float64 $tweakVarP - for _, c := range z.horizontal.contribs[s.i:s.j] { - p += $srcf[sr.Min.X + int(c.coord), sr.Min.Y + int(y)] * c.weight - } - $tweakPr - tmp[t] = [4]float64{ - pr * s.invTotalWeightFFFF, $tweakP - pg * s.invTotalWeightFFFF, $tweakP - pb * s.invTotalWeightFFFF, $tweakP - pa * s.invTotalWeightFFFF, $tweakP - } - t++ - } - } - } - ` - - codeKernelScaleLeafY = ` - func (z *kernelScaler) scaleY_$dTypeRN_$op(dst $dType, dr, adr image.Rectangle, tmp [][4]float64, opts *Options) { - $preOuter - for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { - $preKernelInner - for dy, s := range z.vertical.sources[adr.Min.Y:adr.Max.Y] { $tweakDy - var pr, pg, pb, pa float64 - for _, c := range z.vertical.contribs[s.i:s.j] { - p := &tmp[c.coord*z.dw+dx] - pr += p[0] * c.weight - pg += p[1] * c.weight - pb += p[2] * c.weight - pa += p[3] * c.weight - } - $clampToAlpha - $outputf[dr.Min.X + int(dx), dr.Min.Y + int(adr.Min.Y + dy), ftou, p, s.invTotalWeight] - $tweakD - } - } - } - ` - - codeKernelTransformLeaf = ` - func (q *Kernel) transform_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point, xscale, yscale float64, opts *Options) { - // When shrinking, broaden the effective kernel support so that we still - // visit every source pixel. - xHalfWidth, xKernelArgScale := q.Support, 1.0 - if xscale > 1 { - xHalfWidth *= xscale - xKernelArgScale = 1 / xscale - } - yHalfWidth, yKernelArgScale := q.Support, 1.0 - if yscale > 1 { - yHalfWidth *= yscale - yKernelArgScale = 1 / yscale - } - - xWeights := make([]float64, 1 + 2*int(math.Ceil(xHalfWidth))) - yWeights := make([]float64, 1 + 2*int(math.Ceil(yHalfWidth))) - - $preOuter - for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ { - dyf := float64(dr.Min.Y + int(dy)) + 0.5 - $preInner - for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ { $tweakDx - dxf := float64(dr.Min.X + int(dx)) + 0.5 - sx := d2s[0]*dxf + d2s[1]*dyf + d2s[2] - sy := d2s[3]*dxf + d2s[4]*dyf + d2s[5] - if !(image.Point{int(sx) + bias.X, int(sy) + bias.Y}).In(sr) { - continue - } - - // TODO: adjust the bias so that we can use int(f) instead - // of math.Floor(f) and math.Ceil(f). - sx += float64(bias.X) - sx -= 0.5 - ix := int(math.Floor(sx - xHalfWidth)) - if ix < sr.Min.X { - ix = sr.Min.X - } - jx := int(math.Ceil(sx + xHalfWidth)) - if jx > sr.Max.X { - jx = sr.Max.X - } - - totalXWeight := 0.0 - for kx := ix; kx < jx; kx++ { - xWeight := 0.0 - if t := abs((sx - float64(kx)) * xKernelArgScale); t < q.Support { - xWeight = q.At(t) - } - xWeights[kx - ix] = xWeight - totalXWeight += xWeight - } - for x := range xWeights[:jx-ix] { - xWeights[x] /= totalXWeight - } - - sy += float64(bias.Y) - sy -= 0.5 - iy := int(math.Floor(sy - yHalfWidth)) - if iy < sr.Min.Y { - iy = sr.Min.Y - } - jy := int(math.Ceil(sy + yHalfWidth)) - if jy > sr.Max.Y { - jy = sr.Max.Y - } - - totalYWeight := 0.0 - for ky := iy; ky < jy; ky++ { - yWeight := 0.0 - if t := abs((sy - float64(ky)) * yKernelArgScale); t < q.Support { - yWeight = q.At(t) - } - yWeights[ky - iy] = yWeight - totalYWeight += yWeight - } - for y := range yWeights[:jy-iy] { - yWeights[y] /= totalYWeight - } - - var pr, pg, pb, pa float64 $tweakVarP - for ky := iy; ky < jy; ky++ { - if yWeight := yWeights[ky - iy]; yWeight != 0 { - for kx := ix; kx < jx; kx++ { - if w := xWeights[kx - ix] * yWeight; w != 0 { - p += $srcf[kx, ky] * w - } - } - } - } - $clampToAlpha - $outputf[dr.Min.X + int(dx), dr.Min.Y + int(dy), fffftou, p, 1] - } - } - } - ` -) diff --git a/vendor/golang.org/x/image/font/basicfont/gen.go b/vendor/golang.org/x/image/font/basicfont/gen.go deleted file mode 100644 index 67a21a7..0000000 --- a/vendor/golang.org/x/image/font/basicfont/gen.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build ignore - -// This program generates data.go. -package main - -import ( - "bytes" - "fmt" - "go/format" - "image" - "image/draw" - "io/ioutil" - "log" - "path" - "path/filepath" - - "golang.org/x/image/font" - "golang.org/x/image/font/plan9font" - "golang.org/x/image/math/fixed" -) - -func main() { - // nGlyphs is the number of glyphs to generate: 95 characters in the range - // [0x20, 0x7e], plus the replacement character. - const nGlyphs = 95 + 1 - // The particular font (unicode.7x13.font) leaves the right-most column - // empty in its ASCII glyphs. We don't have to include that column in the - // generated glyphs, so we subtract one off the effective width. - const width, height, ascent = 7 - 1, 13, 11 - - readFile := func(name string) ([]byte, error) { - return ioutil.ReadFile(filepath.FromSlash(path.Join("../testdata/fixed", name))) - } - fontData, err := readFile("unicode.7x13.font") - if err != nil { - log.Fatalf("readFile: %v", err) - } - face, err := plan9font.ParseFont(fontData, readFile) - if err != nil { - log.Fatalf("plan9font.ParseFont: %v", err) - } - - dst := image.NewRGBA(image.Rect(0, 0, width, nGlyphs*height)) - draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src) - d := &font.Drawer{ - Dst: dst, - Src: image.White, - Face: face, - } - for i := 0; i < nGlyphs; i++ { - r := '\ufffd' - if i < nGlyphs-1 { - r = 0x20 + rune(i) - } - d.Dot = fixed.P(0, height*i+ascent) - d.DrawString(string(r)) - } - - w := bytes.NewBuffer(nil) - w.WriteString(preamble) - fmt.Fprintf(w, "// mask7x13 contains %d %d×%d glyphs in %d Pix bytes.\n", nGlyphs, width, height, nGlyphs*width*height) - fmt.Fprintf(w, "var mask7x13 = &image.Alpha{\n") - fmt.Fprintf(w, " Stride: %d,\n", width) - fmt.Fprintf(w, " Rect: image.Rectangle{Max: image.Point{%d, %d*%d}},\n", width, nGlyphs, height) - fmt.Fprintf(w, " Pix: []byte{\n") - b := dst.Bounds() - for y := b.Min.Y; y < b.Max.Y; y++ { - if y%height == 0 { - if y != 0 { - w.WriteByte('\n') - } - i := y / height - if i < nGlyphs-1 { - i += 0x20 - fmt.Fprintf(w, "// %#2x %q\n", i, rune(i)) - } else { - fmt.Fprintf(w, "// U+FFFD REPLACEMENT CHARACTER\n") - } - } - - for x := b.Min.X; x < b.Max.X; x++ { - if dst.RGBAAt(x, y).R > 0 { - w.WriteString("0xff,") - } else { - w.WriteString("0x00,") - } - } - w.WriteByte('\n') - } - w.WriteString("},\n}\n") - - fmted, err := format.Source(w.Bytes()) - if err != nil { - log.Fatalf("format.Source: %v", err) - } - if err := ioutil.WriteFile("data.go", fmted, 0644); err != nil { - log.Fatalf("ioutil.WriteFile: %v", err) - } -} - -const preamble = `// generated by go generate; DO NOT EDIT. - -package basicfont - -// This data is derived from files in the font/fixed directory of the Plan 9 -// Port source code (https://github.com/9fans/plan9port) which were originally -// based on the public domain X11 misc-fixed font files. - -import "image" - -` diff --git a/vendor/modules.txt b/vendor/modules.txt index 8925aeb..215ac27 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/caarlos0/env v3.3.0+incompatible +# github.com/caarlos0/env v3.5.0+incompatible github.com/caarlos0/env # github.com/cjsaylor/chessimage v0.0.0-20190107020940-8abad33612f4 github.com/cjsaylor/chessimage @@ -13,8 +13,10 @@ github.com/golang/freetype/truetype github.com/gorilla/websocket # github.com/mattn/go-sqlite3 v1.9.0 github.com/mattn/go-sqlite3 -# github.com/nlopes/slack v0.5.0 +# github.com/nlopes/slack v0.6.0 github.com/nlopes/slack +github.com/nlopes/slack/internal/errorsx +github.com/nlopes/slack/internal/timex github.com/nlopes/slack/slackevents github.com/nlopes/slack/slackutilsx # github.com/notnil/chess v0.0.0-20181214160432-429595102215