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