summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/slack-go
diff options
context:
space:
mode:
authorWim <wim@42.be>2020-03-01 20:59:19 +0100
committerGitHub <noreply@github.com>2020-03-01 20:59:19 +0100
commit250b3bb5795240d5ebdab5416ab99dbc41be734b (patch)
tree11e44ec2e7e37cd7eb0deaf0a7843fe6bf3c0e40 /vendor/github.com/slack-go
parente9edbfc051afc643d91fc04bc7fb3fe70039c213 (diff)
downloadmatterbridge-msglm-250b3bb5795240d5ebdab5416ab99dbc41be734b.tar.gz
matterbridge-msglm-250b3bb5795240d5ebdab5416ab99dbc41be734b.tar.bz2
matterbridge-msglm-250b3bb5795240d5ebdab5416ab99dbc41be734b.zip
Use upstream slack-go/slack again (#1018)
Diffstat (limited to 'vendor/github.com/slack-go')
-rw-r--r--vendor/github.com/slack-go/slack/.gitignore3
-rw-r--r--vendor/github.com/slack-go/slack/.gometalinter.json14
-rw-r--r--vendor/github.com/slack-go/slack/.travis.yml39
-rw-r--r--vendor/github.com/slack-go/slack/CHANGELOG.md59
-rw-r--r--vendor/github.com/slack-go/slack/LICENSE23
-rw-r--r--vendor/github.com/slack-go/slack/Makefile36
-rw-r--r--vendor/github.com/slack-go/slack/README.md96
-rw-r--r--vendor/github.com/slack-go/slack/TODO.txt3
-rw-r--r--vendor/github.com/slack-go/slack/admin.go207
-rw-r--r--vendor/github.com/slack-go/slack/attachments.go93
-rw-r--r--vendor/github.com/slack-go/slack/auth.go40
-rw-r--r--vendor/github.com/slack-go/slack/backoff.go57
-rw-r--r--vendor/github.com/slack-go/slack/block.go73
-rw-r--r--vendor/github.com/slack-go/slack/block_action.go26
-rw-r--r--vendor/github.com/slack-go/slack/block_context.go32
-rw-r--r--vendor/github.com/slack-go/slack/block_conv.go353
-rw-r--r--vendor/github.com/slack-go/slack/block_divider.go22
-rw-r--r--vendor/github.com/slack-go/slack/block_element.go267
-rw-r--r--vendor/github.com/slack-go/slack/block_image.go28
-rw-r--r--vendor/github.com/slack-go/slack/block_input.go30
-rw-r--r--vendor/github.com/slack-go/slack/block_object.go216
-rw-r--r--vendor/github.com/slack-go/slack/block_section.go42
-rw-r--r--vendor/github.com/slack-go/slack/bots.go58
-rw-r--r--vendor/github.com/slack-go/slack/channels.go412
-rw-r--r--vendor/github.com/slack-go/slack/chat.go627
-rw-r--r--vendor/github.com/slack-go/slack/comment.go10
-rw-r--r--vendor/github.com/slack-go/slack/conversation.go620
-rw-r--r--vendor/github.com/slack-go/slack/dialog.go118
-rw-r--r--vendor/github.com/slack-go/slack/dialog_select.go101
-rw-r--r--vendor/github.com/slack-go/slack/dialog_text.go59
-rw-r--r--vendor/github.com/slack-go/slack/dnd.go151
-rw-r--r--vendor/github.com/slack-go/slack/emoji.go35
-rw-r--r--vendor/github.com/slack-go/slack/errors.go20
-rw-r--r--vendor/github.com/slack-go/slack/files.go404
-rw-r--r--vendor/github.com/slack-go/slack/go.mod12
-rw-r--r--vendor/github.com/slack-go/slack/go.sum12
-rw-r--r--vendor/github.com/slack-go/slack/groups.go355
-rw-r--r--vendor/github.com/slack-go/slack/history.go36
-rw-r--r--vendor/github.com/slack-go/slack/im.go154
-rw-r--r--vendor/github.com/slack-go/slack/info.go195
-rw-r--r--vendor/github.com/slack-go/slack/interactions.go142
-rw-r--r--vendor/github.com/slack-go/slack/internal/errorsx/errorsx.go8
-rw-r--r--vendor/github.com/slack-go/slack/internal/timex/timex.go18
-rw-r--r--vendor/github.com/slack-go/slack/item.go75
-rw-r--r--vendor/github.com/slack-go/slack/logger.go60
-rw-r--r--vendor/github.com/slack-go/slack/messageID.go30
-rw-r--r--vendor/github.com/slack-go/slack/messages.go199
-rw-r--r--vendor/github.com/slack-go/slack/misc.go360
-rw-r--r--vendor/github.com/slack-go/slack/oauth.go64
-rw-r--r--vendor/github.com/slack-go/slack/pagination.go20
-rw-r--r--vendor/github.com/slack-go/slack/pins.go94
-rw-r--r--vendor/github.com/slack-go/slack/reactions.go270
-rw-r--r--vendor/github.com/slack-go/slack/reminders.go75
-rw-r--r--vendor/github.com/slack-go/slack/rtm.go131
-rw-r--r--vendor/github.com/slack-go/slack/search.go156
-rw-r--r--vendor/github.com/slack-go/slack/security.go100
-rw-r--r--vendor/github.com/slack-go/slack/slack.go153
-rw-r--r--vendor/github.com/slack-go/slack/slackutilsx/slackutilsx.go62
-rw-r--r--vendor/github.com/slack-go/slack/slash.go53
-rw-r--r--vendor/github.com/slack-go/slack/stars.go263
-rw-r--r--vendor/github.com/slack-go/slack/team.go167
-rw-r--r--vendor/github.com/slack-go/slack/usergroups.go258
-rw-r--r--vendor/github.com/slack-go/slack/users.go597
-rw-r--r--vendor/github.com/slack-go/slack/views.go221
-rw-r--r--vendor/github.com/slack-go/slack/webhooks.go29
-rw-r--r--vendor/github.com/slack-go/slack/webhooks_go112.go34
-rw-r--r--vendor/github.com/slack-go/slack/webhooks_go113.go33
-rw-r--r--vendor/github.com/slack-go/slack/websocket.go103
-rw-r--r--vendor/github.com/slack-go/slack/websocket_channels.go72
-rw-r--r--vendor/github.com/slack-go/slack/websocket_desktop_notification.go19
-rw-r--r--vendor/github.com/slack-go/slack/websocket_dm.go23
-rw-r--r--vendor/github.com/slack-go/slack/websocket_dnd.go8
-rw-r--r--vendor/github.com/slack-go/slack/websocket_files.go49
-rw-r--r--vendor/github.com/slack-go/slack/websocket_groups.go49
-rw-r--r--vendor/github.com/slack-go/slack/websocket_internals.go101
-rw-r--r--vendor/github.com/slack-go/slack/websocket_managed_conn.go581
-rw-r--r--vendor/github.com/slack-go/slack/websocket_misc.go141
-rw-r--r--vendor/github.com/slack-go/slack/websocket_pins.go16
-rw-r--r--vendor/github.com/slack-go/slack/websocket_reactions.go25
-rw-r--r--vendor/github.com/slack-go/slack/websocket_stars.go14
-rw-r--r--vendor/github.com/slack-go/slack/websocket_subteam.go35
-rw-r--r--vendor/github.com/slack-go/slack/websocket_teams.go33
82 files changed, 10079 insertions, 0 deletions
diff --git a/vendor/github.com/slack-go/slack/.gitignore b/vendor/github.com/slack-go/slack/.gitignore
new file mode 100644
index 00000000..ac6f3eeb
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/.gitignore
@@ -0,0 +1,3 @@
+*.test
+*~
+.idea/
diff --git a/vendor/github.com/slack-go/slack/.gometalinter.json b/vendor/github.com/slack-go/slack/.gometalinter.json
new file mode 100644
index 00000000..5fa629d4
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/.gometalinter.json
@@ -0,0 +1,14 @@
+{
+ "DisableAll": true,
+ "Enable": [
+ "structcheck",
+ "vet",
+ "misspell",
+ "unconvert",
+ "interfacer",
+ "goimports"
+ ],
+ "Vendor": true,
+ "Exclude": ["vendor"],
+ "Deadline": "300s"
+}
diff --git a/vendor/github.com/slack-go/slack/.travis.yml b/vendor/github.com/slack-go/slack/.travis.yml
new file mode 100644
index 00000000..6a968232
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/.travis.yml
@@ -0,0 +1,39 @@
+language: go
+
+env:
+ - GO111MODULE=on
+
+install: true
+
+before_install:
+ - export PATH=$HOME/gopath/bin:$PATH
+ # install gometalinter
+ - curl -L https://git.io/vp6lP | sh
+
+script:
+ - PATH=$PWD/bin:$PATH gometalinter ./...
+ - go test -race -cover ./...
+
+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: "1.12.x"
+ script: go test -v -mod=vendor ./...
+ - go: "1.13.x"
+ script: go test -v -mod=vendor ./...
+ - go: "tip"
+ script: go test -v -mod=vendor ./...
+
+git:
+ depth: 10
diff --git a/vendor/github.com/slack-go/slack/CHANGELOG.md b/vendor/github.com/slack-go/slack/CHANGELOG.md
new file mode 100644
index 00000000..48bcce55
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/CHANGELOG.md
@@ -0,0 +1,59 @@
+### 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.
+- deadlock fix in RTM disconnect.
+
+### v0.4.0 - October 06, 2018
+full differences can be viewed using `git log --oneline --decorate --color v0.3.0..v0.4.0`
+- Breaking Change: renamed ApplyMessageOption, to mark it as unsafe,
+this means it may break without warning in the future.
+- Breaking: Msg structure files field changed to an array.
+- General: implementation for new security headers.
+- RTM: deadlock fix between connect/disconnect.
+- Events: various new fields added.
+- Web: various fixes, new fields exposed, new methods added.
+- Interactions: minor additions expect breaking changes in next release for dialogs/button clicks.
+- Utils: new methods added.
+
+### v0.3.0 - July 30, 2018
+full differences can be viewed using `git log --oneline --decorate --color v0.2.0..v0.3.0`
+- slack events initial support added. (still considered experimental and undergoing changes, stability not promised)
+- vendored depedencies using dep, ensure using up to date tooling before filing issues.
+- RTM has improved its ability to identify dead connections and reconnect automatically (worth calling out in case it has unintended side effects).
+- bug fixes (various timestamp handling, error handling, RTM locking, etc).
+
+### v0.2.0 - Feb 10, 2018
+
+Release adds a bunch of functionality and improvements, mainly to give people a recent version to vendor against.
+
+Please check [0.2.0](https://github.com/nlopes/slack/releases/tag/v0.2.0)
+
+### v0.1.0 - May 28, 2017
+
+This is released before adding context support.
+As the used context package is the one from Go 1.7 this will be the last
+compatible with Go < 1.7.
+
+Please check [0.1.0](https://github.com/nlopes/slack/releases/tag/v0.1.0)
+
+### v0.0.1 - Jul 26, 2015
+
+If you just updated from master and it broke your implementation, please
+check [0.0.1](https://github.com/nlopes/slack/releases/tag/v0.0.1)
diff --git a/vendor/github.com/slack-go/slack/LICENSE b/vendor/github.com/slack-go/slack/LICENSE
new file mode 100644
index 00000000..5145171f
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2015, Norberto Lopes
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/slack-go/slack/Makefile b/vendor/github.com/slack-go/slack/Makefile
new file mode 100644
index 00000000..1c64747d
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/Makefile
@@ -0,0 +1,36 @@
+.PHONY: help deps fmt lint test test-race test-integration
+
+help:
+ @echo ""
+ @echo "Welcome to slack-go/slack make."
+ @echo "The following commands are available:"
+ @echo ""
+ @echo " make deps : Fetch all dependencies"
+ @echo " make fmt : Run go fmt to fix any formatting issues"
+ @echo " make lint : Use go vet to check for linting issues"
+ @echo " make test : Run all short tests"
+ @echo " make test-race : Run all tests with race condition checking"
+ @echo " make test-integration : Run all tests without limiting to short"
+ @echo ""
+ @echo " make pr-prep : Run this before making a PR to run fmt, lint and tests"
+ @echo ""
+
+deps:
+ @go mod tidy
+
+fmt:
+ @go fmt .
+
+lint:
+ @go vet .
+
+test:
+ @go test -count=1 -timeout 300s -short .
+
+test-race:
+ @go test -count=1 -timeout 300s -short -race .
+
+test-integration:
+ @go test -count=1 -timeout 600s .
+
+pr-prep: fmt lint test-race test-integration
diff --git a/vendor/github.com/slack-go/slack/README.md b/vendor/github.com/slack-go/slack/README.md
new file mode 100644
index 00000000..eaf07782
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/README.md
@@ -0,0 +1,96 @@
+Slack API in Go [![GoDoc](https://godoc.org/github.com/slack-go/slack?status.svg)](https://godoc.org/github.com/slack-go/slack) [![Build Status](https://travis-ci.org/slack-go/slack.svg)](https://travis-ci.org/slack-go/slack)
+===============
+This is the original Slack library for Go created by Norberto Lopez, transferred to a Github organization.
+
+[![Join the chat at https://gitter.im/go-slack/Lobby](https://badges.gitter.im/go-slack/Lobby.svg)](https://gitter.im/go-slack/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+This library supports most if not all of the `api.slack.com` REST
+calls, as well as the Real-Time Messaging protocol over websocket, in
+a fully managed way.
+
+
+
+
+## Changelog
+
+[CHANGELOG.md](https://github.com/slack-go/slack/blob/master/CHANGELOG.md) is available. Please visit it for updates.
+
+## Installing
+
+### *go get*
+
+ $ go get -u github.com/slack-go/slack
+
+## Example
+
+### Getting all groups
+
+```golang
+import (
+ "fmt"
+
+ "github.com/slack-go/slack"
+)
+
+func main() {
+ api := slack.New("YOUR_TOKEN_HERE")
+ // If you set debugging, it will log all requests to the console
+ // Useful when encountering issues
+ // slack.New("YOUR_TOKEN_HERE", slack.OptionDebug(true))
+ groups, err := api.GetGroups(false)
+ if err != nil {
+ fmt.Printf("%s\n", err)
+ return
+ }
+ for _, group := range groups {
+ fmt.Printf("ID: %s, Name: %s\n", group.ID, group.Name)
+ }
+}
+```
+
+### Getting User Information
+
+```golang
+import (
+ "fmt"
+
+ "github.com/slack-go/slack"
+)
+
+func main() {
+ api := slack.New("YOUR_TOKEN_HERE")
+ user, err := api.GetUserInfo("U023BECGF")
+ if err != nil {
+ fmt.Printf("%s\n", err)
+ return
+ }
+ fmt.Printf("ID: %s, Fullname: %s, Email: %s\n", user.ID, user.Profile.RealName, user.Profile.Email)
+}
+```
+
+## Minimal RTM usage:
+
+See https://github.com/slack-go/slack/blob/master/examples/websocket/websocket.go
+
+
+## Minimal EventsAPI usage:
+
+See https://github.com/slack-go/slack/blob/master/examples/eventsapi/events.go
+
+
+## Contributing
+
+You are more than welcome to contribute to this project. Fork and
+make a Pull Request, or create an Issue if you see any problem.
+
+Before making any Pull Request please run the following:
+
+```
+make pr-prep
+```
+
+This will check/update code formatting, linting and then run all tests
+
+## License
+
+BSD 2 Clause license
diff --git a/vendor/github.com/slack-go/slack/TODO.txt b/vendor/github.com/slack-go/slack/TODO.txt
new file mode 100644
index 00000000..8607960b
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/TODO.txt
@@ -0,0 +1,3 @@
+- Add more tests!!!
+- Add support to have markdown hints
+ - See section Message Formatting at https://api.slack.com/docs/formatting
diff --git a/vendor/github.com/slack-go/slack/admin.go b/vendor/github.com/slack-go/slack/admin.go
new file mode 100644
index 00000000..d51426b5
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/admin.go
@@ -0,0 +1,207 @@
+package slack
+
+import (
+ "context"
+ "fmt"
+ "net/url"
+ "strings"
+)
+
+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 err
+ }
+
+ return resp.Err()
+}
+
+// DisableUser disabled a user account, given a user ID
+func (api *Client) DisableUser(teamName string, uid string) error {
+ return api.DisableUserContext(context.Background(), teamName, uid)
+}
+
+// DisableUserContext disabled a user account, given a user ID with a custom context
+func (api *Client) DisableUserContext(ctx context.Context, teamName string, uid string) error {
+ values := url.Values{
+ "user": {uid},
+ "token": {api.token},
+ "set_active": {"true"},
+ "_attempts": {"1"},
+ }
+
+ 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
+}
+
+// InviteGuest invites a user to Slack as a single-channel guest
+func (api *Client) InviteGuest(teamName, channel, firstName, lastName, emailAddress string) error {
+ return api.InviteGuestContext(context.Background(), teamName, channel, firstName, lastName, emailAddress)
+}
+
+// InviteGuestContext invites a user to Slack as a single-channel guest with a custom context
+func (api *Client) InviteGuestContext(ctx context.Context, teamName, channel, firstName, lastName, emailAddress string) error {
+ values := url.Values{
+ "email": {emailAddress},
+ "channels": {channel},
+ "first_name": {firstName},
+ "last_name": {lastName},
+ "ultra_restricted": {"1"},
+ "token": {api.token},
+ "resend": {"true"},
+ "set_active": {"true"},
+ "_attempts": {"1"},
+ }
+
+ err := api.adminRequest(ctx, "invite", teamName, values)
+ if err != nil {
+ return fmt.Errorf("Failed to invite single-channel guest: %s", err)
+ }
+
+ return nil
+}
+
+// InviteRestricted invites a user to Slack as a restricted account
+func (api *Client) InviteRestricted(teamName, channel, firstName, lastName, emailAddress string) error {
+ return api.InviteRestrictedContext(context.Background(), teamName, channel, firstName, lastName, emailAddress)
+}
+
+// InviteRestrictedContext invites a user to Slack as a restricted account with a custom context
+func (api *Client) InviteRestrictedContext(ctx context.Context, teamName, channel, firstName, lastName, emailAddress string) error {
+ values := url.Values{
+ "email": {emailAddress},
+ "channels": {channel},
+ "first_name": {firstName},
+ "last_name": {lastName},
+ "restricted": {"1"},
+ "token": {api.token},
+ "resend": {"true"},
+ "set_active": {"true"},
+ "_attempts": {"1"},
+ }
+
+ err := api.adminRequest(ctx, "invite", teamName, values)
+ if err != nil {
+ return fmt.Errorf("Failed to restricted account: %s", err)
+ }
+
+ return nil
+}
+
+// InviteToTeam invites a user to a Slack team
+func (api *Client) InviteToTeam(teamName, firstName, lastName, emailAddress string) error {
+ return api.InviteToTeamContext(context.Background(), teamName, firstName, lastName, emailAddress)
+}
+
+// InviteToTeamContext invites a user to a Slack team with a custom context
+func (api *Client) InviteToTeamContext(ctx context.Context, teamName, firstName, lastName, emailAddress string) error {
+ values := url.Values{
+ "email": {emailAddress},
+ "first_name": {firstName},
+ "last_name": {lastName},
+ "token": {api.token},
+ "set_active": {"true"},
+ "_attempts": {"1"},
+ }
+
+ err := api.adminRequest(ctx, "invite", teamName, values)
+ if err != nil {
+ return fmt.Errorf("Failed to invite to team: %s", err)
+ }
+
+ return nil
+}
+
+// SetRegular enables the specified user
+func (api *Client) SetRegular(teamName, user string) error {
+ return api.SetRegularContext(context.Background(), teamName, user)
+}
+
+// SetRegularContext enables the specified user with a custom context
+func (api *Client) SetRegularContext(ctx context.Context, teamName, user string) error {
+ values := url.Values{
+ "user": {user},
+ "token": {api.token},
+ "set_active": {"true"},
+ "_attempts": {"1"},
+ }
+
+ 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)
+ }
+
+ return nil
+}
+
+// SendSSOBindingEmail sends an SSO binding email to the specified user
+func (api *Client) SendSSOBindingEmail(teamName, user string) error {
+ return api.SendSSOBindingEmailContext(context.Background(), teamName, user)
+}
+
+// SendSSOBindingEmailContext sends an SSO binding email to the specified user with a custom context
+func (api *Client) SendSSOBindingEmailContext(ctx context.Context, teamName, user string) error {
+ values := url.Values{
+ "user": {user},
+ "token": {api.token},
+ "set_active": {"true"},
+ "_attempts": {"1"},
+ }
+
+ 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)
+ }
+
+ return nil
+}
+
+// SetUltraRestricted converts a user into a single-channel guest
+func (api *Client) SetUltraRestricted(teamName, uid, channel string) error {
+ return api.SetUltraRestrictedContext(context.Background(), teamName, uid, channel)
+}
+
+// SetUltraRestrictedContext converts a user into a single-channel guest with a custom context
+func (api *Client) SetUltraRestrictedContext(ctx context.Context, teamName, uid, channel string) error {
+ values := url.Values{
+ "user": {uid},
+ "channel": {channel},
+ "token": {api.token},
+ "set_active": {"true"},
+ "_attempts": {"1"},
+ }
+
+ err := api.adminRequest(ctx, "setUltraRestricted", teamName, values)
+ if err != nil {
+ return fmt.Errorf("Failed to ultra-restrict account: %s", err)
+ }
+
+ return nil
+}
+
+// SetRestricted converts a user into a restricted account
+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, channelIds ...string) error {
+ values := url.Values{
+ "user": {uid},
+ "token": {api.token},
+ "set_active": {"true"},
+ "_attempts": {"1"},
+ "channels": {strings.Join(channelIds, ",")},
+ }
+
+ err := api.adminRequest(ctx, "setRestricted", teamName, values)
+ if err != nil {
+ return fmt.Errorf("failed to restrict account: %s", err)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/slack-go/slack/attachments.go b/vendor/github.com/slack-go/slack/attachments.go
new file mode 100644
index 00000000..b5b79f9f
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/attachments.go
@@ -0,0 +1,93 @@
+package slack
+
+import "encoding/json"
+
+// AttachmentField contains information for an attachment field
+// An Attachment can contain multiple of these
+type AttachmentField struct {
+ Title string `json:"title"`
+ Value string `json:"value"`
+ Short bool `json:"short"`
+}
+
+// AttachmentAction is a button or menu to be included in the attachment. Required when
+// using message buttons or menus and otherwise not useful. A maximum of 5 actions may be
+// provided per attachment.
+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 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.
+ Options []AttachmentActionOption `json:"options,omitempty"` // Optional. Maximum of 100 options can be provided in each menu.
+ SelectedOptions []AttachmentActionOption `json:"selected_options,omitempty"` // Optional. The first element of this array will be set as the pre-selected option for this menu.
+ OptionGroups []AttachmentActionOptionGroup `json:"option_groups,omitempty"` // Optional.
+ Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
+ 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.
+ Value string `json:"value"` // Required.
+ Description string `json:"description,omitempty"` // Optional. Up to 30 characters.
+}
+
+// AttachmentActionOptionGroup is a semi-hierarchal way to list available options to appear in action menu.
+type AttachmentActionOptionGroup struct {
+ Text string `json:"text"` // Required.
+ Options []AttachmentActionOption `json:"options"` // Required.
+}
+
+// AttachmentActionCallback is sent from Slack when a user clicks a button in an interactive message (aka AttachmentAction)
+// DEPRECATED: use InteractionCallback
+type AttachmentActionCallback InteractionCallback
+
+// ConfirmationField are used to ask users to confirm actions
+type ConfirmationField struct {
+ Title string `json:"title,omitempty"` // Optional.
+ Text string `json:"text"` // Required.
+ OkText string `json:"ok_text,omitempty"` // Optional. Defaults to "Okay"
+ DismissText string `json:"dismiss_text,omitempty"` // Optional. Defaults to "Cancel"
+}
+
+// Attachment contains all the information for an attachment
+type Attachment struct {
+ Color string `json:"color,omitempty"`
+ Fallback string `json:"fallback,omitempty"`
+
+ CallbackID string `json:"callback_id,omitempty"`
+ ID int `json:"id,omitempty"`
+
+ AuthorID string `json:"author_id,omitempty"`
+ AuthorName string `json:"author_name,omitempty"`
+ AuthorSubname string `json:"author_subname,omitempty"`
+ AuthorLink string `json:"author_link,omitempty"`
+ AuthorIcon string `json:"author_icon,omitempty"`
+
+ Title string `json:"title,omitempty"`
+ TitleLink string `json:"title_link,omitempty"`
+ Pretext string `json:"pretext,omitempty"`
+ Text string `json:"text,omitempty"`
+
+ ImageURL string `json:"image_url,omitempty"`
+ ThumbURL string `json:"thumb_url,omitempty"`
+
+ Fields []AttachmentField `json:"fields,omitempty"`
+ Actions []AttachmentAction `json:"actions,omitempty"`
+ MarkdownIn []string `json:"mrkdwn_in,omitempty"`
+
+ Blocks Blocks `json:"blocks,omitempty"`
+
+ Footer string `json:"footer,omitempty"`
+ FooterIcon string `json:"footer_icon,omitempty"`
+
+ Ts json.Number `json:"ts,omitempty"`
+}
diff --git a/vendor/github.com/slack-go/slack/auth.go b/vendor/github.com/slack-go/slack/auth.go
new file mode 100644
index 00000000..dc1dbcdf
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/auth.go
@@ -0,0 +1,40 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+)
+
+// AuthRevokeResponse contains our Auth response from the auth.revoke endpoint
+type AuthRevokeResponse struct {
+ SlackResponse // Contains the "ok", and "Error", if any
+ Revoked bool `json:"revoked,omitempty"`
+}
+
+// authRequest sends the actual request, and unmarshals the response
+func (api *Client) authRequest(ctx context.Context, path string, values url.Values) (*AuthRevokeResponse, error) {
+ response := &AuthRevokeResponse{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, response.Err()
+}
+
+// SendAuthRevoke will send a revocation for our token
+func (api *Client) SendAuthRevoke(token string) (*AuthRevokeResponse, error) {
+ return api.SendAuthRevokeContext(context.Background(), token)
+}
+
+// SendAuthRevokeContext will retrieve the satus from api.test
+func (api *Client) SendAuthRevokeContext(ctx context.Context, token string) (*AuthRevokeResponse, error) {
+ if token == "" {
+ token = api.token
+ }
+ values := url.Values{
+ "token": {token},
+ }
+
+ return api.authRequest(ctx, "auth.revoke", values)
+}
diff --git a/vendor/github.com/slack-go/slack/backoff.go b/vendor/github.com/slack-go/slack/backoff.go
new file mode 100644
index 00000000..2ba697e7
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/backoff.go
@@ -0,0 +1,57 @@
+package slack
+
+import (
+ "math/rand"
+ "time"
+)
+
+// This one was ripped from https://github.com/jpillora/backoff/blob/master/backoff.go
+
+// Backoff is a time.Duration counter. It starts at Min. After every
+// call to Duration() it is multiplied by Factor. It is capped at
+// Max. It returns to Min on every call to Reset(). Used in
+// conjunction with the time package.
+type backoff struct {
+ attempts int
+ // 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() (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.Initial == 0 {
+ b.Initial = 100 * time.Millisecond
+ }
+
+ // calculate this duration
+ if dur = time.Duration(1 << uint(b.attempts)); dur > 0 {
+ dur = dur * b.Initial
+ } else {
+ dur = b.Max
+ }
+
+ if b.Jitter > 0 {
+ dur = dur + time.Duration(rand.Intn(int(b.Jitter)))
+ }
+
+ // bump attempts count
+ b.attempts++
+
+ return dur
+}
+
+//Resets the current value of the counter back to Min
+func (b *backoff) Reset() {
+ b.attempts = 0
+}
diff --git a/vendor/github.com/slack-go/slack/block.go b/vendor/github.com/slack-go/slack/block.go
new file mode 100644
index 00000000..32ff260c
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/block.go
@@ -0,0 +1,73 @@
+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"
+ MBTInput MessageBlockType = "input"
+)
+
+// 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"`
+ SelectedOptions []OptionBlockObject `json:"selected_options"`
+ 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/slack-go/slack/block_action.go b/vendor/github.com/slack-go/slack/block_action.go
new file mode 100644
index 00000000..fe46a95c
--- /dev/null
+++ b/vendor/github.com/slack-go/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/slack-go/slack/block_context.go b/vendor/github.com/slack-go/slack/block_context.go
new file mode 100644
index 00000000..c37bf27e
--- /dev/null
+++ b/vendor/github.com/slack-go/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/slack-go/slack/block_conv.go b/vendor/github.com/slack-go/slack/block_conv.go
new file mode 100644
index 00000000..ce48ce19
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/block_conv.go
@@ -0,0 +1,353 @@
+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 "input":
+ block = &InputBlock{}
+ case "section":
+ block = &SectionBlock{}
+ case "rich_text":
+ // for now ignore the (complex) content of rich_text blocks until we can fully support it
+ continue
+ case "file":
+ // for now ignore the file blocks until we can fully support it
+ continue
+ 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
+}
+
+// UnmarshalJSON implements the Unmarshaller interface for InputBlock, so that any JSON
+// unmarshalling is delegated and proper type determination can be made before unmarshal
+func (b *InputBlock) UnmarshalJSON(data []byte) error {
+ type alias InputBlock
+ a := struct {
+ Element json.RawMessage `json:"element"`
+ *alias
+ }{
+ alias: (*alias)(b),
+ }
+
+ if err := json.Unmarshal(data, &a); err != nil {
+ return err
+ }
+
+ s := sumtype{}
+ if err := json.Unmarshal(a.Element, &s); err != nil {
+ return nil
+ }
+
+ var e BlockElement
+ switch s.TypeVal {
+ case "datepicker":
+ e = &DatePickerBlockElement{}
+ case "plain_text_input":
+ e = &PlainTextInputBlockElement{}
+ case "static_select", "external_select", "users_select", "conversations_select", "channels_select":
+ e = &SelectBlockElement{}
+ default:
+ return errors.New("unsupported block element type")
+ }
+
+ if err := json.Unmarshal(a.Element, e); err != nil {
+ return err
+ }
+ b.Element = e
+
+ 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 "plain_text_input":
+ blockElement = &PlainTextInputBlockElement{}
+ 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/slack-go/slack/block_divider.go b/vendor/github.com/slack-go/slack/block_divider.go
new file mode 100644
index 00000000..2d442ba1
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/block_divider.go
@@ -0,0 +1,22 @@
+package slack
+
+// DividerBlock for displaying a divider line between blocks (similar to <hr> 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/slack-go/slack/block_element.go b/vendor/github.com/slack-go/slack/block_element.go
new file mode 100644
index 00000000..e0a7bf96
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/block_element.go
@@ -0,0 +1,267 @@
+package slack
+
+// https://api.slack.com/reference/messaging/block-elements
+
+const (
+ METImage MessageElementType = "image"
+ METButton MessageElementType = "button"
+ METOverflow MessageElementType = "overflow"
+ METDatepicker MessageElementType = "datepicker"
+ METPlainTextInput MessageElementType = "plain_text_input"
+
+ 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,
+ }
+}
+
+// PlainTextInputBlockElement creates a field where a user can enter freeform data.
+// Plain-text input elements are currently only available in modals.
+//
+// More Information: https://api.slack.com/reference/messaging/block-elements#input
+type PlainTextInputBlockElement struct {
+ Type MessageElementType `json:"type"`
+ ActionID string `json:"action_id"`
+ Placeholder *TextBlockObject `json:"placeholder,omitempty"`
+ InitialValue string `json:"initial_value,omitempty"`
+ Multiline bool `json:"multiline,omitempty"`
+ MinLength int `json:"min_length,omitempty"`
+ MaxLength int `json:"max_length,omitempty"`
+}
+
+// ElementType returns the type of the Element
+func (s PlainTextInputBlockElement) ElementType() MessageElementType {
+ return s.Type
+}
+
+// NewPlainTextInputBlockElement returns an instance of a plain-text input element
+func NewPlainTextInputBlockElement(placeholder *TextBlockObject, actionID string) *PlainTextInputBlockElement {
+ return &PlainTextInputBlockElement{
+ Type: METPlainTextInput,
+ ActionID: actionID,
+ Placeholder: placeholder,
+ }
+}
diff --git a/vendor/github.com/slack-go/slack/block_image.go b/vendor/github.com/slack-go/slack/block_image.go
new file mode 100644
index 00000000..6de3f63a
--- /dev/null
+++ b/vendor/github.com/slack-go/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/slack-go/slack/block_input.go b/vendor/github.com/slack-go/slack/block_input.go
new file mode 100644
index 00000000..9d082038
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/block_input.go
@@ -0,0 +1,30 @@
+package slack
+
+// InputBlock defines data that is used to collect information from users -
+// it can hold a plain-text input element, a select menu element,
+// a multi-select menu element, or a datepicker.
+//
+// More Information: https://api.slack.com/reference/messaging/blocks#input
+type InputBlock struct {
+ Type MessageBlockType `json:"type"`
+ BlockID string `json:"block_id,omitempty"`
+ Label *TextBlockObject `json:"label"`
+ Element BlockElement `json:"element"`
+ Hint *TextBlockObject `json:"hint,omitempty"`
+ Optional bool `json:"optional,omitempty"`
+}
+
+// BlockType returns the type of the block
+func (s InputBlock) BlockType() MessageBlockType {
+ return s.Type
+}
+
+// NewInputBlock returns a new instance of an Input Block
+func NewInputBlock(blockID string, label *TextBlockObject, element BlockElement) *InputBlock {
+ return &InputBlock{
+ Type: MBTInput,
+ BlockID: blockID,
+ Label: label,
+ Element: element,
+ }
+}
diff --git a/vendor/github.com/slack-go/slack/block_object.go b/vendor/github.com/slack-go/slack/block_object.go
new file mode 100644
index 00000000..824ec93c
--- /dev/null
+++ b/vendor/github.com/slack-go/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,omitempty"`
+}
+
+// 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/slack-go/slack/block_section.go b/vendor/github.com/slack-go/slack/block_section.go
new file mode 100644
index 00000000..01ffd5a1
--- /dev/null
+++ b/vendor/github.com/slack-go/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/slack-go/slack/bots.go b/vendor/github.com/slack-go/slack/bots.go
new file mode 100644
index 00000000..da21ba0c
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/bots.go
@@ -0,0 +1,58 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+)
+
+// Bot contains information about a bot
+type Bot struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Deleted bool `json:"deleted"`
+ UserID string `json:"user_id"`
+ AppID string `json:"app_id"`
+ Updated JSONTime `json:"updated"`
+ Icons Icons `json:"icons"`
+}
+
+type botResponseFull struct {
+ Bot `json:"bot,omitempty"` // GetBotInfo
+ SlackResponse
+}
+
+func (api *Client) botRequest(ctx context.Context, path string, values url.Values) (*botResponseFull, error) {
+ response := &botResponseFull{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := response.Err(); err != nil {
+ return nil, err
+ }
+
+ return response, nil
+}
+
+// GetBotInfo will retrieve the complete bot information
+func (api *Client) GetBotInfo(bot string) (*Bot, error) {
+ return api.GetBotInfoContext(context.Background(), bot)
+}
+
+// GetBotInfoContext will retrieve the complete bot information using a custom context
+func (api *Client) GetBotInfoContext(ctx context.Context, bot string) (*Bot, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+
+ if bot != "" {
+ values.Add("bot", bot)
+ }
+
+ response, err := api.botRequest(ctx, "bots.info", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Bot, nil
+}
diff --git a/vendor/github.com/slack-go/slack/channels.go b/vendor/github.com/slack-go/slack/channels.go
new file mode 100644
index 00000000..c99e6655
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/channels.go
@@ -0,0 +1,412 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "strconv"
+)
+
+type channelResponseFull struct {
+ Channel Channel `json:"channel"`
+ Channels []Channel `json:"channels"`
+ Purpose string `json:"purpose"`
+ Topic string `json:"topic"`
+ NotInChannel bool `json:"not_in_channel"`
+ History
+ SlackResponse
+}
+
+// Channel contains information about the channel
+type Channel struct {
+ GroupConversation
+ IsChannel bool `json:"is_channel"`
+ IsGeneral bool `json:"is_general"`
+ IsMember bool `json:"is_member"`
+ Locale string `json:"locale"`
+}
+
+func (api *Client) channelRequest(ctx context.Context, path string, values url.Values) (*channelResponseFull, error) {
+ response := &channelResponseFull{}
+ err := postForm(ctx, api.httpclient, api.endpoint+path, values, response, api)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, response.Err()
+}
+
+type channelsConfig struct {
+ values url.Values
+}
+
+// GetChannelsOption option provided when getting channels.
+type GetChannelsOption func(*channelsConfig) error
+
+// GetChannelsOptionExcludeMembers excludes the members collection from each channel.
+func GetChannelsOptionExcludeMembers() GetChannelsOption {
+ return func(config *channelsConfig) error {
+ config.values.Add("exclude_members", "true")
+ return nil
+ }
+}
+
+// GetChannelsOptionExcludeArchived excludes archived channels from results.
+func GetChannelsOptionExcludeArchived() GetChannelsOption {
+ return func(config *channelsConfig) error {
+ config.values.Add("exclude_archived", "true")
+ return nil
+ }
+}
+
+// ArchiveChannel archives the given channel
+// see https://api.slack.com/methods/channels.archive
+func (api *Client) ArchiveChannel(channelID string) error {
+ return api.ArchiveChannelContext(context.Background(), channelID)
+}
+
+// ArchiveChannelContext archives the given channel with a custom context
+// see https://api.slack.com/methods/channels.archive
+func (api *Client) ArchiveChannelContext(ctx context.Context, channelID string) (err error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ }
+
+ _, err = api.channelRequest(ctx, "channels.archive", values)
+ return err
+}
+
+// UnarchiveChannel unarchives the given channel
+// see https://api.slack.com/methods/channels.unarchive
+func (api *Client) UnarchiveChannel(channelID string) error {
+ return api.UnarchiveChannelContext(context.Background(), channelID)
+}
+
+// UnarchiveChannelContext unarchives the given channel with a custom context
+// see https://api.slack.com/methods/channels.unarchive
+func (api *Client) UnarchiveChannelContext(ctx context.Context, channelID string) (err error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ }
+
+ _, err = api.channelRequest(ctx, "channels.unarchive", values)
+ return err
+}
+
+// CreateChannel creates a channel with the given name and returns a *Channel
+// see https://api.slack.com/methods/channels.create
+func (api *Client) CreateChannel(channelName string) (*Channel, error) {
+ return api.CreateChannelContext(context.Background(), channelName)
+}
+
+// CreateChannelContext creates a channel with the given name and returns a *Channel with a custom context
+// see https://api.slack.com/methods/channels.create
+func (api *Client) CreateChannelContext(ctx context.Context, channelName string) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "name": {channelName},
+ }
+
+ response, err := api.channelRequest(ctx, "channels.create", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Channel, nil
+}
+
+// GetChannelHistory retrieves the channel history
+// see https://api.slack.com/methods/channels.history
+func (api *Client) GetChannelHistory(channelID string, params HistoryParameters) (*History, error) {
+ return api.GetChannelHistoryContext(context.Background(), channelID, params)
+}
+
+// GetChannelHistoryContext retrieves the channel history with a custom context
+// see https://api.slack.com/methods/channels.history
+func (api *Client) GetChannelHistoryContext(ctx context.Context, channelID string, params HistoryParameters) (*History, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ }
+ if params.Latest != DEFAULT_HISTORY_LATEST {
+ values.Add("latest", params.Latest)
+ }
+ if params.Oldest != DEFAULT_HISTORY_OLDEST {
+ values.Add("oldest", params.Oldest)
+ }
+ if params.Count != DEFAULT_HISTORY_COUNT {
+ values.Add("count", strconv.Itoa(params.Count))
+ }
+ if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE {
+ if params.Inclusive {
+ values.Add("inclusive", "1")
+ } else {
+ values.Add("inclusive", "0")
+ }
+ }
+
+ if params.Unreads != DEFAULT_HISTORY_UNREADS {
+ if params.Unreads {
+ values.Add("unreads", "1")
+ } else {
+ values.Add("unreads", "0")
+ }
+ }
+
+ response, err := api.channelRequest(ctx, "channels.history", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.History, nil
+}
+
+// GetChannelInfo retrieves the given channel
+// see https://api.slack.com/methods/channels.info
+func (api *Client) GetChannelInfo(channelID string) (*Channel, error) {
+ return api.GetChannelInfoContext(context.Background(), channelID)
+}
+
+// GetChannelInfoContext retrieves the given channel with a custom context
+// see https://api.slack.com/methods/channels.info
+func (api *Client) GetChannelInfoContext(ctx context.Context, channelID string) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "include_locale": {strconv.FormatBool(true)},
+ }
+
+ response, err := api.channelRequest(ctx, "channels.info", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Channel, nil
+}
+
+// InviteUserToChannel invites a user to a given channel and returns a *Channel
+// see https://api.slack.com/methods/channels.invite
+func (api *Client) InviteUserToChannel(channelID, user string) (*Channel, error) {
+ return api.InviteUserToChannelContext(context.Background(), channelID, user)
+}
+
+// InviteUserToChannelContext invites a user to a given channel and returns a *Channel with a custom context
+// see https://api.slack.com/methods/channels.invite
+func (api *Client) InviteUserToChannelContext(ctx context.Context, channelID, user string) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "user": {user},
+ }
+
+ response, err := api.channelRequest(ctx, "channels.invite", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Channel, nil
+}
+
+// JoinChannel joins the currently authenticated user to a channel
+// see https://api.slack.com/methods/channels.join
+func (api *Client) JoinChannel(channelName string) (*Channel, error) {
+ return api.JoinChannelContext(context.Background(), channelName)
+}
+
+// JoinChannelContext joins the currently authenticated user to a channel with a custom context
+// see https://api.slack.com/methods/channels.join
+func (api *Client) JoinChannelContext(ctx context.Context, channelName string) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "name": {channelName},
+ }
+
+ response, err := api.channelRequest(ctx, "channels.join", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Channel, nil
+}
+
+// LeaveChannel makes the authenticated user leave the given channel
+// see https://api.slack.com/methods/channels.leave
+func (api *Client) LeaveChannel(channelID string) (bool, error) {
+ return api.LeaveChannelContext(context.Background(), channelID)
+}
+
+// LeaveChannelContext makes the authenticated user leave the given channel with a custom context
+// see https://api.slack.com/methods/channels.leave
+func (api *Client) LeaveChannelContext(ctx context.Context, channelID string) (bool, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ }
+
+ response, err := api.channelRequest(ctx, "channels.leave", values)
+ if err != nil {
+ return false, err
+ }
+
+ return response.NotInChannel, nil
+}
+
+// KickUserFromChannel kicks a user from a given channel
+// see https://api.slack.com/methods/channels.kick
+func (api *Client) KickUserFromChannel(channelID, user string) error {
+ return api.KickUserFromChannelContext(context.Background(), channelID, user)
+}
+
+// KickUserFromChannelContext kicks a user from a given channel with a custom context
+// see https://api.slack.com/methods/channels.kick
+func (api *Client) KickUserFromChannelContext(ctx context.Context, channelID, user string) (err error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "user": {user},
+ }
+
+ _, err = api.channelRequest(ctx, "channels.kick", values)
+ return err
+}
+
+// GetChannels retrieves all the channels
+// see https://api.slack.com/methods/channels.list
+func (api *Client) GetChannels(excludeArchived bool, options ...GetChannelsOption) ([]Channel, error) {
+ return api.GetChannelsContext(context.Background(), excludeArchived, options...)
+}
+
+// GetChannelsContext retrieves all the channels with a custom context
+// see https://api.slack.com/methods/channels.list
+func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool, options ...GetChannelsOption) ([]Channel, error) {
+ config := channelsConfig{
+ values: url.Values{
+ "token": {api.token},
+ },
+ }
+
+ if excludeArchived {
+ options = append(options, GetChannelsOptionExcludeArchived())
+ }
+
+ for _, opt := range options {
+ if err := opt(&config); err != nil {
+ return nil, err
+ }
+ }
+
+ response, err := api.channelRequest(ctx, "channels.list", config.values)
+ if err != nil {
+ return nil, err
+ }
+ return response.Channels, nil
+}
+
+// SetChannelReadMark sets the read mark of a given channel to a specific point
+// Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a
+// timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls
+// (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A
+// timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
+// see https://api.slack.com/methods/channels.mark
+func (api *Client) SetChannelReadMark(channelID, ts string) error {
+ return api.SetChannelReadMarkContext(context.Background(), channelID, ts)
+}
+
+// SetChannelReadMarkContext sets the read mark of a given channel to a specific point with a custom context
+// For more details see SetChannelReadMark documentation
+// see https://api.slack.com/methods/channels.mark
+func (api *Client) SetChannelReadMarkContext(ctx context.Context, channelID, ts string) (err error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "ts": {ts},
+ }
+
+ _, err = api.channelRequest(ctx, "channels.mark", values)
+ return err
+}
+
+// RenameChannel renames a given channel
+// see https://api.slack.com/methods/channels.rename
+func (api *Client) RenameChannel(channelID, name string) (*Channel, error) {
+ return api.RenameChannelContext(context.Background(), channelID, name)
+}
+
+// RenameChannelContext renames a given channel with a custom context
+// see https://api.slack.com/methods/channels.rename
+func (api *Client) RenameChannelContext(ctx context.Context, channelID, name string) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "name": {name},
+ }
+
+ // 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 := api.channelRequest(ctx, "channels.rename", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Channel, nil
+}
+
+// SetChannelPurpose sets the channel purpose and returns the purpose that was successfully set
+// see https://api.slack.com/methods/channels.setPurpose
+func (api *Client) SetChannelPurpose(channelID, purpose string) (string, error) {
+ return api.SetChannelPurposeContext(context.Background(), channelID, purpose)
+}
+
+// SetChannelPurposeContext sets the channel purpose and returns the purpose that was successfully set with a custom context
+// see https://api.slack.com/methods/channels.setPurpose
+func (api *Client) SetChannelPurposeContext(ctx context.Context, channelID, purpose string) (string, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "purpose": {purpose},
+ }
+
+ response, err := api.channelRequest(ctx, "channels.setPurpose", values)
+ if err != nil {
+ return "", err
+ }
+ return response.Purpose, nil
+}
+
+// SetChannelTopic sets the channel topic and returns the topic that was successfully set
+// see https://api.slack.com/methods/channels.setTopic
+func (api *Client) SetChannelTopic(channelID, topic string) (string, error) {
+ return api.SetChannelTopicContext(context.Background(), channelID, topic)
+}
+
+// SetChannelTopicContext sets the channel topic and returns the topic that was successfully set with a custom context
+// see https://api.slack.com/methods/channels.setTopic
+func (api *Client) SetChannelTopicContext(ctx context.Context, channelID, topic string) (string, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "topic": {topic},
+ }
+
+ response, err := api.channelRequest(ctx, "channels.setTopic", values)
+ if err != nil {
+ return "", err
+ }
+ return response.Topic, nil
+}
+
+// GetChannelReplies gets an entire thread (a message plus all the messages in reply to it).
+// see https://api.slack.com/methods/channels.replies
+func (api *Client) GetChannelReplies(channelID, thread_ts string) ([]Message, error) {
+ return api.GetChannelRepliesContext(context.Background(), channelID, thread_ts)
+}
+
+// GetChannelRepliesContext gets an entire thread (a message plus all the messages in reply to it) with a custom context
+// see https://api.slack.com/methods/channels.replies
+func (api *Client) GetChannelRepliesContext(ctx context.Context, channelID, thread_ts string) ([]Message, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "thread_ts": {thread_ts},
+ }
+ response, err := api.channelRequest(ctx, "channels.replies", values)
+ if err != nil {
+ return nil, err
+ }
+ return response.History.Messages, nil
+}
diff --git a/vendor/github.com/slack-go/slack/chat.go b/vendor/github.com/slack-go/slack/chat.go
new file mode 100644
index 00000000..b54f8d6d
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/chat.go
@@ -0,0 +1,627 @@
+package slack
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+ "net/url"
+
+ "github.com/slack-go/slack/slackutilsx"
+)
+
+const (
+ DEFAULT_MESSAGE_USERNAME = ""
+ DEFAULT_MESSAGE_REPLY_BROADCAST = false
+ DEFAULT_MESSAGE_ASUSER = false
+ DEFAULT_MESSAGE_PARSE = ""
+ DEFAULT_MESSAGE_THREAD_TIMESTAMP = ""
+ DEFAULT_MESSAGE_LINK_NAMES = 0
+ DEFAULT_MESSAGE_UNFURL_LINKS = false
+ DEFAULT_MESSAGE_UNFURL_MEDIA = true
+ DEFAULT_MESSAGE_ICON_URL = ""
+ DEFAULT_MESSAGE_ICON_EMOJI = ""
+ DEFAULT_MESSAGE_MARKDOWN = true
+ DEFAULT_MESSAGE_ESCAPE_TEXT = true
+)
+
+type chatResponseFull struct {
+ Channel string `json:"channel"`
+ Timestamp string `json:"ts"` //Regular message timestamp
+ MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp
+ Text string `json:"text"`
+ SlackResponse
+}
+
+// getMessageTimestamp will inspect the `chatResponseFull` to ruturn a timestamp value
+// in `chat.postMessage` its under `ts`
+// in `chat.postEphemeral` its under `message_ts`
+func (c chatResponseFull) getMessageTimestamp() string {
+ if len(c.Timestamp) > 0 {
+ return c.Timestamp
+ }
+ return c.MessageTimeStamp
+}
+
+// PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request
+type PostMessageParameters struct {
+ Username string `json:"username"`
+ AsUser bool `json:"as_user"`
+ Parse string `json:"parse"`
+ ThreadTimestamp string `json:"thread_ts"`
+ ReplyBroadcast bool `json:"reply_broadcast"`
+ LinkNames int `json:"link_names"`
+ UnfurlLinks bool `json:"unfurl_links"`
+ UnfurlMedia bool `json:"unfurl_media"`
+ IconURL string `json:"icon_url"`
+ IconEmoji string `json:"icon_emoji"`
+ Markdown bool `json:"mrkdwn,omitempty"`
+ EscapeText bool `json:"escape_text"`
+
+ // chat.postEphemeral support
+ Channel string `json:"channel"`
+ User string `json:"user"`
+}
+
+// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set
+func NewPostMessageParameters() PostMessageParameters {
+ return PostMessageParameters{
+ Username: DEFAULT_MESSAGE_USERNAME,
+ User: DEFAULT_MESSAGE_USERNAME,
+ AsUser: DEFAULT_MESSAGE_ASUSER,
+ Parse: DEFAULT_MESSAGE_PARSE,
+ ThreadTimestamp: DEFAULT_MESSAGE_THREAD_TIMESTAMP,
+ LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
+ UnfurlLinks: DEFAULT_MESSAGE_UNFURL_LINKS,
+ UnfurlMedia: DEFAULT_MESSAGE_UNFURL_MEDIA,
+ IconURL: DEFAULT_MESSAGE_ICON_URL,
+ IconEmoji: DEFAULT_MESSAGE_ICON_EMOJI,
+ Markdown: DEFAULT_MESSAGE_MARKDOWN,
+ EscapeText: DEFAULT_MESSAGE_ESCAPE_TEXT,
+ }
+}
+
+// DeleteMessage deletes a message in a channel
+func (api *Client) DeleteMessage(channel, messageTimestamp string) (string, string, error) {
+ respChannel, respTimestamp, _, err := api.SendMessageContext(context.Background(), channel, MsgOptionDelete(messageTimestamp))
+ return respChannel, respTimestamp, err
+}
+
+// DeleteMessageContext deletes a message in a channel with a custom context
+func (api *Client) DeleteMessageContext(ctx context.Context, channel, messageTimestamp string) (string, string, error) {
+ respChannel, respTimestamp, _, err := api.SendMessageContext(ctx, channel, MsgOptionDelete(messageTimestamp))
+ return respChannel, respTimestamp, err
+}
+
+// PostMessage sends a message to a channel.
+// Message is escaped by default according to https://api.slack.com/docs/formatting
+// Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
+func (api *Client) PostMessage(channelID string, options ...MsgOption) (string, string, error) {
+ respChannel, respTimestamp, _, err := api.SendMessageContext(
+ context.Background(),
+ channelID,
+ MsgOptionPost(),
+ MsgOptionCompose(options...),
+ )
+ return respChannel, respTimestamp, err
+}
+
+// PostMessageContext sends a message to a channel with a custom context
+// For more details, see PostMessage documentation.
+func (api *Client) PostMessageContext(ctx context.Context, channelID string, options ...MsgOption) (string, string, error) {
+ respChannel, respTimestamp, _, err := api.SendMessageContext(
+ ctx,
+ channelID,
+ MsgOptionPost(),
+ MsgOptionCompose(options...),
+ )
+ return respChannel, respTimestamp, err
+}
+
+// PostEphemeral sends an ephemeral message to a user in a channel.
+// Message is escaped by default according to https://api.slack.com/docs/formatting
+// Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
+func (api *Client) PostEphemeral(channelID, userID string, options ...MsgOption) (string, error) {
+ return api.PostEphemeralContext(
+ context.Background(),
+ channelID,
+ userID,
+ options...,
+ )
+}
+
+// PostEphemeralContext sends an ephemeal message to a user in a channel with a custom context
+// For more details, see PostEphemeral documentation
+func (api *Client) PostEphemeralContext(ctx context.Context, channelID, userID string, options ...MsgOption) (timestamp string, err error) {
+ _, timestamp, _, err = api.SendMessageContext(ctx, channelID, MsgOptionPostEphemeral(userID), MsgOptionCompose(options...))
+ return timestamp, err
+}
+
+// UpdateMessage updates a message in a channel
+func (api *Client) UpdateMessage(channelID, timestamp string, options ...MsgOption) (string, string, string, error) {
+ return api.SendMessageContext(context.Background(), channelID, MsgOptionUpdate(timestamp), MsgOptionCompose(options...))
+}
+
+// UpdateMessageContext updates a message in a channel
+func (api *Client) UpdateMessageContext(ctx context.Context, channelID, timestamp string, options ...MsgOption) (string, string, string, error) {
+ return api.SendMessageContext(ctx, channelID, MsgOptionUpdate(timestamp), MsgOptionCompose(options...))
+}
+
+// UnfurlMessage unfurls a message in a channel
+func (api *Client) UnfurlMessage(channelID, timestamp string, unfurls map[string]Attachment, options ...MsgOption) (string, string, string, error) {
+ return api.SendMessageContext(context.Background(), channelID, MsgOptionUnfurl(timestamp, unfurls), MsgOptionCompose(options...))
+}
+
+// SendMessage more flexible method for configuring messages.
+func (api *Client) SendMessage(channel string, options ...MsgOption) (string, string, string, error) {
+ return api.SendMessageContext(context.Background(), channel, options...)
+}
+
+// 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) {
+ var (
+ req *http.Request
+ parser func(*chatResponseFull) responseParser
+ response chatResponseFull
+ )
+
+ if req, parser, err = buildSender(api.endpoint, options...).BuildRequest(api.token, channelID); err != nil {
+ return "", "", "", err
+ }
+
+ if err = doPost(ctx, api.httpclient, req, parser(&response), api); err != nil {
+ return "", "", "", err
+ }
+
+ return response.Channel, response.getMessageTimestamp(), response.Text, response.Err()
+}
+
+// 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, 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, apiurl string, options ...MsgOption) (sendConfig, error) {
+ config := sendConfig{
+ apiurl: apiurl,
+ endpoint: apiurl + string(chatPostMessage),
+ values: url.Values{
+ "token": {token},
+ "channel": {channel},
+ },
+ }
+
+ for _, opt := range options {
+ if err := opt(&config); err != nil {
+ return config, err
+ }
+ }
+
+ return config, nil
+}
+
+func buildSender(apiurl string, options ...MsgOption) sendConfig {
+ return sendConfig{
+ apiurl: apiurl,
+ options: options,
+ }
+}
+
+type sendMode string
+
+const (
+ chatUpdate sendMode = "chat.update"
+ 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
+ blocks Blocks
+ 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,
+ blocks: t.blocks,
+ 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
+ blocks Blocks
+ 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,
+ Blocks: t.blocks,
+ 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 = config.apiurl + string(chatPostMessage)
+ config.values.Del("ts")
+ return nil
+ }
+}
+
+// MsgOptionPostEphemeral - posts an ephemeral message to the provided user.
+func MsgOptionPostEphemeral(userID string) MsgOption {
+ return func(config *sendConfig) error {
+ config.endpoint = config.apiurl + string(chatPostEphemeral)
+ MsgOptionUser(userID)(config)
+ config.values.Del("ts")
+
+ return nil
+ }
+}
+
+// MsgOptionMeMessage posts a "me message" type from the calling user
+func MsgOptionMeMessage() MsgOption {
+ return func(config *sendConfig) error {
+ config.endpoint = config.apiurl + string(chatMeMessage)
+ return nil
+ }
+}
+
+// MsgOptionUpdate updates a message based on the timestamp.
+func MsgOptionUpdate(timestamp string) MsgOption {
+ return func(config *sendConfig) error {
+ config.endpoint = config.apiurl + string(chatUpdate)
+ config.values.Add("ts", timestamp)
+ return nil
+ }
+}
+
+// MsgOptionDelete deletes a message based on the timestamp.
+func MsgOptionDelete(timestamp string) MsgOption {
+ return func(config *sendConfig) error {
+ config.endpoint = config.apiurl + string(chatDelete)
+ config.values.Add("ts", timestamp)
+ return nil
+ }
+}
+
+// MsgOptionUnfurl unfurls a message based on the timestamp.
+func MsgOptionUnfurl(timestamp string, unfurls map[string]Attachment) MsgOption {
+ return func(config *sendConfig) error {
+ config.endpoint = config.apiurl + string(chatUnfurl)
+ config.values.Add("ts", timestamp)
+ unfurlsStr, err := json.Marshal(unfurls)
+ if err == nil {
+ config.values.Add("unfurls", string(unfurlsStr))
+ }
+ return err
+ }
+}
+
+// MsgOptionResponseURL supplies a url to use as the endpoint.
+func MsgOptionResponseURL(url string, responseType string) MsgOption {
+ return func(config *sendConfig) error {
+ config.mode = chatResponse
+ config.endpoint = url
+ config.responseType = responseType
+ 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 {
+ if b != DEFAULT_MESSAGE_ASUSER {
+ config.values.Set("as_user", "true")
+ }
+ return nil
+ }
+}
+
+// MsgOptionUser set the user for the message.
+func MsgOptionUser(userID string) MsgOption {
+ return func(config *sendConfig) error {
+ config.values.Set("user", userID)
+ return nil
+ }
+}
+
+// MsgOptionUsername set the username for the message.
+func MsgOptionUsername(username string) MsgOption {
+ return func(config *sendConfig) error {
+ config.values.Set("username", username)
+ return nil
+ }
+}
+
+// MsgOptionText provide the text for the message, optionally escape the provided
+// text.
+func MsgOptionText(text string, escape bool) MsgOption {
+ return func(config *sendConfig) error {
+ if escape {
+ text = slackutilsx.EscapeMessage(text)
+ }
+ config.values.Add("text", text)
+ return nil
+ }
+}
+
+// MsgOptionAttachments provide attachments for the message.
+func MsgOptionAttachments(attachments ...Attachment) MsgOption {
+ return func(config *sendConfig) error {
+ if attachments == nil {
+ return nil
+ }
+
+ 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(attachmentBytes))
+ }
+
+ return err
+ }
+}
+
+// MsgOptionBlocks sets blocks for the message
+func MsgOptionBlocks(blocks ...Block) MsgOption {
+ return func(config *sendConfig) error {
+ if blocks == nil {
+ return nil
+ }
+
+ config.blocks.BlockSet = append(config.blocks.BlockSet, blocks...)
+
+ blocks, err := json.Marshal(blocks)
+ if err == nil {
+ config.values.Set("blocks", string(blocks))
+ }
+ return err
+ }
+}
+
+// MsgOptionEnableLinkUnfurl enables link unfurling
+func MsgOptionEnableLinkUnfurl() MsgOption {
+ return func(config *sendConfig) error {
+ config.values.Set("unfurl_links", "true")
+ return nil
+ }
+}
+
+// MsgOptionDisableLinkUnfurl disables link unfurling
+func MsgOptionDisableLinkUnfurl() MsgOption {
+ return func(config *sendConfig) error {
+ config.values.Set("unfurl_links", "false")
+ return nil
+ }
+}
+
+// MsgOptionDisableMediaUnfurl disables media unfurling.
+func MsgOptionDisableMediaUnfurl() MsgOption {
+ return func(config *sendConfig) error {
+ config.values.Set("unfurl_media", "false")
+ return nil
+ }
+}
+
+// MsgOptionDisableMarkdown disables markdown.
+func MsgOptionDisableMarkdown() MsgOption {
+ return func(config *sendConfig) error {
+ config.values.Set("mrkdwn", "false")
+ return nil
+ }
+}
+
+// MsgOptionTS sets the thread TS of the message to enable creating or replying to a thread
+func MsgOptionTS(ts string) MsgOption {
+ return func(config *sendConfig) error {
+ config.values.Set("thread_ts", ts)
+ return nil
+ }
+}
+
+// MsgOptionBroadcast sets reply_broadcast to true
+func MsgOptionBroadcast() MsgOption {
+ return func(config *sendConfig) error {
+ config.values.Set("reply_broadcast", "true")
+ return nil
+ }
+}
+
+// MsgOptionCompose combines multiple options into a single option.
+func MsgOptionCompose(options ...MsgOption) MsgOption {
+ return func(c *sendConfig) error {
+ for _, opt := range options {
+ if err := opt(c); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+}
+
+// MsgOptionParse set parse option.
+func MsgOptionParse(b bool) MsgOption {
+ return func(c *sendConfig) error {
+ var v string
+ if b {
+ v = "full"
+ } else {
+ 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
+// may result in compilation errors or runtime behaviour changes.
+func UnsafeMsgOptionEndpoint(endpoint string, update func(url.Values)) MsgOption {
+ return func(config *sendConfig) error {
+ config.endpoint = endpoint
+ update(config.values)
+ return nil
+ }
+}
+
+// MsgOptionPostMessageParameters maintain backwards compatibility.
+func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
+ return func(config *sendConfig) error {
+ if params.Username != DEFAULT_MESSAGE_USERNAME {
+ config.values.Set("username", params.Username)
+ }
+
+ // chat.postEphemeral support
+ if params.User != DEFAULT_MESSAGE_USERNAME {
+ config.values.Set("user", params.User)
+ }
+
+ // never generates an error.
+ MsgOptionAsUser(params.AsUser)(config)
+
+ if params.Parse != DEFAULT_MESSAGE_PARSE {
+ config.values.Set("parse", params.Parse)
+ }
+ if params.LinkNames != DEFAULT_MESSAGE_LINK_NAMES {
+ config.values.Set("link_names", "1")
+ }
+
+ if params.UnfurlLinks != DEFAULT_MESSAGE_UNFURL_LINKS {
+ config.values.Set("unfurl_links", "true")
+ }
+
+ // I want to send a message with explicit `as_user` `true` and `unfurl_links` `false` in request.
+ // Because setting `as_user` to `true` will change the default value for `unfurl_links` to `true` on Slack API side.
+ if params.AsUser != DEFAULT_MESSAGE_ASUSER && params.UnfurlLinks == DEFAULT_MESSAGE_UNFURL_LINKS {
+ config.values.Set("unfurl_links", "false")
+ }
+ if params.UnfurlMedia != DEFAULT_MESSAGE_UNFURL_MEDIA {
+ config.values.Set("unfurl_media", "false")
+ }
+ if params.IconURL != DEFAULT_MESSAGE_ICON_URL {
+ config.values.Set("icon_url", params.IconURL)
+ }
+ if params.IconEmoji != DEFAULT_MESSAGE_ICON_EMOJI {
+ config.values.Set("icon_emoji", params.IconEmoji)
+ }
+ if params.Markdown != DEFAULT_MESSAGE_MARKDOWN {
+ config.values.Set("mrkdwn", "false")
+ }
+
+ if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP {
+ config.values.Set("thread_ts", params.ThreadTimestamp)
+ }
+ if params.ReplyBroadcast != DEFAULT_MESSAGE_REPLY_BROADCAST {
+ config.values.Set("reply_broadcast", "true")
+ }
+
+ return nil
+ }
+}
+
+// PermalinkParameters are the parameters required to get a permalink to a
+// message. Slack documentation can be found here:
+// https://api.slack.com/methods/chat.getPermalink
+type PermalinkParameters struct {
+ Channel string
+ Ts string
+}
+
+// GetPermalink returns the permalink for a message. It takes
+// PermalinkParameters and returns a string containing the permalink. It
+// returns an error if unable to retrieve the permalink.
+func (api *Client) GetPermalink(params *PermalinkParameters) (string, error) {
+ return api.GetPermalinkContext(context.Background(), params)
+}
+
+// GetPermalinkContext returns the permalink for a message using a custom context.
+func (api *Client) GetPermalinkContext(ctx context.Context, params *PermalinkParameters) (string, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {params.Channel},
+ "message_ts": {params.Ts},
+ }
+
+ response := struct {
+ Channel string `json:"channel"`
+ Permalink string `json:"permalink"`
+ SlackResponse
+ }{}
+ err := api.getMethod(ctx, "chat.getPermalink", values, &response)
+ if err != nil {
+ return "", err
+ }
+ return response.Permalink, response.Err()
+}
diff --git a/vendor/github.com/slack-go/slack/comment.go b/vendor/github.com/slack-go/slack/comment.go
new file mode 100644
index 00000000..7d1c0d4e
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/comment.go
@@ -0,0 +1,10 @@
+package slack
+
+// Comment contains all the information relative to a comment
+type Comment struct {
+ ID string `json:"id,omitempty"`
+ Created JSONTime `json:"created,omitempty"`
+ Timestamp JSONTime `json:"timestamp,omitempty"`
+ User string `json:"user,omitempty"`
+ Comment string `json:"comment,omitempty"`
+}
diff --git a/vendor/github.com/slack-go/slack/conversation.go b/vendor/github.com/slack-go/slack/conversation.go
new file mode 100644
index 00000000..1e4a61f1
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/conversation.go
@@ -0,0 +1,620 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+// Conversation is the foundation for IM and BaseGroupConversation
+type Conversation struct {
+ ID string `json:"id"`
+ Created JSONTime `json:"created"`
+ IsOpen bool `json:"is_open"`
+ LastRead string `json:"last_read,omitempty"`
+ Latest *Message `json:"latest,omitempty"`
+ UnreadCount int `json:"unread_count,omitempty"`
+ UnreadCountDisplay int `json:"unread_count_display,omitempty"`
+ IsGroup bool `json:"is_group"`
+ IsShared bool `json:"is_shared"`
+ IsIM bool `json:"is_im"`
+ IsExtShared bool `json:"is_ext_shared"`
+ IsOrgShared bool `json:"is_org_shared"`
+ IsPendingExtShared bool `json:"is_pending_ext_shared"`
+ IsPrivate bool `json:"is_private"`
+ IsMpIM bool `json:"is_mpim"`
+ Unlinked int `json:"unlinked"`
+ NameNormalized string `json:"name_normalized"`
+ NumMembers int `json:"num_members"`
+ Priority float64 `json:"priority"`
+ User string `json:"user"`
+
+ // TODO support pending_shared
+ // TODO support previous_names
+}
+
+// GroupConversation is the foundation for Group and Channel
+type GroupConversation struct {
+ Conversation
+ Name string `json:"name"`
+ Creator string `json:"creator"`
+ IsArchived bool `json:"is_archived"`
+ Members []string `json:"members"`
+ Topic Topic `json:"topic"`
+ Purpose Purpose `json:"purpose"`
+}
+
+// Topic contains information about the topic
+type Topic struct {
+ Value string `json:"value"`
+ Creator string `json:"creator"`
+ LastSet JSONTime `json:"last_set"`
+}
+
+// Purpose contains information about the purpose
+type Purpose struct {
+ Value string `json:"value"`
+ Creator string `json:"creator"`
+ LastSet JSONTime `json:"last_set"`
+}
+
+type GetUsersInConversationParameters struct {
+ ChannelID string
+ Cursor string
+ Limit int
+}
+
+type GetConversationsForUserParameters struct {
+ UserID string
+ Cursor string
+ Types []string
+ Limit int
+ ExcludeArchived bool
+}
+
+type responseMetaData struct {
+ NextCursor string `json:"next_cursor"`
+}
+
+// GetUsersInConversation returns the list of users in a conversation
+func (api *Client) GetUsersInConversation(params *GetUsersInConversationParameters) ([]string, string, error) {
+ return api.GetUsersInConversationContext(context.Background(), params)
+}
+
+// GetUsersInConversationContext returns the list of users in a conversation with a custom context
+func (api *Client) GetUsersInConversationContext(ctx context.Context, params *GetUsersInConversationParameters) ([]string, string, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {params.ChannelID},
+ }
+ if params.Cursor != "" {
+ values.Add("cursor", params.Cursor)
+ }
+ if params.Limit != 0 {
+ values.Add("limit", strconv.Itoa(params.Limit))
+ }
+ response := struct {
+ Members []string `json:"members"`
+ ResponseMetaData responseMetaData `json:"response_metadata"`
+ SlackResponse
+ }{}
+
+ err := api.postMethod(ctx, "conversations.members", values, &response)
+ if err != nil {
+ return nil, "", err
+ }
+
+ if err := response.Err(); err != nil {
+ return nil, "", err
+ }
+
+ return response.Members, response.ResponseMetaData.NextCursor, nil
+}
+
+// GetConversationsForUser returns the list conversations for a given user
+func (api *Client) GetConversationsForUser(params *GetConversationsForUserParameters) (channels []Channel, nextCursor string, err error) {
+ return api.GetConversationsForUserContext(context.Background(), params)
+}
+
+// GetConversationsForUserContext returns the list conversations for a given user with a custom context
+func (api *Client) GetConversationsForUserContext(ctx context.Context, params *GetConversationsForUserParameters) (channels []Channel, nextCursor string, err error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+ if params.UserID != "" {
+ values.Add("user", params.UserID)
+ }
+ if params.Cursor != "" {
+ values.Add("cursor", params.Cursor)
+ }
+ if params.Limit != 0 {
+ values.Add("limit", strconv.Itoa(params.Limit))
+ }
+ 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 = api.postMethod(ctx, "users.conversations", values, &response)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return response.Channels, response.ResponseMetaData.NextCursor, response.Err()
+}
+
+// ArchiveConversation archives a conversation
+func (api *Client) ArchiveConversation(channelID string) error {
+ return api.ArchiveConversationContext(context.Background(), channelID)
+}
+
+// ArchiveConversationContext archives a conversation with a custom context
+func (api *Client) ArchiveConversationContext(ctx context.Context, channelID string) error {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ }
+
+ response := SlackResponse{}
+ err := api.postMethod(ctx, "conversations.archive", values, &response)
+ if err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// UnArchiveConversation reverses conversation archival
+func (api *Client) UnArchiveConversation(channelID string) error {
+ return api.UnArchiveConversationContext(context.Background(), channelID)
+}
+
+// UnArchiveConversationContext reverses conversation archival with a custom context
+func (api *Client) UnArchiveConversationContext(ctx context.Context, channelID string) error {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ }
+ response := SlackResponse{}
+ err := api.postMethod(ctx, "conversations.unarchive", values, &response)
+ if err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// SetTopicOfConversation sets the topic for a conversation
+func (api *Client) SetTopicOfConversation(channelID, topic string) (*Channel, error) {
+ return api.SetTopicOfConversationContext(context.Background(), channelID, topic)
+}
+
+// SetTopicOfConversationContext sets the topic for a conversation with a custom context
+func (api *Client) SetTopicOfConversationContext(ctx context.Context, channelID, topic string) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "topic": {topic},
+ }
+ response := struct {
+ SlackResponse
+ Channel *Channel `json:"channel"`
+ }{}
+ err := api.postMethod(ctx, "conversations.setTopic", values, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response.Channel, response.Err()
+}
+
+// SetPurposeOfConversation sets the purpose for a conversation
+func (api *Client) SetPurposeOfConversation(channelID, purpose string) (*Channel, error) {
+ return api.SetPurposeOfConversationContext(context.Background(), channelID, purpose)
+}
+
+// SetPurposeOfConversationContext sets the purpose for a conversation with a custom context
+func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelID, purpose string) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "purpose": {purpose},
+ }
+ response := struct {
+ SlackResponse
+ Channel *Channel `json:"channel"`
+ }{}
+
+ err := api.postMethod(ctx, "conversations.setPurpose", values, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response.Channel, response.Err()
+}
+
+// RenameConversation renames a conversation
+func (api *Client) RenameConversation(channelID, channelName string) (*Channel, error) {
+ return api.RenameConversationContext(context.Background(), channelID, channelName)
+}
+
+// RenameConversationContext renames a conversation with a custom context
+func (api *Client) RenameConversationContext(ctx context.Context, channelID, channelName string) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "name": {channelName},
+ }
+ response := struct {
+ SlackResponse
+ Channel *Channel `json:"channel"`
+ }{}
+
+ err := api.postMethod(ctx, "conversations.rename", values, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response.Channel, response.Err()
+}
+
+// InviteUsersToConversation invites users to a channel
+func (api *Client) InviteUsersToConversation(channelID string, users ...string) (*Channel, error) {
+ return api.InviteUsersToConversationContext(context.Background(), channelID, users...)
+}
+
+// InviteUsersToConversationContext invites users to a channel with a custom context
+func (api *Client) InviteUsersToConversationContext(ctx context.Context, channelID string, users ...string) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "users": {strings.Join(users, ",")},
+ }
+ response := struct {
+ SlackResponse
+ Channel *Channel `json:"channel"`
+ }{}
+
+ err := api.postMethod(ctx, "conversations.invite", values, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response.Channel, response.Err()
+}
+
+// KickUserFromConversation removes a user from a conversation
+func (api *Client) KickUserFromConversation(channelID string, user string) error {
+ return api.KickUserFromConversationContext(context.Background(), channelID, user)
+}
+
+// KickUserFromConversationContext removes a user from a conversation with a custom context
+func (api *Client) KickUserFromConversationContext(ctx context.Context, channelID string, user string) error {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "user": {user},
+ }
+
+ response := SlackResponse{}
+ err := api.postMethod(ctx, "conversations.kick", values, &response)
+ if err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// CloseConversation closes a direct message or multi-person direct message
+func (api *Client) CloseConversation(channelID string) (noOp bool, alreadyClosed bool, err error) {
+ return api.CloseConversationContext(context.Background(), channelID)
+}
+
+// CloseConversationContext closes a direct message or multi-person direct message with a custom context
+func (api *Client) CloseConversationContext(ctx context.Context, channelID string) (noOp bool, alreadyClosed bool, err error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ }
+ response := struct {
+ SlackResponse
+ NoOp bool `json:"no_op"`
+ AlreadyClosed bool `json:"already_closed"`
+ }{}
+
+ err = api.postMethod(ctx, "conversations.close", values, &response)
+ if err != nil {
+ return false, false, err
+ }
+
+ return response.NoOp, response.AlreadyClosed, response.Err()
+}
+
+// CreateConversation initiates a public or private channel-based conversation
+func (api *Client) CreateConversation(channelName string, isPrivate bool) (*Channel, error) {
+ return api.CreateConversationContext(context.Background(), channelName, isPrivate)
+}
+
+// CreateConversationContext initiates a public or private channel-based conversation with a custom context
+func (api *Client) CreateConversationContext(ctx context.Context, channelName string, isPrivate bool) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "name": {channelName},
+ "is_private": {strconv.FormatBool(isPrivate)},
+ }
+ response, err := api.channelRequest(ctx, "conversations.create", values)
+ if err != nil {
+ return nil, err
+ }
+
+ return &response.Channel, nil
+}
+
+// GetConversationInfo retrieves information about a conversation
+func (api *Client) GetConversationInfo(channelID string, includeLocale bool) (*Channel, error) {
+ return api.GetConversationInfoContext(context.Background(), channelID, includeLocale)
+}
+
+// GetConversationInfoContext retrieves information about a conversation with a custom context
+func (api *Client) GetConversationInfoContext(ctx context.Context, channelID string, includeLocale bool) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ "include_locale": {strconv.FormatBool(includeLocale)},
+ }
+ response, err := api.channelRequest(ctx, "conversations.info", values)
+ if err != nil {
+ return nil, err
+ }
+
+ return &response.Channel, response.Err()
+}
+
+// LeaveConversation leaves a conversation
+func (api *Client) LeaveConversation(channelID string) (bool, error) {
+ return api.LeaveConversationContext(context.Background(), channelID)
+}
+
+// LeaveConversationContext leaves a conversation with a custom context
+func (api *Client) LeaveConversationContext(ctx context.Context, channelID string) (bool, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channelID},
+ }
+
+ response, err := api.channelRequest(ctx, "conversations.leave", values)
+ if err != nil {
+ return false, err
+ }
+
+ return response.NotInChannel, err
+}
+
+type GetConversationRepliesParameters struct {
+ ChannelID string
+ Timestamp string
+ Cursor string
+ Inclusive bool
+ Latest string
+ Limit int
+ Oldest string
+}
+
+// GetConversationReplies retrieves a thread of messages posted to a conversation
+func (api *Client) GetConversationReplies(params *GetConversationRepliesParameters) (msgs []Message, hasMore bool, nextCursor string, err error) {
+ return api.GetConversationRepliesContext(context.Background(), params)
+}
+
+// GetConversationRepliesContext retrieves a thread of messages posted to a conversation with a custom context
+func (api *Client) GetConversationRepliesContext(ctx context.Context, params *GetConversationRepliesParameters) (msgs []Message, hasMore bool, nextCursor string, err error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {params.ChannelID},
+ "ts": {params.Timestamp},
+ }
+ if params.Cursor != "" {
+ values.Add("cursor", params.Cursor)
+ }
+ if params.Latest != "" {
+ values.Add("latest", params.Latest)
+ }
+ if params.Limit != 0 {
+ values.Add("limit", strconv.Itoa(params.Limit))
+ }
+ if params.Oldest != "" {
+ values.Add("oldest", params.Oldest)
+ }
+ if params.Inclusive {
+ values.Add("inclusive", "1")
+ } else {
+ values.Add("inclusive", "0")
+ }
+ response := struct {
+ SlackResponse
+ HasMore bool `json:"has_more"`
+ ResponseMetaData struct {
+ NextCursor string `json:"next_cursor"`
+ } `json:"response_metadata"`
+ Messages []Message `json:"messages"`
+ }{}
+
+ err = api.postMethod(ctx, "conversations.replies", values, &response)
+ if err != nil {
+ return nil, false, "", err
+ }
+
+ return response.Messages, response.HasMore, response.ResponseMetaData.NextCursor, response.Err()
+}
+
+type GetConversationsParameters struct {
+ Cursor string
+ ExcludeArchived string
+ Limit int
+ Types []string
+}
+
+// GetConversations returns the list of channels in a Slack team
+func (api *Client) GetConversations(params *GetConversationsParameters) (channels []Channel, nextCursor string, err error) {
+ return api.GetConversationsContext(context.Background(), params)
+}
+
+// GetConversationsContext returns the list of channels in a Slack team with a custom context
+func (api *Client) GetConversationsContext(ctx context.Context, params *GetConversationsParameters) (channels []Channel, nextCursor string, err error) {
+ values := url.Values{
+ "token": {api.token},
+ "exclude_archived": {params.ExcludeArchived},
+ }
+ if params.Cursor != "" {
+ values.Add("cursor", params.Cursor)
+ }
+ if params.Limit != 0 {
+ values.Add("limit", strconv.Itoa(params.Limit))
+ }
+ if params.Types != nil {
+ values.Add("types", strings.Join(params.Types, ","))
+ }
+ response := struct {
+ Channels []Channel `json:"channels"`
+ ResponseMetaData responseMetaData `json:"response_metadata"`
+ SlackResponse
+ }{}
+
+ err = api.postMethod(ctx, "conversations.list", values, &response)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return response.Channels, response.ResponseMetaData.NextCursor, response.Err()
+}
+
+type OpenConversationParameters struct {
+ ChannelID string
+ ReturnIM bool
+ Users []string
+}
+
+// OpenConversation opens or resumes a direct message or multi-person direct message
+func (api *Client) OpenConversation(params *OpenConversationParameters) (*Channel, bool, bool, error) {
+ return api.OpenConversationContext(context.Background(), params)
+}
+
+// OpenConversationContext opens or resumes a direct message or multi-person direct message with a custom context
+func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConversationParameters) (*Channel, bool, bool, error) {
+ values := url.Values{
+ "token": {api.token},
+ "return_im": {strconv.FormatBool(params.ReturnIM)},
+ }
+ if params.ChannelID != "" {
+ values.Add("channel", params.ChannelID)
+ }
+ if params.Users != nil {
+ values.Add("users", strings.Join(params.Users, ","))
+ }
+ response := struct {
+ Channel *Channel `json:"channel"`
+ NoOp bool `json:"no_op"`
+ AlreadyOpen bool `json:"already_open"`
+ SlackResponse
+ }{}
+
+ err := api.postMethod(ctx, "conversations.open", values, &response)
+ if err != nil {
+ return nil, false, false, err
+ }
+
+ return response.Channel, response.NoOp, response.AlreadyOpen, response.Err()
+}
+
+// JoinConversation joins an existing conversation
+func (api *Client) JoinConversation(channelID string) (*Channel, string, []string, error) {
+ return api.JoinConversationContext(context.Background(), channelID)
+}
+
+// JoinConversationContext joins an existing conversation with a custom context
+func (api *Client) JoinConversationContext(ctx context.Context, channelID string) (*Channel, string, []string, error) {
+ values := url.Values{"token": {api.token}, "channel": {channelID}}
+ response := struct {
+ Channel *Channel `json:"channel"`
+ Warning string `json:"warning"`
+ ResponseMetaData *struct {
+ Warnings []string `json:"warnings"`
+ } `json:"response_metadata"`
+ SlackResponse
+ }{}
+
+ err := api.postMethod(ctx, "conversations.join", values, &response)
+ if err != nil {
+ return nil, "", nil, err
+ }
+ if response.Err() != nil {
+ return nil, "", nil, response.Err()
+ }
+ var warnings []string
+ if response.ResponseMetaData != nil {
+ warnings = response.ResponseMetaData.Warnings
+ }
+ return response.Channel, response.Warning, warnings, nil
+}
+
+type GetConversationHistoryParameters struct {
+ ChannelID string
+ Cursor string
+ Inclusive bool
+ Latest string
+ Limit int
+ Oldest string
+}
+
+type GetConversationHistoryResponse struct {
+ SlackResponse
+ HasMore bool `json:"has_more"`
+ PinCount int `json:"pin_count"`
+ Latest string `json:"latest"`
+ ResponseMetaData struct {
+ NextCursor string `json:"next_cursor"`
+ } `json:"response_metadata"`
+ Messages []Message `json:"messages"`
+}
+
+// GetConversationHistory joins an existing conversation
+func (api *Client) GetConversationHistory(params *GetConversationHistoryParameters) (*GetConversationHistoryResponse, error) {
+ return api.GetConversationHistoryContext(context.Background(), params)
+}
+
+// GetConversationHistoryContext joins an existing conversation with a custom context
+func (api *Client) GetConversationHistoryContext(ctx context.Context, params *GetConversationHistoryParameters) (*GetConversationHistoryResponse, error) {
+ values := url.Values{"token": {api.token}, "channel": {params.ChannelID}}
+ if params.Cursor != "" {
+ values.Add("cursor", params.Cursor)
+ }
+ if params.Inclusive {
+ values.Add("inclusive", "1")
+ } else {
+ values.Add("inclusive", "0")
+ }
+ if params.Latest != "" {
+ values.Add("latest", params.Latest)
+ }
+ if params.Limit != 0 {
+ values.Add("limit", strconv.Itoa(params.Limit))
+ }
+ if params.Oldest != "" {
+ values.Add("oldest", params.Oldest)
+ }
+
+ response := GetConversationHistoryResponse{}
+
+ err := api.postMethod(ctx, "conversations.history", values, &response)
+ if err != nil {
+ return nil, err
+ }
+
+ return &response, response.Err()
+}
diff --git a/vendor/github.com/slack-go/slack/dialog.go b/vendor/github.com/slack-go/slack/dialog.go
new file mode 100644
index 00000000..376cd9e6
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/dialog.go
@@ -0,0 +1,118 @@
+package slack
+
+import (
+ "context"
+ "encoding/json"
+ "strings"
+)
+
+// InputType is the type of the dialog input type
+type InputType string
+
+const (
+ // InputTypeText textfield input
+ InputTypeText InputType = "text"
+ // InputTypeTextArea textarea input
+ InputTypeTextArea InputType = "textarea"
+ // InputTypeSelect select menus input
+ InputTypeSelect InputType = "select"
+)
+
+// DialogInput for dialogs input type text or menu
+type DialogInput struct {
+ Type InputType `json:"type"`
+ Label string `json:"label"`
+ Name string `json:"name"`
+ Placeholder string `json:"placeholder"`
+ Optional bool `json:"optional"`
+ Hint string `json:"hint"`
+}
+
+// DialogTrigger ...
+type DialogTrigger struct {
+ TriggerID string `json:"trigger_id"` //Required. Must respond within 3 seconds.
+ Dialog Dialog `json:"dialog"` //Required.
+}
+
+// Dialog as in Slack dialogs
+// https://api.slack.com/dialogs#option_element_attributes#top-level_dialog_attributes
+type Dialog struct {
+ TriggerID string `json:"trigger_id"` // Required
+ CallbackID string `json:"callback_id"` // Required
+ State string `json:"state,omitempty"` // Optional
+ Title string `json:"title"`
+ SubmitLabel string `json:"submit_label,omitempty"`
+ NotifyOnCancel bool `json:"notify_on_cancel"`
+ Elements []DialogElement `json:"elements"`
+}
+
+// DialogElement abstract type for dialogs.
+type DialogElement interface{}
+
+// DialogCallback DEPRECATED use InteractionCallback
+type DialogCallback InteractionCallback
+
+// DialogSubmissionCallback is sent from Slack when a user submits a form from within a dialog
+type DialogSubmissionCallback struct {
+ State string `json:"state,omitempty"`
+ Submission map[string]string `json:"submission"`
+}
+
+// DialogOpenResponse response from `dialog.open`
+type DialogOpenResponse struct {
+ SlackResponse
+ DialogResponseMetadata DialogResponseMetadata `json:"response_metadata"`
+}
+
+// DialogResponseMetadata lists the error messages
+type DialogResponseMetadata struct {
+ Messages []string `json:"messages"`
+}
+
+// DialogInputValidationError is an error when user inputs incorrect value to form from within a dialog
+type DialogInputValidationError struct {
+ Name string `json:"name"`
+ Error string `json:"error"`
+}
+
+// DialogInputValidationErrors lists the name of field and that error messages
+type DialogInputValidationErrors struct {
+ Errors []DialogInputValidationError `json:"errors"`
+}
+
+// OpenDialog opens a dialog window where the triggerID originated from.
+// EXPERIMENTAL: dialog functionality is currently experimental, api is not considered stable.
+func (api *Client) OpenDialog(triggerID string, dialog Dialog) (err error) {
+ return api.OpenDialogContext(context.Background(), triggerID, dialog)
+}
+
+// OpenDialogContext opens a dialog window where the triggerId originated from with a custom context
+// 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 ErrParametersMissing
+ }
+
+ req := DialogTrigger{
+ TriggerID: triggerID,
+ Dialog: dialog,
+ }
+
+ encoded, err := json.Marshal(req)
+ if err != nil {
+ return err
+ }
+
+ response := &DialogOpenResponse{}
+ 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/slack-go/slack/dialog_select.go b/vendor/github.com/slack-go/slack/dialog_select.go
new file mode 100644
index 00000000..385cef68
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/dialog_select.go
@@ -0,0 +1,101 @@
+package slack
+
+// SelectDataSource types of select datasource
+type SelectDataSource string
+
+const (
+ // DialogDataSourceStatic menu with static Options/OptionGroups
+ DialogDataSourceStatic SelectDataSource = "static"
+ // DialogDataSourceExternal dynamic datasource
+ DialogDataSourceExternal SelectDataSource = "external"
+ // DialogDataSourceConversations provides a list of conversations
+ DialogDataSourceConversations SelectDataSource = "conversations"
+ // DialogDataSourceChannels provides a list of channels
+ DialogDataSourceChannels SelectDataSource = "channels"
+ // DialogDataSourceUsers provides a list of users
+ DialogDataSourceUsers SelectDataSource = "users"
+)
+
+// DialogInputSelect dialog support for select boxes.
+type DialogInputSelect struct {
+ DialogInput
+ Value string `json:"value,omitempty"` //Optional.
+ DataSource SelectDataSource `json:"data_source,omitempty"` //Optional. Allowed values: "users", "channels", "conversations", "external".
+ 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
+type DialogSelectOption struct {
+ Label string `json:"label"`
+ Value string `json:"value"`
+}
+
+// DialogOptionGroup is a collection of options for creating a segmented table
+type DialogOptionGroup struct {
+ Label string `json:"label"`
+ Options []DialogSelectOption `json:"options"`
+}
+
+// NewStaticSelectDialogInput constructor for a `static` datasource menu input
+func NewStaticSelectDialogInput(name, label string, options []DialogSelectOption) *DialogInputSelect {
+ return &DialogInputSelect{
+ DialogInput: DialogInput{
+ Type: InputTypeSelect,
+ Name: name,
+ Label: label,
+ Optional: true,
+ },
+ DataSource: DialogDataSourceStatic,
+ Options: options,
+ }
+}
+
+// NewGroupedSelectDialogInput creates grouped options select input for Dialogs.
+func NewGroupedSelectDialogInput(name, label string, options []DialogOptionGroup) *DialogInputSelect {
+ return &DialogInputSelect{
+ DialogInput: DialogInput{
+ Type: InputTypeSelect,
+ Name: name,
+ Label: label,
+ },
+ DataSource: DialogDataSourceStatic,
+ OptionGroups: options}
+}
+
+// NewDialogOptionGroup creates a DialogOptionGroup from several select options
+func NewDialogOptionGroup(label string, options ...DialogSelectOption) DialogOptionGroup {
+ return DialogOptionGroup{
+ Label: label,
+ Options: options,
+ }
+}
+
+// NewConversationsSelect returns a `Conversations` select
+func NewConversationsSelect(name, label string) *DialogInputSelect {
+ return newPresetSelect(name, label, DialogDataSourceConversations)
+}
+
+// NewChannelsSelect returns a `Channels` select
+func NewChannelsSelect(name, label string) *DialogInputSelect {
+ return newPresetSelect(name, label, DialogDataSourceChannels)
+}
+
+// NewUsersSelect returns a `Users` select
+func NewUsersSelect(name, label string) *DialogInputSelect {
+ return newPresetSelect(name, label, DialogDataSourceUsers)
+}
+
+func newPresetSelect(name, label string, dataSourceType SelectDataSource) *DialogInputSelect {
+ return &DialogInputSelect{
+ DialogInput: DialogInput{
+ Type: InputTypeSelect,
+ Label: label,
+ Name: name,
+ },
+ DataSource: dataSourceType,
+ }
+}
diff --git a/vendor/github.com/slack-go/slack/dialog_text.go b/vendor/github.com/slack-go/slack/dialog_text.go
new file mode 100644
index 00000000..da06bd6d
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/dialog_text.go
@@ -0,0 +1,59 @@
+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"
+ // InputSubtypeNumber numeric keyboard
+ InputSubtypeNumber TextInputSubtype = "number"
+ // InputSubtypeTel Phone keyboard
+ InputSubtypeTel TextInputSubtype = "tel"
+ // InputSubtypeURL Phone keyboard
+ InputSubtypeURL TextInputSubtype = "url"
+)
+
+// TextInputElement subtype of DialogInput
+// https://api.slack.com/dialogs#option_element_attributes#text_element_attributes
+type TextInputElement struct {
+ DialogInput
+ MaxLength int `json:"max_length,omitempty"`
+ MinLength int `json:"min_length,omitempty"`
+ Hint string `json:"hint,omitempty"`
+ Subtype TextInputSubtype `json:"subtype"`
+ Value string `json:"value"`
+}
+
+// NewTextInput constructor for a `text` input
+func NewTextInput(name, label, text string, options ...TextInputOption) *TextInputElement {
+ t := &TextInputElement{
+ DialogInput: DialogInput{
+ Type: InputTypeText,
+ Name: name,
+ Label: label,
+ },
+ Value: text,
+ }
+
+ for _, opt := range options {
+ opt(t)
+ }
+
+ return t
+}
+
+// NewTextAreaInput constructor for a `textarea` input
+func NewTextAreaInput(name, label, text string) *TextInputElement {
+ return &TextInputElement{
+ DialogInput: DialogInput{
+ Type: InputTypeTextArea,
+ Name: name,
+ Label: label,
+ },
+ Value: text,
+ }
+}
diff --git a/vendor/github.com/slack-go/slack/dnd.go b/vendor/github.com/slack-go/slack/dnd.go
new file mode 100644
index 00000000..a3aa680c
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/dnd.go
@@ -0,0 +1,151 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+type SnoozeDebug struct {
+ SnoozeEndDate string `json:"snooze_end_date"`
+}
+
+type SnoozeInfo struct {
+ SnoozeEnabled bool `json:"snooze_enabled,omitempty"`
+ SnoozeEndTime int `json:"snooze_endtime,omitempty"`
+ SnoozeRemaining int `json:"snooze_remaining,omitempty"`
+ SnoozeDebug SnoozeDebug `json:"snooze_debug,omitempty"`
+}
+
+type DNDStatus struct {
+ Enabled bool `json:"dnd_enabled"`
+ NextStartTimestamp int `json:"next_dnd_start_ts"`
+ NextEndTimestamp int `json:"next_dnd_end_ts"`
+ SnoozeInfo
+}
+
+type dndResponseFull struct {
+ DNDStatus
+ SlackResponse
+}
+
+type dndTeamInfoResponse struct {
+ Users map[string]DNDStatus `json:"users"`
+ SlackResponse
+}
+
+func (api *Client) dndRequest(ctx context.Context, path string, values url.Values) (*dndResponseFull, error) {
+ response := &dndResponseFull{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, response.Err()
+}
+
+// EndDND ends the user's scheduled Do Not Disturb session
+func (api *Client) EndDND() error {
+ return api.EndDNDContext(context.Background())
+}
+
+// EndDNDContext ends the user's scheduled Do Not Disturb session with a custom context
+func (api *Client) EndDNDContext(ctx context.Context) error {
+ values := url.Values{
+ "token": {api.token},
+ }
+
+ response := &SlackResponse{}
+
+ if err := api.postMethod(ctx, "dnd.endDnd", values, response); err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// EndSnooze ends the current user's snooze mode
+func (api *Client) EndSnooze() (*DNDStatus, error) {
+ return api.EndSnoozeContext(context.Background())
+}
+
+// EndSnoozeContext ends the current user's snooze mode with a custom context
+func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+
+ response, err := api.dndRequest(ctx, "dnd.endSnooze", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.DNDStatus, nil
+}
+
+// GetDNDInfo provides information about a user's current Do Not Disturb settings.
+func (api *Client) GetDNDInfo(user *string) (*DNDStatus, error) {
+ return api.GetDNDInfoContext(context.Background(), user)
+}
+
+// GetDNDInfoContext provides information about a user's current Do Not Disturb settings with a custom context.
+func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDStatus, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+ if user != nil {
+ values.Set("user", *user)
+ }
+
+ response, err := api.dndRequest(ctx, "dnd.info", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.DNDStatus, nil
+}
+
+// GetDNDTeamInfo provides information about a user's current Do Not Disturb settings.
+func (api *Client) GetDNDTeamInfo(users []string) (map[string]DNDStatus, error) {
+ return api.GetDNDTeamInfoContext(context.Background(), users)
+}
+
+// GetDNDTeamInfoContext provides information about a user's current Do Not Disturb settings with a custom context.
+func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (map[string]DNDStatus, error) {
+ values := url.Values{
+ "token": {api.token},
+ "users": {strings.Join(users, ",")},
+ }
+ response := &dndTeamInfoResponse{}
+
+ 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
+}
+
+// SetSnooze adjusts the snooze duration for a user's Do Not Disturb
+// settings. If a snooze session is not already active for the user, invoking
+// this method will begin one for the specified duration.
+func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) {
+ return api.SetSnoozeContext(context.Background(), minutes)
+}
+
+// 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{
+ "token": {api.token},
+ "num_minutes": {strconv.Itoa(minutes)},
+ }
+
+ response, err := api.dndRequest(ctx, "dnd.setSnooze", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.DNDStatus, nil
+}
diff --git a/vendor/github.com/slack-go/slack/emoji.go b/vendor/github.com/slack-go/slack/emoji.go
new file mode 100644
index 00000000..b2b0c6c9
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/emoji.go
@@ -0,0 +1,35 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+)
+
+type emojiResponseFull struct {
+ Emoji map[string]string `json:"emoji"`
+ SlackResponse
+}
+
+// GetEmoji retrieves all the emojis
+func (api *Client) GetEmoji() (map[string]string, error) {
+ return api.GetEmojiContext(context.Background())
+}
+
+// GetEmojiContext retrieves all the emojis with a custom context
+func (api *Client) GetEmojiContext(ctx context.Context) (map[string]string, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+ response := &emojiResponseFull{}
+
+ err := api.postMethod(ctx, "emoji.list", values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ if response.Err() != nil {
+ return nil, response.Err()
+ }
+
+ return response.Emoji, nil
+}
diff --git a/vendor/github.com/slack-go/slack/errors.go b/vendor/github.com/slack-go/slack/errors.go
new file mode 100644
index 00000000..a1dfec25
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/errors.go
@@ -0,0 +1,20 @@
+package slack
+
+import "github.com/slack-go/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")
+ ErrRTMGoodbye = errorsx.String("goodbye detected")
+ ErrRTMDeadman = errorsx.String("deadman switch triggered")
+ 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/slack-go/slack/files.go b/vendor/github.com/slack-go/slack/files.go
new file mode 100644
index 00000000..3a7363de
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/files.go
@@ -0,0 +1,404 @@
+package slack
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+const (
+ // Add here the defaults in the siten
+ DEFAULT_FILES_USER = ""
+ DEFAULT_FILES_CHANNEL = ""
+ DEFAULT_FILES_TS_FROM = 0
+ DEFAULT_FILES_TS_TO = -1
+ DEFAULT_FILES_TYPES = "all"
+ DEFAULT_FILES_COUNT = 100
+ DEFAULT_FILES_PAGE = 1
+)
+
+// File contains all the information for a file
+type File struct {
+ ID string `json:"id"`
+ Created JSONTime `json:"created"`
+ Timestamp JSONTime `json:"timestamp"`
+
+ Name string `json:"name"`
+ Title string `json:"title"`
+ Mimetype string `json:"mimetype"`
+ ImageExifRotation int `json:"image_exif_rotation"`
+ Filetype string `json:"filetype"`
+ PrettyType string `json:"pretty_type"`
+ User string `json:"user"`
+
+ Mode string `json:"mode"`
+ Editable bool `json:"editable"`
+ IsExternal bool `json:"is_external"`
+ ExternalType string `json:"external_type"`
+
+ Size int `json:"size"`
+
+ URL string `json:"url"` // Deprecated - never set
+ URLDownload string `json:"url_download"` // Deprecated - never set
+ URLPrivate string `json:"url_private"`
+ URLPrivateDownload string `json:"url_private_download"`
+
+ OriginalH int `json:"original_h"`
+ OriginalW int `json:"original_w"`
+ Thumb64 string `json:"thumb_64"`
+ Thumb80 string `json:"thumb_80"`
+ Thumb160 string `json:"thumb_160"`
+ Thumb360 string `json:"thumb_360"`
+ Thumb360Gif string `json:"thumb_360_gif"`
+ Thumb360W int `json:"thumb_360_w"`
+ Thumb360H int `json:"thumb_360_h"`
+ Thumb480 string `json:"thumb_480"`
+ Thumb480W int `json:"thumb_480_w"`
+ Thumb480H int `json:"thumb_480_h"`
+ Thumb720 string `json:"thumb_720"`
+ Thumb720W int `json:"thumb_720_w"`
+ Thumb720H int `json:"thumb_720_h"`
+ Thumb960 string `json:"thumb_960"`
+ Thumb960W int `json:"thumb_960_w"`
+ Thumb960H int `json:"thumb_960_h"`
+ Thumb1024 string `json:"thumb_1024"`
+ Thumb1024W int `json:"thumb_1024_w"`
+ Thumb1024H int `json:"thumb_1024_h"`
+
+ Permalink string `json:"permalink"`
+ PermalinkPublic string `json:"permalink_public"`
+
+ EditLink string `json:"edit_link"`
+ Preview string `json:"preview"`
+ PreviewHighlight string `json:"preview_highlight"`
+ Lines int `json:"lines"`
+ LinesMore int `json:"lines_more"`
+
+ IsPublic bool `json:"is_public"`
+ PublicURLShared bool `json:"public_url_shared"`
+ Channels []string `json:"channels"`
+ Groups []string `json:"groups"`
+ IMs []string `json:"ims"`
+ InitialComment Comment `json:"initial_comment"`
+ CommentsCount int `json:"comments_count"`
+ NumStars int `json:"num_stars"`
+ IsStarred bool `json:"is_starred"`
+ Shares Share `json:"shares"`
+}
+
+type Share struct {
+ Public map[string][]ShareFileInfo `json:"public"`
+ Private map[string][]ShareFileInfo `json:"private"`
+}
+
+type ShareFileInfo struct {
+ ReplyUsers []string `json:"reply_users"`
+ ReplyUsersCount int `json:"reply_users_count"`
+ ReplyCount int `json:"reply_count"`
+ Ts string `json:"ts"`
+ ThreadTs string `json:"thread_ts"`
+ LatestReply string `json:"latest_reply"`
+ ChannelName string `json:"channel_name"`
+ TeamID string `json:"team_id"`
+}
+
+// FileUploadParameters contains all the parameters necessary (including the optional ones) for an UploadFile() request.
+//
+// There are three ways to upload a file. You can either set Content if file is small, set Reader if file is large,
+// or provide a local file path in File to upload it from your filesystem.
+//
+// Note that when using the Reader option, you *must* specify the Filename, otherwise the Slack API isn't happy.
+type FileUploadParameters struct {
+ File string
+ Content string
+ Reader io.Reader
+ Filetype string
+ Filename string
+ Title string
+ InitialComment string
+ Channels []string
+ ThreadTimestamp string
+}
+
+// GetFilesParameters contains all the parameters necessary (including the optional ones) for a GetFiles() request
+type GetFilesParameters struct {
+ User string
+ Channel string
+ TimestampFrom JSONTime
+ TimestampTo JSONTime
+ Types string
+ Count int
+ 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"`
+ Metadata ResponseMetadata `json:"response_metadata"`
+
+ SlackResponse
+}
+
+// NewGetFilesParameters provides an instance of GetFilesParameters with all the sane default values set
+func NewGetFilesParameters() GetFilesParameters {
+ return GetFilesParameters{
+ User: DEFAULT_FILES_USER,
+ Channel: DEFAULT_FILES_CHANNEL,
+ TimestampFrom: DEFAULT_FILES_TS_FROM,
+ TimestampTo: DEFAULT_FILES_TS_TO,
+ Types: DEFAULT_FILES_TYPES,
+ Count: DEFAULT_FILES_COUNT,
+ Page: DEFAULT_FILES_PAGE,
+ }
+}
+
+func (api *Client) fileRequest(ctx context.Context, path string, values url.Values) (*fileResponseFull, error) {
+ response := &fileResponseFull{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, response.Err()
+}
+
+// GetFileInfo retrieves a file and related comments
+func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) {
+ return api.GetFileInfoContext(context.Background(), fileID, count, page)
+}
+
+// GetFileInfoContext retrieves a file and related comments with a custom context
+func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count, page int) (*File, []Comment, *Paging, error) {
+ values := url.Values{
+ "token": {api.token},
+ "file": {fileID},
+ "count": {strconv.Itoa(count)},
+ "page": {strconv.Itoa(page)},
+ }
+
+ 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, &params, 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{
+ "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.TimestampFrom != DEFAULT_FILES_TS_FROM {
+ values.Add("ts_from", strconv.FormatInt(int64(params.TimestampFrom), 10))
+ }
+ if params.TimestampTo != DEFAULT_FILES_TS_TO {
+ values.Add("ts_to", strconv.FormatInt(int64(params.TimestampTo), 10))
+ }
+ if params.Types != DEFAULT_FILES_TYPES {
+ values.Add("types", params.Types)
+ }
+ if params.Count != DEFAULT_FILES_COUNT {
+ values.Add("count", strconv.Itoa(params.Count))
+ }
+ if params.Page != DEFAULT_FILES_PAGE {
+ values.Add("page", strconv.Itoa(params.Page))
+ }
+
+ response, err := api.fileRequest(ctx, "files.list", values)
+ if err != nil {
+ return nil, nil, err
+ }
+ return response.Files, &response.Paging, nil
+}
+
+// UploadFile uploads a file
+func (api *Client) UploadFile(params FileUploadParameters) (file *File, err error) {
+ return api.UploadFileContext(context.Background(), params)
+}
+
+// UploadFileContext uploads a file and setting a custom context
+func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParameters) (file *File, err error) {
+ // Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More
+ // investigation needed, but for now this will do.
+ _, err = api.AuthTest()
+ if err != nil {
+ return nil, err
+ }
+ response := &fileResponseFull{}
+ values := url.Values{
+ "token": {api.token},
+ }
+ if params.Filetype != "" {
+ values.Add("filetype", params.Filetype)
+ }
+ if params.Filename != "" {
+ values.Add("filename", params.Filename)
+ }
+ if params.Title != "" {
+ values.Add("title", params.Title)
+ }
+ if params.InitialComment != "" {
+ values.Add("initial_comment", params.InitialComment)
+ }
+ if params.ThreadTimestamp != "" {
+ values.Add("thread_ts", params.ThreadTimestamp)
+ }
+ if len(params.Channels) != 0 {
+ values.Add("channels", strings.Join(params.Channels, ","))
+ }
+ if params.Content != "" {
+ values.Add("content", params.Content)
+ err = api.postMethod(ctx, "files.upload", values, response)
+ } else if params.File != "" {
+ err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.File, "file", values, response, api)
+ } else if params.Reader != nil {
+ 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
+ }
+
+ return &response.File, response.Err()
+}
+
+// DeleteFileComment deletes a file's comment
+func (api *Client) DeleteFileComment(commentID, fileID string) error {
+ return api.DeleteFileCommentContext(context.Background(), fileID, commentID)
+}
+
+// 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 ErrParametersMissing
+ }
+
+ values := url.Values{
+ "token": {api.token},
+ "file": {fileID},
+ "id": {commentID},
+ }
+ _, err = api.fileRequest(ctx, "files.comments.delete", values)
+ return err
+}
+
+// DeleteFile deletes a file
+func (api *Client) DeleteFile(fileID string) error {
+ return api.DeleteFileContext(context.Background(), fileID)
+}
+
+// DeleteFileContext deletes a file with a custom context
+func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err error) {
+ values := url.Values{
+ "token": {api.token},
+ "file": {fileID},
+ }
+
+ _, err = api.fileRequest(ctx, "files.delete", values)
+ return err
+}
+
+// RevokeFilePublicURL disables public/external sharing for a file
+func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) {
+ return api.RevokeFilePublicURLContext(context.Background(), fileID)
+}
+
+// RevokeFilePublicURLContext disables public/external sharing for a file with a custom context
+func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string) (*File, error) {
+ values := url.Values{
+ "token": {api.token},
+ "file": {fileID},
+ }
+
+ response, err := api.fileRequest(ctx, "files.revokePublicURL", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.File, nil
+}
+
+// ShareFilePublicURL enabled public/external sharing for a file
+func (api *Client) ShareFilePublicURL(fileID string) (*File, []Comment, *Paging, error) {
+ return api.ShareFilePublicURLContext(context.Background(), fileID)
+}
+
+// ShareFilePublicURLContext enabled public/external sharing for a file with a custom context
+func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) (*File, []Comment, *Paging, error) {
+ values := url.Values{
+ "token": {api.token},
+ "file": {fileID},
+ }
+
+ response, err := api.fileRequest(ctx, "files.sharedPublicURL", values)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ return &response.File, response.Comments, &response.Paging, nil
+}
diff --git a/vendor/github.com/slack-go/slack/go.mod b/vendor/github.com/slack-go/slack/go.mod
new file mode 100644
index 00000000..cbe11808
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/go.mod
@@ -0,0 +1,12 @@
+module github.com/slack-go/slack
+
+go 1.13
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/go-test/deep v1.0.4
+ 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/slack-go/slack/go.sum b/vendor/github.com/slack-go/slack/go.sum
new file mode 100644
index 00000000..7a0ae46e
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/go.sum
@@ -0,0 +1,12 @@
+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/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
+github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
+github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
+github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+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.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
diff --git a/vendor/github.com/slack-go/slack/groups.go b/vendor/github.com/slack-go/slack/groups.go
new file mode 100644
index 00000000..23374869
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/groups.go
@@ -0,0 +1,355 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "strconv"
+)
+
+// Group contains all the information for a group
+type Group struct {
+ GroupConversation
+ IsGroup bool `json:"is_group"`
+}
+
+type groupResponseFull struct {
+ Group Group `json:"group"`
+ Groups []Group `json:"groups"`
+ Purpose string `json:"purpose"`
+ Topic string `json:"topic"`
+ NotInGroup bool `json:"not_in_group"`
+ NoOp bool `json:"no_op"`
+ AlreadyClosed bool `json:"already_closed"`
+ AlreadyOpen bool `json:"already_open"`
+ AlreadyInGroup bool `json:"already_in_group"`
+ Channel Channel `json:"channel"`
+ History
+ SlackResponse
+}
+
+func (api *Client) groupRequest(ctx context.Context, path string, values url.Values) (*groupResponseFull, error) {
+ response := &groupResponseFull{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, response.Err()
+}
+
+// ArchiveGroup archives a private group
+func (api *Client) ArchiveGroup(group string) error {
+ return api.ArchiveGroupContext(context.Background(), group)
+}
+
+// ArchiveGroupContext archives a private group
+func (api *Client) ArchiveGroupContext(ctx context.Context, group string) error {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ }
+
+ _, err := api.groupRequest(ctx, "groups.archive", values)
+ return err
+}
+
+// UnarchiveGroup unarchives a private group
+func (api *Client) UnarchiveGroup(group string) error {
+ return api.UnarchiveGroupContext(context.Background(), group)
+}
+
+// UnarchiveGroupContext unarchives a private group
+func (api *Client) UnarchiveGroupContext(ctx context.Context, group string) error {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ }
+
+ _, err := api.groupRequest(ctx, "groups.unarchive", values)
+ return err
+}
+
+// CreateGroup creates a private group
+func (api *Client) CreateGroup(group string) (*Group, error) {
+ return api.CreateGroupContext(context.Background(), group)
+}
+
+// CreateGroupContext creates a private group
+func (api *Client) CreateGroupContext(ctx context.Context, group string) (*Group, error) {
+ values := url.Values{
+ "token": {api.token},
+ "name": {group},
+ }
+
+ response, err := api.groupRequest(ctx, "groups.create", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Group, nil
+}
+
+// CreateChildGroup creates a new private group archiving the old one
+// This method takes an existing private group and performs the following steps:
+// 1. Renames the existing group (from "example" to "example-archived").
+// 2. Archives the existing group.
+// 3. Creates a new group with the name of the existing group.
+// 4. Adds all members of the existing group to the new group.
+func (api *Client) CreateChildGroup(group string) (*Group, error) {
+ return api.CreateChildGroupContext(context.Background(), group)
+}
+
+// CreateChildGroupContext creates a new private group archiving the old one with a custom context
+// For more information see CreateChildGroup
+func (api *Client) CreateChildGroupContext(ctx context.Context, group string) (*Group, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ }
+
+ response, err := api.groupRequest(ctx, "groups.createChild", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Group, nil
+}
+
+// GetGroupHistory fetches all the history for a private group
+func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*History, error) {
+ return api.GetGroupHistoryContext(context.Background(), group, params)
+}
+
+// GetGroupHistoryContext fetches all the history for a private group with a custom context
+func (api *Client) GetGroupHistoryContext(ctx context.Context, group string, params HistoryParameters) (*History, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ }
+ if params.Latest != DEFAULT_HISTORY_LATEST {
+ values.Add("latest", params.Latest)
+ }
+ if params.Oldest != DEFAULT_HISTORY_OLDEST {
+ values.Add("oldest", params.Oldest)
+ }
+ if params.Count != DEFAULT_HISTORY_COUNT {
+ values.Add("count", strconv.Itoa(params.Count))
+ }
+ if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE {
+ if params.Inclusive {
+ values.Add("inclusive", "1")
+ } else {
+ values.Add("inclusive", "0")
+ }
+ }
+ if params.Unreads != DEFAULT_HISTORY_UNREADS {
+ if params.Unreads {
+ values.Add("unreads", "1")
+ } else {
+ values.Add("unreads", "0")
+ }
+ }
+
+ response, err := api.groupRequest(ctx, "groups.history", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.History, nil
+}
+
+// InviteUserToGroup invites a specific user to a private group
+func (api *Client) InviteUserToGroup(group, user string) (*Group, bool, error) {
+ return api.InviteUserToGroupContext(context.Background(), group, user)
+}
+
+// InviteUserToGroupContext invites a specific user to a private group with a custom context
+func (api *Client) InviteUserToGroupContext(ctx context.Context, group, user string) (*Group, bool, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ "user": {user},
+ }
+
+ response, err := api.groupRequest(ctx, "groups.invite", values)
+ if err != nil {
+ return nil, false, err
+ }
+ return &response.Group, response.AlreadyInGroup, nil
+}
+
+// LeaveGroup makes authenticated user leave the group
+func (api *Client) LeaveGroup(group string) error {
+ return api.LeaveGroupContext(context.Background(), group)
+}
+
+// LeaveGroupContext makes authenticated user leave the group with a custom context
+func (api *Client) LeaveGroupContext(ctx context.Context, group string) (err error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ }
+
+ _, err = api.groupRequest(ctx, "groups.leave", values)
+ return err
+}
+
+// KickUserFromGroup kicks a user from a group
+func (api *Client) KickUserFromGroup(group, user string) error {
+ return api.KickUserFromGroupContext(context.Background(), group, user)
+}
+
+// KickUserFromGroupContext kicks a user from a group with a custom context
+func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user string) (err error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ "user": {user},
+ }
+
+ _, err = api.groupRequest(ctx, "groups.kick", values)
+ return err
+}
+
+// GetGroups retrieves all groups
+func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) {
+ return api.GetGroupsContext(context.Background(), excludeArchived)
+}
+
+// GetGroupsContext retrieves all groups with a custom context
+func (api *Client) GetGroupsContext(ctx context.Context, excludeArchived bool) ([]Group, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+ if excludeArchived {
+ values.Add("exclude_archived", "1")
+ }
+
+ response, err := api.groupRequest(ctx, "groups.list", values)
+ if err != nil {
+ return nil, err
+ }
+ return response.Groups, nil
+}
+
+// GetGroupInfo retrieves the given group
+func (api *Client) GetGroupInfo(group string) (*Group, error) {
+ return api.GetGroupInfoContext(context.Background(), group)
+}
+
+// GetGroupInfoContext retrieves the given group with a custom context
+func (api *Client) GetGroupInfoContext(ctx context.Context, group string) (*Group, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ "include_locale": {strconv.FormatBool(true)},
+ }
+
+ response, err := api.groupRequest(ctx, "groups.info", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Group, nil
+}
+
+// SetGroupReadMark sets the read mark on a private group
+// Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a
+// timer before making the call. In this way, any further updates needed during the timeout will not generate extra
+// calls (just one per channel). This is useful for when reading scroll-back history, or following a busy live
+// channel. A timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
+func (api *Client) SetGroupReadMark(group, ts string) error {
+ return api.SetGroupReadMarkContext(context.Background(), group, ts)
+}
+
+// SetGroupReadMarkContext sets the read mark on a private group with a custom context
+// For more details see SetGroupReadMark
+func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string) (err error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ "ts": {ts},
+ }
+
+ _, err = api.groupRequest(ctx, "groups.mark", values)
+ return err
+}
+
+// OpenGroup opens a private group
+func (api *Client) OpenGroup(group string) (bool, bool, error) {
+ return api.OpenGroupContext(context.Background(), group)
+}
+
+// OpenGroupContext opens a private group with a custom context
+func (api *Client) OpenGroupContext(ctx context.Context, group string) (bool, bool, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ }
+
+ response, err := api.groupRequest(ctx, "groups.open", values)
+ if err != nil {
+ return false, false, err
+ }
+ return response.NoOp, response.AlreadyOpen, nil
+}
+
+// RenameGroup renames a group
+// XXX: They return a channel, not a group. What is this crap? :(
+// Inconsistent api it seems.
+func (api *Client) RenameGroup(group, name string) (*Channel, error) {
+ return api.RenameGroupContext(context.Background(), group, name)
+}
+
+// RenameGroupContext renames a group with a custom context
+func (api *Client) RenameGroupContext(ctx context.Context, group, name string) (*Channel, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ "name": {name},
+ }
+
+ // 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 := api.groupRequest(ctx, "groups.rename", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Channel, nil
+}
+
+// SetGroupPurpose sets the group purpose
+func (api *Client) SetGroupPurpose(group, purpose string) (string, error) {
+ return api.SetGroupPurposeContext(context.Background(), group, purpose)
+}
+
+// SetGroupPurposeContext sets the group purpose with a custom context
+func (api *Client) SetGroupPurposeContext(ctx context.Context, group, purpose string) (string, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ "purpose": {purpose},
+ }
+
+ response, err := api.groupRequest(ctx, "groups.setPurpose", values)
+ if err != nil {
+ return "", err
+ }
+ return response.Purpose, nil
+}
+
+// SetGroupTopic sets the group topic
+func (api *Client) SetGroupTopic(group, topic string) (string, error) {
+ return api.SetGroupTopicContext(context.Background(), group, topic)
+}
+
+// SetGroupTopicContext sets the group topic with a custom context
+func (api *Client) SetGroupTopicContext(ctx context.Context, group, topic string) (string, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {group},
+ "topic": {topic},
+ }
+
+ response, err := api.groupRequest(ctx, "groups.setTopic", values)
+ if err != nil {
+ return "", err
+ }
+ return response.Topic, nil
+}
diff --git a/vendor/github.com/slack-go/slack/history.go b/vendor/github.com/slack-go/slack/history.go
new file mode 100644
index 00000000..87b2e1ed
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/history.go
@@ -0,0 +1,36 @@
+package slack
+
+const (
+ DEFAULT_HISTORY_LATEST = ""
+ DEFAULT_HISTORY_OLDEST = "0"
+ DEFAULT_HISTORY_COUNT = 100
+ DEFAULT_HISTORY_INCLUSIVE = false
+ DEFAULT_HISTORY_UNREADS = false
+)
+
+// HistoryParameters contains all the necessary information to help in the retrieval of history for Channels/Groups/DMs
+type HistoryParameters struct {
+ Latest string
+ Oldest string
+ Count int
+ Inclusive bool
+ Unreads bool
+}
+
+// History contains message history information needed to navigate a Channel / Group / DM history
+type History struct {
+ Latest string `json:"latest"`
+ Messages []Message `json:"messages"`
+ HasMore bool `json:"has_more"`
+}
+
+// NewHistoryParameters provides an instance of HistoryParameters with all the sane default values set
+func NewHistoryParameters() HistoryParameters {
+ return HistoryParameters{
+ Latest: DEFAULT_HISTORY_LATEST,
+ Oldest: DEFAULT_HISTORY_OLDEST,
+ Count: DEFAULT_HISTORY_COUNT,
+ Inclusive: DEFAULT_HISTORY_INCLUSIVE,
+ Unreads: DEFAULT_HISTORY_UNREADS,
+ }
+}
diff --git a/vendor/github.com/slack-go/slack/im.go b/vendor/github.com/slack-go/slack/im.go
new file mode 100644
index 00000000..ee784fef
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/im.go
@@ -0,0 +1,154 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "strconv"
+)
+
+type imChannel struct {
+ ID string `json:"id"`
+}
+
+type imResponseFull struct {
+ NoOp bool `json:"no_op"`
+ AlreadyClosed bool `json:"already_closed"`
+ AlreadyOpen bool `json:"already_open"`
+ Channel imChannel `json:"channel"`
+ IMs []IM `json:"ims"`
+ History
+ SlackResponse
+}
+
+// IM contains information related to the Direct Message channel
+type IM struct {
+ Conversation
+ IsUserDeleted bool `json:"is_user_deleted"`
+}
+
+func (api *Client) imRequest(ctx context.Context, path string, values url.Values) (*imResponseFull, error) {
+ response := &imResponseFull{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, response.Err()
+}
+
+// CloseIMChannel closes the direct message channel
+func (api *Client) CloseIMChannel(channel string) (bool, bool, error) {
+ return api.CloseIMChannelContext(context.Background(), channel)
+}
+
+// CloseIMChannelContext closes the direct message channel with a custom context
+func (api *Client) CloseIMChannelContext(ctx context.Context, channel string) (bool, bool, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channel},
+ }
+
+ response, err := api.imRequest(ctx, "im.close", values)
+ if err != nil {
+ return false, false, err
+ }
+ return response.NoOp, response.AlreadyClosed, nil
+}
+
+// OpenIMChannel opens a direct message channel to the user provided as argument
+// Returns some status and the channel ID
+func (api *Client) OpenIMChannel(user string) (bool, bool, string, error) {
+ return api.OpenIMChannelContext(context.Background(), user)
+}
+
+// OpenIMChannelContext opens a direct message channel to the user provided as argument with a custom context
+// Returns some status and the channel ID
+func (api *Client) OpenIMChannelContext(ctx context.Context, user string) (bool, bool, string, error) {
+ values := url.Values{
+ "token": {api.token},
+ "user": {user},
+ }
+
+ response, err := api.imRequest(ctx, "im.open", values)
+ if err != nil {
+ return false, false, "", err
+ }
+ return response.NoOp, response.AlreadyOpen, response.Channel.ID, nil
+}
+
+// MarkIMChannel sets the read mark of a direct message channel to a specific point
+func (api *Client) MarkIMChannel(channel, ts string) (err error) {
+ return api.MarkIMChannelContext(context.Background(), channel, ts)
+}
+
+// MarkIMChannelContext sets the read mark of a direct message channel to a specific point with a custom context
+func (api *Client) MarkIMChannelContext(ctx context.Context, channel, ts string) error {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channel},
+ "ts": {ts},
+ }
+
+ _, err := api.imRequest(ctx, "im.mark", values)
+ return err
+}
+
+// GetIMHistory retrieves the direct message channel history
+func (api *Client) GetIMHistory(channel string, params HistoryParameters) (*History, error) {
+ return api.GetIMHistoryContext(context.Background(), channel, params)
+}
+
+// GetIMHistoryContext retrieves the direct message channel history with a custom context
+func (api *Client) GetIMHistoryContext(ctx context.Context, channel string, params HistoryParameters) (*History, error) {
+ values := url.Values{
+ "token": {api.token},
+ "channel": {channel},
+ }
+ if params.Latest != DEFAULT_HISTORY_LATEST {
+ values.Add("latest", params.Latest)
+ }
+ if params.Oldest != DEFAULT_HISTORY_OLDEST {
+ values.Add("oldest", params.Oldest)
+ }
+ if params.Count != DEFAULT_HISTORY_COUNT {
+ values.Add("count", strconv.Itoa(params.Count))
+ }
+ if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE {
+ if params.Inclusive {
+ values.Add("inclusive", "1")
+ } else {
+ values.Add("inclusive", "0")
+ }
+ }
+ if params.Unreads != DEFAULT_HISTORY_UNREADS {
+ if params.Unreads {
+ values.Add("unreads", "1")
+ } else {
+ values.Add("unreads", "0")
+ }
+ }
+
+ response, err := api.imRequest(ctx, "im.history", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.History, nil
+}
+
+// GetIMChannels returns the list of direct message channels
+func (api *Client) GetIMChannels() ([]IM, error) {
+ return api.GetIMChannelsContext(context.Background())
+}
+
+// GetIMChannelsContext returns the list of direct message channels with a custom context
+func (api *Client) GetIMChannelsContext(ctx context.Context) ([]IM, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+
+ response, err := api.imRequest(ctx, "im.list", values)
+ if err != nil {
+ return nil, err
+ }
+ return response.IMs, nil
+}
diff --git a/vendor/github.com/slack-go/slack/info.go b/vendor/github.com/slack-go/slack/info.go
new file mode 100644
index 00000000..31f459f1
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/info.go
@@ -0,0 +1,195 @@
+package slack
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+ "time"
+)
+
+// UserPrefs needs to be implemented
+type UserPrefs struct {
+ // "highlight_words":"",
+ // "user_colors":"",
+ // "color_names_in_list":true,
+ // "growls_enabled":true,
+ // "tz":"Europe\/London",
+ // "push_dm_alert":true,
+ // "push_mention_alert":true,
+ // "push_everything":true,
+ // "push_idle_wait":2,
+ // "push_sound":"b2.mp3",
+ // "push_loud_channels":"",
+ // "push_mention_channels":"",
+ // "push_loud_channels_set":"",
+ // "email_alerts":"instant",
+ // "email_alerts_sleep_until":0,
+ // "email_misc":false,
+ // "email_weekly":true,
+ // "welcome_message_hidden":false,
+ // "all_channels_loud":true,
+ // "loud_channels":"",
+ // "never_channels":"",
+ // "loud_channels_set":"",
+ // "show_member_presence":true,
+ // "search_sort":"timestamp",
+ // "expand_inline_imgs":true,
+ // "expand_internal_inline_imgs":true,
+ // "expand_snippets":false,
+ // "posts_formatting_guide":true,
+ // "seen_welcome_2":true,
+ // "seen_ssb_prompt":false,
+ // "search_only_my_channels":false,
+ // "emoji_mode":"default",
+ // "has_invited":true,
+ // "has_uploaded":false,
+ // "has_created_channel":true,
+ // "search_exclude_channels":"",
+ // "messages_theme":"default",
+ // "webapp_spellcheck":true,
+ // "no_joined_overlays":false,
+ // "no_created_overlays":true,
+ // "dropbox_enabled":false,
+ // "seen_user_menu_tip_card":true,
+ // "seen_team_menu_tip_card":true,
+ // "seen_channel_menu_tip_card":true,
+ // "seen_message_input_tip_card":true,
+ // "seen_channels_tip_card":true,
+ // "seen_domain_invite_reminder":false,
+ // "seen_member_invite_reminder":false,
+ // "seen_flexpane_tip_card":true,
+ // "seen_search_input_tip_card":true,
+ // "mute_sounds":false,
+ // "arrow_history":false,
+ // "tab_ui_return_selects":true,
+ // "obey_inline_img_limit":true,
+ // "new_msg_snd":"knock_brush.mp3",
+ // "collapsible":false,
+ // "collapsible_by_click":true,
+ // "require_at":false,
+ // "mac_ssb_bounce":"",
+ // "mac_ssb_bullet":true,
+ // "win_ssb_bullet":true,
+ // "expand_non_media_attachments":true,
+ // "show_typing":true,
+ // "pagekeys_handled":true,
+ // "last_snippet_type":"",
+ // "display_real_names_override":0,
+ // "time24":false,
+ // "enter_is_special_in_tbt":false,
+ // "graphic_emoticons":false,
+ // "convert_emoticons":true,
+ // "autoplay_chat_sounds":true,
+ // "ss_emojis":true,
+ // "sidebar_behavior":"",
+ // "mark_msgs_read_immediately":true,
+ // "start_scroll_at_oldest":true,
+ // "snippet_editor_wrap_long_lines":false,
+ // "ls_disabled":false,
+ // "sidebar_theme":"default",
+ // "sidebar_theme_custom_values":"",
+ // "f_key_search":false,
+ // "k_key_omnibox":true,
+ // "speak_growls":false,
+ // "mac_speak_voice":"com.apple.speech.synthesis.voice.Alex",
+ // "mac_speak_speed":250,
+ // "comma_key_prefs":false,
+ // "at_channel_suppressed_channels":"",
+ // "push_at_channel_suppressed_channels":"",
+ // "prompted_for_email_disabling":false,
+ // "full_text_extracts":false,
+ // "no_text_in_notifications":false,
+ // "muted_channels":"",
+ // "no_macssb1_banner":false,
+ // "privacy_policy_seen":true,
+ // "search_exclude_bots":false,
+ // "fuzzy_matching":false
+}
+
+// UserDetails contains user details coming in the initial response from StartRTM
+type UserDetails struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Created JSONTime `json:"created"`
+ ManualPresence string `json:"manual_presence"`
+ Prefs UserPrefs `json:"prefs"`
+}
+
+// JSONTime exists so that we can have a String method converting the date
+type JSONTime int64
+
+// String converts the unix timestamp into a string
+func (t JSONTime) String() string {
+ tm := t.Time()
+ return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
+}
+
+// Time returns a `time.Time` representation of this value.
+func (t JSONTime) Time() time.Time {
+ return time.Unix(int64(t), 0)
+}
+
+// UnmarshalJSON will unmarshal both string and int JSON values
+func (t *JSONTime) UnmarshalJSON(buf []byte) error {
+ s := bytes.Trim(buf, `"`)
+
+ v, err := strconv.Atoi(string(s))
+ if err != nil {
+ return err
+ }
+
+ *t = JSONTime(int64(v))
+ return nil
+}
+
+// Team contains details about a team
+type Team struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Domain string `json:"domain"`
+}
+
+// Icons XXX: needs further investigation
+type Icons struct {
+ Image36 string `json:"image_36,omitempty"`
+ Image48 string `json:"image_48,omitempty"`
+ Image72 string `json:"image_72,omitempty"`
+}
+
+// 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"`
+}
+
+type infoResponseFull struct {
+ Info
+ SlackResponse
+}
+
+// GetBotByID is deprecated and returns nil
+func (info Info) GetBotByID(botID string) *Bot {
+ return nil
+}
+
+// GetUserByID is deprecated and returns nil
+func (info Info) GetUserByID(userID string) *User {
+ return nil
+}
+
+// GetChannelByID is deprecated and returns nil
+func (info Info) GetChannelByID(channelID string) *Channel {
+ return nil
+}
+
+// GetGroupByID is deprecated and returns nil
+func (info Info) GetGroupByID(groupID string) *Group {
+ return nil
+}
+
+// GetIMByID is deprecated and returns nil
+func (info Info) GetIMByID(imID string) *IM {
+ return nil
+}
diff --git a/vendor/github.com/slack-go/slack/interactions.go b/vendor/github.com/slack-go/slack/interactions.go
new file mode 100644
index 00000000..26a8b6db
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/interactions.go
@@ -0,0 +1,142 @@
+package slack
+
+import (
+ "bytes"
+ "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")
+ InteractionTypeDialogSubmission = InteractionType("dialog_submission")
+ 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.
+type InteractionCallback struct {
+ Type InteractionType `json:"type"`
+ Token string `json:"token"`
+ CallbackID string `json:"callback_id"`
+ ResponseURL string `json:"response_url"`
+ TriggerID string `json:"trigger_id"`
+ ActionTs string `json:"action_ts"`
+ Team Team `json:"team"`
+ Channel Channel `json:"channel"`
+ User User `json:"user"`
+ OriginalMessage Message `json:"original_message"`
+ Message Message `json:"message"`
+ Name string `json:"name"`
+ Value string `json:"value"`
+ MessageTs string `json:"message_ts"`
+ AttachmentID string `json:"attachment_id"`
+ ActionCallback ActionCallbacks `json:"actions"`
+ APIAppID string `json:"api_app_id"`
+ 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
+}
+
+// MarshalJSON implements the Marshaller interface in order to combine both
+// action callback types back into a single array, like how the api responds.
+// This makes Marshaling and Unmarshaling an InteractionCallback symmetrical
+func (a ActionCallbacks) MarshalJSON() ([]byte, error) {
+ count := 0
+ length := len(a.AttachmentActions) + len(a.BlockActions)
+ buffer := bytes.NewBufferString("[")
+
+ f := func(obj interface{}) error {
+ js, err := json.Marshal(obj)
+ if err != nil {
+ return err
+ }
+ _, err = buffer.Write(js)
+ if err != nil {
+ return err
+ }
+
+ count++
+ if count < length {
+ _, err = buffer.WriteString(",")
+ return err
+ }
+ return nil
+ }
+
+ for _, act := range a.AttachmentActions {
+ err := f(act)
+ if err != nil {
+ return nil, err
+ }
+ }
+ for _, blk := range a.BlockActions {
+ err := f(blk)
+ if err != nil {
+ return nil, err
+ }
+ }
+ buffer.WriteString("]")
+ return buffer.Bytes(), nil
+}
+
+// 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/slack-go/slack/internal/errorsx/errorsx.go b/vendor/github.com/slack-go/slack/internal/errorsx/errorsx.go
new file mode 100644
index 00000000..cb850577
--- /dev/null
+++ b/vendor/github.com/slack-go/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/slack-go/slack/internal/timex/timex.go b/vendor/github.com/slack-go/slack/internal/timex/timex.go
new file mode 100644
index 00000000..40063f73
--- /dev/null
+++ b/vendor/github.com/slack-go/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/slack-go/slack/item.go b/vendor/github.com/slack-go/slack/item.go
new file mode 100644
index 00000000..89af4eb1
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/item.go
@@ -0,0 +1,75 @@
+package slack
+
+const (
+ TYPE_MESSAGE = "message"
+ TYPE_FILE = "file"
+ TYPE_FILE_COMMENT = "file_comment"
+ TYPE_CHANNEL = "channel"
+ TYPE_IM = "im"
+ TYPE_GROUP = "group"
+)
+
+// Item is any type of slack message - message, file, or file comment.
+type Item struct {
+ Type string `json:"type"`
+ Channel string `json:"channel,omitempty"`
+ Message *Message `json:"message,omitempty"`
+ File *File `json:"file,omitempty"`
+ Comment *Comment `json:"comment,omitempty"`
+ Timestamp string `json:"ts,omitempty"`
+}
+
+// NewMessageItem turns a message on a channel into a typed message struct.
+func NewMessageItem(ch string, m *Message) Item {
+ return Item{Type: TYPE_MESSAGE, Channel: ch, Message: m}
+}
+
+// NewFileItem turns a file into a typed file struct.
+func NewFileItem(f *File) Item {
+ return Item{Type: TYPE_FILE, File: f}
+}
+
+// NewFileCommentItem turns a file and comment into a typed file_comment struct.
+func NewFileCommentItem(f *File, c *Comment) Item {
+ return Item{Type: TYPE_FILE_COMMENT, File: f, Comment: c}
+}
+
+// NewChannelItem turns a channel id into a typed channel struct.
+func NewChannelItem(ch string) Item {
+ return Item{Type: TYPE_CHANNEL, Channel: ch}
+}
+
+// NewIMItem turns a channel id into a typed im struct.
+func NewIMItem(ch string) Item {
+ return Item{Type: TYPE_IM, Channel: ch}
+}
+
+// NewGroupItem turns a channel id into a typed group struct.
+func NewGroupItem(ch string) Item {
+ return Item{Type: TYPE_GROUP, Channel: ch}
+}
+
+// ItemRef is a reference to a message of any type. One of FileID,
+// CommentId, or the combination of ChannelId and Timestamp must be
+// specified.
+type ItemRef struct {
+ Channel string `json:"channel"`
+ Timestamp string `json:"timestamp"`
+ File string `json:"file"`
+ Comment string `json:"file_comment"`
+}
+
+// NewRefToMessage initializes a reference to to a message.
+func NewRefToMessage(channel, timestamp string) ItemRef {
+ return ItemRef{Channel: channel, Timestamp: timestamp}
+}
+
+// NewRefToFile initializes a reference to a file.
+func NewRefToFile(file string) ItemRef {
+ return ItemRef{File: file}
+}
+
+// NewRefToComment initializes a reference to a file comment.
+func NewRefToComment(comment string) ItemRef {
+ return ItemRef{Comment: comment}
+}
diff --git a/vendor/github.com/slack-go/slack/logger.go b/vendor/github.com/slack-go/slack/logger.go
new file mode 100644
index 00000000..6a3533a9
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/logger.go
@@ -0,0 +1,60 @@
+package slack
+
+import (
+ "fmt"
+)
+
+// logger is a logger interface compatible with both stdlib and some
+// 3rd party loggers.
+type logger interface {
+ Output(int, string) error
+}
+
+// ilogger represents the internal logging api we use.
+type ilogger interface {
+ logger
+ Print(...interface{})
+ Printf(string, ...interface{})
+ Println(...interface{})
+}
+
+type debug interface {
+ Debug() bool
+
+ // Debugf print a formatted debug line.
+ Debugf(format string, v ...interface{})
+ // Debugln print a debug line.
+ Debugln(v ...interface{})
+}
+
+// internalLog implements the additional methods used by our internal logging.
+type internalLog struct {
+ logger
+}
+
+// Println replicates the behaviour of the standard logger.
+func (t internalLog) Println(v ...interface{}) {
+ t.Output(2, fmt.Sprintln(v...))
+}
+
+// Printf replicates the behaviour of the standard logger.
+func (t internalLog) Printf(format string, v ...interface{}) {
+ t.Output(2, fmt.Sprintf(format, v...))
+}
+
+// Print replicates the behaviour of the standard logger.
+func (t internalLog) Print(v ...interface{}) {
+ t.Output(2, fmt.Sprint(v...))
+}
+
+type discard struct{}
+
+func (t discard) Debug() bool {
+ return false
+}
+
+// Debugf print a formatted debug line.
+func (t discard) Debugf(format string, v ...interface{}) {}
+
+// Debugln print a debug line.
+func (t discard) Debugln(v ...interface{}) {}
diff --git a/vendor/github.com/slack-go/slack/messageID.go b/vendor/github.com/slack-go/slack/messageID.go
new file mode 100644
index 00000000..a17472b4
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/messageID.go
@@ -0,0 +1,30 @@
+package slack
+
+import "sync"
+
+// IDGenerator provides an interface for generating integer ID values.
+type IDGenerator interface {
+ Next() int
+}
+
+// NewSafeID returns a new instance of an IDGenerator which is safe for
+// concurrent use by multiple goroutines.
+func NewSafeID(startID int) IDGenerator {
+ return &safeID{
+ nextID: startID,
+ mutex: &sync.Mutex{},
+ }
+}
+
+type safeID struct {
+ nextID int
+ mutex *sync.Mutex
+}
+
+func (s *safeID) Next() int {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+ id := s.nextID
+ s.nextID++
+ return id
+}
diff --git a/vendor/github.com/slack-go/slack/messages.go b/vendor/github.com/slack-go/slack/messages.go
new file mode 100644
index 00000000..f2f5b145
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/messages.go
@@ -0,0 +1,199 @@
+package slack
+
+// OutgoingMessage is used for the realtime API, and seems incomplete.
+type OutgoingMessage struct {
+ ID int `json:"id"`
+ // channel ID
+ Channel string `json:"channel,omitempty"`
+ Text string `json:"text,omitempty"`
+ Type string `json:"type,omitempty"`
+ ThreadTimestamp string `json:"thread_ts,omitempty"`
+ ThreadBroadcast bool `json:"reply_broadcast,omitempty"`
+ IDs []string `json:"ids,omitempty"`
+}
+
+// Message is an auxiliary type to allow us to have a message containing sub messages
+type Message struct {
+ Msg
+ SubMessage *Msg `json:"message,omitempty"`
+ PreviousMessage *Msg `json:"previous_message,omitempty"`
+}
+
+// Msg contains information about a slack message
+type Msg struct {
+ // Basic Message
+ ClientMsgID string `json:"client_msg_id"`
+ Type string `json:"type,omitempty"`
+ Channel string `json:"channel,omitempty"`
+ User string `json:"user,omitempty"`
+ Text string `json:"text,omitempty"`
+ Timestamp string `json:"ts,omitempty"`
+ ThreadTimestamp string `json:"thread_ts,omitempty"`
+ IsStarred bool `json:"is_starred,omitempty"`
+ PinnedTo []string `json:"pinned_to,omitempty"`
+ Attachments []Attachment `json:"attachments,omitempty"`
+ Edited *Edited `json:"edited,omitempty"`
+ LastRead string `json:"last_read,omitempty"`
+ Subscribed bool `json:"subscribed,omitempty"`
+ UnreadCount int `json:"unread_count,omitempty"`
+
+ // Message Subtypes
+ SubType string `json:"subtype,omitempty"`
+
+ // Hidden Subtypes
+ Hidden bool `json:"hidden,omitempty"` // message_changed, message_deleted, unpinned_item
+ DeletedTimestamp string `json:"deleted_ts,omitempty"` // message_deleted
+ EventTimestamp string `json:"event_ts,omitempty"`
+
+ // bot_message (https://api.slack.com/events/message/bot_message)
+ BotID string `json:"bot_id,omitempty"`
+ Username string `json:"username,omitempty"`
+ Icons *Icon `json:"icons,omitempty"`
+
+ // channel_join, group_join
+ Inviter string `json:"inviter,omitempty"`
+
+ // channel_topic, group_topic
+ Topic string `json:"topic,omitempty"`
+
+ // channel_purpose, group_purpose
+ Purpose string `json:"purpose,omitempty"`
+
+ // channel_name, group_name
+ Name string `json:"name,omitempty"`
+ OldName string `json:"old_name,omitempty"`
+
+ // channel_archive, group_archive
+ Members []string `json:"members,omitempty"`
+
+ // channels.replies, groups.replies, im.replies, mpim.replies
+ ReplyCount int `json:"reply_count,omitempty"`
+ Replies []Reply `json:"replies,omitempty"`
+ ParentUserId string `json:"parent_user_id,omitempty"`
+
+ // file_share, file_comment, file_mention
+ Files []File `json:"files,omitempty"`
+
+ // file_share
+ Upload bool `json:"upload,omitempty"`
+
+ // file_comment
+ Comment *Comment `json:"comment,omitempty"`
+
+ // pinned_item
+ ItemType string `json:"item_type,omitempty"`
+
+ // https://api.slack.com/rtm
+ ReplyTo int `json:"reply_to,omitempty"`
+ Team string `json:"team,omitempty"`
+
+ // reactions
+ Reactions []ItemReaction `json:"reactions,omitempty"`
+
+ // slash commands and interactive messages
+ 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 response for slash commands.
+ ResponseTypeEphemeral = "ephemeral"
+)
+
+// Icon is used for bot messages
+type Icon struct {
+ IconURL string `json:"icon_url,omitempty"`
+ IconEmoji string `json:"icon_emoji,omitempty"`
+}
+
+// Edited indicates that a message has been edited.
+type Edited struct {
+ User string `json:"user,omitempty"`
+ Timestamp string `json:"ts,omitempty"`
+}
+
+// Reply contains information about a reply for a thread
+type Reply struct {
+ User string `json:"user,omitempty"`
+ Timestamp string `json:"ts,omitempty"`
+}
+
+// Event contains the event type
+type Event struct {
+ Type string `json:"type,omitempty"`
+}
+
+// Ping contains information about a Ping Event
+type Ping struct {
+ ID int `json:"id"`
+ Type string `json:"type"`
+ Timestamp int64 `json:"timestamp"`
+}
+
+// Pong contains information about a Pong Event
+type Pong struct {
+ Type string `json:"type"`
+ ReplyTo int `json:"reply_to"`
+ Timestamp int64 `json:"timestamp"`
+}
+
+// NewOutgoingMessage prepares an OutgoingMessage that the user can
+// use to send a message. Use this function to properly set the
+// messageID.
+func (rtm *RTM) NewOutgoingMessage(text string, channelID string, options ...RTMsgOption) *OutgoingMessage {
+ id := rtm.idGen.Next()
+ msg := OutgoingMessage{
+ ID: id,
+ Type: "message",
+ Channel: channelID,
+ Text: text,
+ }
+ for _, option := range options {
+ option(&msg)
+ }
+ return &msg
+}
+
+// NewSubscribeUserPresence prepares an OutgoingMessage that the user can
+// use to subscribe presence events for the specified users.
+func (rtm *RTM) NewSubscribeUserPresence(ids []string) *OutgoingMessage {
+ return &OutgoingMessage{
+ Type: "presence_sub",
+ IDs: ids,
+ }
+}
+
+// NewTypingMessage prepares an OutgoingMessage that the user can
+// use to send as a typing indicator. Use this function to properly set the
+// messageID.
+func (rtm *RTM) NewTypingMessage(channelID string) *OutgoingMessage {
+ id := rtm.idGen.Next()
+ return &OutgoingMessage{
+ ID: id,
+ Type: "typing",
+ Channel: channelID,
+ }
+}
+
+// RTMsgOption allows configuration of various options available for sending an RTM message
+type RTMsgOption func(*OutgoingMessage)
+
+// RTMsgOptionTS sets thead timestamp of an outgoing message in order to respond to a thread
+func RTMsgOptionTS(threadTimestamp string) RTMsgOption {
+ return func(msg *OutgoingMessage) {
+ msg.ThreadTimestamp = threadTimestamp
+ }
+}
+
+// RTMsgOptionBroadcast sets broadcast reply to channel to "true"
+func RTMsgOptionBroadcast() RTMsgOption {
+ return func(msg *OutgoingMessage) {
+ msg.ThreadBroadcast = true
+ }
+}
diff --git a/vendor/github.com/slack-go/slack/misc.go b/vendor/github.com/slack-go/slack/misc.go
new file mode 100644
index 00000000..0dcee950
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/misc.go
@@ -0,0 +1,360 @@
+package slack
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "mime"
+ "mime/multipart"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// SlackResponse handles parsing out errors from the web api.
+type SlackResponse struct {
+ Ok bool `json:"ok"`
+ Error string `json:"error"`
+}
+
+func (t SlackResponse) Err() error {
+ if t.Ok {
+ return nil
+ }
+
+ // handle pure text based responses like chat.post
+ // which while they have a slack response in their data structure
+ // it doesn't actually get set during parsing.
+ if strings.TrimSpace(t.Error) == "" {
+ return nil
+ }
+
+ return errors.New(t.Error)
+}
+
+// StatusCodeError represents an http response error.
+// type httpStatusCode interface { HTTPStatusCode() int } to handle it.
+type statusCodeError struct {
+ Code int
+ Status string
+}
+
+func (t statusCodeError) Error() string {
+ 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)
+}
+
+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.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
+}
+
+func parseResponseBody(body io.ReadCloser, intf interface{}, d debug) error {
+ response, err := ioutil.ReadAll(body)
+ if err != nil {
+ return err
+ }
+
+ if d.Debug() {
+ d.Debugln("parseResponseBody", string(response))
+ }
+
+ return json.Unmarshal(response, intf)
+}
+
+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
+ }
+ file, err := os.Open(fullpath)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ 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 {
+ pipeReader, pipeWriter := io.Pipe()
+ wr := multipart.NewWriter(pipeWriter)
+ errc := make(chan error)
+ go func() {
+ defer pipeWriter.Close()
+ ioWriter, err := wr.CreateFormFile(fieldname, name)
+ if err != nil {
+ errc <- err
+ return
+ }
+ _, err = io.Copy(ioWriter, r)
+ if err != nil {
+ errc <- err
+ return
+ }
+ if err = wr.Close(); err != nil {
+ errc <- err
+ return
+ }
+ }()
+ req, err := fileUploadReq(ctx, path, values, pipeReader)
+ if err != nil {
+ return err
+ }
+ req.Header.Add("Content-Type", wr.FormDataContentType())
+ req = req.WithContext(ctx)
+ resp, err := client.Do(req)
+
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ err = checkStatusCode(resp, d)
+ if err != nil {
+ return err
+ }
+
+ select {
+ case err = <-errc:
+ return err
+ default:
+ return newJSONParser(intf)(resp)
+ }
+}
+
+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 {
+ return err
+ }
+ defer resp.Body.Close()
+
+ err = checkStatusCode(resp, d)
+ if err != nil {
+ return err
+ }
+
+ return parser(resp)
+}
+
+// post JSON.
+func postJSON(ctx context.Context, client httpClient, endpoint, token string, json []byte, intf interface{}, d debug) error {
+ reqBody := bytes.NewBuffer(json)
+ req, err := http.NewRequest("POST", endpoint, reqBody)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
+
+ return doPost(ctx, client, req, newJSONParser(intf), d)
+}
+
+// post a url encoded form.
+func postForm(ctx context.Context, client httpClient, endpoint string, values url.Values, intf interface{}, d debug) error {
+ reqBody := strings.NewReader(values.Encode())
+ req, err := http.NewRequest("POST", endpoint, reqBody)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ 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 {
+ req, err := http.NewRequest("GET", endpoint, nil)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ req.URL.RawQuery = values.Encode()
+
+ 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 {
+ endpoint := fmt.Sprintf(WEBAPIURLFormat, teamName, method, time.Now().Unix())
+ return postForm(ctx, client, endpoint, values, intf, d)
+}
+
+func logResponse(resp *http.Response, d debug) error {
+ if d.Debug() {
+ text, err := httputil.DumpResponse(resp, true)
+ if err != nil {
+ return err
+ }
+ d.Debugln(string(text))
+ }
+
+ return nil
+}
+
+func okJSONHandler(rw http.ResponseWriter, r *http.Request) {
+ rw.Header().Set("Content-Type", "application/json")
+ response, _ := json.Marshal(SlackResponse{
+ Ok: true,
+ })
+ rw.Write(response)
+}
+
+// timerReset safely reset a timer, see time.Timer.Reset for details.
+func timerReset(t *time.Timer, d time.Duration) {
+ if !t.Stop() {
+ <-t.C
+ }
+ 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/slack-go/slack/oauth.go b/vendor/github.com/slack-go/slack/oauth.go
new file mode 100644
index 00000000..29d6dce9
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/oauth.go
@@ -0,0 +1,64 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+)
+
+// OAuthResponseIncomingWebhook ...
+type OAuthResponseIncomingWebhook struct {
+ URL string `json:"url"`
+ Channel string `json:"channel"`
+ ChannelID string `json:"channel_id,omitempty"`
+ ConfigurationURL string `json:"configuration_url"`
+}
+
+// OAuthResponseBot ...
+type OAuthResponseBot struct {
+ BotUserID string `json:"bot_user_id"`
+ BotAccessToken string `json:"bot_access_token"`
+}
+
+// OAuthResponse ...
+type OAuthResponse struct {
+ AccessToken string `json:"access_token"`
+ Scope string `json:"scope"`
+ TeamName string `json:"team_name"`
+ TeamID string `json:"team_id"`
+ IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"`
+ Bot OAuthResponseBot `json:"bot"`
+ UserID string `json:"user_id,omitempty"`
+ SlackResponse
+}
+
+// GetOAuthToken retrieves an AccessToken
+func GetOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, err error) {
+ return GetOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI)
+}
+
+// GetOAuthTokenContext retrieves an AccessToken with a custom context
+func GetOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (accessToken string, scope string, err error) {
+ response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI)
+ if err != nil {
+ return "", "", err
+ }
+ return response.AccessToken, response.Scope, nil
+}
+
+func GetOAuthResponse(client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthResponse, err error) {
+ return GetOAuthResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI)
+}
+
+func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string) (resp *OAuthResponse, err error) {
+ values := url.Values{
+ "client_id": {clientID},
+ "client_secret": {clientSecret},
+ "code": {code},
+ "redirect_uri": {redirectURI},
+ }
+ response := &OAuthResponse{}
+ 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/slack-go/slack/pagination.go b/vendor/github.com/slack-go/slack/pagination.go
new file mode 100644
index 00000000..87dd136a
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/pagination.go
@@ -0,0 +1,20 @@
+package slack
+
+// Paging contains paging information
+type Paging struct {
+ Count int `json:"count"`
+ Total int `json:"total"`
+ Page int `json:"page"`
+ Pages int `json:"pages"`
+}
+
+// Pagination contains pagination information
+// This is different from Paging in that it contains additional details
+type Pagination struct {
+ TotalCount int `json:"total_count"`
+ Page int `json:"page"`
+ PerPage int `json:"per_page"`
+ PageCount int `json:"page_count"`
+ First int `json:"first"`
+ Last int `json:"last"`
+}
diff --git a/vendor/github.com/slack-go/slack/pins.go b/vendor/github.com/slack-go/slack/pins.go
new file mode 100644
index 00000000..ef97c8df
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/pins.go
@@ -0,0 +1,94 @@
+package slack
+
+import (
+ "context"
+ "errors"
+ "net/url"
+)
+
+type listPinsResponseFull struct {
+ Items []Item
+ Paging `json:"paging"`
+ SlackResponse
+}
+
+// AddPin pins an item in a channel
+func (api *Client) AddPin(channel string, item ItemRef) error {
+ return api.AddPinContext(context.Background(), channel, item)
+}
+
+// AddPinContext pins an item in a channel with a custom context
+func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemRef) error {
+ values := url.Values{
+ "channel": {channel},
+ "token": {api.token},
+ }
+ if item.Timestamp != "" {
+ values.Set("timestamp", item.Timestamp)
+ }
+ if item.File != "" {
+ values.Set("file", item.File)
+ }
+ if item.Comment != "" {
+ values.Set("file_comment", item.Comment)
+ }
+
+ response := &SlackResponse{}
+ if err := api.postMethod(ctx, "pins.add", values, response); err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// RemovePin un-pins an item from a channel
+func (api *Client) RemovePin(channel string, item ItemRef) error {
+ return api.RemovePinContext(context.Background(), channel, item)
+}
+
+// RemovePinContext un-pins an item from a channel with a custom context
+func (api *Client) RemovePinContext(ctx context.Context, channel string, item ItemRef) error {
+ values := url.Values{
+ "channel": {channel},
+ "token": {api.token},
+ }
+ if item.Timestamp != "" {
+ values.Set("timestamp", item.Timestamp)
+ }
+ if item.File != "" {
+ values.Set("file", item.File)
+ }
+ if item.Comment != "" {
+ values.Set("file_comment", item.Comment)
+ }
+
+ response := &SlackResponse{}
+ if err := api.postMethod(ctx, "pins.remove", values, response); err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// ListPins returns information about the items a user reacted to.
+func (api *Client) ListPins(channel string) ([]Item, *Paging, error) {
+ return api.ListPinsContext(context.Background(), channel)
+}
+
+// ListPinsContext returns information about the items a user reacted to with a custom context.
+func (api *Client) ListPinsContext(ctx context.Context, channel string) ([]Item, *Paging, error) {
+ values := url.Values{
+ "channel": {channel},
+ "token": {api.token},
+ }
+
+ response := &listPinsResponseFull{}
+ err := api.postMethod(ctx, "pins.list", values, response)
+ if err != nil {
+ return nil, nil, err
+ }
+ if !response.Ok {
+ return nil, nil, errors.New(response.Error)
+ }
+ return response.Items, &response.Paging, nil
+}
diff --git a/vendor/github.com/slack-go/slack/reactions.go b/vendor/github.com/slack-go/slack/reactions.go
new file mode 100644
index 00000000..2a9bd42e
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/reactions.go
@@ -0,0 +1,270 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "strconv"
+)
+
+// ItemReaction is the reactions that have happened on an item.
+type ItemReaction struct {
+ Name string `json:"name"`
+ Count int `json:"count"`
+ Users []string `json:"users"`
+}
+
+// ReactedItem is an item that was reacted to, and the details of the
+// reactions.
+type ReactedItem struct {
+ Item
+ Reactions []ItemReaction
+}
+
+// GetReactionsParameters is the inputs to get reactions to an item.
+type GetReactionsParameters struct {
+ Full bool
+}
+
+// NewGetReactionsParameters initializes the inputs to get reactions to an item.
+func NewGetReactionsParameters() GetReactionsParameters {
+ return GetReactionsParameters{
+ Full: false,
+ }
+}
+
+type getReactionsResponseFull struct {
+ Type string
+ M struct {
+ Reactions []ItemReaction
+ } `json:"message"`
+ F struct {
+ Reactions []ItemReaction
+ } `json:"file"`
+ FC struct {
+ Reactions []ItemReaction
+ } `json:"comment"`
+ SlackResponse
+}
+
+func (res getReactionsResponseFull) extractReactions() []ItemReaction {
+ switch res.Type {
+ case "message":
+ return res.M.Reactions
+ case "file":
+ return res.F.Reactions
+ case "file_comment":
+ return res.FC.Reactions
+ }
+ return []ItemReaction{}
+}
+
+const (
+ DEFAULT_REACTIONS_USER = ""
+ DEFAULT_REACTIONS_COUNT = 100
+ DEFAULT_REACTIONS_PAGE = 1
+ DEFAULT_REACTIONS_FULL = false
+)
+
+// ListReactionsParameters is the inputs to find all reactions by a user.
+type ListReactionsParameters struct {
+ User string
+ Count int
+ Page int
+ Full bool
+}
+
+// NewListReactionsParameters initializes the inputs to find all reactions
+// performed by a user.
+func NewListReactionsParameters() ListReactionsParameters {
+ return ListReactionsParameters{
+ User: DEFAULT_REACTIONS_USER,
+ Count: DEFAULT_REACTIONS_COUNT,
+ Page: DEFAULT_REACTIONS_PAGE,
+ Full: DEFAULT_REACTIONS_FULL,
+ }
+}
+
+type listReactionsResponseFull struct {
+ Items []struct {
+ Type string
+ Channel string
+ M struct {
+ *Message
+ } `json:"message"`
+ F struct {
+ *File
+ Reactions []ItemReaction
+ } `json:"file"`
+ FC struct {
+ *Comment
+ Reactions []ItemReaction
+ } `json:"comment"`
+ }
+ Paging `json:"paging"`
+ SlackResponse
+}
+
+func (res listReactionsResponseFull) extractReactedItems() []ReactedItem {
+ items := make([]ReactedItem, len(res.Items))
+ for i, input := range res.Items {
+ item := ReactedItem{}
+ item.Type = input.Type
+ switch input.Type {
+ case "message":
+ item.Channel = input.Channel
+ item.Message = input.M.Message
+ item.Reactions = input.M.Reactions
+ case "file":
+ item.File = input.F.File
+ item.Reactions = input.F.Reactions
+ case "file_comment":
+ item.File = input.F.File
+ item.Comment = input.FC.Comment
+ item.Reactions = input.FC.Reactions
+ }
+ items[i] = item
+ }
+ return items
+}
+
+// AddReaction adds a reaction emoji to a message, file or file comment.
+func (api *Client) AddReaction(name string, item ItemRef) error {
+ return api.AddReactionContext(context.Background(), name, item)
+}
+
+// AddReactionContext adds a reaction emoji to a message, file or file comment with a custom context.
+func (api *Client) AddReactionContext(ctx context.Context, name string, item ItemRef) error {
+ values := url.Values{
+ "token": {api.token},
+ }
+ if name != "" {
+ values.Set("name", name)
+ }
+ if item.Channel != "" {
+ values.Set("channel", item.Channel)
+ }
+ if item.Timestamp != "" {
+ values.Set("timestamp", item.Timestamp)
+ }
+ if item.File != "" {
+ values.Set("file", item.File)
+ }
+ if item.Comment != "" {
+ values.Set("file_comment", item.Comment)
+ }
+
+ response := &SlackResponse{}
+ if err := api.postMethod(ctx, "reactions.add", values, response); err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// RemoveReaction removes a reaction emoji from a message, file or file comment.
+func (api *Client) RemoveReaction(name string, item ItemRef) error {
+ return api.RemoveReactionContext(context.Background(), name, item)
+}
+
+// RemoveReactionContext removes a reaction emoji from a message, file or file comment with a custom context.
+func (api *Client) RemoveReactionContext(ctx context.Context, name string, item ItemRef) error {
+ values := url.Values{
+ "token": {api.token},
+ }
+ if name != "" {
+ values.Set("name", name)
+ }
+ if item.Channel != "" {
+ values.Set("channel", item.Channel)
+ }
+ if item.Timestamp != "" {
+ values.Set("timestamp", item.Timestamp)
+ }
+ if item.File != "" {
+ values.Set("file", item.File)
+ }
+ if item.Comment != "" {
+ values.Set("file_comment", item.Comment)
+ }
+
+ response := &SlackResponse{}
+ if err := api.postMethod(ctx, "reactions.remove", values, response); err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// GetReactions returns details about the reactions on an item.
+func (api *Client) GetReactions(item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) {
+ return api.GetReactionsContext(context.Background(), item, params)
+}
+
+// GetReactionsContext returns details about the reactions on an item with a custom context
+func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+ if item.Channel != "" {
+ values.Set("channel", item.Channel)
+ }
+ if item.Timestamp != "" {
+ values.Set("timestamp", item.Timestamp)
+ }
+ if item.File != "" {
+ values.Set("file", item.File)
+ }
+ if item.Comment != "" {
+ values.Set("file_comment", item.Comment)
+ }
+ if params.Full != DEFAULT_REACTIONS_FULL {
+ values.Set("full", strconv.FormatBool(params.Full))
+ }
+
+ response := &getReactionsResponseFull{}
+ if err := api.postMethod(ctx, "reactions.get", values, response); err != nil {
+ return nil, err
+ }
+
+ if err := response.Err(); err != nil {
+ return nil, err
+ }
+
+ return response.extractReactions(), nil
+}
+
+// ListReactions returns information about the items a user reacted to.
+func (api *Client) ListReactions(params ListReactionsParameters) ([]ReactedItem, *Paging, error) {
+ return api.ListReactionsContext(context.Background(), params)
+}
+
+// ListReactionsContext returns information about the items a user reacted to with a custom context.
+func (api *Client) ListReactionsContext(ctx context.Context, params ListReactionsParameters) ([]ReactedItem, *Paging, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+ if params.User != DEFAULT_REACTIONS_USER {
+ values.Add("user", params.User)
+ }
+ if params.Count != DEFAULT_REACTIONS_COUNT {
+ values.Add("count", strconv.Itoa(params.Count))
+ }
+ if params.Page != DEFAULT_REACTIONS_PAGE {
+ values.Add("page", strconv.Itoa(params.Page))
+ }
+ if params.Full != DEFAULT_REACTIONS_FULL {
+ values.Add("full", strconv.FormatBool(params.Full))
+ }
+
+ response := &listReactionsResponseFull{}
+ err := api.postMethod(ctx, "reactions.list", values, response)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if err := response.Err(); err != nil {
+ return nil, nil, err
+ }
+
+ return response.extractReactedItems(), &response.Paging, nil
+}
diff --git a/vendor/github.com/slack-go/slack/reminders.go b/vendor/github.com/slack-go/slack/reminders.go
new file mode 100644
index 00000000..9b905387
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/reminders.go
@@ -0,0 +1,75 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "time"
+)
+
+type Reminder struct {
+ ID string `json:"id"`
+ Creator string `json:"creator"`
+ User string `json:"user"`
+ Text string `json:"text"`
+ Recurring bool `json:"recurring"`
+ Time time.Time `json:"time"`
+ CompleteTS int `json:"complete_ts"`
+}
+
+type reminderResp struct {
+ SlackResponse
+ Reminder Reminder `json:"reminder"`
+}
+
+func (api *Client) doReminder(ctx context.Context, path string, values url.Values) (*Reminder, error) {
+ response := &reminderResp{}
+ if err := api.postMethod(ctx, path, values, response); err != nil {
+ return nil, err
+ }
+ return &response.Reminder, response.Err()
+}
+
+// AddChannelReminder adds a reminder for a channel.
+//
+// See https://api.slack.com/methods/reminders.add (NOTE: the ability to set
+// reminders on a channel is currently undocumented but has been tested to
+// work)
+func (api *Client) AddChannelReminder(channelID, text, time string) (*Reminder, error) {
+ values := url.Values{
+ "token": {api.token},
+ "text": {text},
+ "time": {time},
+ "channel": {channelID},
+ }
+ return api.doReminder(context.Background(), "reminders.add", values)
+}
+
+// AddUserReminder adds a reminder for a user.
+//
+// See https://api.slack.com/methods/reminders.add (NOTE: the ability to set
+// reminders on a channel is currently undocumented but has been tested to
+// work)
+func (api *Client) AddUserReminder(userID, text, time string) (*Reminder, error) {
+ values := url.Values{
+ "token": {api.token},
+ "text": {text},
+ "time": {time},
+ "user": {userID},
+ }
+ return api.doReminder(context.Background(), "reminders.add", values)
+}
+
+// DeleteReminder deletes an existing reminder.
+//
+// See https://api.slack.com/methods/reminders.delete
+func (api *Client) DeleteReminder(id string) error {
+ values := url.Values{
+ "token": {api.token},
+ "reminder": {id},
+ }
+ response := &SlackResponse{}
+ if err := api.postMethod(context.Background(), "reminders.delete", values, response); err != nil {
+ return err
+ }
+ return response.Err()
+}
diff --git a/vendor/github.com/slack-go/slack/rtm.go b/vendor/github.com/slack-go/slack/rtm.go
new file mode 100644
index 00000000..ef6ba343
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/rtm.go
@@ -0,0 +1,131 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "sync"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+const (
+ websocketDefaultTimeout = 10 * time.Second
+ defaultPingInterval = 30 * time.Second
+)
+
+const (
+ rtmEventTypeAck = ""
+ rtmEventTypeHello = "hello"
+ rtmEventTypeGoodbye = "goodbye"
+ rtmEventTypePong = "pong"
+ rtmEventTypeDesktopNotification = "desktop_notification"
+)
+
+// StartRTM calls the "rtm.start" endpoint and returns the provided URL and the full Info block.
+//
+// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
+func (api *Client) StartRTM() (info *Info, websocketURL string, err error) {
+ ctx, cancel := context.WithTimeout(context.Background(), websocketDefaultTimeout)
+ defer cancel()
+
+ return api.StartRTMContext(ctx)
+}
+
+// StartRTMContext calls the "rtm.start" endpoint and returns the provided URL and the full Info block with a custom context.
+//
+// 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 = api.postMethod(ctx, "rtm.start", url.Values{"token": {api.token}}, response)
+ if err != nil {
+ return nil, "", err
+ }
+
+ api.Debugln("Using URL:", response.Info.URL)
+ return &response.Info, response.Info.URL, response.Err()
+}
+
+// ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block.
+//
+// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
+func (api *Client) ConnectRTM() (info *Info, websocketURL string, err error) {
+ ctx, cancel := context.WithTimeout(context.Background(), websocketDefaultTimeout)
+ defer cancel()
+
+ return api.ConnectRTMContext(ctx)
+}
+
+// ConnectRTMContext calls the "rtm.connect" endpoint and returns the
+// provided URL and the compact Info block with a custom context.
+//
+// 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 = 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
+ }
+
+ api.Debugln("Using URL:", response.Info.URL)
+ return &response.Info, response.Info.URL, response.Err()
+}
+
+// RTMOption options for the managed RTM.
+type RTMOption func(*RTM)
+
+// RTMOptionUseStart as of 11th July 2017 you should prefer setting this to false, see:
+// https://api.slack.com/changelog/2017-04-start-using-rtm-connect-and-stop-using-rtm-start
+func RTMOptionUseStart(b bool) RTMOption {
+ return func(rtm *RTM) {
+ rtm.useRTMStart = b
+ }
+}
+
+// RTMOptionDialer takes a gorilla websocket Dialer and uses it as the
+// Dialer when opening the websocket for the RTM connection.
+func RTMOptionDialer(d *websocket.Dialer) RTMOption {
+ return func(rtm *RTM) {
+ rtm.dialer = d
+ }
+}
+
+// RTMOptionPingInterval determines how often to deliver a ping message to slack.
+func RTMOptionPingInterval(d time.Duration) RTMOption {
+ return func(rtm *RTM) {
+ rtm.pingInterval = d
+ rtm.resetDeadman()
+ }
+}
+
+// RTMOptionConnParams installs parameters to embed into the connection URL.
+func RTMOptionConnParams(connParams url.Values) RTMOption {
+ return func(rtm *RTM) {
+ rtm.connParams = connParams
+ }
+}
+
+// NewRTM returns a RTM, which provides a fully managed connection to
+// Slack's websocket-based Real-Time Messaging protocol.
+func (api *Client) NewRTM(options ...RTMOption) *RTM {
+ result := &RTM{
+ Client: *api,
+ 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{}),
+ disconnectedm: &sync.Once{},
+ forcePing: make(chan bool),
+ idGen: NewSafeID(1),
+ mu: &sync.Mutex{},
+ }
+
+ for _, opt := range options {
+ opt(result)
+ }
+
+ return result
+}
diff --git a/vendor/github.com/slack-go/slack/search.go b/vendor/github.com/slack-go/slack/search.go
new file mode 100644
index 00000000..de6b40ac
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/search.go
@@ -0,0 +1,156 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "strconv"
+)
+
+const (
+ DEFAULT_SEARCH_SORT = "score"
+ DEFAULT_SEARCH_SORT_DIR = "desc"
+ DEFAULT_SEARCH_HIGHLIGHT = false
+ DEFAULT_SEARCH_COUNT = 20
+ DEFAULT_SEARCH_PAGE = 1
+)
+
+type SearchParameters struct {
+ Sort string
+ SortDirection string
+ Highlight bool
+ Count int
+ Page int
+}
+
+type CtxChannel struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ IsExtShared bool `json:"is_ext_shared"`
+ IsMPIM bool `json:"is_mpim"`
+ ISOrgShared bool `json:"is_org_shared"`
+ IsPendingExtShared bool `json:"is_pending_ext_shared"`
+ IsPrivate bool `json:"is_private"`
+ IsShared bool `json:"is_shared"`
+}
+
+type CtxMessage struct {
+ User string `json:"user"`
+ Username string `json:"username"`
+ Text string `json:"text"`
+ Timestamp string `json:"ts"`
+ Type string `json:"type"`
+}
+
+type SearchMessage struct {
+ Type string `json:"type"`
+ Channel CtxChannel `json:"channel"`
+ 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"`
+ Previous CtxMessage `json:"previous"`
+ Previous2 CtxMessage `json:"previous_2"`
+ Next CtxMessage `json:"next"`
+ Next2 CtxMessage `json:"next_2"`
+}
+
+type SearchMessages struct {
+ Matches []SearchMessage `json:"matches"`
+ Paging `json:"paging"`
+ Pagination `json:"pagination"`
+ Total int `json:"total"`
+}
+
+type SearchFiles struct {
+ Matches []File `json:"matches"`
+ Paging `json:"paging"`
+ Pagination `json:"pagination"`
+ Total int `json:"total"`
+}
+
+type searchResponseFull struct {
+ Query string `json:"query"`
+ SearchMessages `json:"messages"`
+ SearchFiles `json:"files"`
+ SlackResponse
+}
+
+func NewSearchParameters() SearchParameters {
+ return SearchParameters{
+ Sort: DEFAULT_SEARCH_SORT,
+ SortDirection: DEFAULT_SEARCH_SORT_DIR,
+ Highlight: DEFAULT_SEARCH_HIGHLIGHT,
+ Count: DEFAULT_SEARCH_COUNT,
+ Page: DEFAULT_SEARCH_PAGE,
+ }
+}
+
+func (api *Client) _search(ctx context.Context, path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) {
+ values := url.Values{
+ "token": {api.token},
+ "query": {query},
+ }
+ if params.Sort != DEFAULT_SEARCH_SORT {
+ values.Add("sort", params.Sort)
+ }
+ if params.SortDirection != DEFAULT_SEARCH_SORT_DIR {
+ values.Add("sort_dir", params.SortDirection)
+ }
+ if params.Highlight != DEFAULT_SEARCH_HIGHLIGHT {
+ values.Add("highlight", strconv.Itoa(1))
+ }
+ if params.Count != DEFAULT_SEARCH_COUNT {
+ values.Add("count", strconv.Itoa(params.Count))
+ }
+ if params.Page != DEFAULT_SEARCH_PAGE {
+ values.Add("page", strconv.Itoa(params.Page))
+ }
+
+ response = &searchResponseFull{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, response.Err()
+
+}
+
+func (api *Client) Search(query string, params SearchParameters) (*SearchMessages, *SearchFiles, error) {
+ return api.SearchContext(context.Background(), query, params)
+}
+
+func (api *Client) SearchContext(ctx context.Context, query string, params SearchParameters) (*SearchMessages, *SearchFiles, error) {
+ response, err := api._search(ctx, "search.all", query, params, true, true)
+ if err != nil {
+ return nil, nil, err
+ }
+ return &response.SearchMessages, &response.SearchFiles, nil
+}
+
+func (api *Client) SearchFiles(query string, params SearchParameters) (*SearchFiles, error) {
+ return api.SearchFilesContext(context.Background(), query, params)
+}
+
+func (api *Client) SearchFilesContext(ctx context.Context, query string, params SearchParameters) (*SearchFiles, error) {
+ response, err := api._search(ctx, "search.files", query, params, true, false)
+ if err != nil {
+ return nil, err
+ }
+ return &response.SearchFiles, nil
+}
+
+func (api *Client) SearchMessages(query string, params SearchParameters) (*SearchMessages, error) {
+ return api.SearchMessagesContext(context.Background(), query, params)
+}
+
+func (api *Client) SearchMessagesContext(ctx context.Context, query string, params SearchParameters) (*SearchMessages, error) {
+ response, err := api._search(ctx, "search.messages", query, params, false, true)
+ if err != nil {
+ return nil, err
+ }
+ return &response.SearchMessages, nil
+}
diff --git a/vendor/github.com/slack-go/slack/security.go b/vendor/github.com/slack-go/slack/security.go
new file mode 100644
index 00000000..dbe8fb2d
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/security.go
@@ -0,0 +1,100 @@
+package slack
+
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "hash"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Signature headers
+const (
+ hSignature = "X-Slack-Signature"
+ hTimestamp = "X-Slack-Request-Timestamp"
+)
+
+// SecretsVerifier contains the information needed to verify that the request comes from Slack
+type SecretsVerifier struct {
+ signature []byte
+ hmac hash.Hash
+}
+
+func unsafeSignatureVerifier(header http.Header, secret string) (_ SecretsVerifier, err error) {
+ var (
+ bsignature []byte
+ )
+
+ signature := header.Get(hSignature)
+ stimestamp := header.Get(hTimestamp)
+
+ if signature == "" || stimestamp == "" {
+ return SecretsVerifier{}, ErrMissingHeaders
+ }
+
+ if bsignature, err = hex.DecodeString(strings.TrimPrefix(signature, "v0=")); err != nil {
+ return SecretsVerifier{}, err
+ }
+
+ hash := hmac.New(sha256.New, []byte(secret))
+ if _, err = hash.Write([]byte(fmt.Sprintf("v0:%s:", stimestamp))); err != nil {
+ return SecretsVerifier{}, err
+ }
+
+ return SecretsVerifier{
+ signature: bsignature,
+ hmac: hash,
+ }, nil
+}
+
+// NewSecretsVerifier returns a SecretsVerifier object in exchange for an http.Header object and signing secret
+func NewSecretsVerifier(header http.Header, secret string) (sv SecretsVerifier, err error) {
+ var (
+ timestamp int64
+ )
+
+ stimestamp := header.Get(hTimestamp)
+
+ if sv, err = unsafeSignatureVerifier(header, secret); err != nil {
+ return SecretsVerifier{}, err
+ }
+
+ if timestamp, err = strconv.ParseInt(stimestamp, 10, 64); err != nil {
+ return SecretsVerifier{}, err
+ }
+
+ diff := absDuration(time.Since(time.Unix(timestamp, 0)))
+ if diff > 5*time.Minute {
+ return SecretsVerifier{}, ErrExpiredTimestamp
+ }
+
+ return sv, err
+}
+
+func (v *SecretsVerifier) Write(body []byte) (n int, err error) {
+ return v.hmac.Write(body)
+}
+
+// Ensure compares the signature sent from Slack with the actual computed hash to judge validity
+func (v SecretsVerifier) Ensure() error {
+ computed := v.hmac.Sum(nil)
+ // use hmac.Equal prevent leaking timing information.
+ if hmac.Equal(computed, v.signature) {
+ return nil
+ }
+
+ return fmt.Errorf("Expected signing signature: %s, but computed: %s", hex.EncodeToString(v.signature), hex.EncodeToString(computed))
+}
+
+func abs64(n int64) int64 {
+ y := n >> 63
+ return (n ^ y) - y
+}
+
+func absDuration(n time.Duration) time.Duration {
+ return time.Duration(abs64(int64(n)))
+}
diff --git a/vendor/github.com/slack-go/slack/slack.go b/vendor/github.com/slack-go/slack/slack.go
new file mode 100644
index 00000000..d342a3e8
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/slack.go
@@ -0,0 +1,153 @@
+package slack
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "net/http"
+ "net/url"
+ "os"
+)
+
+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 {
+ Do(*http.Request) (*http.Response, error)
+}
+
+// ResponseMetadata holds pagination metadata
+type ResponseMetadata struct {
+ Cursor string `json:"next_cursor"`
+}
+
+func (t *ResponseMetadata) initialize() *ResponseMetadata {
+ if t != nil {
+ return t
+ }
+
+ return &ResponseMetadata{}
+}
+
+// AuthTestResponse ...
+type AuthTestResponse struct {
+ URL string `json:"url"`
+ Team string `json:"team"`
+ 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 {
+ SlackResponse
+ AuthTestResponse
+}
+
+// Client for the slack api.
+type ParamOption func(*url.Values)
+
+type Client struct {
+ token string
+ endpoint string
+ debug bool
+ log ilogger
+ httpclient httpClient
+}
+
+// Option defines an option for a Client
+type Option func(*Client)
+
+// OptionHTTPClient - provide a custom http client to the slack client.
+func OptionHTTPClient(client httpClient) func(*Client) {
+ return func(c *Client) {
+ c.httpclient = client
+ }
+}
+
+// OptionDebug enable debugging for the client
+func OptionDebug(b bool) func(*Client) {
+ return func(c *Client) {
+ c.debug = b
+ }
+}
+
+// OptionLog set logging for client.
+func OptionLog(l logger) func(*Client) {
+ return func(c *Client) {
+ c.log = internalLog{logger: l}
+ }
+}
+
+// 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, "slack-go/slack", log.LstdFlags|log.Lshortfile),
+ }
+
+ for _, opt := range options {
+ opt(s)
+ }
+
+ return s
+}
+
+// AuthTest tests if the user is able to do authenticated requests or not
+func (api *Client) AuthTest() (response *AuthTestResponse, error error) {
+ return api.AuthTestContext(context.Background())
+}
+
+// AuthTestContext tests if the user is able to do authenticated requests or not with a custom context
+func (api *Client) AuthTestContext(ctx context.Context) (response *AuthTestResponse, err error) {
+ api.Debugf("Challenging auth...")
+ responseFull := &authTestResponseFull{}
+ err = api.postMethod(ctx, "auth.test", url.Values{"token": {api.token}}, responseFull)
+ if err != nil {
+ return nil, err
+ }
+
+ return &responseFull.AuthTestResponse, responseFull.Err()
+}
+
+// Debugf print a formatted debug line.
+func (api *Client) Debugf(format string, v ...interface{}) {
+ if api.debug {
+ api.log.Output(2, fmt.Sprintf(format, v...))
+ }
+}
+
+// Debugln print a debug line.
+func (api *Client) Debugln(v ...interface{}) {
+ if api.debug {
+ api.log.Output(2, fmt.Sprintln(v...))
+ }
+}
+
+// Debug returns if debug is enabled.
+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/slack-go/slack/slackutilsx/slackutilsx.go b/vendor/github.com/slack-go/slack/slackutilsx/slackutilsx.go
new file mode 100644
index 00000000..1f7b2b8c
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/slackutilsx/slackutilsx.go
@@ -0,0 +1,62 @@
+// Package slackutilsx is a utility package that doesn't promise API stability.
+// its for experimental functionality and utilities.
+package slackutilsx
+
+import (
+ "strings"
+ "unicode/utf8"
+)
+
+// ChannelType the type of channel based on the channelID
+type ChannelType int
+
+func (t ChannelType) String() string {
+ switch t {
+ case CTypeDM:
+ return "Direct"
+ case CTypeGroup:
+ return "Group"
+ case CTypeChannel:
+ return "Channel"
+ default:
+ return "Unknown"
+ }
+}
+
+const (
+ // CTypeUnknown represents channels we cannot properly detect.
+ CTypeUnknown ChannelType = iota
+ // CTypeDM is a private channel between two slack users.
+ CTypeDM
+ // CTypeGroup is a group channel.
+ CTypeGroup
+ // CTypeChannel is a public channel.
+ CTypeChannel
+)
+
+// DetectChannelType converts a channelID to a ChannelType.
+// channelID must not be empty. However, if it is empty, the channel type will default to Unknown.
+func DetectChannelType(channelID string) ChannelType {
+ // intentionally ignore the error and just default to CTypeUnknown
+ switch r, _ := utf8.DecodeRuneInString(channelID); r {
+ case 'C':
+ return CTypeChannel
+ case 'G':
+ return CTypeGroup
+ case 'D':
+ return CTypeDM
+ default:
+ return CTypeUnknown
+ }
+}
+
+// EscapeMessage text
+func EscapeMessage(message string) string {
+ replacer := strings.NewReplacer("&", "&amp;", "<", "&lt;", ">", "&gt;")
+ return replacer.Replace(message)
+}
+
+// Retryable errors return true.
+type Retryable interface {
+ Retryable() bool
+}
diff --git a/vendor/github.com/slack-go/slack/slash.go b/vendor/github.com/slack-go/slack/slash.go
new file mode 100644
index 00000000..f62065a2
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/slash.go
@@ -0,0 +1,53 @@
+package slack
+
+import (
+ "net/http"
+)
+
+// SlashCommand contains information about a request of the slash command
+type SlashCommand struct {
+ Token string `json:"token"`
+ TeamID string `json:"team_id"`
+ TeamDomain string `json:"team_domain"`
+ EnterpriseID string `json:"enterprise_id,omitempty"`
+ EnterpriseName string `json:"enterprise_name,omitempty"`
+ ChannelID string `json:"channel_id"`
+ ChannelName string `json:"channel_name"`
+ UserID string `json:"user_id"`
+ UserName string `json:"user_name"`
+ Command string `json:"command"`
+ Text string `json:"text"`
+ ResponseURL string `json:"response_url"`
+ TriggerID string `json:"trigger_id"`
+}
+
+// SlashCommandParse will parse the request of the slash command
+func SlashCommandParse(r *http.Request) (s SlashCommand, err error) {
+ if err = r.ParseForm(); err != nil {
+ return s, err
+ }
+ s.Token = r.PostForm.Get("token")
+ s.TeamID = r.PostForm.Get("team_id")
+ s.TeamDomain = r.PostForm.Get("team_domain")
+ s.EnterpriseID = r.PostForm.Get("enterprise_id")
+ s.EnterpriseName = r.PostForm.Get("enterprise_name")
+ s.ChannelID = r.PostForm.Get("channel_id")
+ s.ChannelName = r.PostForm.Get("channel_name")
+ s.UserID = r.PostForm.Get("user_id")
+ s.UserName = r.PostForm.Get("user_name")
+ s.Command = r.PostForm.Get("command")
+ s.Text = r.PostForm.Get("text")
+ s.ResponseURL = r.PostForm.Get("response_url")
+ s.TriggerID = r.PostForm.Get("trigger_id")
+ return s, nil
+}
+
+// ValidateToken validates verificationTokens
+func (s SlashCommand) ValidateToken(verificationTokens ...string) bool {
+ for _, token := range verificationTokens {
+ if s.Token == token {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/slack-go/slack/stars.go b/vendor/github.com/slack-go/slack/stars.go
new file mode 100644
index 00000000..52967604
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/stars.go
@@ -0,0 +1,263 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "strconv"
+ "time"
+)
+
+const (
+ DEFAULT_STARS_USER = ""
+ DEFAULT_STARS_COUNT = 100
+ DEFAULT_STARS_PAGE = 1
+)
+
+type StarsParameters struct {
+ User string
+ Count int
+ Page int
+}
+
+type StarredItem Item
+
+type listResponseFull struct {
+ Items []Item `json:"items"`
+ Paging `json:"paging"`
+ SlackResponse
+}
+
+// NewStarsParameters initialises StarsParameters with default values
+func NewStarsParameters() StarsParameters {
+ return StarsParameters{
+ User: DEFAULT_STARS_USER,
+ Count: DEFAULT_STARS_COUNT,
+ Page: DEFAULT_STARS_PAGE,
+ }
+}
+
+// AddStar stars an item in a channel
+func (api *Client) AddStar(channel string, item ItemRef) error {
+ return api.AddStarContext(context.Background(), channel, item)
+}
+
+// AddStarContext stars an item in a channel with a custom context
+func (api *Client) AddStarContext(ctx context.Context, channel string, item ItemRef) error {
+ values := url.Values{
+ "channel": {channel},
+ "token": {api.token},
+ }
+ if item.Timestamp != "" {
+ values.Set("timestamp", item.Timestamp)
+ }
+ if item.File != "" {
+ values.Set("file", item.File)
+ }
+ if item.Comment != "" {
+ values.Set("file_comment", item.Comment)
+ }
+
+ response := &SlackResponse{}
+ if err := api.postMethod(ctx, "stars.add", values, response); err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// RemoveStar removes a starred item from a channel
+func (api *Client) RemoveStar(channel string, item ItemRef) error {
+ return api.RemoveStarContext(context.Background(), channel, item)
+}
+
+// RemoveStarContext removes a starred item from a channel with a custom context
+func (api *Client) RemoveStarContext(ctx context.Context, channel string, item ItemRef) error {
+ values := url.Values{
+ "channel": {channel},
+ "token": {api.token},
+ }
+ if item.Timestamp != "" {
+ values.Set("timestamp", item.Timestamp)
+ }
+ if item.File != "" {
+ values.Set("file", item.File)
+ }
+ if item.Comment != "" {
+ values.Set("file_comment", item.Comment)
+ }
+
+ response := &SlackResponse{}
+ if err := api.postMethod(ctx, "stars.remove", values, response); err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// ListStars returns information about the stars a user added
+func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
+ return api.ListStarsContext(context.Background(), params)
+}
+
+// ListStarsContext returns information about the stars a user added with a custom context
+func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) ([]Item, *Paging, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+ if params.User != DEFAULT_STARS_USER {
+ values.Add("user", params.User)
+ }
+ if params.Count != DEFAULT_STARS_COUNT {
+ values.Add("count", strconv.Itoa(params.Count))
+ }
+ if params.Page != DEFAULT_STARS_PAGE {
+ values.Add("page", strconv.Itoa(params.Page))
+ }
+
+ response := &listResponseFull{}
+ err := api.postMethod(ctx, "stars.list", values, response)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if err := response.Err(); err != nil {
+ return nil, nil, err
+ }
+
+ return response.Items, &response.Paging, nil
+}
+
+// GetStarred returns a list of StarredItem items.
+//
+// The user then has to iterate over them and figure out what they should
+// be looking at according to what is in the Type.
+// for _, item := range items {
+// switch c.Type {
+// case "file_comment":
+// log.Println(c.Comment)
+// case "file":
+// ...
+//
+// }
+// This function still exists to maintain backwards compatibility.
+// I exposed it as returning []StarredItem, so it shall stay as StarredItem
+func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) {
+ return api.GetStarredContext(context.Background(), params)
+}
+
+// GetStarredContext returns a list of StarredItem items with a custom context
+//
+// For more details see GetStarred
+func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters) ([]StarredItem, *Paging, error) {
+ items, paging, err := api.ListStarsContext(ctx, params)
+ if err != nil {
+ return nil, nil, err
+ }
+ starredItems := make([]StarredItem, len(items))
+ for i, item := range items {
+ starredItems[i] = StarredItem(item)
+ }
+ return starredItems, paging, nil
+}
+
+type listResponsePaginated struct {
+ Items []Item `json:"items"`
+ SlackResponse
+ Metadata ResponseMetadata `json:"response_metadata"`
+}
+
+// StarredItemPagination allows for paginating over the starred items
+type StarredItemPagination struct {
+ Items []Item
+ limit int
+ previousResp *ResponseMetadata
+ c *Client
+}
+
+// ListStarsOption options for the GetUsers method call.
+type ListStarsOption func(*StarredItemPagination)
+
+// ListAllStars returns the complete list of starred items
+func (api *Client) ListAllStars() ([]Item, error) {
+ return api.ListAllStarsContext(context.Background())
+}
+
+// ListAllStarsContext returns the list of users (with their detailed information) with a custom context
+func (api *Client) ListAllStarsContext(ctx context.Context) (results []Item, err error) {
+ p := api.ListStarsPaginated()
+ for err == nil {
+ p, err = p.next(ctx)
+ if err == nil {
+ results = append(results, p.Items...)
+ } 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)
+}
+
+// ListStarsPaginated fetches users in a paginated fashion, see ListStarsPaginationContext for usage.
+func (api *Client) ListStarsPaginated(options ...ListStarsOption) StarredItemPagination {
+ return newStarPagination(api, options...)
+}
+
+func newStarPagination(c *Client, options ...ListStarsOption) (sip StarredItemPagination) {
+ sip = StarredItemPagination{
+ c: c,
+ limit: 200, // per slack api documentation.
+ }
+
+ for _, opt := range options {
+ opt(&sip)
+ }
+
+ return sip
+}
+
+// done checks if the pagination has completed
+func (StarredItemPagination) done(err error) bool {
+ return err == errPaginationComplete
+}
+
+// done checks if pagination failed.
+func (t StarredItemPagination) failure(err error) error {
+ if t.done(err) {
+ return nil
+ }
+
+ return err
+}
+
+// next gets the next list of starred items based on the cursor value
+func (t StarredItemPagination) next(ctx context.Context) (_ StarredItemPagination, err error) {
+ var (
+ resp *listResponsePaginated
+ )
+
+ if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") {
+ return t, errPaginationComplete
+ }
+
+ t.previousResp = t.previousResp.initialize()
+
+ values := url.Values{
+ "limit": {strconv.Itoa(t.limit)},
+ "token": {t.c.token},
+ "cursor": {t.previousResp.Cursor},
+ }
+
+ if err = t.c.postMethod(ctx, "stars.list", values, &resp); err != nil {
+ return t, err
+ }
+
+ t.previousResp = &resp.Metadata
+ t.Items = resp.Items
+
+ return t, nil
+}
diff --git a/vendor/github.com/slack-go/slack/team.go b/vendor/github.com/slack-go/slack/team.go
new file mode 100644
index 00000000..029e2b5b
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/team.go
@@ -0,0 +1,167 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "strconv"
+)
+
+const (
+ DEFAULT_LOGINS_COUNT = 100
+ DEFAULT_LOGINS_PAGE = 1
+)
+
+type TeamResponse struct {
+ Team TeamInfo `json:"team"`
+ SlackResponse
+}
+
+type TeamInfo struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Domain string `json:"domain"`
+ EmailDomain string `json:"email_domain"`
+ Icon map[string]interface{} `json:"icon"`
+}
+
+type LoginResponse struct {
+ Logins []Login `json:"logins"`
+ Paging `json:"paging"`
+ SlackResponse
+}
+
+type Login struct {
+ UserID string `json:"user_id"`
+ Username string `json:"username"`
+ DateFirst int `json:"date_first"`
+ DateLast int `json:"date_last"`
+ Count int `json:"count"`
+ IP string `json:"ip"`
+ UserAgent string `json:"user_agent"`
+ ISP string `json:"isp"`
+ Country string `json:"country"`
+ Region string `json:"region"`
+}
+
+type BillableInfoResponse struct {
+ BillableInfo map[string]BillingActive `json:"billable_info"`
+ SlackResponse
+}
+
+type BillingActive struct {
+ BillingActive bool `json:"billing_active"`
+}
+
+// AccessLogParameters contains all the parameters necessary (including the optional ones) for a GetAccessLogs() request
+type AccessLogParameters struct {
+ Count int
+ Page int
+}
+
+// NewAccessLogParameters provides an instance of AccessLogParameters with all the sane default values set
+func NewAccessLogParameters() AccessLogParameters {
+ return AccessLogParameters{
+ Count: DEFAULT_LOGINS_COUNT,
+ Page: DEFAULT_LOGINS_PAGE,
+ }
+}
+
+func (api *Client) teamRequest(ctx context.Context, path string, values url.Values) (*TeamResponse, error) {
+ response := &TeamResponse{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, response.Err()
+}
+
+func (api *Client) billableInfoRequest(ctx context.Context, path string, values url.Values) (map[string]BillingActive, error) {
+ response := &BillableInfoResponse{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response.BillableInfo, response.Err()
+}
+
+func (api *Client) accessLogsRequest(ctx context.Context, path string, values url.Values) (*LoginResponse, error) {
+ response := &LoginResponse{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+ return response, response.Err()
+}
+
+// GetTeamInfo gets the Team Information of the user
+func (api *Client) GetTeamInfo() (*TeamInfo, error) {
+ return api.GetTeamInfoContext(context.Background())
+}
+
+// GetTeamInfoContext gets the Team Information of the user with a custom context
+func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+
+ response, err := api.teamRequest(ctx, "team.info", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.Team, nil
+}
+
+// GetAccessLogs retrieves a page of logins according to the parameters given
+func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging, error) {
+ return api.GetAccessLogsContext(context.Background(), params)
+}
+
+// GetAccessLogsContext retrieves a page of logins according to the parameters given with a custom context
+func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogParameters) ([]Login, *Paging, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+ if params.Count != DEFAULT_LOGINS_COUNT {
+ values.Add("count", strconv.Itoa(params.Count))
+ }
+ if params.Page != DEFAULT_LOGINS_PAGE {
+ values.Add("page", strconv.Itoa(params.Page))
+ }
+
+ 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 api.billableInfoRequest(ctx, "team.billableInfo", values)
+}
+
+// GetBillableInfoForTeam returns the billing_active status of all users on the team.
+func (api *Client) GetBillableInfoForTeam() (map[string]BillingActive, error) {
+ return api.GetBillableInfoForTeamContext(context.Background())
+}
+
+// GetBillableInfoForTeamContext returns the billing_active status of all users on the team with a custom context
+func (api *Client) GetBillableInfoForTeamContext(ctx context.Context) (map[string]BillingActive, error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+
+ return api.billableInfoRequest(ctx, "team.billableInfo", values)
+}
diff --git a/vendor/github.com/slack-go/slack/usergroups.go b/vendor/github.com/slack-go/slack/usergroups.go
new file mode 100644
index 00000000..9417f817
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/usergroups.go
@@ -0,0 +1,258 @@
+package slack
+
+import (
+ "context"
+ "net/url"
+ "strings"
+)
+
+// UserGroup contains all the information of a user group
+type UserGroup struct {
+ ID string `json:"id"`
+ TeamID string `json:"team_id"`
+ IsUserGroup bool `json:"is_usergroup"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Handle string `json:"handle"`
+ IsExternal bool `json:"is_external"`
+ DateCreate JSONTime `json:"date_create"`
+ DateUpdate JSONTime `json:"date_update"`
+ DateDelete JSONTime `json:"date_delete"`
+ AutoType string `json:"auto_type"`
+ CreatedBy string `json:"created_by"`
+ UpdatedBy string `json:"updated_by"`
+ DeletedBy string `json:"deleted_by"`
+ Prefs UserGroupPrefs `json:"prefs"`
+ UserCount int `json:"user_count"`
+ Users []string `json:"users"`
+}
+
+// UserGroupPrefs contains default channels and groups (private channels)
+type UserGroupPrefs struct {
+ Channels []string `json:"channels"`
+ Groups []string `json:"groups"`
+}
+
+type userGroupResponseFull struct {
+ UserGroups []UserGroup `json:"usergroups"`
+ UserGroup UserGroup `json:"usergroup"`
+ Users []string `json:"users"`
+ SlackResponse
+}
+
+func (api *Client) userGroupRequest(ctx context.Context, path string, values url.Values) (*userGroupResponseFull, error) {
+ response := &userGroupResponseFull{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, response.Err()
+}
+
+// CreateUserGroup creates a new user group
+func (api *Client) CreateUserGroup(userGroup UserGroup) (UserGroup, error) {
+ return api.CreateUserGroupContext(context.Background(), userGroup)
+}
+
+// CreateUserGroupContext creates a new user group with a custom context
+func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
+ values := url.Values{
+ "token": {api.token},
+ "name": {userGroup.Name},
+ }
+
+ if userGroup.Handle != "" {
+ values["handle"] = []string{userGroup.Handle}
+ }
+
+ if userGroup.Description != "" {
+ values["description"] = []string{userGroup.Description}
+ }
+
+ if len(userGroup.Prefs.Channels) > 0 {
+ values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")}
+ }
+
+ response, err := api.userGroupRequest(ctx, "usergroups.create", values)
+ if err != nil {
+ return UserGroup{}, err
+ }
+ return response.UserGroup, nil
+}
+
+// DisableUserGroup disables an existing user group
+func (api *Client) DisableUserGroup(userGroup string) (UserGroup, error) {
+ return api.DisableUserGroupContext(context.Background(), userGroup)
+}
+
+// DisableUserGroupContext disables an existing user group with a custom context
+func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
+ values := url.Values{
+ "token": {api.token},
+ "usergroup": {userGroup},
+ }
+
+ response, err := api.userGroupRequest(ctx, "usergroups.disable", values)
+ if err != nil {
+ return UserGroup{}, err
+ }
+ return response.UserGroup, nil
+}
+
+// EnableUserGroup enables an existing user group
+func (api *Client) EnableUserGroup(userGroup string) (UserGroup, error) {
+ return api.EnableUserGroupContext(context.Background(), userGroup)
+}
+
+// EnableUserGroupContext enables an existing user group with a custom context
+func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
+ values := url.Values{
+ "token": {api.token},
+ "usergroup": {userGroup},
+ }
+
+ response, err := api.userGroupRequest(ctx, "usergroups.enable", values)
+ if err != nil {
+ return UserGroup{}, err
+ }
+ return response.UserGroup, nil
+}
+
+// GetUserGroupsOption options for the GetUserGroups method call.
+type GetUserGroupsOption func(*GetUserGroupsParams)
+
+// GetUserGroupsOptionIncludeCount include the number of users in each User Group (default: false)
+func GetUserGroupsOptionIncludeCount(b bool) GetUserGroupsOption {
+ return func(params *GetUserGroupsParams) {
+ params.IncludeCount = b
+ }
+}
+
+// GetUserGroupsOptionIncludeDisabled include disabled User Groups (default: false)
+func GetUserGroupsOptionIncludeDisabled(b bool) GetUserGroupsOption {
+ return func(params *GetUserGroupsParams) {
+ params.IncludeDisabled = b
+ }
+}
+
+// GetUserGroupsOptionIncludeUsers include the list of users for each User Group (default: false)
+func GetUserGroupsOptionIncludeUsers(b bool) GetUserGroupsOption {
+ return func(params *GetUserGroupsParams) {
+ params.IncludeUsers = b
+ }
+}
+
+// GetUserGroupsParams contains arguments for GetUserGroups method call
+type GetUserGroupsParams struct {
+ IncludeCount bool
+ IncludeDisabled bool
+ IncludeUsers bool
+}
+
+// GetUserGroups returns a list of user groups for the team
+func (api *Client) GetUserGroups(options ...GetUserGroupsOption) ([]UserGroup, error) {
+ return api.GetUserGroupsContext(context.Background(), options...)
+}
+
+// GetUserGroupsContext returns a list of user groups for the team with a custom context
+func (api *Client) GetUserGroupsContext(ctx context.Context, options ...GetUserGroupsOption) ([]UserGroup, error) {
+ params := GetUserGroupsParams{}
+
+ for _, opt := range options {
+ opt(&params)
+ }
+
+ values := url.Values{
+ "token": {api.token},
+ }
+ if params.IncludeCount {
+ values.Add("include_count", "true")
+ }
+ if params.IncludeDisabled {
+ values.Add("include_disabled", "true")
+ }
+ if params.IncludeUsers {
+ values.Add("include_users", "true")
+ }
+
+ response, err := api.userGroupRequest(ctx, "usergroups.list", values)
+ if err != nil {
+ return nil, err
+ }
+ return response.UserGroups, nil
+}
+
+// UpdateUserGroup will update an existing user group
+func (api *Client) UpdateUserGroup(userGroup UserGroup) (UserGroup, error) {
+ return api.UpdateUserGroupContext(context.Background(), userGroup)
+}
+
+// UpdateUserGroupContext will update an existing user group with a custom context
+func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
+ values := url.Values{
+ "token": {api.token},
+ "usergroup": {userGroup.ID},
+ }
+
+ if userGroup.Name != "" {
+ values["name"] = []string{userGroup.Name}
+ }
+
+ if userGroup.Handle != "" {
+ values["handle"] = []string{userGroup.Handle}
+ }
+
+ if userGroup.Description != "" {
+ values["description"] = []string{userGroup.Description}
+ }
+
+ if len(userGroup.Prefs.Channels) > 0 {
+ values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")}
+ }
+
+ response, err := api.userGroupRequest(ctx, "usergroups.update", values)
+ if err != nil {
+ return UserGroup{}, err
+ }
+ return response.UserGroup, nil
+}
+
+// GetUserGroupMembers will retrieve the current list of users in a group
+func (api *Client) GetUserGroupMembers(userGroup string) ([]string, error) {
+ return api.GetUserGroupMembersContext(context.Background(), userGroup)
+}
+
+// GetUserGroupMembersContext will retrieve the current list of users in a group with a custom context
+func (api *Client) GetUserGroupMembersContext(ctx context.Context, userGroup string) ([]string, error) {
+ values := url.Values{
+ "token": {api.token},
+ "usergroup": {userGroup},
+ }
+
+ response, err := api.userGroupRequest(ctx, "usergroups.users.list", values)
+ if err != nil {
+ return []string{}, err
+ }
+ return response.Users, nil
+}
+
+// UpdateUserGroupMembers will update the members of an existing user group
+func (api *Client) UpdateUserGroupMembers(userGroup string, members string) (UserGroup, error) {
+ return api.UpdateUserGroupMembersContext(context.Background(), userGroup, members)
+}
+
+// UpdateUserGroupMembersContext will update the members of an existing user group with a custom context
+func (api *Client) UpdateUserGroupMembersContext(ctx context.Context, userGroup string, members string) (UserGroup, error) {
+ values := url.Values{
+ "token": {api.token},
+ "usergroup": {userGroup},
+ "users": {members},
+ }
+
+ response, err := api.userGroupRequest(ctx, "usergroups.users.update", values)
+ if err != nil {
+ return UserGroup{}, err
+ }
+ return response.UserGroup, nil
+}
diff --git a/vendor/github.com/slack-go/slack/users.go b/vendor/github.com/slack-go/slack/users.go
new file mode 100644
index 00000000..6001e0fa
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/users.go
@@ -0,0 +1,597 @@
+package slack
+
+import (
+ "context"
+ "encoding/json"
+ "net/url"
+ "strconv"
+ "time"
+)
+
+const (
+ DEFAULT_USER_PHOTO_CROP_X = -1
+ DEFAULT_USER_PHOTO_CROP_Y = -1
+ DEFAULT_USER_PHOTO_CROP_W = -1
+)
+
+// UserProfile contains all the information details of a given user
+type UserProfile struct {
+ FirstName string `json:"first_name"`
+ LastName string `json:"last_name"`
+ RealName string `json:"real_name"`
+ RealNameNormalized string `json:"real_name_normalized"`
+ DisplayName string `json:"display_name"`
+ DisplayNameNormalized string `json:"display_name_normalized"`
+ Email string `json:"email"`
+ Skype string `json:"skype"`
+ Phone string `json:"phone"`
+ Image24 string `json:"image_24"`
+ Image32 string `json:"image_32"`
+ Image48 string `json:"image_48"`
+ Image72 string `json:"image_72"`
+ Image192 string `json:"image_192"`
+ ImageOriginal string `json:"image_original"`
+ Title string `json:"title"`
+ BotID string `json:"bot_id,omitempty"`
+ 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"`
+}
+
+// UserProfileCustomFields represents user profile's custom fields.
+// Slack API's response data type is inconsistent so we use the struct.
+// For detail, please see below.
+// https://github.com/slack-go/slack/pull/298#discussion_r185159233
+type UserProfileCustomFields struct {
+ fields map[string]UserProfileCustomField
+}
+
+// UnmarshalJSON is the implementation of the json.Unmarshaler interface.
+func (fields *UserProfileCustomFields) UnmarshalJSON(b []byte) error {
+ // https://github.com/slack-go/slack/pull/298#discussion_r185159233
+ if string(b) == "[]" {
+ return nil
+ }
+ return json.Unmarshal(b, &fields.fields)
+}
+
+// MarshalJSON is the implementation of the json.Marshaler interface.
+func (fields UserProfileCustomFields) MarshalJSON() ([]byte, error) {
+ if len(fields.fields) == 0 {
+ return []byte("[]"), nil
+ }
+ return json.Marshal(fields.fields)
+}
+
+// ToMap returns a map of custom fields.
+func (fields *UserProfileCustomFields) ToMap() map[string]UserProfileCustomField {
+ return fields.fields
+}
+
+// Len returns the number of custom fields.
+func (fields *UserProfileCustomFields) Len() int {
+ return len(fields.fields)
+}
+
+// SetMap sets a map of custom fields.
+func (fields *UserProfileCustomFields) SetMap(m map[string]UserProfileCustomField) {
+ fields.fields = m
+}
+
+// FieldsMap returns a map of custom fields.
+func (profile *UserProfile) FieldsMap() map[string]UserProfileCustomField {
+ return profile.Fields.ToMap()
+}
+
+// SetFieldsMap sets a map of custom fields.
+func (profile *UserProfile) SetFieldsMap(m map[string]UserProfileCustomField) {
+ profile.Fields.SetMap(m)
+}
+
+// UserProfileCustomField represents a custom user profile field
+type UserProfileCustomField struct {
+ Value string `json:"value"`
+ Alt string `json:"alt"`
+ Label string `json:"label"`
+}
+
+// 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"`
+ 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
+type UserPresence struct {
+ Presence string `json:"presence,omitempty"`
+ Online bool `json:"online,omitempty"`
+ AutoAway bool `json:"auto_away,omitempty"`
+ ManualAway bool `json:"manual_away,omitempty"`
+ ConnectionCount int `json:"connection_count,omitempty"`
+ LastActivity JSONTime `json:"last_activity,omitempty"`
+}
+
+type UserIdentityResponse struct {
+ User UserIdentity `json:"user"`
+ Team TeamIdentity `json:"team"`
+ SlackResponse
+}
+
+type UserIdentity struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Email string `json:"email"`
+ Image24 string `json:"image_24"`
+ Image32 string `json:"image_32"`
+ Image48 string `json:"image_48"`
+ Image72 string `json:"image_72"`
+ Image192 string `json:"image_192"`
+ 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"`
+ Domain string `json:"domain"`
+ Image34 string `json:"image_34"`
+ Image44 string `json:"image_44"`
+ Image68 string `json:"image_68"`
+ Image88 string `json:"image_88"`
+ Image102 string `json:"image_102"`
+ Image132 string `json:"image_132"`
+ Image230 string `json:"image_230"`
+ ImageDefault bool `json:"image_default"`
+ ImageOriginal string `json:"image_original"`
+}
+
+type userResponseFull struct {
+ Members []User `json:"members,omitempty"`
+ User `json:"user,omitempty"`
+ UserPresence
+ SlackResponse
+ Metadata ResponseMetadata `json:"response_metadata"`
+}
+
+type UserSetPhotoParams struct {
+ CropX int
+ CropY int
+ CropW int
+}
+
+func NewUserSetPhotoParams() UserSetPhotoParams {
+ return UserSetPhotoParams{
+ CropX: DEFAULT_USER_PHOTO_CROP_X,
+ CropY: DEFAULT_USER_PHOTO_CROP_Y,
+ CropW: DEFAULT_USER_PHOTO_CROP_W,
+ }
+}
+
+func (api *Client) userRequest(ctx context.Context, path string, values url.Values) (*userResponseFull, error) {
+ response := &userResponseFull{}
+ err := api.postMethod(ctx, path, values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ return response, response.Err()
+}
+
+// GetUserPresence will retrieve the current presence status of given user.
+func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
+ return api.GetUserPresenceContext(context.Background(), user)
+}
+
+// GetUserPresenceContext will retrieve the current presence status of given user with a custom context.
+func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*UserPresence, error) {
+ values := url.Values{
+ "token": {api.token},
+ "user": {user},
+ }
+
+ response, err := api.userRequest(ctx, "users.getPresence", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.UserPresence, nil
+}
+
+// GetUserInfo will retrieve the complete user information
+func (api *Client) GetUserInfo(user string) (*User, error) {
+ return api.GetUserInfoContext(context.Background(), user)
+}
+
+// GetUserInfoContext will retrieve the complete user information with a custom context
+func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, error) {
+ values := url.Values{
+ "token": {api.token},
+ "user": {user},
+ "include_locale": {strconv.FormatBool(true)},
+ }
+
+ response, err := api.userRequest(ctx, "users.info", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.User, nil
+}
+
+// GetUsersOption options for the GetUsers method call.
+type GetUsersOption func(*UserPagination)
+
+// GetUsersOptionLimit limit the number of users returned
+func GetUsersOptionLimit(n int) GetUsersOption {
+ return func(p *UserPagination) {
+ p.limit = n
+ }
+}
+
+// GetUsersOptionPresence include user presence
+func GetUsersOptionPresence(n bool) GetUsersOption {
+ return func(p *UserPagination) {
+ p.presence = n
+ }
+}
+
+func newUserPagination(c *Client, options ...GetUsersOption) (up UserPagination) {
+ up = UserPagination{
+ c: c,
+ limit: 200, // per slack api documentation.
+ }
+
+ for _, opt := range options {
+ opt(&up)
+ }
+
+ return up
+}
+
+// UserPagination allows for paginating over the users
+type UserPagination struct {
+ Users []User
+ limit int
+ presence bool
+ previousResp *ResponseMetadata
+ c *Client
+}
+
+// Done checks if the pagination has completed
+func (UserPagination) Done(err error) bool {
+ return err == errPaginationComplete
+}
+
+// Failure checks if pagination failed.
+func (t UserPagination) Failure(err error) error {
+ if t.Done(err) {
+ return nil
+ }
+
+ return err
+}
+
+func (t UserPagination) Next(ctx context.Context) (_ UserPagination, err error) {
+ var (
+ resp *userResponseFull
+ )
+
+ if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") {
+ return t, errPaginationComplete
+ }
+
+ t.previousResp = t.previousResp.initialize()
+
+ values := url.Values{
+ "limit": {strconv.Itoa(t.limit)},
+ "presence": {strconv.FormatBool(t.presence)},
+ "token": {t.c.token},
+ "cursor": {t.previousResp.Cursor},
+ "include_locale": {strconv.FormatBool(true)},
+ }
+
+ if resp, err = t.c.userRequest(ctx, "users.list", values); err != nil {
+ return t, err
+ }
+
+ t.c.Debugf("GetUsersContext: got %d users; metadata %v", len(resp.Members), resp.Metadata)
+ t.Users = resp.Members
+ t.previousResp = &resp.Metadata
+
+ return t, nil
+}
+
+// GetUsersPaginated fetches users in a paginated fashion, see GetUsersContext for usage.
+func (api *Client) GetUsersPaginated(options ...GetUsersOption) UserPagination {
+ return newUserPagination(api, options...)
+}
+
+// GetUsers returns the list of users (with their detailed information)
+func (api *Client) GetUsers() ([]User, error) {
+ return api.GetUsersContext(context.Background())
+}
+
+// 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) {
+ 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)
+}
+
+// GetUserByEmail will retrieve the complete user information by email
+func (api *Client) GetUserByEmail(email string) (*User, error) {
+ return api.GetUserByEmailContext(context.Background(), email)
+}
+
+// GetUserByEmailContext will retrieve the complete user information by email with a custom context
+func (api *Client) GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
+ values := url.Values{
+ "token": {api.token},
+ "email": {email},
+ }
+ response, err := api.userRequest(ctx, "users.lookupByEmail", values)
+ if err != nil {
+ return nil, err
+ }
+ return &response.User, nil
+}
+
+// SetUserAsActive marks the currently authenticated user as active
+func (api *Client) SetUserAsActive() error {
+ return api.SetUserAsActiveContext(context.Background())
+}
+
+// SetUserAsActiveContext marks the currently authenticated user as active with a custom context
+func (api *Client) SetUserAsActiveContext(ctx context.Context) (err error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+
+ _, err = api.userRequest(ctx, "users.setActive", values)
+ return err
+}
+
+// SetUserPresence changes the currently authenticated user presence
+func (api *Client) SetUserPresence(presence string) error {
+ return api.SetUserPresenceContext(context.Background(), presence)
+}
+
+// SetUserPresenceContext changes the currently authenticated user presence with a custom context
+func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) error {
+ values := url.Values{
+ "token": {api.token},
+ "presence": {presence},
+ }
+
+ _, err := api.userRequest(ctx, "users.setPresence", values)
+ return err
+}
+
+// GetUserIdentity will retrieve user info available per identity scopes
+func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) {
+ return api.GetUserIdentityContext(context.Background())
+}
+
+// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context
+func (api *Client) GetUserIdentityContext(ctx context.Context) (response *UserIdentityResponse, err error) {
+ values := url.Values{
+ "token": {api.token},
+ }
+ response = &UserIdentityResponse{}
+
+ err = api.postMethod(ctx, "users.identity", values, response)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := response.Err(); err != nil {
+ return nil, err
+ }
+
+ return response, nil
+}
+
+// SetUserPhoto changes the currently authenticated user's profile image
+func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error {
+ return api.SetUserPhotoContext(context.Background(), image, params)
+}
+
+// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
+func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) (err error) {
+ response := &SlackResponse{}
+ values := url.Values{
+ "token": {api.token},
+ }
+ if params.CropX != DEFAULT_USER_PHOTO_CROP_X {
+ values.Add("crop_x", strconv.Itoa(params.CropX))
+ }
+ if params.CropY != DEFAULT_USER_PHOTO_CROP_Y {
+ values.Add("crop_y", strconv.Itoa(params.CropX))
+ }
+ if params.CropW != DEFAULT_USER_PHOTO_CROP_W {
+ values.Add("crop_w", strconv.Itoa(params.CropW))
+ }
+
+ err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"users.setPhoto", image, "image", values, response, api)
+ if err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// DeleteUserPhoto deletes the current authenticated user's profile image
+func (api *Client) DeleteUserPhoto() error {
+ return api.DeleteUserPhotoContext(context.Background())
+}
+
+// DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context
+func (api *Client) DeleteUserPhotoContext(ctx context.Context) (err error) {
+ response := &SlackResponse{}
+ values := url.Values{
+ "token": {api.token},
+ }
+
+ err = api.postMethod(ctx, "users.deletePhoto", values, response)
+ if err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// 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. 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, 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.
+ //
+ // We use an anonymous struct over UserProfile because to unset the values
+ // on the User's profile we cannot use the `json:"omitempty"` tag. This is
+ // because an empty string ("") is what's used to unset the values. Check
+ // out the API docs for more details:
+ //
+ // - 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"`
+ StatusExpiration int64 `json:"status_expiration"`
+ }{
+ StatusText: statusText,
+ StatusEmoji: statusEmoji,
+ StatusExpiration: statusExpiration,
+ },
+ )
+
+ if err != nil {
+ return err
+ }
+
+ values := url.Values{
+ "user": {user},
+ "token": {api.token},
+ "profile": {string(profile)},
+ }
+
+ response := &userResponseFull{}
+ if err = api.postMethod(ctx, "users.profile.set", values, response); err != nil {
+ return err
+ }
+
+ return response.Err()
+}
+
+// UnsetUserCustomStatus removes the custom status message for the currently
+// authenticated user. This is a convenience method that wraps (*Client).SetUserCustomStatus().
+func (api *Client) UnsetUserCustomStatus() error {
+ return api.UnsetUserCustomStatusContext(context.Background())
+}
+
+// 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, "", "", 0)
+}
+
+// GetUserProfile retrieves a user's profile information.
+func (api *Client) GetUserProfile(userID string, includeLabels bool) (*UserProfile, error) {
+ return api.GetUserProfileContext(context.Background(), userID, includeLabels)
+}
+
+type getUserProfileResponse struct {
+ SlackResponse
+ Profile *UserProfile `json:"profile"`
+}
+
+// GetUserProfileContext retrieves a user's profile information with a context.
+func (api *Client) GetUserProfileContext(ctx context.Context, userID string, includeLabels bool) (*UserProfile, error) {
+ values := url.Values{"token": {api.token}, "user": {userID}}
+ if includeLabels {
+ values.Add("include_labels", "true")
+ }
+ resp := &getUserProfileResponse{}
+
+ err := api.postMethod(ctx, "users.profile.get", values, &resp)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := resp.Err(); err != nil {
+ return nil, err
+ }
+
+ return resp.Profile, nil
+}
diff --git a/vendor/github.com/slack-go/slack/views.go b/vendor/github.com/slack-go/slack/views.go
new file mode 100644
index 00000000..afe391bf
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/views.go
@@ -0,0 +1,221 @@
+package slack
+
+import (
+ "context"
+ "encoding/json"
+)
+
+const (
+ VTModal ViewType = "modal"
+ VTHomeTab ViewType = "home"
+)
+
+type ViewType string
+
+type View struct {
+ SlackResponse
+ ID string `json:"id"`
+ TeamID string `json:"team_id"`
+ Type ViewType `json:"type"`
+ Title *TextBlockObject `json:"title"`
+ Close *TextBlockObject `json:"close"`
+ Submit *TextBlockObject `json:"submit"`
+ Blocks Blocks `json:"blocks"`
+ PrivateMetadata string `json:"private_metadata"`
+ CallbackID string `json:"callback_id"`
+ State interface{} `json:"state"`
+ Hash string `json:"hash"`
+ ClearOnClose bool `json:"clear_on_close"`
+ NotifyOnClose bool `json:"notify_on_close"`
+ RootViewID string `json:"root_view_id"`
+ PreviousViewID string `json:"previous_view_id"`
+ AppID string `json:"app_id"`
+ ExternalID string `json:"external_id"`
+ BotID string `json:"bot_id"`
+}
+
+type ModalViewRequest struct {
+ Type ViewType `json:"type"`
+ Title *TextBlockObject `json:"title"`
+ Blocks Blocks `json:"blocks"`
+ Close *TextBlockObject `json:"close"`
+ Submit *TextBlockObject `json:"submit"`
+ PrivateMetadata string `json:"private_metadata"`
+ CallbackID string `json:"callback_id"`
+ ClearOnClose bool `json:"clear_on_close"`
+ NotifyOnClose bool `json:"notify_on_close"`
+ ExternalID string `json:"external_id"`
+}
+
+func (v *ModalViewRequest) ViewType() ViewType {
+ return v.Type
+}
+
+type HomeTabViewRequest struct {
+ Type ViewType `json:"type"`
+ Blocks Blocks `json:"blocks"`
+ PrivateMetadata string `json:"private_metadata"`
+ CallbackID string `json:"callback_id"`
+ ExternalID string `json:"external_id"`
+}
+
+func (v *HomeTabViewRequest) ViewType() ViewType {
+ return v.Type
+}
+
+type openViewRequest struct {
+ TriggerID string `json:"trigger_id"`
+ View ModalViewRequest `json:"view"`
+}
+
+type publishViewRequest struct {
+ UserID string `json:"user_id"`
+ View HomeTabViewRequest `json:"view"`
+ Hash string `json:"hash"`
+}
+
+type pushViewRequest struct {
+ TriggerID string `json:"trigger_id"`
+ View ModalViewRequest `json:"view"`
+}
+
+type updateViewRequest struct {
+ View ModalViewRequest `json:"view"`
+ ExternalID string `json:"external_id"`
+ Hash string `json:"hash"`
+ ViewID string `json:"view_id"`
+}
+
+type ViewResponse struct {
+ SlackResponse
+ View `json:"view"`
+}
+
+// OpenView opens a view for a user.
+func (api *Client) OpenView(triggerID string, view ModalViewRequest) (*ViewResponse, error) {
+ return api.OpenViewContext(context.Background(), triggerID, view)
+}
+
+// OpenViewContext opens a view for a user with a custom context.
+func (api *Client) OpenViewContext(
+ ctx context.Context,
+ triggerID string,
+ view ModalViewRequest,
+) (*ViewResponse, error) {
+ if triggerID == "" {
+ return nil, ErrParametersMissing
+ }
+ req := openViewRequest{
+ TriggerID: triggerID,
+ View: view,
+ }
+ encoded, err := json.Marshal(req)
+ if err != nil {
+ return nil, err
+ }
+ endpoint := api.endpoint + "views.open"
+ resp := &ViewResponse{}
+ err = postJSON(ctx, api.httpclient, endpoint, api.token, encoded, resp, api)
+ if err != nil {
+ return nil, err
+ }
+ return resp, resp.Err()
+}
+
+// PublishView publishes a static view for a user.
+func (api *Client) PublishView(userID string, view HomeTabViewRequest, hash string) (*ViewResponse, error) {
+ return api.PublishViewContext(context.Background(), userID, view, hash)
+}
+
+// PublishViewContext publishes a static view for a user with a custom context.
+func (api *Client) PublishViewContext(
+ ctx context.Context,
+ userID string,
+ view HomeTabViewRequest,
+ hash string,
+) (*ViewResponse, error) {
+ if userID == "" {
+ return nil, ErrParametersMissing
+ }
+ req := publishViewRequest{
+ UserID: userID,
+ View: view,
+ Hash: hash,
+ }
+ encoded, err := json.Marshal(req)
+ if err != nil {
+ return nil, err
+ }
+ endpoint := api.endpoint + "views.publish"
+ resp := &ViewResponse{}
+ err = postJSON(ctx, api.httpclient, endpoint, api.token, encoded, resp, api)
+ if err != nil {
+ return nil, err
+ }
+ return resp, resp.Err()
+}
+
+// PushView pushes a view onto the stack of a root view.
+func (api *Client) PushView(triggerID string, view ModalViewRequest) (*ViewResponse, error) {
+ return api.PushViewContext(context.Background(), triggerID, view)
+}
+
+// PublishViewContext pushes a view onto the stack of a root view with a custom context.
+func (api *Client) PushViewContext(
+ ctx context.Context,
+ triggerID string,
+ view ModalViewRequest,
+) (*ViewResponse, error) {
+ if triggerID == "" {
+ return nil, ErrParametersMissing
+ }
+ req := pushViewRequest{
+ TriggerID: triggerID,
+ View: view,
+ }
+ encoded, err := json.Marshal(req)
+ if err != nil {
+ return nil, err
+ }
+ endpoint := api.endpoint + "views.push"
+ resp := &ViewResponse{}
+ err = postJSON(ctx, api.httpclient, endpoint, api.token, encoded, resp, api)
+ if err != nil {
+ return nil, err
+ }
+ return resp, resp.Err()
+}
+
+// UpdateView updates an existing view.
+func (api *Client) UpdateView(view ModalViewRequest, externalID, hash, viewID string) (*ViewResponse, error) {
+ return api.UpdateViewContext(context.Background(), view, externalID, hash, viewID)
+}
+
+// UpdateViewContext updates an existing view with a custom context.
+func (api *Client) UpdateViewContext(
+ ctx context.Context,
+ view ModalViewRequest,
+ externalID, hash,
+ viewID string,
+) (*ViewResponse, error) {
+ if externalID == "" && viewID == "" {
+ return nil, ErrParametersMissing
+ }
+ req := updateViewRequest{
+ View: view,
+ ExternalID: externalID,
+ Hash: hash,
+ ViewID: viewID,
+ }
+ encoded, err := json.Marshal(req)
+ if err != nil {
+ return nil, err
+ }
+ endpoint := api.endpoint + "views.update"
+ resp := &ViewResponse{}
+ err = postJSON(ctx, api.httpclient, endpoint, api.token, encoded, resp, api)
+ if err != nil {
+ return nil, err
+ }
+ return resp, resp.Err()
+}
diff --git a/vendor/github.com/slack-go/slack/webhooks.go b/vendor/github.com/slack-go/slack/webhooks.go
new file mode 100644
index 00000000..1016cb4f
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/webhooks.go
@@ -0,0 +1,29 @@
+package slack
+
+import (
+ "context"
+ "net/http"
+)
+
+type WebhookMessage struct {
+ 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 PostWebhookCustomHTTPContext(context.Background(), url, http.DefaultClient, msg)
+}
+
+func PostWebhookContext(ctx context.Context, url string, msg *WebhookMessage) error {
+ return PostWebhookCustomHTTPContext(ctx, url, http.DefaultClient, msg)
+}
+
+func PostWebhookCustomHTTP(url string, httpClient *http.Client, msg *WebhookMessage) error {
+ return PostWebhookCustomHTTPContext(context.Background(), url, httpClient, msg)
+}
diff --git a/vendor/github.com/slack-go/slack/webhooks_go112.go b/vendor/github.com/slack-go/slack/webhooks_go112.go
new file mode 100644
index 00000000..4e0db0e4
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/webhooks_go112.go
@@ -0,0 +1,34 @@
+// +build !go1.13
+
+package slack
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "github.com/pkg/errors"
+)
+
+func PostWebhookCustomHTTPContext(ctx context.Context, url string, httpClient *http.Client, msg *WebhookMessage) error {
+ raw, err := json.Marshal(msg)
+ if err != nil {
+ return errors.Wrap(err, "marshal failed")
+ }
+
+ req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(raw))
+ if err != nil {
+ return errors.Wrap(err, "failed new request")
+ }
+ req = req.WithContext(ctx)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := httpClient.Do(req)
+ if err != nil {
+ return errors.Wrap(err, "failed to post webhook")
+ }
+ defer resp.Body.Close()
+
+ return checkStatusCode(resp, discard{})
+}
diff --git a/vendor/github.com/slack-go/slack/webhooks_go113.go b/vendor/github.com/slack-go/slack/webhooks_go113.go
new file mode 100644
index 00000000..99c243f5
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/webhooks_go113.go
@@ -0,0 +1,33 @@
+// +build go1.13
+
+package slack
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "github.com/pkg/errors"
+)
+
+func PostWebhookCustomHTTPContext(ctx context.Context, url string, httpClient *http.Client, msg *WebhookMessage) error {
+ raw, err := json.Marshal(msg)
+ if err != nil {
+ return errors.Wrap(err, "marshal failed")
+ }
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(raw))
+ if err != nil {
+ return errors.Wrap(err, "failed new request")
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := httpClient.Do(req)
+ if err != nil {
+ return errors.Wrap(err, "failed to post webhook")
+ }
+ defer resp.Body.Close()
+
+ return checkStatusCode(resp, discard{})
+}
diff --git a/vendor/github.com/slack-go/slack/websocket.go b/vendor/github.com/slack-go/slack/websocket.go
new file mode 100644
index 00000000..d6895f2a
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket.go
@@ -0,0 +1,103 @@
+package slack
+
+import (
+ "net/url"
+ "sync"
+ "time"
+
+ "github.com/gorilla/websocket"
+)
+
+const (
+ // MaxMessageTextLength is the current maximum message length in number of characters as defined here
+ // https://api.slack.com/rtm#limits
+ MaxMessageTextLength = 4000
+)
+
+// RTM represents a managed websocket connection. It also supports
+// all the methods of the `Client` type.
+//
+// Create this element with Client's NewRTM() or NewRTMWithOptions(*RTMOptions)
+type RTM struct {
+ // Client is the main API, embedded
+ Client
+
+ idGen IDGenerator
+ pingInterval time.Duration
+ pingDeadman *time.Timer
+
+ // Connection life-cycle
+ conn *websocket.Conn
+ IncomingEvents chan RTMEvent
+ outgoingMessages chan OutgoingMessage
+ killChannel chan bool
+ disconnected chan struct{}
+ disconnectedm *sync.Once
+ forcePing chan bool
+
+ // UserDetails upon connection
+ info *Info
+
+ // useRTMStart should be set to true if you want to use
+ // rtm.start to connect to Slack, otherwise it will use
+ // rtm.connect
+ useRTMStart bool
+
+ // dialer is a gorilla/websocket Dialer. If nil, use the default
+ // Dialer.
+ dialer *websocket.Dialer
+
+ // mu is mutex used to prevent RTM connection race conditions
+ mu *sync.Mutex
+
+ // connParams is a map of flags for connection parameters.
+ 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 {
+ // 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.killChannel <- true:
+ return nil
+ case <-rtm.disconnected:
+ return ErrAlreadyDisconnected
+ }
+}
+
+// GetInfo returns the info structure received when calling
+// "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
+}
+
+// SendMessage submits a simple message through the websocket. For
+// more complicated messages, use `rtm.PostMessage` with a complete
+// struct describing your attachments and all.
+func (rtm *RTM) SendMessage(msg *OutgoingMessage) {
+ if msg == nil {
+ rtm.Debugln("Error: Attempted to SendMessage(nil)")
+ return
+ }
+
+ rtm.outgoingMessages <- *msg
+}
+
+func (rtm *RTM) resetDeadman() {
+ rtm.pingDeadman.Reset(deadmanDuration(rtm.pingInterval))
+}
+
+func deadmanDuration(d time.Duration) time.Duration {
+ return d * 4
+}
diff --git a/vendor/github.com/slack-go/slack/websocket_channels.go b/vendor/github.com/slack-go/slack/websocket_channels.go
new file mode 100644
index 00000000..7dd3319b
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_channels.go
@@ -0,0 +1,72 @@
+package slack
+
+// ChannelCreatedEvent represents the Channel created event
+type ChannelCreatedEvent struct {
+ Type string `json:"type"`
+ Channel ChannelCreatedInfo `json:"channel"`
+ EventTimestamp string `json:"event_ts"`
+}
+
+// ChannelCreatedInfo represents the information associated with the Channel created event
+type ChannelCreatedInfo struct {
+ ID string `json:"id"`
+ IsChannel bool `json:"is_channel"`
+ Name string `json:"name"`
+ Created int `json:"created"`
+ Creator string `json:"creator"`
+}
+
+// ChannelJoinedEvent represents the Channel joined event
+type ChannelJoinedEvent struct {
+ Type string `json:"type"`
+ Channel Channel `json:"channel"`
+}
+
+// ChannelInfoEvent represents the Channel info event
+type ChannelInfoEvent struct {
+ // channel_left
+ // channel_deleted
+ // channel_archive
+ // channel_unarchive
+ Type string `json:"type"`
+ Channel string `json:"channel"`
+ User string `json:"user,omitempty"`
+ Timestamp string `json:"ts,omitempty"`
+}
+
+// ChannelRenameEvent represents the Channel rename event
+type ChannelRenameEvent struct {
+ Type string `json:"type"`
+ Channel ChannelRenameInfo `json:"channel"`
+ Timestamp string `json:"event_ts"`
+}
+
+// ChannelRenameInfo represents the information associated with a Channel rename event
+type ChannelRenameInfo struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Created string `json:"created"`
+}
+
+// ChannelHistoryChangedEvent represents the Channel history changed event
+type ChannelHistoryChangedEvent struct {
+ Type string `json:"type"`
+ Latest string `json:"latest"`
+ Timestamp string `json:"ts"`
+ EventTimestamp string `json:"event_ts"`
+}
+
+// ChannelMarkedEvent represents the Channel marked event
+type ChannelMarkedEvent ChannelInfoEvent
+
+// ChannelLeftEvent represents the Channel left event
+type ChannelLeftEvent ChannelInfoEvent
+
+// ChannelDeletedEvent represents the Channel deleted event
+type ChannelDeletedEvent ChannelInfoEvent
+
+// ChannelArchiveEvent represents the Channel archive event
+type ChannelArchiveEvent ChannelInfoEvent
+
+// ChannelUnarchiveEvent represents the Channel unarchive event
+type ChannelUnarchiveEvent ChannelInfoEvent
diff --git a/vendor/github.com/slack-go/slack/websocket_desktop_notification.go b/vendor/github.com/slack-go/slack/websocket_desktop_notification.go
new file mode 100644
index 00000000..7c61abf7
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_desktop_notification.go
@@ -0,0 +1,19 @@
+package slack
+
+// DesktopNotificationEvent represents the update event for Desktop Notification.
+type DesktopNotificationEvent struct {
+ Type string `json:"type"`
+ Title string `json:"title"`
+ Subtitle string `json:"subtitle"`
+ Message string `json:"msg"`
+ Timestamp string `json:"ts"`
+ Content string `json:"content"`
+ Channel string `json:"channel"`
+ LaunchURI string `json:"launchUri"`
+ AvatarImage string `json:"avatarImage"`
+ SsbFilename string `json:"ssbFilename"`
+ ImageURI string `json:"imageUri"`
+ IsShared bool `json:"is_shared"`
+ IsChannelInvite bool `json:"is_channel_invite"`
+ EventTimestamp string `json:"event_ts"`
+}
diff --git a/vendor/github.com/slack-go/slack/websocket_dm.go b/vendor/github.com/slack-go/slack/websocket_dm.go
new file mode 100644
index 00000000..98bf6f88
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_dm.go
@@ -0,0 +1,23 @@
+package slack
+
+// IMCreatedEvent represents the IM created event
+type IMCreatedEvent struct {
+ Type string `json:"type"`
+ User string `json:"user"`
+ Channel ChannelCreatedInfo `json:"channel"`
+}
+
+// IMHistoryChangedEvent represents the IM history changed event
+type IMHistoryChangedEvent ChannelHistoryChangedEvent
+
+// IMOpenEvent represents the IM open event
+type IMOpenEvent ChannelInfoEvent
+
+// IMCloseEvent represents the IM close event
+type IMCloseEvent ChannelInfoEvent
+
+// IMMarkedEvent represents the IM marked event
+type IMMarkedEvent ChannelInfoEvent
+
+// IMMarkedHistoryChanged represents the IM marked history changed event
+type IMMarkedHistoryChanged ChannelInfoEvent
diff --git a/vendor/github.com/slack-go/slack/websocket_dnd.go b/vendor/github.com/slack-go/slack/websocket_dnd.go
new file mode 100644
index 00000000..62ddea3a
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_dnd.go
@@ -0,0 +1,8 @@
+package slack
+
+// DNDUpdatedEvent represents the update event for Do Not Disturb
+type DNDUpdatedEvent struct {
+ Type string `json:"type"`
+ User string `json:"user"`
+ Status DNDStatus `json:"dnd_status"`
+}
diff --git a/vendor/github.com/slack-go/slack/websocket_files.go b/vendor/github.com/slack-go/slack/websocket_files.go
new file mode 100644
index 00000000..8c5bd4f8
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_files.go
@@ -0,0 +1,49 @@
+package slack
+
+// FileActionEvent represents the File action event
+type fileActionEvent struct {
+ Type string `json:"type"`
+ EventTimestamp string `json:"event_ts"`
+ File File `json:"file"`
+ // FileID is used for FileDeletedEvent
+ FileID string `json:"file_id,omitempty"`
+}
+
+// FileCreatedEvent represents the File created event
+type FileCreatedEvent fileActionEvent
+
+// FileSharedEvent represents the File shared event
+type FileSharedEvent fileActionEvent
+
+// FilePublicEvent represents the File public event
+type FilePublicEvent fileActionEvent
+
+// FileUnsharedEvent represents the File unshared event
+type FileUnsharedEvent fileActionEvent
+
+// FileChangeEvent represents the File change event
+type FileChangeEvent fileActionEvent
+
+// FileDeletedEvent represents the File deleted event
+type FileDeletedEvent fileActionEvent
+
+// FilePrivateEvent represents the File private event
+type FilePrivateEvent fileActionEvent
+
+// FileCommentAddedEvent represents the File comment added event
+type FileCommentAddedEvent struct {
+ fileActionEvent
+ Comment Comment `json:"comment"`
+}
+
+// FileCommentEditedEvent represents the File comment edited event
+type FileCommentEditedEvent struct {
+ fileActionEvent
+ Comment Comment `json:"comment"`
+}
+
+// FileCommentDeletedEvent represents the File comment deleted event
+type FileCommentDeletedEvent struct {
+ fileActionEvent
+ Comment string `json:"comment"`
+}
diff --git a/vendor/github.com/slack-go/slack/websocket_groups.go b/vendor/github.com/slack-go/slack/websocket_groups.go
new file mode 100644
index 00000000..eb88985c
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_groups.go
@@ -0,0 +1,49 @@
+package slack
+
+// GroupCreatedEvent represents the Group created event
+type GroupCreatedEvent struct {
+ Type string `json:"type"`
+ User string `json:"user"`
+ Channel ChannelCreatedInfo `json:"channel"`
+}
+
+// XXX: Should we really do this? event.Group is probably nicer than event.Channel
+// even though the api returns "channel"
+
+// GroupMarkedEvent represents the Group marked event
+type GroupMarkedEvent ChannelInfoEvent
+
+// GroupOpenEvent represents the Group open event
+type GroupOpenEvent ChannelInfoEvent
+
+// GroupCloseEvent represents the Group close event
+type GroupCloseEvent ChannelInfoEvent
+
+// GroupArchiveEvent represents the Group archive event
+type GroupArchiveEvent ChannelInfoEvent
+
+// GroupUnarchiveEvent represents the Group unarchive event
+type GroupUnarchiveEvent ChannelInfoEvent
+
+// GroupLeftEvent represents the Group left event
+type GroupLeftEvent ChannelInfoEvent
+
+// GroupJoinedEvent represents the Group joined event
+type GroupJoinedEvent ChannelJoinedEvent
+
+// GroupRenameEvent represents the Group rename event
+type GroupRenameEvent struct {
+ Type string `json:"type"`
+ Group GroupRenameInfo `json:"channel"`
+ Timestamp string `json:"ts"`
+}
+
+// GroupRenameInfo represents the group info related to the renamed group
+type GroupRenameInfo struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Created string `json:"created"`
+}
+
+// GroupHistoryChangedEvent represents the Group history changed event
+type GroupHistoryChangedEvent ChannelHistoryChangedEvent
diff --git a/vendor/github.com/slack-go/slack/websocket_internals.go b/vendor/github.com/slack-go/slack/websocket_internals.go
new file mode 100644
index 00000000..3e1906ee
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_internals.go
@@ -0,0 +1,101 @@
+package slack
+
+import (
+ "fmt"
+ "time"
+)
+
+/**
+ * Internal events, created by this lib and not mapped to Slack APIs.
+ */
+
+// ConnectedEvent is used for when we connect to Slack
+type ConnectedEvent struct {
+ ConnectionCount int // 1 = first time, 2 = second time
+ Info *Info
+}
+
+// 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
+}
+
+func (c *ConnectionErrorEvent) Error() string {
+ return c.ErrorObj.Error()
+}
+
+// ConnectingEvent contains information about our connection attempt
+type ConnectingEvent struct {
+ Attempt int // 1 = first attempt, 2 = second attempt
+ ConnectionCount int
+}
+
+// DisconnectedEvent contains information about how we disconnected
+type DisconnectedEvent struct {
+ Intentional bool
+ Cause error
+}
+
+// LatencyReport contains information about connection latency
+type LatencyReport struct {
+ Value time.Duration
+}
+
+// InvalidAuthEvent is used in case we can't even authenticate with the API
+type InvalidAuthEvent struct{}
+
+// UnmarshallingErrorEvent is used when there are issues deconstructing a response
+type UnmarshallingErrorEvent struct {
+ ErrorObj error
+}
+
+func (u UnmarshallingErrorEvent) Error() string {
+ return u.ErrorObj.Error()
+}
+
+// MessageTooLongEvent is used when sending a message that is too long
+type MessageTooLongEvent struct {
+ Message OutgoingMessage
+ MaxLength int
+}
+
+func (m *MessageTooLongEvent) Error() string {
+ return fmt.Sprintf("Message too long (max %d characters)", m.MaxLength)
+}
+
+// RateLimitEvent is used when Slack warns that rate-limits are being hit.
+type RateLimitEvent struct{}
+
+func (e *RateLimitEvent) Error() string {
+ return "Messages are being sent too fast."
+}
+
+// OutgoingErrorEvent contains information in case there were errors sending messages
+type OutgoingErrorEvent struct {
+ Message OutgoingMessage
+ ErrorObj error
+}
+
+func (o OutgoingErrorEvent) Error() string {
+ return o.ErrorObj.Error()
+}
+
+// IncomingEventError contains information about an unexpected error receiving a websocket event
+type IncomingEventError struct {
+ ErrorObj error
+}
+
+func (i *IncomingEventError) Error() string {
+ return i.ErrorObj.Error()
+}
+
+// AckErrorEvent i
+type AckErrorEvent struct {
+ ErrorObj error
+}
+
+func (a *AckErrorEvent) Error() string {
+ return a.ErrorObj.Error()
+}
diff --git a/vendor/github.com/slack-go/slack/websocket_managed_conn.go b/vendor/github.com/slack-go/slack/websocket_managed_conn.go
new file mode 100644
index 00000000..6395938c
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_managed_conn.go
@@ -0,0 +1,581 @@
+package slack
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ stdurl "net/url"
+ "reflect"
+ "time"
+
+ "github.com/gorilla/websocket"
+ "github.com/slack-go/slack/internal/errorsx"
+ "github.com/slack-go/slack/internal/timex"
+)
+
+// ManageConnection can be called on a Slack RTM instance returned by the
+// NewRTM method. It will connect to the slack RTM API and handle all incoming
+// and outgoing events. If a connection fails then it will attempt to reconnect
+// and will notify any listeners through an error event on the IncomingEvents
+// channel.
+//
+// If the connection ends and the disconnect was unintentional then this will
+// attempt to reconnect.
+//
+// This should only be called once per slack API! Otherwise expect undefined
+// behavior.
+//
+// The defined error events are located in websocket_internals.go.
+func (rtm *RTM) ManageConnection() {
+ var (
+ err error
+ info *Info
+ conn *websocket.Conn
+ )
+
+ for connectionCount := 0; ; connectionCount++ {
+ // start trying to connect
+ // the returned err is already passed onto the IncomingEvents channel
+ 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
+ }
+
+ // lock to prevent data races with Disconnect particularly around isConnected
+ // and conn.
+ rtm.mu.Lock()
+ rtm.conn = conn
+ rtm.info = info
+ rtm.mu.Unlock()
+
+ rtm.IncomingEvents <- RTMEvent{"connected", &ConnectedEvent{
+ ConnectionCount: connectionCount,
+ Info: info,
+ }}
+
+ rtm.Debugf("RTM connection succeeded on try %d", connectionCount)
+
+ rawEvents := make(chan json.RawMessage)
+ // we're now connected so we can set up listeners
+ go rtm.handleIncomingEvents(rawEvents)
+ // this should be a blocking call until the connection has ended
+ rtm.handleEvents(rawEvents)
+
+ select {
+ case <-rtm.disconnected:
+ // after handle events returns we need to check if we're disconnected
+ // when this happens we need to cleanup the newly created connection.
+ if err = conn.Close(); err != nil {
+ rtm.Debugln("failed to close conn on disconnected RTM", err)
+ }
+ return
+ default:
+ // otherwise continue and run the loop again to reconnect
+ }
+ }
+}
+
+// connect attempts to connect to the slack websocket API. It handles any
+// errors that occur while connecting and will return once a connection
+// has been successfully opened.
+// If useRTMStart is false then it uses rtm.connect to create the connection,
+// otherwise it uses rtm.start.
+func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocket.Conn, error) {
+ const (
+ errInvalidAuth = "invalid_auth"
+ errInactiveAccount = "account_inactive"
+ errMissingAuthToken = "not_authed"
+ )
+
+ // used to provide exponential backoff wait time with jitter before trying
+ // to connect to slack again
+ boff := &backoff{
+ 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 {
+ return info, conn, nil
+ }
+
+ // check for fatal errors
+ switch err.Error() {
+ case errInvalidAuth, errInactiveAccount, errMissingAuthToken:
+ 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,
+ }}
+
+ // 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:
+ return nil, nil, ErrRTMDisconnected
+ }
+ }
+}
+
+// startRTMAndDial attempts to connect to the slack websocket. If useRTMStart is true,
+// then it returns the full information returned by the "rtm.start" method on the
+// slack API. Else it uses the "rtm.connect" method to connect
+func (rtm *RTM) startRTMAndDial(useRTMStart bool) (info *Info, _ *websocket.Conn, err error) {
+ var (
+ url string
+ )
+
+ if useRTMStart {
+ rtm.Debugf("Starting RTM")
+ info, url, err = rtm.StartRTM()
+ } else {
+ rtm.Debugf("Connecting to RTM")
+ info, url, err = rtm.ConnectRTM()
+ }
+ if err != nil {
+ rtm.Debugf("Failed to start or connect to RTM: %s", err)
+ return nil, nil, err
+ }
+
+ // install connection parameters
+ u, err := stdurl.Parse(url)
+ if err != nil {
+ return nil, nil, err
+ }
+ u.RawQuery = rtm.connParams.Encode()
+ url = u.String()
+
+ rtm.Debugf("Dialing to websocket on url %s", url)
+ // Only use HTTPS for connections to prevent MITM attacks on the connection.
+ upgradeHeader := http.Header{}
+ upgradeHeader.Add("Origin", "https://api.slack.com")
+ dialer := websocket.DefaultDialer
+ if rtm.dialer != nil {
+ dialer = rtm.dialer
+ }
+ conn, _, err := dialer.Dial(url, upgradeHeader)
+ if err != nil {
+ rtm.Debugf("Failed to dial to the websocket: %s", err)
+ return nil, nil, err
+ }
+ return info, conn, err
+}
+
+// killConnection stops the websocket connection and signals to all goroutines
+// that they should cease listening to the connection for events.
+//
+// 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(intentional bool, cause error) (err error) {
+ rtm.Debugln("killing connection", cause)
+
+ if rtm.conn != nil {
+ err = rtm.conn.Close()
+ }
+
+ rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{Intentional: intentional, Cause: cause}}
+
+ if intentional {
+ rtm.disconnect()
+ }
+
+ return err
+}
+
+// handleEvents is a blocking function that handles all events. This sends
+// pings when asked to (on rtm.forcePing) and upon every given elapsed
+// 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(events chan json.RawMessage) {
+ ticker := time.NewTicker(rtm.pingInterval)
+ defer ticker.Stop()
+ for {
+ select {
+ // catch "stop" signal on channel close
+ case intentional := <-rtm.killChannel:
+ _ = rtm.killConnection(intentional, errorsx.String("signaled"))
+ return
+ // detect when the connection is dead.
+ case <-rtm.pingDeadman.C:
+ _ = rtm.killConnection(false, ErrRTMDeadman)
+ return
+ // send pings on ticker interval
+ case <-ticker.C:
+ if err := rtm.ping(); err != nil {
+ _ = rtm.killConnection(false, err)
+ return
+ }
+ case <-rtm.forcePing:
+ if err := rtm.ping(); err != nil {
+ _ = rtm.killConnection(false, err)
+ return
+ }
+ // listen for messages that need to be sent
+ case msg := <-rtm.outgoingMessages:
+ rtm.sendOutgoingMessage(msg)
+ // listen for incoming messages that need to be parsed
+ case rawEvent := <-events:
+ switch rtm.handleRawEvent(rawEvent) {
+ case rtmEventTypeGoodbye:
+ // kill the connection, but DO NOT RETURN, a follow up kill signal will
+ // be sent that still needs to be processed. this duplication is because
+ // the event reader restarts once it emits the goodbye event.
+ // unlike the other cases in this function a final read will be triggered
+ // against the connection which will emit a kill signal. if we return early
+ // this kill signal will be processed by the next connection.
+ _ = rtm.killConnection(false, ErrRTMGoodbye)
+ default:
+ }
+ }
+ }
+}
+
+// handleIncomingEvents monitors the RTM's opened websocket for any incoming
+// events. It pushes the raw events into the channel.
+//
+// This will stop executing once the RTM's when a fatal error is detected, or
+// a disconnect occurs.
+func (rtm *RTM) handleIncomingEvents(events chan json.RawMessage) {
+ for {
+ if err := rtm.receiveIncomingEvent(events); err != nil {
+ select {
+ case rtm.killChannel <- false:
+ case <-rtm.disconnected:
+ }
+ return
+ }
+ }
+}
+
+func (rtm *RTM) sendWithDeadline(msg interface{}) error {
+ // set a write deadline on the connection
+ if err := rtm.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)); err != nil {
+ return err
+ }
+ if err := rtm.conn.WriteJSON(msg); err != nil {
+ return err
+ }
+ // remove write deadline
+ return rtm.conn.SetWriteDeadline(time.Time{})
+}
+
+// sendOutgoingMessage sends the given OutgoingMessage to the slack websocket.
+//
+// It does not currently detect if a outgoing message fails due to a disconnect
+// and instead lets a future failed 'PING' detect the failed connection.
+func (rtm *RTM) sendOutgoingMessage(msg OutgoingMessage) {
+ rtm.Debugln("Sending message:", msg)
+ if len([]rune(msg.Text)) > MaxMessageTextLength {
+ rtm.IncomingEvents <- RTMEvent{"outgoing_error", &MessageTooLongEvent{
+ Message: msg,
+ MaxLength: MaxMessageTextLength,
+ }}
+ return
+ }
+
+ if err := rtm.sendWithDeadline(msg); err != nil {
+ rtm.IncomingEvents <- RTMEvent{"outgoing_error", &OutgoingErrorEvent{
+ Message: msg,
+ ErrorObj: err,
+ }}
+ }
+}
+
+// ping sends a 'PING' message to the RTM's websocket. If the 'PING' message
+// fails to send then this returns an error signifying that the connection
+// should be considered disconnected.
+//
+// This does not handle incoming 'PONG' responses but does store the time of
+// each successful 'PING' send so latency can be detected upon a 'PONG'
+// response.
+func (rtm *RTM) ping() error {
+ id := rtm.idGen.Next()
+ rtm.Debugln("Sending PING ", id)
+ msg := &Ping{ID: id, Type: "ping", Timestamp: time.Now().Unix()}
+
+ if err := rtm.sendWithDeadline(msg); err != nil {
+ rtm.Debugf("RTM Error sending 'PING %d': %s", id, err.Error())
+ return err
+ }
+ return nil
+}
+
+// receiveIncomingEvent attempts to receive an event from the RTM's websocket.
+// This will block until a frame is available from the websocket.
+// If the read from the websocket results in a fatal error, this function will return non-nil.
+func (rtm *RTM) receiveIncomingEvent(events chan json.RawMessage) error {
+ event := json.RawMessage{}
+ err := rtm.conn.ReadJSON(&event)
+
+ // check if the connection was closed.
+ if websocket.IsUnexpectedCloseError(err) {
+ return err
+ }
+
+ switch {
+ case err == io.ErrUnexpectedEOF:
+ // EOF's don't seem to signify a failed connection so instead we ignore
+ // them here and detect a failed connection upon attempting to send a
+ // 'PING' message
+
+ // trigger a 'PING' to detect potential websocket disconnect
+ 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,
+ }}
+
+ return err
+ case len(event) == 0:
+ rtm.Debugln("Received empty event")
+ default:
+ rtm.Debugln("Incoming Event:", string(event))
+ select {
+ case events <- event:
+ case <-rtm.disconnected:
+ rtm.Debugln("disonnected while attempting to send raw event")
+ }
+ }
+
+ return nil
+}
+
+// handleRawEvent takes a raw JSON message received from the slack websocket
+// and handles the encoded event.
+// returns the event type of the message.
+func (rtm *RTM) handleRawEvent(rawEvent json.RawMessage) string {
+ event := &Event{}
+ err := json.Unmarshal(rawEvent, event)
+ if err != nil {
+ rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
+ return ""
+ }
+
+ switch event.Type {
+ case rtmEventTypeAck:
+ rtm.handleAck(rawEvent)
+ case rtmEventTypeHello:
+ rtm.IncomingEvents <- RTMEvent{"hello", &HelloEvent{}}
+ case rtmEventTypePong:
+ rtm.handlePong(rawEvent)
+ case rtmEventTypeGoodbye:
+ // just return the event type up for goodbye, will be handled by caller.
+ default:
+ rtm.handleEvent(event.Type, rawEvent)
+ }
+
+ return event.Type
+}
+
+// handleAck handles an incoming 'ACK' message.
+func (rtm *RTM) handleAck(event json.RawMessage) {
+ ack := &AckMessage{}
+ if err := json.Unmarshal(event, ack); err != nil {
+ rtm.Debugln("RTM Error unmarshalling 'ack' event:", err)
+ rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
+ return
+ }
+
+ if ack.Ok {
+ rtm.IncomingEvents <- RTMEvent{"ack", ack}
+ } else if ack.RTMResponse.Error != nil {
+ // As there is no documentation for RTM error-codes, this
+ // identification of a rate-limit warning is very brittle.
+ if ack.RTMResponse.Error.Code == -1 && ack.RTMResponse.Error.Msg == "slow down, too many messages..." {
+ rtm.IncomingEvents <- RTMEvent{"ack_error", &RateLimitEvent{}}
+ } else {
+ rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
+ }
+ } else {
+ rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{fmt.Errorf("ack decode failure")}}
+ }
+}
+
+// handlePong handles an incoming 'PONG' message which should be in response to
+// a previously sent 'PING' message. This is then used to compute the
+// connection's latency.
+func (rtm *RTM) handlePong(event json.RawMessage) {
+ var (
+ p Pong
+ )
+
+ rtm.resetDeadman()
+
+ if err := json.Unmarshal(event, &p); err != nil {
+ rtm.Client.log.Println("RTM Error unmarshalling 'pong' event:", err)
+ return
+ }
+
+ latency := time.Since(time.Unix(p.Timestamp, 0))
+ rtm.IncomingEvents <- RTMEvent{"latency_report", &LatencyReport{Value: latency}}
+}
+
+// handleEvent is the "default" response to an event that does not have a
+// special case. It matches the command's name to a mapping of defined events
+// and then sends the corresponding event struct to the IncomingEvents channel.
+// If the event type is not found or the event cannot be unmarshalled into the
+// correct struct then this sends an UnmarshallingErrorEvent to the
+// IncomingEvents channel.
+func (rtm *RTM) handleEvent(typeStr string, event json.RawMessage) {
+ v, exists := EventMapping[typeStr]
+ if !exists {
+ rtm.Debugf("RTM Error - received unmapped event %q: %s\n", typeStr, string(event))
+ err := fmt.Errorf("RTM Error: Received unmapped event %q: %s", typeStr, string(event))
+ rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
+ return
+ }
+ t := reflect.TypeOf(v)
+ recvEvent := reflect.New(t).Interface()
+ err := json.Unmarshal(event, recvEvent)
+ if err != nil {
+ rtm.Debugf("RTM Error, could not unmarshall event %q: %s\n", typeStr, string(event))
+ err := fmt.Errorf("RTM Error: Could not unmarshall event %q: %s", typeStr, string(event))
+ rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
+ return
+ }
+ rtm.IncomingEvents <- RTMEvent{typeStr, recvEvent}
+}
+
+// EventMapping holds a mapping of event names to their corresponding struct
+// implementations. The structs should be instances of the unmarshalling
+// target for the matching event type.
+var EventMapping = map[string]interface{}{
+ "message": MessageEvent{},
+ "presence_change": PresenceChangeEvent{},
+ "user_typing": UserTypingEvent{},
+
+ "channel_marked": ChannelMarkedEvent{},
+ "channel_created": ChannelCreatedEvent{},
+ "channel_joined": ChannelJoinedEvent{},
+ "channel_left": ChannelLeftEvent{},
+ "channel_deleted": ChannelDeletedEvent{},
+ "channel_rename": ChannelRenameEvent{},
+ "channel_archive": ChannelArchiveEvent{},
+ "channel_unarchive": ChannelUnarchiveEvent{},
+ "channel_history_changed": ChannelHistoryChangedEvent{},
+
+ "dnd_updated": DNDUpdatedEvent{},
+ "dnd_updated_user": DNDUpdatedEvent{},
+
+ "im_created": IMCreatedEvent{},
+ "im_open": IMOpenEvent{},
+ "im_close": IMCloseEvent{},
+ "im_marked": IMMarkedEvent{},
+ "im_history_changed": IMHistoryChangedEvent{},
+
+ "group_marked": GroupMarkedEvent{},
+ "group_open": GroupOpenEvent{},
+ "group_joined": GroupJoinedEvent{},
+ "group_left": GroupLeftEvent{},
+ "group_close": GroupCloseEvent{},
+ "group_rename": GroupRenameEvent{},
+ "group_archive": GroupArchiveEvent{},
+ "group_unarchive": GroupUnarchiveEvent{},
+ "group_history_changed": GroupHistoryChangedEvent{},
+
+ "file_created": FileCreatedEvent{},
+ "file_shared": FileSharedEvent{},
+ "file_unshared": FileUnsharedEvent{},
+ "file_public": FilePublicEvent{},
+ "file_private": FilePrivateEvent{},
+ "file_change": FileChangeEvent{},
+ "file_deleted": FileDeletedEvent{},
+ "file_comment_added": FileCommentAddedEvent{},
+ "file_comment_edited": FileCommentEditedEvent{},
+ "file_comment_deleted": FileCommentDeletedEvent{},
+
+ "pin_added": PinAddedEvent{},
+ "pin_removed": PinRemovedEvent{},
+
+ "star_added": StarAddedEvent{},
+ "star_removed": StarRemovedEvent{},
+
+ "reaction_added": ReactionAddedEvent{},
+ "reaction_removed": ReactionRemovedEvent{},
+
+ "pref_change": PrefChangeEvent{},
+
+ "team_join": TeamJoinEvent{},
+ "team_rename": TeamRenameEvent{},
+ "team_pref_change": TeamPrefChangeEvent{},
+ "team_domain_change": TeamDomainChangeEvent{},
+ "team_migration_started": TeamMigrationStartedEvent{},
+
+ "manual_presence_change": ManualPresenceChangeEvent{},
+
+ "user_change": UserChangeEvent{},
+
+ "emoji_changed": EmojiChangedEvent{},
+
+ "commands_changed": CommandsChangedEvent{},
+
+ "email_domain_changed": EmailDomainChangedEvent{},
+
+ "bot_added": BotAddedEvent{},
+ "bot_changed": BotChangedEvent{},
+
+ "accounts_changed": AccountsChangedEvent{},
+
+ "reconnect_url": ReconnectUrlEvent{},
+
+ "member_joined_channel": MemberJoinedChannelEvent{},
+ "member_left_channel": MemberLeftChannelEvent{},
+
+ "subteam_created": SubteamCreatedEvent{},
+ "subteam_self_added": SubteamSelfAddedEvent{},
+ "subteam_self_removed": SubteamSelfRemovedEvent{},
+ "subteam_updated": SubteamUpdatedEvent{},
+
+ "desktop_notification": DesktopNotificationEvent{},
+}
diff --git a/vendor/github.com/slack-go/slack/websocket_misc.go b/vendor/github.com/slack-go/slack/websocket_misc.go
new file mode 100644
index 00000000..65a8bb65
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_misc.go
@@ -0,0 +1,141 @@
+package slack
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// AckMessage is used for messages received in reply to other messages
+type AckMessage struct {
+ ReplyTo int `json:"reply_to"`
+ Timestamp string `json:"ts"`
+ Text string `json:"text"`
+ RTMResponse
+}
+
+// RTMResponse encapsulates response details as returned by the Slack API
+type RTMResponse struct {
+ Ok bool `json:"ok"`
+ Error *RTMError `json:"error"`
+}
+
+// RTMError encapsulates error information as returned by the Slack API
+type RTMError struct {
+ Code int
+ Msg string
+}
+
+func (s RTMError) Error() string {
+ return fmt.Sprintf("Code %d - %s", s.Code, s.Msg)
+}
+
+// MessageEvent represents a Slack Message (used as the event type for an incoming message)
+type MessageEvent Message
+
+// RTMEvent is the main wrapper. You will find all the other messages attached
+type RTMEvent struct {
+ Type string
+ Data interface{}
+}
+
+// HelloEvent represents the hello event
+type HelloEvent struct{}
+
+// PresenceChangeEvent represents the presence change event
+type PresenceChangeEvent struct {
+ Type string `json:"type"`
+ Presence string `json:"presence"`
+ User string `json:"user"`
+ Users []string `json:"users"`
+}
+
+// UserTypingEvent represents the user typing event
+type UserTypingEvent struct {
+ Type string `json:"type"`
+ User string `json:"user"`
+ Channel string `json:"channel"`
+}
+
+// PrefChangeEvent represents a user preferences change event
+type PrefChangeEvent struct {
+ Type string `json:"type"`
+ Name string `json:"name"`
+ Value json.RawMessage `json:"value"`
+}
+
+// ManualPresenceChangeEvent represents the manual presence change event
+type ManualPresenceChangeEvent struct {
+ Type string `json:"type"`
+ Presence string `json:"presence"`
+}
+
+// UserChangeEvent represents the user change event
+type UserChangeEvent struct {
+ Type string `json:"type"`
+ User User `json:"user"`
+}
+
+// EmojiChangedEvent represents the emoji changed event
+type EmojiChangedEvent struct {
+ Type string `json:"type"`
+ SubType string `json:"subtype"`
+ Name string `json:"name"`
+ Names []string `json:"names"`
+ Value string `json:"value"`
+ EventTimestamp string `json:"event_ts"`
+}
+
+// CommandsChangedEvent represents the commands changed event
+type CommandsChangedEvent struct {
+ Type string `json:"type"`
+ EventTimestamp string `json:"event_ts"`
+}
+
+// EmailDomainChangedEvent represents the email domain changed event
+type EmailDomainChangedEvent struct {
+ Type string `json:"type"`
+ EventTimestamp string `json:"event_ts"`
+ EmailDomain string `json:"email_domain"`
+}
+
+// BotAddedEvent represents the bot added event
+type BotAddedEvent struct {
+ Type string `json:"type"`
+ Bot Bot `json:"bot"`
+}
+
+// BotChangedEvent represents the bot changed event
+type BotChangedEvent struct {
+ Type string `json:"type"`
+ Bot Bot `json:"bot"`
+}
+
+// AccountsChangedEvent represents the accounts changed event
+type AccountsChangedEvent struct {
+ Type string `json:"type"`
+}
+
+// ReconnectUrlEvent represents the receiving reconnect url event
+type ReconnectUrlEvent struct {
+ Type string `json:"type"`
+ URL string `json:"url"`
+}
+
+// MemberJoinedChannelEvent, a user joined a public or private channel
+type MemberJoinedChannelEvent struct {
+ Type string `json:"type"`
+ User string `json:"user"`
+ Channel string `json:"channel"`
+ ChannelType string `json:"channel_type"`
+ Team string `json:"team"`
+ Inviter string `json:"inviter"`
+}
+
+// MemberLeftChannelEvent a user left a public or private channel
+type MemberLeftChannelEvent struct {
+ Type string `json:"type"`
+ User string `json:"user"`
+ Channel string `json:"channel"`
+ ChannelType string `json:"channel_type"`
+ Team string `json:"team"`
+}
diff --git a/vendor/github.com/slack-go/slack/websocket_pins.go b/vendor/github.com/slack-go/slack/websocket_pins.go
new file mode 100644
index 00000000..95445e28
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_pins.go
@@ -0,0 +1,16 @@
+package slack
+
+type pinEvent struct {
+ Type string `json:"type"`
+ User string `json:"user"`
+ Item Item `json:"item"`
+ Channel string `json:"channel_id"`
+ EventTimestamp string `json:"event_ts"`
+ HasPins bool `json:"has_pins,omitempty"`
+}
+
+// PinAddedEvent represents the Pin added event
+type PinAddedEvent pinEvent
+
+// PinRemovedEvent represents the Pin removed event
+type PinRemovedEvent pinEvent
diff --git a/vendor/github.com/slack-go/slack/websocket_reactions.go b/vendor/github.com/slack-go/slack/websocket_reactions.go
new file mode 100644
index 00000000..e4973878
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_reactions.go
@@ -0,0 +1,25 @@
+package slack
+
+// reactionItem is a lighter-weight item than is returned by the reactions list.
+type reactionItem struct {
+ Type string `json:"type"`
+ Channel string `json:"channel,omitempty"`
+ File string `json:"file,omitempty"`
+ FileComment string `json:"file_comment,omitempty"`
+ Timestamp string `json:"ts,omitempty"`
+}
+
+type reactionEvent struct {
+ Type string `json:"type"`
+ User string `json:"user"`
+ ItemUser string `json:"item_user"`
+ Item reactionItem `json:"item"`
+ Reaction string `json:"reaction"`
+ EventTimestamp string `json:"event_ts"`
+}
+
+// ReactionAddedEvent represents the Reaction added event
+type ReactionAddedEvent reactionEvent
+
+// ReactionRemovedEvent represents the Reaction removed event
+type ReactionRemovedEvent reactionEvent
diff --git a/vendor/github.com/slack-go/slack/websocket_stars.go b/vendor/github.com/slack-go/slack/websocket_stars.go
new file mode 100644
index 00000000..e0f2dda3
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_stars.go
@@ -0,0 +1,14 @@
+package slack
+
+type starEvent struct {
+ Type string `json:"type"`
+ User string `json:"user"`
+ Item StarredItem `json:"item"`
+ EventTimestamp string `json:"event_ts"`
+}
+
+// StarAddedEvent represents the Star added event
+type StarAddedEvent starEvent
+
+// StarRemovedEvent represents the Star removed event
+type StarRemovedEvent starEvent
diff --git a/vendor/github.com/slack-go/slack/websocket_subteam.go b/vendor/github.com/slack-go/slack/websocket_subteam.go
new file mode 100644
index 00000000..a23b274c
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_subteam.go
@@ -0,0 +1,35 @@
+package slack
+
+// SubteamCreatedEvent represents the Subteam created event
+type SubteamCreatedEvent struct {
+ Type string `json:"type"`
+ Subteam UserGroup `json:"subteam"`
+}
+
+// SubteamCreatedEvent represents the membership of an existing User Group has changed event
+type SubteamMembersChangedEvent struct {
+ Type string `json:"type"`
+ SubteamID string `json:"subteam_id"`
+ TeamID string `json:"team_id"`
+ DatePreviousUpdate JSONTime `json:"date_previous_update"`
+ DateUpdate JSONTime `json:"date_update"`
+ AddedUsers []string `json:"added_users"`
+ AddedUsersCount string `json:"added_users_count"`
+ RemovedUsers []string `json:"removed_users"`
+ RemovedUsersCount string `json:"removed_users_count"`
+}
+
+// SubteamSelfAddedEvent represents an event of you have been added to a User Group
+type SubteamSelfAddedEvent struct {
+ Type string `json:"type"`
+ SubteamID string `json:"subteam_id"`
+}
+
+// SubteamSelfRemovedEvent represents an event of you have been removed from a User Group
+type SubteamSelfRemovedEvent SubteamSelfAddedEvent
+
+// SubteamUpdatedEvent represents an event of an existing User Group has been updated or its members changed
+type SubteamUpdatedEvent struct {
+ Type string `json:"type"`
+ Subteam UserGroup `json:"subteam"`
+}
diff --git a/vendor/github.com/slack-go/slack/websocket_teams.go b/vendor/github.com/slack-go/slack/websocket_teams.go
new file mode 100644
index 00000000..3898c833
--- /dev/null
+++ b/vendor/github.com/slack-go/slack/websocket_teams.go
@@ -0,0 +1,33 @@
+package slack
+
+// TeamJoinEvent represents the Team join event
+type TeamJoinEvent struct {
+ Type string `json:"type"`
+ User User `json:"user"`
+}
+
+// TeamRenameEvent represents the Team rename event
+type TeamRenameEvent struct {
+ Type string `json:"type"`
+ Name string `json:"name,omitempty"`
+ EventTimestamp string `json:"event_ts,omitempty"`
+}
+
+// TeamPrefChangeEvent represents the Team preference change event
+type TeamPrefChangeEvent struct {
+ Type string `json:"type"`
+ Name string `json:"name,omitempty"`
+ Value []string `json:"value,omitempty"`
+}
+
+// TeamDomainChangeEvent represents the Team domain change event
+type TeamDomainChangeEvent struct {
+ Type string `json:"type"`
+ URL string `json:"url"`
+ Domain string `json:"domain"`
+}
+
+// TeamMigrationStartedEvent represents the Team migration started event
+type TeamMigrationStartedEvent struct {
+ Type string `json:"type"`
+}