pax_global_header 0000666 0000000 0000000 00000000064 14307410331 0014506 g ustar 00root root 0000000 0000000 52 comment=6f5eda2a6c1e50c8430bb07657bbe8be0def2b37
slack-0.11.3/ 0000775 0000000 0000000 00000000000 14307410331 0012665 5 ustar 00root root 0000000 0000000 slack-0.11.3/.github/ 0000775 0000000 0000000 00000000000 14307410331 0014225 5 ustar 00root root 0000000 0000000 slack-0.11.3/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14307410331 0016410 5 ustar 00root root 0000000 0000000 slack-0.11.3/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000000327 14307410331 0021104 0 ustar 00root root 0000000 0000000 ---
name: Bug Report
about: Create a report to help us improve
---
### What happened
### Expected behavior
### Steps to reproduce
#### reproducible code
#### manifest.yaml
### Versions
- Go:
- slack-go/slack:
slack-0.11.3/.github/ISSUE_TEMPLATE/feature_request.md 0000664 0000000 0000000 00000000162 14307410331 0022134 0 ustar 00root root 0000000 0000000 ---
name: Feature Request
about: Request an enhancement
---
### Description
### (Optional) Slack's documentation
slack-0.11.3/.github/pull_request_template.md 0000664 0000000 0000000 00000002167 14307410331 0021174 0 ustar 00root root 0000000 0000000 ##### Pull Request Guidelines
These are recommendations for pull requests.
They are strictly guidelines to help manage expectations.
##### PR preparation
Run `make pr-prep` from the root of the repository to run formatting, linting and tests.
##### Should this be an issue instead
- [ ] is it a convenience method? (no new functionality, streamlines some use case)
- [ ] exposes a previously private type, const, method, etc.
- [ ] is it application specific (caching, retry logic, rate limiting, etc)
- [ ] is it performance related.
##### API changes
Since API changes have to be maintained they undergo a more detailed review and are more likely to require changes.
- no tests, if you're adding to the API include at least a single test of the happy case.
- If you can accomplish your goal without changing the API, then do so.
- dependency changes. updates are okay. adding/removing need justification.
###### Examples of API changes that do not meet guidelines:
- in library cache for users. caches are use case specific.
- Convenience methods for Sending Messages, update, post, ephemeral, etc. consider opening an issue instead.
slack-0.11.3/.github/workflows/ 0000775 0000000 0000000 00000000000 14307410331 0016262 5 ustar 00root root 0000000 0000000 slack-0.11.3/.github/workflows/test.yml 0000664 0000000 0000000 00000001331 14307410331 0017762 0 ustar 00root root 0000000 0000000 name: Test
on:
push:
branches:
- master
pull_request:
jobs:
test:
runs-on: ubuntu-22.04
strategy:
matrix:
go:
- '1.17'
- '1.18'
- '1.19'
name: test go-${{ matrix.go }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go }}
- name: run test
run: go test -v -race ./...
env:
GO111MODULE: on
lint:
runs-on: ubuntu-22.04
name: lint
steps:
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@537aa1903e5d359d0b27dbc19ddd22c5087f3fbc # v3.2.0
with:
version: v1.48.0
slack-0.11.3/.gitignore 0000664 0000000 0000000 00000000021 14307410331 0014646 0 ustar 00root root 0000000 0000000 *.test
*~
.idea/
slack-0.11.3/.golangci.yml 0000664 0000000 0000000 00000000302 14307410331 0015244 0 ustar 00root root 0000000 0000000 run:
timeout: 6m
issues-exit-code: 1
linters:
disable-all: true
enable:
- goimports
- govet
- interfacer
- misspell
- structcheck
- unconvert
issues:
new: true
slack-0.11.3/CHANGELOG.md 0000664 0000000 0000000 00000012244 14307410331 0014501 0 ustar 00root root 0000000 0000000 ### v0.7.0 - October 2, 2020
full differences can be viewed using `git log --oneline --decorate --color v0.6.6..v0.7.0`
Thank you for many contributions!
#### Breaking Changes
- Add ScheduledMessage type ([#753])
- Add description field to option block object ([#783])
- Fix wrong conditional branch ([#782])
- The behavior of the user's application may change.(The current behavior is incorrect)
#### Highlights
- example: fix to start up a server ([#773])
- example: Add explanation how the message could be sent in a proper way ([#787])
- example: fix typo in error log ([#779])
- refactor: Make GetConversationsParameters.ExcludeArchived optional ([#791])
- refactor: Unify variables to "config" ([#800])
- refactor: Rename wrong file name ([#810])
- feature: Add SetUserRealName for change user's realName([#755])
- feature: Add response metadata to slack response ([#772])
- feature: Add response metadata to slack response ([#778])
- feature: Add select block element conversations filter field ([#790])
- feature: Add Root field to MessageEvent to support thread_broadcast subtype ([#793])
- feature: Add bot_profile to messages ([#794])
- doc: Add logo to README ([#813])
- doc: Update current project status and Add changelog for v0.7.0 ([#814])
[#753]: https://github.com/slack-go/slack/pull/753
[#755]: https://github.com/slack-go/slack/pull/755
[#772]: https://github.com/slack-go/slack/pull/772
[#773]: https://github.com/slack-go/slack/pull/773
[#778]: https://github.com/slack-go/slack/pull/778
[#779]: https://github.com/slack-go/slack/pull/779
[#782]: https://github.com/slack-go/slack/pull/782
[#783]: https://github.com/slack-go/slack/pull/783
[#787]: https://github.com/slack-go/slack/pull/787
[#790]: https://github.com/slack-go/slack/pull/790
[#791]: https://github.com/slack-go/slack/pull/791
[#793]: https://github.com/slack-go/slack/pull/793
[#794]: https://github.com/slack-go/slack/pull/794
[#800]: https://github.com/slack-go/slack/pull/800
[#810]: https://github.com/slack-go/slack/pull/810
[#813]: https://github.com/slack-go/slack/pull/813
[#814]: https://github.com/slack-go/slack/pull/814
### 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)
slack-0.11.3/LICENSE 0000664 0000000 0000000 00000002415 14307410331 0013674 0 ustar 00root root 0000000 0000000 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.
slack-0.11.3/Makefile 0000664 0000000 0000000 00000001756 14307410331 0014336 0 ustar 00root root 0000000 0000000 .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 -v -count=1 -timeout 300s -short ./...
test-race:
@go test -v -count=1 -timeout 300s -short -race ./...
test-integration:
@go test -v -count=1 -timeout 600s ./...
pr-prep: fmt lint test-race test-integration
slack-0.11.3/README.md 0000664 0000000 0000000 00000006047 14307410331 0014153 0 ustar 00root root 0000000 0000000 Slack API in Go [](https://pkg.go.dev/github.com/slack-go/slack)
===============
This is the original Slack library for Go created by Norberto Lopes, transferred to a GitHub organization.
You can also chat with us on the #slack-go, #slack-go-ja Slack channel on the Gophers Slack.

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.
## Project Status
There is currently no major version released.
Therefore, minor version releases may include backward incompatible changes.
See [CHANGELOG.md](https://github.com/slack-go/slack/blob/master/CHANGELOG.md) or [Releases](https://github.com/slack-go/slack/releases) for more information about the changes.
## 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.GetUserGroups(slack.GetUserGroupsOptionIncludeUsers(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 Socket Mode usage:
See https://github.com/slack-go/slack/blob/master/examples/socketmode/socketmode.go
## Minimal RTM usage:
As mentioned in https://api.slack.com/rtm - for most applications, Socket Mode is a better way to communicate with Slack.
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
## Socketmode Event Handler (Experimental)
When using socket mode, dealing with an event can be pretty lengthy as it requires you to route the event to the right place.
Instead, you can use `SocketmodeHandler` much like you use an HTTP handler to register which event you would like to listen to and what callback function will process that event when it occurs.
See [./examples/socketmode_handler/socketmode_handler.go](./examples/socketmode_handler/socketmode_handler.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
slack-0.11.3/TODO.txt 0000664 0000000 0000000 00000000205 14307410331 0014170 0 ustar 00root root 0000000 0000000 - Add more tests!!!
- Add support to have markdown hints
- See section Message Formatting at https://api.slack.com/docs/formatting
slack-0.11.3/admin.go 0000664 0000000 0000000 00000014755 14307410331 0014320 0 ustar 00root root 0000000 0000000 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
}
slack-0.11.3/apps.go 0000664 0000000 0000000 00000003550 14307410331 0014162 0 ustar 00root root 0000000 0000000 package slack
import (
"context"
"encoding/json"
"net/url"
)
type listEventAuthorizationsResponse struct {
SlackResponse
Authorizations []EventAuthorization `json:"authorizations"`
}
type EventAuthorization struct {
EnterpriseID string `json:"enterprise_id"`
TeamID string `json:"team_id"`
UserID string `json:"user_id"`
IsBot bool `json:"is_bot"`
IsEnterpriseInstall bool `json:"is_enterprise_install"`
}
func (api *Client) ListEventAuthorizations(eventContext string) ([]EventAuthorization, error) {
return api.ListEventAuthorizationsContext(context.Background(), eventContext)
}
// ListEventAuthorizationsContext lists authed users and teams for the given event_context. You must provide an app-level token to the client using OptionAppLevelToken. More info: https://api.slack.com/methods/apps.event.authorizations.list
func (api *Client) ListEventAuthorizationsContext(ctx context.Context, eventContext string) ([]EventAuthorization, error) {
resp := &listEventAuthorizationsResponse{}
request, _ := json.Marshal(map[string]string{
"event_context": eventContext,
})
err := postJSON(ctx, api.httpclient, api.endpoint+"apps.event.authorizations.list", api.appLevelToken, request, &resp, api)
if err != nil {
return nil, err
}
if !resp.Ok {
return nil, resp.Err()
}
return resp.Authorizations, nil
}
func (api *Client) UninstallApp(clientID, clientSecret string) error {
return api.UninstallAppContext(context.Background(), clientID, clientSecret)
}
func (api *Client) UninstallAppContext(ctx context.Context, clientID, clientSecret string) error {
values := url.Values{
"client_id": {clientID},
"client_secret": {clientSecret},
}
response := SlackResponse{}
err := api.getMethod(ctx, "apps.uninstall", api.token, values, &response)
if err != nil {
return err
}
return response.Err()
}
slack-0.11.3/apps_test.go 0000664 0000000 0000000 00000002742 14307410331 0015223 0 ustar 00root root 0000000 0000000 package slack
import (
"encoding/json"
"net/http"
"testing"
)
func TestListEventAuthorizations(t *testing.T) {
http.HandleFunc("/apps.event.authorizations.list", testListEventAuthorizationsHandler)
once.Do(startServer)
api := New("", OptionAppLevelToken("test-token"), OptionAPIURL("http://"+serverAddr+"/"))
authorizations, err := api.ListEventAuthorizations("1-message-T012345678-DR12345678")
if err != nil {
t.Errorf("Failed, but should have succeeded")
} else if len(authorizations) != 1 {
t.Errorf("Didn't get 1 authorization")
} else if authorizations[0].UserID != "U123456789" {
t.Errorf("User ID is wrong")
}
}
func testListEventAuthorizationsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(listEventAuthorizationsResponse{
SlackResponse: SlackResponse{Ok: true},
Authorizations: []EventAuthorization{
{
UserID: "U123456789",
TeamID: "T012345678",
},
},
})
w.Write(response)
}
func TestUninstallApp(t *testing.T) {
http.HandleFunc("/apps.uninstall", testUninstallAppHandler)
once.Do(startServer)
api := New("test-token", OptionAPIURL("http://"+serverAddr+"/"))
err := api.UninstallApp("", "")
if err != nil {
t.Errorf("Failed, but should have succeeded")
}
}
func testUninstallAppHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(SlackResponse{Ok: true})
w.Write(response)
}
slack-0.11.3/attachments.go 0000664 0000000 0000000 00000010774 14307410331 0015540 0 ustar 00root root 0000000 0000000 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"`
ServiceName string `json:"service_name,omitempty"`
ServiceIcon string `json:"service_icon,omitempty"`
FromURL string `json:"from_url,omitempty"`
OriginalURL string `json:"original_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"`
}
slack-0.11.3/attachments_test.go 0000664 0000000 0000000 00000003177 14307410331 0016576 0 ustar 00root root 0000000 0000000 package slack
import (
"encoding/json"
"strings"
"testing"
"github.com/go-test/deep"
)
func TestAttachment_UnmarshalMarshalJSON_WithBlocks(t *testing.T) {
originalAttachmentJson := `{
"id": 1,
"blocks": [
{
"type": "section",
"block_id": "xxxx",
"text": {
"type": "mrkdwn",
"text": "Pick something:",
"verbatim": true
},
"accessory": {
"type": "static_select",
"action_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"placeholder": {
"type": "plain_text",
"text": "Select one item",
"emoji": true
},
"options": [
{
"text": {
"type": "plain_text",
"text": "ghi",
"emoji": true
},
"value": "ghi"
}
]
}
}
],
"color": "#13A554",
"fallback": "[no preview available]"
}`
attachment := new(Attachment)
err := json.Unmarshal([]byte(originalAttachmentJson), attachment)
if err != nil {
t.Fatalf("expected no error unmarshaling attachment with blocks, got: %v", err)
}
actualAttachmentJson, err := json.Marshal(attachment)
if err != nil {
t.Fatal(err)
}
var (
actual interface{}
expected interface{}
)
if err = json.Unmarshal([]byte(originalAttachmentJson), &expected); err != nil {
t.Fatal(err)
}
if err = json.Unmarshal(actualAttachmentJson, &actual); err != nil {
t.Fatal(err)
}
if diff := deep.Equal(actual, expected); diff != nil {
t.Fatal("actual does not match expected\n", strings.Join(diff, "\n"))
}
}
slack-0.11.3/audit.go 0000664 0000000 0000000 00000010472 14307410331 0014326 0 ustar 00root root 0000000 0000000 package slack
import (
"context"
"net/url"
"strconv"
)
type AuditLogResponse struct {
Entries []AuditEntry `json:"entries"`
SlackResponse
}
type AuditEntry struct {
ID string `json:"id"`
DateCreate int `json:"date_create"`
Action string `json:"action"`
Actor struct {
Type string `json:"type"`
User AuditUser `json:"user"`
} `json:"actor"`
Entity struct {
Type string `json:"type"`
// Only one of the below will be completed, based on the value of Type a user, a channel, a file, an app, a workspace, or an enterprise
User AuditUser `json:"user"`
Channel AuditChannel `json:"channel"`
File AuditFile `json:"file"`
App AuditApp `json:"app"`
Workspace AuditWorkspace `json:"workspace"`
Enterprise AuditEnterprise `json:"enterprise"`
} `json:"entity"`
Context struct {
Location struct {
Type string `json:"type"`
ID string `json:"id"`
Name string `json:"name"`
Domain string `json:"domain"`
} `json:"location"`
UA string `json:"ua"`
IPAddress string `json:"ip_address"`
} `json:"context"`
Details struct {
NewValue interface{} `json:"new_value"`
PreviousValue interface{} `json:"previous_value"`
MobileOnly bool `json:"mobile_only"`
WebOnly bool `json:"web_only"`
NonSSOOnly bool `json:"non_sso_only"`
ExportType string `json:"export_type"`
ExportStart string `json:"export_start_ts"`
ExportEnd string `json:"export_end_ts"`
} `json:"details"`
}
type AuditUser struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Team string `json:"team"`
}
type AuditChannel struct {
ID string `json:"id"`
Name string `json:"name"`
Privacy string `json:"privacy"`
IsShared bool `json:"is_shared"`
IsOrgShared bool `json:"is_org_shared"`
}
type AuditFile struct {
ID string `json:"id"`
Name string `json:"name"`
Filetype string `json:"filetype"`
Title string `json:"title"`
}
type AuditApp struct {
ID string `json:"id"`
Name string `json:"name"`
IsDistributed bool `json:"is_distributed"`
IsDirectoryApproved bool `json:"is_directory_approved"`
IsWorkflowApp bool `json:"is_workflow_app"`
Scopes []string `json:"scopes"`
}
type AuditWorkspace struct {
ID string `json:"id"`
Name string `json:"name"`
Domain string `json:"domain"`
}
type AuditEnterprise struct {
ID string `json:"id"`
Name string `json:"name"`
Domain string `json:"domain"`
}
// AuditLogParameters contains all the parameters necessary (including the optional ones) for a GetAuditLogs() request
type AuditLogParameters struct {
Limit int
Cursor string
Latest int
Oldest int
Action string
Actor string
Entity string
}
func (api *Client) auditLogsRequest(ctx context.Context, path string, values url.Values) (*AuditLogResponse, error) {
response := &AuditLogResponse{}
err := api.getMethod(ctx, path, api.token, values, response)
if err != nil {
return nil, err
}
return response, response.Err()
}
// GetAuditLogs retrieves a page of audit entires according to the parameters given
func (api *Client) GetAuditLogs(params AuditLogParameters) (entries []AuditEntry, nextCursor string, err error) {
return api.GetAuditLogsContext(context.Background(), params)
}
// GetAuditLogsContext retrieves a page of audit entries according to the parameters given with a custom context
func (api *Client) GetAuditLogsContext(ctx context.Context, params AuditLogParameters) (entries []AuditEntry, nextCursor string, err error) {
values := url.Values{}
if params.Limit != 0 {
values.Add("limit", strconv.Itoa(params.Limit))
}
if params.Oldest != 0 {
values.Add("oldest", strconv.Itoa(params.Oldest))
}
if params.Latest != 0 {
values.Add("latest", strconv.Itoa(params.Latest))
}
if params.Cursor != "" {
values.Add("cursor", params.Cursor)
}
if params.Action != "" {
values.Add("action", params.Action)
}
if params.Actor != "" {
values.Add("actor", params.Actor)
}
if params.Entity != "" {
values.Add("entity", params.Entity)
}
response, err := api.auditLogsRequest(ctx, "audit/v1/logs", values)
if err != nil {
return nil, "", err
}
return response.Entries, response.ResponseMetadata.Cursor, response.Err()
}
slack-0.11.3/audit_test.go 0000664 0000000 0000000 00000004014 14307410331 0015360 0 ustar 00root root 0000000 0000000 package slack
import (
"net/http"
"testing"
)
func getAuditLogs(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response := []byte(`{"entries": [
{
"id": "0123a45b-6c7d-8900-e12f-3456789gh0i1",
"date_create": 1521214343,
"action": "user_login",
"actor": {
"type": "user",
"user": {
"id": "W123AB456",
"name": "Charlie Parker",
"email": "bird@slack.com"
}
},
"entity": {
"type": "user",
"user": {
"id": "W123AB456",
"name": "Charlie Parker",
"email": "bird@slack.com"
}
},
"context": {
"location": {
"type": "enterprise",
"id": "E1701NCCA",
"name": "Birdland",
"domain": "birdland"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36",
"ip_address": "1.23.45.678"
}
}
]
}`)
rw.Write(response)
}
func TestGetAuditLogs(t *testing.T) {
http.HandleFunc("/audit/v1/logs", getAuditLogs)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
events, nextCursor, err := api.GetAuditLogs(AuditLogParameters{})
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
if len(events) != 1 {
t.Fatal("Should have been 1 event")
}
// test the first login
event1 := events[0]
if event1.Action != "user_login" {
t.Fatal(ErrIncorrectResponse)
}
if event1.Entity.User.Email != "bird@slack.com" {
t.Fatal(ErrIncorrectResponse)
}
if event1.Context.Location.Domain != "birdland" {
t.Fatal(ErrIncorrectResponse)
}
if event1.DateCreate != 1521214343 {
t.Fatal(ErrIncorrectResponse)
}
if event1.Context.IPAddress != "1.23.45.678" {
t.Fatal(ErrIncorrectResponse)
}
if nextCursor != "" {
t.Fatal(ErrIncorrectResponse)
}
}
slack-0.11.3/auth.go 0000664 0000000 0000000 00000002215 14307410331 0014155 0 ustar 00root root 0000000 0000000 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 send a revocation request for our token to api.revoke with context
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)
}
slack-0.11.3/block.go 0000664 0000000 0000000 00000006134 14307410331 0014312 0 ustar 00root root 0000000 0000000 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"
MBTFile MessageBlockType = "file"
MBTInput MessageBlockType = "input"
MBTHeader MessageBlockType = "header"
MBTRichText MessageBlockType = "rich_text"
)
// 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"`
SelectedUsers []string `json:"selected_users"`
SelectedChannel string `json:"selected_channel"`
SelectedChannels []string `json:"selected_channels"`
SelectedConversation string `json:"selected_conversation"`
SelectedConversations []string `json:"selected_conversations"`
SelectedDate string `json:"selected_date"`
SelectedTime string `json:"selected_time"`
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"`
InitialTime string `json:"initial_time"`
}
// 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
}
slack-0.11.3/block_action.go 0000664 0000000 0000000 00000001312 14307410331 0015640 0 ustar 00root root 0000000 0000000 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,
},
}
}
slack-0.11.3/block_action_test.go 0000664 0000000 0000000 00000000737 14307410331 0016711 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewActionBlock(t *testing.T) {
approveBtnTxt := NewTextBlockObject("plain_text", "Approve", false, false)
approveBtn := NewButtonBlockElement("", "click_me_123", approveBtnTxt)
actionBlock := NewActionBlock("test", approveBtn)
assert.Equal(t, string(actionBlock.Type), "actions")
assert.Equal(t, actionBlock.BlockID, "test")
assert.Equal(t, len(actionBlock.Elements.ElementSet), 1)
}
slack-0.11.3/block_context.go 0000664 0000000 0000000 00000001576 14307410331 0016063 0 ustar 00root root 0000000 0000000 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#context
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,
}
}
slack-0.11.3/block_context_test.go 0000664 0000000 0000000 00000001213 14307410331 0017106 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewContextBlock(t *testing.T) {
locationPinImage := NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/tripAgentLocationMarker.png", "Location Pin Icon")
textExample := NewTextBlockObject("plain_text", "Location: Central Business District", true, false)
elements := []MixedElement{locationPinImage, textExample}
contextBlock := NewContextBlock("test", elements...)
assert.Equal(t, string(contextBlock.Type), "context")
assert.Equal(t, contextBlock.BlockID, "test")
assert.Equal(t, len(contextBlock.ContextElements.Elements), 2)
}
slack-0.11.3/block_conv.go 0000664 0000000 0000000 00000024365 14307410331 0015345 0 ustar 00root root 0000000 0000000 package slack
import (
"encoding/json"
"errors"
"fmt"
)
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 "file":
block = &FileBlock{}
case "header":
block = &HeaderBlock{}
case "image":
block = &ImageBlock{}
case "input":
block = &InputBlock{}
case "rich_text":
block = &RichTextBlock{}
case "section":
block = &SectionBlock{}
default:
block = &UnknownBlock{}
}
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 "timepicker":
e = &TimePickerBlockElement{}
case "plain_text_input":
e = &PlainTextInputBlockElement{}
case "static_select", "external_select", "users_select", "conversations_select", "channels_select":
e = &SelectBlockElement{}
case "multi_static_select", "multi_external_select", "multi_users_select", "multi_conversations_select", "multi_channels_select":
e = &MultiSelectBlockElement{}
case "checkboxes":
e = &CheckboxGroupsBlockElement{}
case "overflow":
e = &OverflowBlockElement{}
case "radio_buttons":
e = &RadioButtonsBlockElement{}
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 "timepicker":
blockElement = &TimePickerBlockElement{}
case "plain_text_input":
blockElement = &PlainTextInputBlockElement{}
case "checkboxes":
blockElement = &CheckboxGroupsBlockElement{}
case "radio_buttons":
blockElement = &RadioButtonsBlockElement{}
case "static_select", "external_select", "users_select", "conversations_select", "channels_select":
blockElement = &SelectBlockElement{}
default:
return fmt.Errorf("unsupported block element type %v", blockElementType)
}
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 "timepicker":
element, err := unmarshalBlockElement(r, &TimePickerBlockElement{})
if err != nil {
return err
}
a.TimePickerElement = element.(*TimePickerBlockElement)
case "plain_text_input":
element, err := unmarshalBlockElement(r, &PlainTextInputBlockElement{})
if err != nil {
return err
}
a.PlainTextInputElement = element.(*PlainTextInputBlockElement)
case "radio_buttons":
element, err := unmarshalBlockElement(r, &RadioButtonsBlockElement{})
if err != nil {
return err
}
a.RadioButtonsElement = element.(*RadioButtonsBlockElement)
case "static_select", "external_select", "users_select", "conversations_select", "channels_select":
element, err := unmarshalBlockElement(r, &SelectBlockElement{})
if err != nil {
return err
}
a.SelectElement = element.(*SelectBlockElement)
case "multi_static_select", "multi_external_select", "multi_users_select", "multi_conversations_select", "multi_channels_select":
element, err := unmarshalBlockElement(r, &MultiSelectBlockElement{})
if err != nil {
return err
}
a.MultiSelectElement = element.(*MultiSelectBlockElement)
case "checkboxes":
element, err := unmarshalBlockElement(r, &CheckboxGroupsBlockElement{})
if err != nil {
return err
}
a.CheckboxGroupsBlockElement = element.(*CheckboxGroupsBlockElement)
default:
element, err := unmarshalBlockElement(r, &UnknownBlockElement{})
if err != nil {
return err
}
a.UnknownElement = element.(*UnknownBlockElement)
}
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.TimePickerElement != nil {
return element.TimePickerElement
}
if element.PlainTextInputElement != nil {
return element.PlainTextInputElement
}
if element.RadioButtonsElement != nil {
return element.RadioButtonsElement
}
if element.CheckboxGroupsBlockElement != nil {
return element.CheckboxGroupsBlockElement
}
if element.SelectElement != nil {
return element.SelectElement
}
if element.MultiSelectElement != nil {
return element.MultiSelectElement
}
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
}
slack-0.11.3/block_divider.go 0000664 0000000 0000000 00000001076 14307410331 0016020 0 ustar 00root root 0000000 0000000 package slack
// DividerBlock for displaying a divider line between blocks (similar to
tag in html)
//
// More Information: https://api.slack.com/reference/messaging/blocks#divider
type DividerBlock struct {
Type MessageBlockType `json:"type"`
BlockID string `json:"block_id,omitempty"`
}
// BlockType returns the type of the block
func (s DividerBlock) BlockType() MessageBlockType {
return s.Type
}
// NewDividerBlock returns a new instance of a divider block
func NewDividerBlock() *DividerBlock {
return &DividerBlock{
Type: MBTDivider,
}
}
slack-0.11.3/block_divider_test.go 0000664 0000000 0000000 00000000324 14307410331 0017052 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewDividerBlock(t *testing.T) {
dividerBlock := NewDividerBlock()
assert.Equal(t, string(dividerBlock.Type), "divider")
}
slack-0.11.3/block_element.go 0000664 0000000 0000000 00000045424 14307410331 0016030 0 ustar 00root root 0000000 0000000 package slack
// https://api.slack.com/reference/messaging/block-elements
const (
METCheckboxGroups MessageElementType = "checkboxes"
METImage MessageElementType = "image"
METButton MessageElementType = "button"
METOverflow MessageElementType = "overflow"
METDatepicker MessageElementType = "datepicker"
METTimepicker MessageElementType = "timepicker"
METPlainTextInput MessageElementType = "plain_text_input"
METRadioButtons MessageElementType = "radio_buttons"
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"
MultiOptTypeStatic string = "multi_static_select"
MultiOptTypeExternal string = "multi_external_select"
MultiOptTypeUser string = "multi_users_select"
MultiOptTypeConversations string = "multi_conversations_select"
MultiOptTypeChannels string = "multi_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
TimePickerElement *TimePickerBlockElement
PlainTextInputElement *PlainTextInputBlockElement
RadioButtonsElement *RadioButtonsBlockElement
SelectElement *SelectBlockElement
MultiSelectElement *MultiSelectBlockElement
CheckboxGroupsBlockElement *CheckboxGroupsBlockElement
UnknownElement *UnknownBlockElement
}
// 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 *TimePickerBlockElement:
return &Accessory{TimePickerElement: element.(*TimePickerBlockElement)}
case *PlainTextInputBlockElement:
return &Accessory{PlainTextInputElement: element.(*PlainTextInputBlockElement)}
case *RadioButtonsBlockElement:
return &Accessory{RadioButtonsElement: element.(*RadioButtonsBlockElement)}
case *SelectBlockElement:
return &Accessory{SelectElement: element.(*SelectBlockElement)}
case *MultiSelectBlockElement:
return &Accessory{MultiSelectElement: element.(*MultiSelectBlockElement)}
case *CheckboxGroupsBlockElement:
return &Accessory{CheckboxGroupsBlockElement: element.(*CheckboxGroupsBlockElement)}
default:
return &Accessory{UnknownElement: element.(*UnknownBlockElement)}
}
}
// 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"`
}
// UnknownBlockElement any block element that this library does not directly support.
// See the "Rich Elements" section at the following URL:
// https://api.slack.com/changelog/2019-09-what-they-see-is-what-you-get-and-more-and-less
// New block element types may be introduced by Slack at any time; this is a catch-all for any such block elements.
type UnknownBlockElement struct {
Type MessageElementType `json:"type"`
Elements BlockElements
}
// ElementType returns the type of the Element
func (s UnknownBlockElement) ElementType() MessageElementType {
return s.Type
}
// 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,
}
}
// Style is a style of Button element
// https://api.slack.com/reference/block-kit/block-elements#button__fields
type Style string
const (
StyleDefault Style = ""
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
}
// WithStyle adds styling to the button object and returns the modified ButtonBlockElement
func (s *ButtonBlockElement) WithStyle(style Style) *ButtonBlockElement {
s.Style = style
return s
}
// WithConfirm adds a confirmation dialogue to the button object and returns the modified ButtonBlockElement
func (s *ButtonBlockElement) WithConfirm(confirm *ConfirmationBlockObject) *ButtonBlockElement {
s.Confirm = confirm
return s
}
// 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,
}
}
// OptionsResponse defines the response used for select block typahead.
//
// More Information: https://api.slack.com/reference/block-kit/block-elements#external_multi_select
type OptionsResponse struct {
Options []*OptionBlockObject `json:"options,omitempty"`
}
// OptionGroupsResponse defines the response used for select block typahead.
//
// More Information: https://api.slack.com/reference/block-kit/block-elements#external_multi_select
type OptionGroupsResponse struct {
OptionGroups []*OptionGroupBlockObject `json:"option_groups,omitempty"`
}
// 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"`
DefaultToCurrentConversation bool `json:"default_to_current_conversation,omitempty"`
ResponseURLEnabled bool `json:"response_url_enabled,omitempty"`
Filter *SelectBlockElementFilter `json:"filter,omitempty"`
MinQueryLength *int `json:"min_query_length,omitempty"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
}
// SelectBlockElementFilter allows to filter select element conversation options by type.
//
// More Information: https://api.slack.com/reference/block-kit/composition-objects#filter_conversations
type SelectBlockElementFilter struct {
Include []string `json:"include,omitempty"`
ExcludeExternalSharedChannels bool `json:"exclude_external_shared_channels,omitempty"`
ExcludeBotUsers bool `json:"exclude_bot_users,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,
}
}
// MultiSelectBlockElement defines a multiselect menu, with a static list
// of options passed in when defining the element.
//
// More Information: https://api.slack.com/reference/messaging/block-elements#multi_select
type MultiSelectBlockElement 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"`
InitialOptions []*OptionBlockObject `json:"initial_options,omitempty"`
InitialUsers []string `json:"initial_users,omitempty"`
InitialConversations []string `json:"initial_conversations,omitempty"`
InitialChannels []string `json:"initial_channels,omitempty"`
MinQueryLength *int `json:"min_query_length,omitempty"`
MaxSelectedItems *int `json:"max_selected_items,omitempty"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
}
// ElementType returns the type of the Element
func (s MultiSelectBlockElement) ElementType() MessageElementType {
return MessageElementType(s.Type)
}
// NewOptionsMultiSelectBlockElement returns a new instance of SelectBlockElement for use with
// the Options object only.
func NewOptionsMultiSelectBlockElement(optType string, placeholder *TextBlockObject, actionID string, options ...*OptionBlockObject) *MultiSelectBlockElement {
return &MultiSelectBlockElement{
Type: optType,
Placeholder: placeholder,
ActionID: actionID,
Options: options,
}
}
// NewOptionsGroupMultiSelectBlockElement returns a new instance of MultiSelectBlockElement for use with
// the Options object only.
func NewOptionsGroupMultiSelectBlockElement(
optType string,
placeholder *TextBlockObject,
actionID string,
optGroups ...*OptionGroupBlockObject,
) *MultiSelectBlockElement {
return &MultiSelectBlockElement{
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,omitempty"`
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,
}
}
// TimePickerBlockElement defines an element which lets users easily select a
// time from nice UI. Time picker elements can be used inside of
// section and actions blocks.
//
// More Information: https://api.slack.com/reference/messaging/block-elements#timepicker
type TimePickerBlockElement struct {
Type MessageElementType `json:"type"`
ActionID string `json:"action_id,omitempty"`
Placeholder *TextBlockObject `json:"placeholder,omitempty"`
InitialTime string `json:"initial_time,omitempty"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
}
// ElementType returns the type of the Element
func (s TimePickerBlockElement) ElementType() MessageElementType {
return s.Type
}
// NewTimePickerBlockElement returns an instance of a date picker element
func NewTimePickerBlockElement(actionID string) *TimePickerBlockElement {
return &TimePickerBlockElement{
Type: METTimepicker,
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/block-kit/block-elements#input
type PlainTextInputBlockElement struct {
Type MessageElementType `json:"type"`
ActionID string `json:"action_id,omitempty"`
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"`
DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"`
}
type DispatchActionConfig struct {
TriggerActionsOn []string `json:"trigger_actions_on,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,
}
}
// CheckboxGroupsBlockElement defines an element which allows users to choose
// one or more items from a list of possible options.
//
// More Information: https://api.slack.com/reference/block-kit/block-elements#checkboxes
type CheckboxGroupsBlockElement struct {
Type MessageElementType `json:"type"`
ActionID string `json:"action_id,omitempty"`
Options []*OptionBlockObject `json:"options"`
InitialOptions []*OptionBlockObject `json:"initial_options,omitempty"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
}
// ElementType returns the type of the Element
func (c CheckboxGroupsBlockElement) ElementType() MessageElementType {
return c.Type
}
// NewCheckboxGroupsBlockElement returns an instance of a checkbox-group block element
func NewCheckboxGroupsBlockElement(actionID string, options ...*OptionBlockObject) *CheckboxGroupsBlockElement {
return &CheckboxGroupsBlockElement{
Type: METCheckboxGroups,
ActionID: actionID,
Options: options,
}
}
// RadioButtonsBlockElement defines an element which lets users choose one item
// from a list of possible options.
//
// More Information: https://api.slack.com/reference/block-kit/block-elements#radio
type RadioButtonsBlockElement struct {
Type MessageElementType `json:"type"`
ActionID string `json:"action_id,omitempty"`
Options []*OptionBlockObject `json:"options"`
InitialOption *OptionBlockObject `json:"initial_option,omitempty"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
}
// ElementType returns the type of the Element
func (s RadioButtonsBlockElement) ElementType() MessageElementType {
return s.Type
}
// NewRadioButtonsBlockElement returns an instance of a radio buttons element.
func NewRadioButtonsBlockElement(actionID string, options ...*OptionBlockObject) *RadioButtonsBlockElement {
return &RadioButtonsBlockElement{
Type: METRadioButtons,
ActionID: actionID,
Options: options,
}
}
slack-0.11.3/block_element_test.go 0000664 0000000 0000000 00000016705 14307410331 0017067 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewImageBlockElement(t *testing.T) {
imageElement := NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/tripAgentLocationMarker.png", "Location Pin Icon")
assert.Equal(t, string(imageElement.Type), "image")
assert.Contains(t, imageElement.ImageURL, "tripAgentLocationMarker")
assert.Equal(t, imageElement.AltText, "Location Pin Icon")
}
func TestNewButtonBlockElement(t *testing.T) {
btnTxt := NewTextBlockObject("plain_text", "Next 2 Results", false, false)
btnElement := NewButtonBlockElement("test", "click_me_123", btnTxt)
assert.Equal(t, string(btnElement.Type), "button")
assert.Equal(t, btnElement.ActionID, "test")
assert.Equal(t, btnElement.Value, "click_me_123")
assert.Equal(t, btnElement.Text.Text, "Next 2 Results")
}
func TestWithStyleForButtonElement(t *testing.T) {
// these values are irrelevant in this test
btnTxt := NewTextBlockObject("plain_text", "Next 2 Results", false, false)
btnElement := NewButtonBlockElement("test", "click_me_123", btnTxt)
btnElement.WithStyle(StyleDefault)
assert.Equal(t, btnElement.Style, Style(""))
btnElement.WithStyle(StylePrimary)
assert.Equal(t, btnElement.Style, Style("primary"))
btnElement.WithStyle(StyleDanger)
assert.Equal(t, btnElement.Style, Style("danger"))
}
func TestNewOptionsSelectBlockElement(t *testing.T) {
testOptionText := NewTextBlockObject("plain_text", "Option One", false, false)
testOption := NewOptionBlockObject("test", testOptionText, nil)
option := NewOptionsSelectBlockElement("static_select", nil, "test", testOption)
assert.Equal(t, option.Type, "static_select")
assert.Equal(t, len(option.Options), 1)
assert.Nil(t, option.OptionGroups)
}
func TestNewOptionsGroupSelectBlockElement(t *testing.T) {
testOptionText := NewTextBlockObject("plain_text", "Option One", false, false)
testOption := NewOptionBlockObject("test", testOptionText, nil)
testLabel := NewTextBlockObject("plain_text", "Test Label", false, false)
testGroupOption := NewOptionGroupBlockElement(testLabel, testOption)
optGroup := NewOptionsGroupSelectBlockElement("static_select", nil, "test", testGroupOption)
assert.Equal(t, optGroup.Type, "static_select")
assert.Equal(t, optGroup.ActionID, "test")
assert.Equal(t, len(optGroup.OptionGroups), 1)
}
func TestNewOptionsMultiSelectBlockElement(t *testing.T) {
testOptionText := NewTextBlockObject("plain_text", "Option One", false, false)
testDescriptionText := NewTextBlockObject("plain_text", "Description One", false, false)
testOption := NewOptionBlockObject("test", testOptionText, testDescriptionText)
option := NewOptionsMultiSelectBlockElement("static_select", nil, "test", testOption)
assert.Equal(t, option.Type, "static_select")
assert.Equal(t, len(option.Options), 1)
assert.Nil(t, option.OptionGroups)
}
func TestNewOptionsGroupMultiSelectBlockElement(t *testing.T) {
testOptionText := NewTextBlockObject("plain_text", "Option One", false, false)
testOption := NewOptionBlockObject("test", testOptionText, nil)
testLabel := NewTextBlockObject("plain_text", "Test Label", false, false)
testGroupOption := NewOptionGroupBlockElement(testLabel, testOption)
optGroup := NewOptionsGroupMultiSelectBlockElement("static_select", nil, "test", testGroupOption)
assert.Equal(t, optGroup.Type, "static_select")
assert.Equal(t, optGroup.ActionID, "test")
assert.Equal(t, len(optGroup.OptionGroups), 1)
}
func TestNewOverflowBlockElement(t *testing.T) {
// Build Text Objects associated with each option
overflowOptionTextOne := NewTextBlockObject("plain_text", "Option One", false, false)
overflowOptionTextTwo := NewTextBlockObject("plain_text", "Option Two", false, false)
overflowOptionTextThree := NewTextBlockObject("plain_text", "Option Three", false, false)
// Build each option, providing a value for the option
overflowOptionOne := NewOptionBlockObject("value-0", overflowOptionTextOne, nil)
overflowOptionTwo := NewOptionBlockObject("value-1", overflowOptionTextTwo, nil)
overflowOptionThree := NewOptionBlockObject("value-2", overflowOptionTextThree, nil)
// Build overflow section
overflowElement := NewOverflowBlockElement("test", overflowOptionOne, overflowOptionTwo, overflowOptionThree)
assert.Equal(t, string(overflowElement.Type), "overflow")
assert.Equal(t, overflowElement.ActionID, "test")
assert.Equal(t, len(overflowElement.Options), 3)
}
func TestNewDatePickerBlockElement(t *testing.T) {
datepickerElement := NewDatePickerBlockElement("test")
assert.Equal(t, string(datepickerElement.Type), "datepicker")
assert.Equal(t, datepickerElement.ActionID, "test")
}
func TestNewTimePickerBlockElement(t *testing.T) {
timepickerElement := NewTimePickerBlockElement("test")
assert.Equal(t, string(timepickerElement.Type), "timepicker")
assert.Equal(t, timepickerElement.ActionID, "test")
}
func TestNewPlainTextInputBlockElement(t *testing.T) {
plainTextInputElement := NewPlainTextInputBlockElement(nil, "test")
assert.Equal(t, string(plainTextInputElement.Type), "plain_text_input")
assert.Equal(t, plainTextInputElement.ActionID, "test")
}
func TestNewCheckboxGroupsBlockElement(t *testing.T) {
// Build Text Objects associated with each option
checkBoxOptionTextOne := NewTextBlockObject("plain_text", "Check One", false, false)
checkBoxOptionTextTwo := NewTextBlockObject("plain_text", "Check Two", false, false)
checkBoxOptionTextThree := NewTextBlockObject("plain_text", "Check Three", false, false)
checkBoxDescriptionTextOne := NewTextBlockObject("plain_text", "Description One", false, false)
checkBoxDescriptionTextTwo := NewTextBlockObject("plain_text", "Description Two", false, false)
checkBoxDescriptionTextThree := NewTextBlockObject("plain_text", "Description Three", false, false)
// Build each option, providing a value for the option
checkBoxOptionOne := NewOptionBlockObject("value-0", checkBoxOptionTextOne, checkBoxDescriptionTextOne)
checkBoxOptionTwo := NewOptionBlockObject("value-1", checkBoxOptionTextTwo, checkBoxDescriptionTextTwo)
checkBoxOptionThree := NewOptionBlockObject("value-2", checkBoxOptionTextThree, checkBoxDescriptionTextThree)
// Build checkbox-group element
checkBoxGroupElement := NewCheckboxGroupsBlockElement("test", checkBoxOptionOne, checkBoxOptionTwo, checkBoxOptionThree)
assert.Equal(t, string(checkBoxGroupElement.Type), "checkboxes")
assert.Equal(t, checkBoxGroupElement.ActionID, "test")
assert.Equal(t, len(checkBoxGroupElement.Options), 3)
}
func TestNewRadioButtonsBlockElement(t *testing.T) {
// Build Text Objects associated with each option
radioButtonsOptionTextOne := NewTextBlockObject("plain_text", "Option One", false, false)
radioButtonsOptionTextTwo := NewTextBlockObject("plain_text", "Option Two", false, false)
radioButtonsOptionTextThree := NewTextBlockObject("plain_text", "Option Three", false, false)
// Build each option, providing a value for the option
radioButtonsOptionOne := NewOptionBlockObject("value-0", radioButtonsOptionTextOne, nil)
radioButtonsOptionTwo := NewOptionBlockObject("value-1", radioButtonsOptionTextTwo, nil)
radioButtonsOptionThree := NewOptionBlockObject("value-2", radioButtonsOptionTextThree, nil)
// Build radio button element
radioButtonsElement := NewRadioButtonsBlockElement("test", radioButtonsOptionOne, radioButtonsOptionTwo, radioButtonsOptionThree)
assert.Equal(t, string(radioButtonsElement.Type), "radio_buttons")
assert.Equal(t, radioButtonsElement.ActionID, "test")
assert.Equal(t, len(radioButtonsElement.Options), 3)
}
slack-0.11.3/block_file.go 0000664 0000000 0000000 00000001352 14307410331 0015306 0 ustar 00root root 0000000 0000000 package slack
// FileBlock defines data that is used to display a remote file.
//
// More Information: https://api.slack.com/reference/block-kit/blocks#file
type FileBlock struct {
Type MessageBlockType `json:"type"`
BlockID string `json:"block_id,omitempty"`
ExternalID string `json:"external_id"`
Source string `json:"source"`
}
// BlockType returns the type of the block
func (s FileBlock) BlockType() MessageBlockType {
return s.Type
}
// NewFileBlock returns a new instance of a file block
func NewFileBlock(blockID string, externalID string, source string) *FileBlock {
return &FileBlock{
Type: MBTFile,
BlockID: blockID,
ExternalID: externalID,
Source: source,
}
}
slack-0.11.3/block_file_test.go 0000664 0000000 0000000 00000000561 14307410331 0016346 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewFileBlock(t *testing.T) {
fileBlock := NewFileBlock("test", "external_id", "source")
assert.Equal(t, string(fileBlock.Type), "file")
assert.Equal(t, fileBlock.BlockID, "test")
assert.Equal(t, fileBlock.ExternalID, "external_id")
assert.Equal(t, fileBlock.Source, "source")
}
slack-0.11.3/block_header.go 0000664 0000000 0000000 00000001727 14307410331 0015625 0 ustar 00root root 0000000 0000000 package slack
// HeaderBlock defines a new block of type header
//
// More Information: https://api.slack.com/reference/messaging/blocks#header
type HeaderBlock struct {
Type MessageBlockType `json:"type"`
Text *TextBlockObject `json:"text,omitempty"`
BlockID string `json:"block_id,omitempty"`
}
// BlockType returns the type of the block
func (s HeaderBlock) BlockType() MessageBlockType {
return s.Type
}
// HeaderBlockOption allows configuration of options for a new header block
type HeaderBlockOption func(*HeaderBlock)
func HeaderBlockOptionBlockID(blockID string) HeaderBlockOption {
return func(block *HeaderBlock) {
block.BlockID = blockID
}
}
// NewHeaderBlock returns a new instance of a header block to be rendered
func NewHeaderBlock(textObj *TextBlockObject, options ...HeaderBlockOption) *HeaderBlock {
block := HeaderBlock{
Type: MBTHeader,
Text: textObj,
}
for _, option := range options {
option(&block)
}
return &block
}
slack-0.11.3/block_header_test.go 0000664 0000000 0000000 00000001000 14307410331 0016644 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewHeaderBlock(t *testing.T) {
textInfo := NewTextBlockObject("plain_text", "This is quite the header", false, false)
headerBlock := NewHeaderBlock(textInfo, HeaderBlockOptionBlockID("test_block"))
assert.Equal(t, string(headerBlock.Type), "header")
assert.Equal(t, headerBlock.BlockID, "test_block")
assert.Equal(t, headerBlock.Text.Type, "plain_text")
assert.Contains(t, headerBlock.Text.Text, "quite the header")
}
slack-0.11.3/block_image.go 0000664 0000000 0000000 00000001502 14307410331 0015446 0 ustar 00root root 0000000 0000000 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,omitempty"`
}
// 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,
}
}
slack-0.11.3/block_image_test.go 0000664 0000000 0000000 00000001145 14307410331 0016510 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewImageBlock(t *testing.T) {
imageText := NewTextBlockObject("plain_text", "Location", false, false)
imageBlock := NewImageBlock("https://api.slack.com/img/blocks/bkb_template_images/tripAgentLocationMarker.png", "Marker", "test", imageText)
assert.Equal(t, string(imageBlock.Type), "image")
assert.Equal(t, imageBlock.Title.Type, "plain_text")
assert.Equal(t, imageBlock.BlockID, "test")
assert.Contains(t, imageBlock.Title.Text, "Location")
assert.Contains(t, imageBlock.ImageURL, "tripAgentLocationMarker.png")
}
slack-0.11.3/block_input.go 0000664 0000000 0000000 00000001721 14307410331 0015526 0 ustar 00root root 0000000 0000000 package slack
// InputBlock defines data that is used to display user input fields.
//
// More Information: https://api.slack.com/reference/block-kit/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"`
DispatchAction bool `json:"dispatch_action,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, hint *TextBlockObject, element BlockElement) *InputBlock {
return &InputBlock{
Type: MBTInput,
BlockID: blockID,
Label: label,
Element: element,
Hint: hint,
}
}
slack-0.11.3/block_input_test.go 0000664 0000000 0000000 00000001036 14307410331 0016564 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewInputBlock(t *testing.T) {
label := NewTextBlockObject("plain_text", "label", false, false)
element := NewDatePickerBlockElement("action_id")
hint := NewTextBlockObject("plain_text", "hint", false, false)
inputBlock := NewInputBlock("test", label, hint, element)
assert.Equal(t, string(inputBlock.Type), "input")
assert.Equal(t, inputBlock.BlockID, "test")
assert.Equal(t, inputBlock.Label, label)
assert.Equal(t, inputBlock.Element, element)
}
slack-0.11.3/block_object.go 0000664 0000000 0000000 00000016300 14307410331 0015634 0 ustar 00root root 0000000 0000000 package slack
import (
"encoding/json"
"errors"
)
// 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
}
// Validate checks if TextBlockObject has valid values
func (s TextBlockObject) Validate() error {
if s.Type != "plain_text" && s.Type != "mrkdwn" {
return errors.New("type must be either of plain_text or mrkdwn")
}
// https://github.com/slack-go/slack/issues/881
if s.Type == "mrkdwn" && s.Emoji {
return errors.New("emoji cannot be true in mrkdown")
}
return nil
}
// 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,
}
}
// BlockType returns the type of the block
func (t TextBlockObject) BlockType() MessageBlockType {
if t.Type == "mrkdwn" {
return MarkdownType
}
return PlainTextType
}
// 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"`
Style Style `json:"style,omitempty"`
}
// validateType enforces block objects for element and block parameters
func (s ConfirmationBlockObject) validateType() MessageObjectType {
return motConfirmation
}
// WithStyle add styling to confirmation object
func (s *ConfirmationBlockObject) WithStyle(style Style) *ConfirmationBlockObject {
s.Style = style
return s
}
// 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"`
Description *TextBlockObject `json:"description,omitempty"`
URL string `json:"url,omitempty"`
}
// NewOptionBlockObject returns an instance of a new Option Block Element
func NewOptionBlockObject(value string, text, description *TextBlockObject) *OptionBlockObject {
return &OptionBlockObject{
Text: text,
Value: value,
Description: description,
}
}
// 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,
}
}
slack-0.11.3/block_object_test.go 0000664 0000000 0000000 00000007362 14307410331 0016703 0 ustar 00root root 0000000 0000000 package slack
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewImageBlockObject(t *testing.T) {
imageObject := NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/beagle.png", "Beagle")
assert.Equal(t, string(imageObject.Type), "image")
assert.Equal(t, imageObject.AltText, "Beagle")
assert.Contains(t, imageObject.ImageURL, "beagle.png")
}
func TestNewTextBlockObject(t *testing.T) {
textObject := NewTextBlockObject("plain_text", "test", true, false)
assert.Equal(t, textObject.Type, "plain_text")
assert.Equal(t, textObject.Text, "test")
assert.True(t, textObject.Emoji, "Emoji property should be true")
assert.False(t, textObject.Verbatim, "Verbatim should be false")
}
func TestNewConfirmationBlockObject(t *testing.T) {
titleObj := NewTextBlockObject("plain_text", "testTitle", false, false)
textObj := NewTextBlockObject("plain_text", "testText", false, false)
confirmObj := NewTextBlockObject("plain_text", "testConfirm", false, false)
confirmation := NewConfirmationBlockObject(titleObj, textObj, confirmObj, nil)
assert.Equal(t, confirmation.Title.Text, "testTitle")
assert.Equal(t, confirmation.Text.Text, "testText")
assert.Equal(t, confirmation.Confirm.Text, "testConfirm")
assert.Nil(t, confirmation.Deny, "Deny should be nil")
}
func TestWithStyleForConfirmation(t *testing.T) {
// these values are irrelevant in this test
titleObj := NewTextBlockObject("plain_text", "testTitle", false, false)
textObj := NewTextBlockObject("plain_text", "testText", false, false)
confirmObj := NewTextBlockObject("plain_text", "testConfirm", false, false)
confirmation := NewConfirmationBlockObject(titleObj, textObj, confirmObj, nil)
confirmation.WithStyle(StyleDefault)
assert.Equal(t, confirmation.Style, Style(""))
confirmation.WithStyle(StylePrimary)
assert.Equal(t, confirmation.Style, Style("primary"))
confirmation.WithStyle(StyleDanger)
assert.Equal(t, confirmation.Style, Style("danger"))
}
func TestNewOptionBlockObject(t *testing.T) {
valTextObj := NewTextBlockObject("plain_text", "testText", false, false)
valDescriptionObj := NewTextBlockObject("plain_text", "testDescription", false, false)
optObj := NewOptionBlockObject("testOpt", valTextObj, valDescriptionObj)
assert.Equal(t, optObj.Text.Text, "testText")
assert.Equal(t, optObj.Description.Text, "testDescription")
assert.Equal(t, optObj.Value, "testOpt")
}
func TestNewOptionGroupBlockElement(t *testing.T) {
labelObj := NewTextBlockObject("plain_text", "testLabel", false, false)
valTextObj := NewTextBlockObject("plain_text", "testText", false, false)
optObj := NewOptionBlockObject("testOpt", valTextObj, nil)
optGroup := NewOptionGroupBlockElement(labelObj, optObj)
assert.Equal(t, optGroup.Label.Text, "testLabel")
assert.Len(t, optGroup.Options, 1, "Options should contain one element")
}
func TestValidateTextBlockObject(t *testing.T) {
tests := []struct {
input TextBlockObject
expected error
}{
{
input: TextBlockObject{
Type: "plain_text",
Text: "testText",
Emoji: false,
Verbatim: false,
},
expected: nil,
},
{
input: TextBlockObject{
Type: "mrkdwn",
Text: "testText",
Emoji: false,
Verbatim: false,
},
expected: nil,
},
{
input: TextBlockObject{
Type: "invalid",
Text: "testText",
Emoji: false,
Verbatim: false,
},
expected: errors.New("type must be either of plain_text or mrkdwn"),
},
{
input: TextBlockObject{
Type: "mrkdwn",
Text: "testText",
Emoji: true,
Verbatim: false,
},
expected: errors.New("emoji cannot be true in mrkdown"),
},
}
for _, test := range tests {
err := test.input.Validate()
assert.Equal(t, err, test.expected)
}
}
slack-0.11.3/block_rich_text.go 0000664 0000000 0000000 00000024272 14307410331 0016366 0 ustar 00root root 0000000 0000000 package slack
import (
"encoding/json"
)
// RichTextBlock defines a new block of type rich_text.
// More Information: https://api.slack.com/changelog/2019-09-what-they-see-is-what-you-get-and-more-and-less
type RichTextBlock struct {
Type MessageBlockType `json:"type"`
BlockID string `json:"block_id,omitempty"`
Elements []RichTextElement `json:"elements"`
}
func (b RichTextBlock) BlockType() MessageBlockType {
return b.Type
}
func (e *RichTextBlock) UnmarshalJSON(b []byte) error {
var raw struct {
Type MessageBlockType `json:"type"`
BlockID string `json:"block_id"`
RawElements []json.RawMessage `json:"elements"`
}
if string(b) == "{}" {
return nil
}
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
elems := make([]RichTextElement, 0, len(raw.RawElements))
for _, r := range raw.RawElements {
var s struct {
Type RichTextElementType `json:"type"`
}
if err := json.Unmarshal(r, &s); err != nil {
return err
}
var elem RichTextElement
switch s.Type {
case RTESection:
elem = &RichTextSection{}
default:
elems = append(elems, &RichTextUnknown{
Type: s.Type,
Raw: string(r),
})
continue
}
if err := json.Unmarshal(r, &elem); err != nil {
return err
}
elems = append(elems, elem)
}
*e = RichTextBlock{
Type: raw.Type,
BlockID: raw.BlockID,
Elements: elems,
}
return nil
}
// NewRichTextBlock returns a new instance of RichText Block.
func NewRichTextBlock(blockID string, elements ...RichTextElement) *RichTextBlock {
return &RichTextBlock{
Type: MBTRichText,
BlockID: blockID,
Elements: elements,
}
}
type RichTextElementType string
type RichTextElement interface {
RichTextElementType() RichTextElementType
}
const (
RTEList RichTextElementType = "rich_text_list"
RTEPreformatted RichTextElementType = "rich_text_preformatted"
RTEQuote RichTextElementType = "rich_text_quote"
RTESection RichTextElementType = "rich_text_section"
RTEUnknown RichTextElementType = "rich_text_unknown"
)
type RichTextUnknown struct {
Type RichTextElementType
Raw string
}
func (u RichTextUnknown) RichTextElementType() RichTextElementType {
return u.Type
}
type RichTextSection struct {
Type RichTextElementType `json:"type"`
Elements []RichTextSectionElement `json:"elements"`
}
// ElementType returns the type of the Element
func (s RichTextSection) RichTextElementType() RichTextElementType {
return s.Type
}
func (e *RichTextSection) UnmarshalJSON(b []byte) error {
var raw struct {
RawElements []json.RawMessage `json:"elements"`
}
if string(b) == "{}" {
return nil
}
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
elems := make([]RichTextSectionElement, 0, len(raw.RawElements))
for _, r := range raw.RawElements {
var s struct {
Type RichTextSectionElementType `json:"type"`
}
if err := json.Unmarshal(r, &s); err != nil {
return err
}
var elem RichTextSectionElement
switch s.Type {
case RTSEText:
elem = &RichTextSectionTextElement{}
case RTSEChannel:
elem = &RichTextSectionChannelElement{}
case RTSEUser:
elem = &RichTextSectionUserElement{}
case RTSEEmoji:
elem = &RichTextSectionEmojiElement{}
case RTSELink:
elem = &RichTextSectionLinkElement{}
case RTSETeam:
elem = &RichTextSectionTeamElement{}
case RTSEUserGroup:
elem = &RichTextSectionUserGroupElement{}
case RTSEDate:
elem = &RichTextSectionDateElement{}
case RTSEBroadcast:
elem = &RichTextSectionBroadcastElement{}
case RTSEColor:
elem = &RichTextSectionColorElement{}
default:
elems = append(elems, &RichTextSectionUnknownElement{
Type: s.Type,
Raw: string(r),
})
continue
}
if err := json.Unmarshal(r, elem); err != nil {
return err
}
elems = append(elems, elem)
}
*e = RichTextSection{
Type: RTESection,
Elements: elems,
}
return nil
}
// NewRichTextSectionBlockElement .
func NewRichTextSection(elements ...RichTextSectionElement) *RichTextSection {
return &RichTextSection{
Type: RTESection,
Elements: elements,
}
}
type RichTextSectionElementType string
const (
RTSEBroadcast RichTextSectionElementType = "broadcast"
RTSEChannel RichTextSectionElementType = "channel"
RTSEColor RichTextSectionElementType = "color"
RTSEDate RichTextSectionElementType = "date"
RTSEEmoji RichTextSectionElementType = "emoji"
RTSELink RichTextSectionElementType = "link"
RTSETeam RichTextSectionElementType = "team"
RTSEText RichTextSectionElementType = "text"
RTSEUser RichTextSectionElementType = "user"
RTSEUserGroup RichTextSectionElementType = "usergroup"
RTSEUnknown RichTextSectionElementType = "unknown"
)
type RichTextSectionElement interface {
RichTextSectionElementType() RichTextSectionElementType
}
type RichTextSectionTextStyle struct {
Bold bool `json:"bold,omitempty"`
Italic bool `json:"italic,omitempty"`
Strike bool `json:"strike,omitempty"`
Code bool `json:"code,omitempty"`
}
type RichTextSectionTextElement struct {
Type RichTextSectionElementType `json:"type"`
Text string `json:"text"`
Style *RichTextSectionTextStyle `json:"style,omitempty"`
}
func (r RichTextSectionTextElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
func NewRichTextSectionTextElement(text string, style *RichTextSectionTextStyle) *RichTextSectionTextElement {
return &RichTextSectionTextElement{
Type: RTSEText,
Text: text,
Style: style,
}
}
type RichTextSectionChannelElement struct {
Type RichTextSectionElementType `json:"type"`
ChannelID string `json:"channel_id"`
Style *RichTextSectionTextStyle `json:"style,omitempty"`
}
func (r RichTextSectionChannelElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
func NewRichTextSectionChannelElement(channelID string, style *RichTextSectionTextStyle) *RichTextSectionChannelElement {
return &RichTextSectionChannelElement{
Type: RTSEText,
ChannelID: channelID,
Style: style,
}
}
type RichTextSectionUserElement struct {
Type RichTextSectionElementType `json:"type"`
UserID string `json:"user_id"`
Style *RichTextSectionTextStyle `json:"style,omitempty"`
}
func (r RichTextSectionUserElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
func NewRichTextSectionUserElement(userID string, style *RichTextSectionTextStyle) *RichTextSectionUserElement {
return &RichTextSectionUserElement{
Type: RTSEUser,
UserID: userID,
Style: style,
}
}
type RichTextSectionEmojiElement struct {
Type RichTextSectionElementType `json:"type"`
Name string `json:"name"`
SkinTone int `json:"skin_tone"`
Style *RichTextSectionTextStyle `json:"style,omitempty"`
}
func (r RichTextSectionEmojiElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
func NewRichTextSectionEmojiElement(name string, skinTone int, style *RichTextSectionTextStyle) *RichTextSectionEmojiElement {
return &RichTextSectionEmojiElement{
Type: RTSEEmoji,
Name: name,
SkinTone: skinTone,
Style: style,
}
}
type RichTextSectionLinkElement struct {
Type RichTextSectionElementType `json:"type"`
URL string `json:"url"`
Text string `json:"text"`
Style *RichTextSectionTextStyle `json:"style,omitempty"`
}
func (r RichTextSectionLinkElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
func NewRichTextSectionLinkElement(url, text string, style *RichTextSectionTextStyle) *RichTextSectionLinkElement {
return &RichTextSectionLinkElement{
Type: RTSELink,
URL: url,
Text: text,
Style: style,
}
}
type RichTextSectionTeamElement struct {
Type RichTextSectionElementType `json:"type"`
TeamID string `json:"team_id"`
Style *RichTextSectionTextStyle `json:"style.omitempty"`
}
func (r RichTextSectionTeamElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
func NewRichTextSectionTeamElement(teamID string, style *RichTextSectionTextStyle) *RichTextSectionTeamElement {
return &RichTextSectionTeamElement{
Type: RTSETeam,
TeamID: teamID,
Style: style,
}
}
type RichTextSectionUserGroupElement struct {
Type RichTextSectionElementType `json:"type"`
UsergroupID string `json:"usergroup_id"`
}
func (r RichTextSectionUserGroupElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
func NewRichTextSectionUserGroupElement(usergroupID string) *RichTextSectionUserGroupElement {
return &RichTextSectionUserGroupElement{
Type: RTSEUserGroup,
UsergroupID: usergroupID,
}
}
type RichTextSectionDateElement struct {
Type RichTextSectionElementType `json:"type"`
Timestamp string `json:"timestamp"`
}
func (r RichTextSectionDateElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
func NewRichTextSectionDateElement(timestamp string) *RichTextSectionDateElement {
return &RichTextSectionDateElement{
Type: RTSEDate,
Timestamp: timestamp,
}
}
type RichTextSectionBroadcastElement struct {
Type RichTextSectionElementType `json:"type"`
Range string `json:"range"`
}
func (r RichTextSectionBroadcastElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
func NewRichTextSectionBroadcastElement(rangeStr string) *RichTextSectionBroadcastElement {
return &RichTextSectionBroadcastElement{
Type: RTSEBroadcast,
Range: rangeStr,
}
}
type RichTextSectionColorElement struct {
Type RichTextSectionElementType `json:"type"`
Value string `json:"value"`
}
func (r RichTextSectionColorElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
func NewRichTextSectionColorElement(value string) *RichTextSectionColorElement {
return &RichTextSectionColorElement{
Type: RTSEColor,
Value: value,
}
}
type RichTextSectionUnknownElement struct {
Type RichTextSectionElementType `json:"type"`
Raw string
}
func (r RichTextSectionUnknownElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type
}
slack-0.11.3/block_rich_text_test.go 0000664 0000000 0000000 00000005151 14307410331 0017420 0 ustar 00root root 0000000 0000000 package slack
import (
"encoding/json"
"testing"
"github.com/go-test/deep"
)
const (
dummyPayload = `{
"type":"rich_text",
"block_id":"FaYCD",
"elements": [
{
"type":"rich_text_section",
"elements": [
{
"type":"channel",
"channel_id":"C012345678"
},
{
"type":"text",
"text":"dummy_text"
}
]
}
]
}`
)
func TestRichTextBlock_UnmarshalJSON(t *testing.T) {
cases := []struct {
raw []byte
expected RichTextBlock
err error
}{
{
[]byte(`{"elements":[{"type":"rich_text_unknown"},{"type":"rich_text_section"}]}`),
RichTextBlock{
Elements: []RichTextElement{
&RichTextUnknown{Type: RTEUnknown, Raw: `{"type":"rich_text_unknown"}`},
&RichTextSection{Type: RTESection, Elements: []RichTextSectionElement{}},
},
},
nil,
},
{
[]byte(`{"type": "rich_text","block_id":"blk","elements":[]}`),
RichTextBlock{
Type: MBTRichText,
BlockID: "blk",
Elements: []RichTextElement{},
},
nil,
},
}
for _, tc := range cases {
var actual RichTextBlock
err := json.Unmarshal(tc.raw, &actual)
if err != nil {
if tc.err == nil {
t.Errorf("unexpected error: %s", err)
}
t.Errorf("expected error is %s, but got %s", tc.err, err)
}
if tc.err != nil {
t.Errorf("expected to raise an error %s", tc.err)
}
if diff := deep.Equal(actual, tc.expected); diff != nil {
t.Errorf("actual value does not match expected one\n%s", diff)
}
}
}
func TestRichTextSection_UnmarshalJSON(t *testing.T) {
cases := []struct {
raw []byte
expected RichTextSection
err error
}{
{
[]byte(`{"elements":[{"type":"unknown","value":10},{"type":"text","text":"hi"}]}`),
RichTextSection{
Type: RTESection,
Elements: []RichTextSectionElement{
&RichTextSectionUnknownElement{Type: RTSEUnknown, Raw: `{"type":"unknown","value":10}`},
&RichTextSectionTextElement{Type: RTSEText, Text: "hi"},
},
},
nil,
},
{
[]byte(`{"type": "rich_text_section","elements":[]}`),
RichTextSection{
Type: RTESection,
Elements: []RichTextSectionElement{},
},
nil,
},
}
for _, tc := range cases {
var actual RichTextSection
err := json.Unmarshal(tc.raw, &actual)
if err != nil {
if tc.err == nil {
t.Errorf("unexpected error: %s", err)
}
t.Errorf("expected error is %s, but got %s", tc.err, err)
}
if tc.err != nil {
t.Errorf("expected to raise an error %s", tc.err)
}
if diff := deep.Equal(actual, tc.expected); diff != nil {
t.Errorf("actual value does not match expected one\n%s", diff)
}
}
}
slack-0.11.3/block_section.go 0000664 0000000 0000000 00000002321 14307410331 0016030 0 ustar 00root root 0000000 0000000 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
}
slack-0.11.3/block_section_test.go 0000664 0000000 0000000 00000003007 14307410331 0017071 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSectionBlock(t *testing.T) {
textInfo := NewTextBlockObject("mrkdwn", "**\n★★★★★\n$340 per night\nRated: 9.1 - Excellent", false, false)
sectionBlock := NewSectionBlock(textInfo, nil, nil, SectionBlockOptionBlockID("test_block"))
assert.Equal(t, string(sectionBlock.Type), "section")
assert.Equal(t, sectionBlock.BlockID, "test_block")
assert.Equal(t, len(sectionBlock.Fields), 0)
assert.Nil(t, sectionBlock.Accessory)
assert.Equal(t, sectionBlock.Text.Type, "mrkdwn")
assert.Contains(t, sectionBlock.Text.Text, "New Orleans")
}
func TestNewBlockSectionContainsAddedTextBlockAndAccessory(t *testing.T) {
textBlockObject := NewTextBlockObject("mrkdwn", "You have a new test: *Hi there* :wave:", true, false)
conflictImage := NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/notificationsWarningIcon.png", "notifications warning icon")
sectionBlock := NewSectionBlock(textBlockObject, nil, NewAccessory(conflictImage))
assert.Equal(t, sectionBlock.BlockType(), MBTSection)
assert.Equal(t, len(sectionBlock.BlockID), 0)
textBlockInSection := sectionBlock.Text
assert.Equal(t, textBlockInSection.Text, textBlockObject.Text)
assert.Equal(t, textBlockInSection.Type, textBlockObject.Type)
assert.True(t, textBlockInSection.Emoji)
assert.False(t, textBlockInSection.Verbatim)
assert.Equal(t, sectionBlock.Accessory.ImageElement, conflictImage)
}
slack-0.11.3/block_test.go 0000664 0000000 0000000 00000000410 14307410331 0015340 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewBlockMessage(t *testing.T) {
dividerBlock := NewDividerBlock()
blockMessage := NewBlockMessage(dividerBlock)
assert.Equal(t, len(blockMessage.Msg.Blocks.BlockSet), 1)
}
slack-0.11.3/block_unknown.go 0000664 0000000 0000000 00000000654 14307410331 0016072 0 ustar 00root root 0000000 0000000 package slack
// UnknownBlock represents a block type that is not yet known. This block type exists to prevent Slack from introducing
// new and unknown block types that break this library.
type UnknownBlock struct {
Type MessageBlockType `json:"type"`
BlockID string `json:"block_id,omitempty"`
}
// BlockType returns the type of the block
func (b UnknownBlock) BlockType() MessageBlockType {
return b.Type
}
slack-0.11.3/bookmarks.go 0000664 0000000 0000000 00000010752 14307410331 0015211 0 ustar 00root root 0000000 0000000 package slack
import (
"context"
"net/url"
)
type Bookmark struct {
ID string `json:"id"`
ChannelID string `json:"channel_id"`
Title string `json:"title"`
Link string `json:"link"`
Emoji string `json:"emoji"`
IconURL string `json:"icon_url"`
Type string `json:"type"`
Created JSONTime `json:"date_created"`
Updated JSONTime `json:"date_updated"`
Rank string `json:"rank"`
LastUpdatedByUserID string `json:"last_updated_by_user_id"`
LastUpdatedByTeamID string `json:"last_updated_by_team_id"`
ShortcutID string `json:"shortcut_id"`
EntityID string `json:"entity_id"`
AppID string `json:"app_id"`
}
type AddBookmarkParameters struct {
Title string // A required title for the bookmark
Type string // A required type for the bookmark
Link string // URL required for type:link
Emoji string // An optional emoji
EntityID string
ParentID string
}
type EditBookmarkParameters struct {
Title *string // Change the title. Set to "" to clear
Emoji *string // Change the emoji. Set to "" to clear
Link string // Change the link
}
type addBookmarkResponse struct {
Bookmark Bookmark `json:"bookmark"`
SlackResponse
}
type editBookmarkResponse struct {
Bookmark Bookmark `json:"bookmark"`
SlackResponse
}
type listBookmarksResponse struct {
Bookmarks []Bookmark `json:"bookmarks"`
SlackResponse
}
// AddBookmark adds a bookmark in a channel
func (api *Client) AddBookmark(channelID string, params AddBookmarkParameters) (Bookmark, error) {
return api.AddBookmarkContext(context.Background(), channelID, params)
}
// AddBookmarkContext adds a bookmark in a channel with a custom context
func (api *Client) AddBookmarkContext(ctx context.Context, channelID string, params AddBookmarkParameters) (Bookmark, error) {
values := url.Values{
"channel_id": {channelID},
"token": {api.token},
"title": {params.Title},
"type": {params.Type},
}
if params.Link != "" {
values.Set("link", params.Link)
}
if params.Emoji != "" {
values.Set("emoji", params.Emoji)
}
if params.EntityID != "" {
values.Set("entity_id", params.EntityID)
}
if params.ParentID != "" {
values.Set("parent_id", params.ParentID)
}
response := &addBookmarkResponse{}
if err := api.postMethod(ctx, "bookmarks.add", values, response); err != nil {
return Bookmark{}, err
}
return response.Bookmark, response.Err()
}
// RemoveBookmark removes a bookmark from a channel
func (api *Client) RemoveBookmark(channelID, bookmarkID string) error {
return api.RemoveBookmarkContext(context.Background(), channelID, bookmarkID)
}
// RemoveBookmarkContext removes a bookmark from a channel with a custom context
func (api *Client) RemoveBookmarkContext(ctx context.Context, channelID, bookmarkID string) error {
values := url.Values{
"channel_id": {channelID},
"token": {api.token},
"bookmark_id": {bookmarkID},
}
response := &SlackResponse{}
if err := api.postMethod(ctx, "bookmarks.remove", values, response); err != nil {
return err
}
return response.Err()
}
// ListBookmarks returns all bookmarks for a channel.
func (api *Client) ListBookmarks(channelID string) ([]Bookmark, error) {
return api.ListBookmarksContext(context.Background(), channelID)
}
// ListBookmarksContext returns all bookmarks for a channel with a custom context.
func (api *Client) ListBookmarksContext(ctx context.Context, channelID string) ([]Bookmark, error) {
values := url.Values{
"channel_id": {channelID},
"token": {api.token},
}
response := &listBookmarksResponse{}
err := api.postMethod(ctx, "bookmarks.list", values, response)
if err != nil {
return nil, err
}
return response.Bookmarks, response.Err()
}
func (api *Client) EditBookmark(channelID, bookmarkID string, params EditBookmarkParameters) (Bookmark, error) {
return api.EditBookmarkContext(context.Background(), channelID, bookmarkID, params)
}
func (api *Client) EditBookmarkContext(ctx context.Context, channelID, bookmarkID string, params EditBookmarkParameters) (Bookmark, error) {
values := url.Values{
"channel_id": {channelID},
"token": {api.token},
"bookmark_id": {bookmarkID},
}
if params.Link != "" {
values.Set("link", params.Link)
}
if params.Emoji != nil {
values.Set("emoji", *params.Emoji)
}
if params.Title != nil {
values.Set("title", *params.Title)
}
response := &editBookmarkResponse{}
if err := api.postMethod(ctx, "bookmarks.edit", values, response); err != nil {
return Bookmark{}, err
}
return response.Bookmark, response.Err()
}
slack-0.11.3/bookmarks_test.go 0000664 0000000 0000000 00000015156 14307410331 0016253 0 ustar 00root root 0000000 0000000 package slack
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
)
func getTestBookmark(channelID, bookmarkID string) Bookmark {
return Bookmark{
ID: bookmarkID,
ChannelID: channelID,
Title: "bookmark",
Type: "link",
Link: "https://example.com",
IconURL: "https://example.com/icon.png",
}
}
func addBookmarkLinkHandler(rw http.ResponseWriter, r *http.Request) {
channelID := r.FormValue("channel_id")
title := r.FormValue("title")
bookmarkType := r.FormValue("type")
link := r.FormValue("link")
rw.Header().Set("Content-Type", "application/json")
if bookmarkType == "link" && link != "" && channelID != "" && title != "" {
bookmark := getTestBookmark(channelID, "Bk123RBZG8GZ")
bookmark.Title = title
bookmark.Type = bookmarkType
bookmark.Link = link
resp, _ := json.Marshal(&addBookmarkResponse{
SlackResponse: SlackResponse{Ok: true},
Bookmark: bookmark})
rw.Write(resp)
} else {
rw.Write([]byte(`{ "ok": false, "error": "errored" }`))
}
}
func TestAddBookmarkLink(t *testing.T) {
http.HandleFunc("/bookmarks.add", addBookmarkLinkHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
params := AddBookmarkParameters{
Title: "test",
Type: "link",
Link: "https://example.com",
}
_, err := api.AddBookmark("CXXXXXXXX", params)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
}
func listBookmarksHandler(rw http.ResponseWriter, r *http.Request) {
channelID := r.FormValue("channel_id")
rw.Header().Set("Content-Type", "application/json")
if channelID != "" {
bookmarks := []Bookmark{
getTestBookmark(channelID, "Bk001"),
getTestBookmark(channelID, "Bk002"),
getTestBookmark(channelID, "Bk003"),
getTestBookmark(channelID, "Bk004"),
}
resp, _ := json.Marshal(&listBookmarksResponse{
SlackResponse: SlackResponse{Ok: true},
Bookmarks: bookmarks})
rw.Write(resp)
} else {
rw.Write([]byte(`{ "ok": false, "error": "errored" }`))
}
}
func TestListBookmarks(t *testing.T) {
http.HandleFunc("/bookmarks.list", listBookmarksHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
channel := "CXXXXXXXX"
bookmarks, err := api.ListBookmarks(channel)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if !reflect.DeepEqual([]Bookmark{
getTestBookmark(channel, "Bk001"),
getTestBookmark(channel, "Bk002"),
getTestBookmark(channel, "Bk003"),
getTestBookmark(channel, "Bk004"),
}, bookmarks) {
t.Fatal(ErrIncorrectResponse)
}
}
func removeBookmarkHandler(bookmark *Bookmark) func(rw http.ResponseWriter, r *http.Request) {
return func(rw http.ResponseWriter, r *http.Request) {
channelID := r.FormValue("channel_id")
bookmarkID := r.FormValue("bookmark_id")
rw.Header().Set("Content-Type", "application/json")
if channelID == bookmark.ChannelID && bookmarkID == bookmark.ID {
rw.Write([]byte(`{ "ok": true }`))
} else {
rw.Write([]byte(`{ "ok": false, "error": "errored" }`))
}
}
}
func TestRemoveBookmark(t *testing.T) {
channel := "CXXXXXXXX"
bookmark := getTestBookmark(channel, "BkXXXXX")
http.HandleFunc("/bookmarks.remove", removeBookmarkHandler(&bookmark))
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
err := api.RemoveBookmark(channel, bookmark.ID)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
}
func editBookmarkHandler(bookmarks []Bookmark) func(rw http.ResponseWriter, r *http.Request) {
return func(rw http.ResponseWriter, r *http.Request) {
channelID := r.FormValue("channel_id")
bookmarkID := r.FormValue("bookmark_id")
rw.Header().Set("Content-Type", "application/json")
if err := r.ParseForm(); err != nil {
httpTestErrReply(rw, true, fmt.Sprintf("err parsing form: %s", err.Error()))
return
}
for _, bookmark := range bookmarks {
if bookmark.ID == bookmarkID && bookmark.ChannelID == channelID {
if v := r.Form.Get("link"); v != "" {
bookmark.Link = v
}
// Emoji and title require special handling since empty string sets to null
if _, ok := r.Form["emoji"]; ok {
bookmark.Emoji = r.Form.Get("emoji")
}
if _, ok := r.Form["title"]; ok {
bookmark.Title = r.Form.Get("title")
}
resp, _ := json.Marshal(&editBookmarkResponse{
SlackResponse: SlackResponse{Ok: true},
Bookmark: bookmark})
rw.Write(resp)
return
}
}
// Fail if the bookmark doesn't exist
rw.Write([]byte(`{ "ok": false, "error": "not_found" }`))
}
}
func TestEditBookmark(t *testing.T) {
channel := "CXXXXXXXX"
bookmarks := []Bookmark{
getTestBookmark(channel, "Bk001"),
getTestBookmark(channel, "Bk002"),
getTestBookmark(channel, "Bk003"),
getTestBookmark(channel, "Bk004"),
}
http.HandleFunc("/bookmarks.edit", editBookmarkHandler(bookmarks))
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
smileEmoji := ":smile:"
empty := ""
title := "hello, world!"
changes := []struct {
ID string
Params EditBookmarkParameters
}{
{ // add emoji
ID: "Bk001",
Params: EditBookmarkParameters{Emoji: &smileEmoji},
},
{ // delete emoji
ID: "Bk001",
Params: EditBookmarkParameters{Emoji: &empty},
},
{ // add title
ID: "Bk002",
Params: EditBookmarkParameters{Title: &title},
},
{ // delete title
ID: "Bk002",
Params: EditBookmarkParameters{Title: &empty},
},
{ // Change multiple fields at once
ID: "Bk003",
Params: EditBookmarkParameters{
Title: &title,
Emoji: &empty,
Link: "https://example.com/changed",
},
},
{ // noop
ID: "Bk004",
},
}
for _, change := range changes {
bookmark, err := api.EditBookmark(channel, change.ID, change.Params)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if change.ID != bookmark.ID {
t.Fatalf("expected to modify bookmark with ID = %s, got %s", change.ID, bookmark.ID)
}
if change.Params.Emoji != nil && bookmark.Emoji != *change.Params.Emoji {
t.Fatalf("expected bookmark.Emoji = %s, got %s", *change.Params.Emoji, bookmark.Emoji)
}
if change.Params.Title != nil && bookmark.Title != *change.Params.Title {
t.Fatalf("expected bookmark.Title = %s, got %s", *change.Params.Title, bookmark.Emoji)
}
if change.Params.Link != "" && change.Params.Link != bookmark.Link {
t.Fatalf("expected bookmark.Link = %s, got %s", change.Params.Link, bookmark.Link)
}
}
// Cover the final case of trying to edit a bookmark which doesn't exist
bookmark, err := api.EditBookmark(channel, "BkMissing", EditBookmarkParameters{})
if err == nil {
t.Fatalf("Expected not found error, but got bookmark %s", bookmark.ID)
}
}
slack-0.11.3/bots.go 0000664 0000000 0000000 00000002455 14307410331 0014171 0 ustar 00root root 0000000 0000000 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
}
slack-0.11.3/bots_test.go 0000664 0000000 0000000 00000003036 14307410331 0015224 0 ustar 00root root 0000000 0000000 package slack
import (
"net/http"
"testing"
)
func getBotInfo(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response := []byte(`{"ok": true, "bot": {
"id":"B02875YLA",
"deleted":false,
"name":"github",
"updated": 1449272004,
"app_id":"A161CLERW",
"user_id": "U012ABCDEF",
"icons": {
"image_36":"https:\/\/a.slack-edge.com\/2fac\/plugins\/github\/assets\/service_36.png",
"image_48":"https:\/\/a.slack-edge.com\/2fac\/plugins\/github\/assets\/service_48.png",
"image_72":"https:\/\/a.slack-edge.com\/2fac\/plugins\/github\/assets\/service_72.png"
}
}}`)
rw.Write(response)
}
func TestGetBotInfo(t *testing.T) {
http.HandleFunc("/bots.info", getBotInfo)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
bot, err := api.GetBotInfo("B02875YLA")
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
if bot.ID != "B02875YLA" {
t.Fatal("Incorrect ID")
}
if bot.Name != "github" {
t.Fatal("Incorrect Name")
}
if bot.Deleted {
t.Fatal("Incorrect Deleted flag")
}
if bot.AppID != "A161CLERW" {
t.Fatal("Incorrect App ID")
}
if bot.UserID != "U012ABCDEF" {
t.Fatal("Incorrect User ID")
}
if bot.Updated != 1449272004 {
t.Fatal("Incorrect Updated")
}
if len(bot.Icons.Image36) == 0 {
t.Fatal("Missing Image36")
}
if len(bot.Icons.Image48) == 0 {
t.Fatal("Missing Image38")
}
if len(bot.Icons.Image72) == 0 {
t.Fatal("Missing Image72")
}
}
slack-0.11.3/channels.go 0000664 0000000 0000000 00000001635 14307410331 0015014 0 ustar 00root root 0000000 0000000 package slack
import (
"context"
"net/url"
)
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
Metadata ResponseMetadata `json:"response_metadata"`
}
// 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()
}
slack-0.11.3/chat.go 0000664 0000000 0000000 00000066661 14307410331 0014152 0 ustar 00root root 0000000 0000000 package slack
import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"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
ScheduledMessageID string `json:"scheduled_message_id,omitempty"` //Scheduled message id
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"`
// chat metadata support
MetaData SlackMetadata `json:"metadata"`
}
// 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) {
return api.DeleteMessageContext(context.Background(), channel, messageTimestamp)
}
// 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
}
// ScheduleMessage 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) ScheduleMessage(channelID, postAt string, options ...MsgOption) (string, string, error) {
return api.ScheduleMessageContext(context.Background(), channelID, postAt, options...)
}
// ScheduleMessageContext sends a message to a channel with a custom context
//
// For more details, see ScheduleMessage documentation.
func (api *Client) ScheduleMessageContext(ctx context.Context, channelID, postAt string, options ...MsgOption) (string, string, error) {
respChannel, respTimestamp, _, err := api.SendMessageContext(
ctx,
channelID,
MsgOptionSchedule(postAt),
MsgOptionCompose(options...),
)
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) {
return api.PostMessageContext(context.Background(), channelID, options...)
}
// 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.UpdateMessageContext(context.Background(), channelID, timestamp, 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.UnfurlMessageContext(context.Background(), channelID, timestamp, unfurls, options...)
}
// UnfurlMessageContext unfurls a message in a channel with a custom context
func (api *Client) UnfurlMessageContext(ctx context.Context, channelID, timestamp string, unfurls map[string]Attachment, options ...MsgOption) (string, string, string, error) {
return api.SendMessageContext(ctx, channelID, MsgOptionUnfurl(timestamp, unfurls), MsgOptionCompose(options...))
}
// UnfurlMessageWithAuthURL sends an unfurl request containing an
// authentication URL.
// For more details see:
// https://api.slack.com/reference/messaging/link-unfurling#authenticated_unfurls
func (api *Client) UnfurlMessageWithAuthURL(channelID, timestamp string, userAuthURL string, options ...MsgOption) (string, string, string, error) {
return api.UnfurlMessageWithAuthURLContext(context.Background(), channelID, timestamp, userAuthURL, options...)
}
// UnfurlMessageWithAuthURLContext sends an unfurl request containing an
// authentication URL.
// For more details see:
// https://api.slack.com/reference/messaging/link-unfurling#authenticated_unfurls
func (api *Client) UnfurlMessageWithAuthURLContext(ctx context.Context, channelID, timestamp string, userAuthURL string, options ...MsgOption) (string, string, string, error) {
return api.SendMessageContext(ctx, channelID, MsgOptionUnfurlAuthURL(timestamp, userAuthURL), 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...).BuildRequestContext(ctx, api.token, channelID); err != nil {
return "", "", "", err
}
if api.Debug() {
reqBody, err := ioutil.ReadAll(req.Body)
if err != nil {
return "", "", "", err
}
req.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody))
api.Debugf("Sending request: %s", string(reqBody))
}
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"
chatScheduleMessage sendMode = "chat.scheduleMessage"
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
metadata SlackMetadata
blocks Blocks
responseType string
replaceOriginal bool
deleteOriginal bool
}
func (t sendConfig) BuildRequest(token, channelID string) (req *http.Request, _ func(*chatResponseFull) responseParser, err error) {
return t.BuildRequestContext(context.Background(), token, channelID)
}
func (t sendConfig) BuildRequestContext(ctx context.Context, 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,
metadata: t.metadata,
blocks: t.blocks,
responseType: t.responseType,
replaceOriginal: t.replaceOriginal,
deleteOriginal: t.deleteOriginal,
}.BuildRequestContext(ctx)
default:
return formSender{endpoint: t.endpoint, values: t.values}.BuildRequestContext(ctx)
}
}
type formSender struct {
endpoint string
values url.Values
}
func (t formSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) {
return t.BuildRequestContext(context.Background())
}
func (t formSender) BuildRequestContext(ctx context.Context) (*http.Request, func(*chatResponseFull) responseParser, error) {
req, err := formReq(ctx, t.endpoint, t.values)
return req, func(resp *chatResponseFull) responseParser {
return newJSONParser(resp)
}, err
}
type responseURLSender struct {
endpoint string
values url.Values
attachments []Attachment
metadata SlackMetadata
blocks Blocks
responseType string
replaceOriginal bool
deleteOriginal bool
}
func (t responseURLSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) {
return t.BuildRequestContext(context.Background())
}
func (t responseURLSender) BuildRequestContext(ctx context.Context) (*http.Request, func(*chatResponseFull) responseParser, error) {
req, err := jsonReq(ctx, t.endpoint, Msg{
Text: t.values.Get("text"),
Timestamp: t.values.Get("ts"),
Attachments: t.attachments,
Blocks: t.blocks,
Metadata: t.metadata,
ResponseType: t.responseType,
ReplaceOriginal: t.replaceOriginal,
DeleteOriginal: t.deleteOriginal,
})
return req, func(resp *chatResponseFull) responseParser {
return newContentTypeParser(resp)
}, err
}
// MsgOption option provided when sending a message.
type MsgOption func(*sendConfig) error
// MsgOptionSchedule schedules a messages.
func MsgOptionSchedule(postAt string) MsgOption {
return func(config *sendConfig) error {
config.endpoint = config.apiurl + string(chatScheduleMessage)
config.values.Add("post_at", postAt)
return nil
}
}
// 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
}
}
// MsgOptionUnfurlAuthURL unfurls a message using an auth url based on the timestamp.
func MsgOptionUnfurlAuthURL(timestamp string, userAuthURL string) MsgOption {
return func(config *sendConfig) error {
config.endpoint = config.apiurl + string(chatUnfurl)
config.values.Add("ts", timestamp)
config.values.Add("user_auth_url", userAuthURL)
return nil
}
}
// MsgOptionUnfurlAuthRequired requests that the user installs the
// Slack app for unfurling.
func MsgOptionUnfurlAuthRequired(timestamp string) MsgOption {
return func(config *sendConfig) error {
config.endpoint = config.apiurl + string(chatUnfurl)
config.values.Add("ts", timestamp)
config.values.Add("user_auth_required", "true")
return nil
}
}
// MsgOptionUnfurlAuthMessage attaches a message inviting the user to
// authenticate.
func MsgOptionUnfurlAuthMessage(timestamp string, msg string) MsgOption {
return func(config *sendConfig) error {
config.endpoint = config.apiurl + string(chatUnfurl)
config.values.Add("ts", timestamp)
config.values.Add("user_auth_message", msg)
return nil
}
}
// 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
}
}
// MsgOptionReplaceOriginal replaces original message with response url
func MsgOptionReplaceOriginal(responseURL string) MsgOption {
return func(config *sendConfig) error {
config.mode = chatResponse
config.endpoint = responseURL
config.replaceOriginal = true
return nil
}
}
// MsgOptionDeleteOriginal deletes original message with response url
func MsgOptionDeleteOriginal(responseURL string) MsgOption {
return func(config *sendConfig) error {
config.mode = chatResponse
config.endpoint = responseURL
config.deleteOriginal = true
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(config *sendConfig) error {
for _, opt := range options {
if err := opt(config); err != nil {
return err
}
}
return nil
}
}
// MsgOptionParse set parse option.
func MsgOptionParse(b bool) MsgOption {
return func(config *sendConfig) error {
var v string
if b {
v = "full"
} else {
v = "none"
}
config.values.Set("parse", v)
return nil
}
}
// MsgOptionIconURL sets an icon URL
func MsgOptionIconURL(iconURL string) MsgOption {
return func(config *sendConfig) error {
config.values.Set("icon_url", iconURL)
return nil
}
}
// MsgOptionIconEmoji sets an icon emoji
func MsgOptionIconEmoji(iconEmoji string) MsgOption {
return func(config *sendConfig) error {
config.values.Set("icon_emoji", iconEmoji)
return nil
}
}
// MsgOptionMetadata sets message metadata
func MsgOptionMetadata(metadata SlackMetadata) MsgOption {
return func(config *sendConfig) error {
config.metadata = metadata
meta, err := json.Marshal(metadata)
if err == nil {
config.values.Set("metadata", string(meta))
}
return err
}
}
// 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{
"channel": {params.Channel},
"message_ts": {params.Ts},
}
response := struct {
Channel string `json:"channel"`
Permalink string `json:"permalink"`
SlackResponse
}{}
err := api.getMethod(ctx, "chat.getPermalink", api.token, values, &response)
if err != nil {
return "", err
}
return response.Permalink, response.Err()
}
type GetScheduledMessagesParameters struct {
Channel string
Cursor string
Latest string
Limit int
Oldest string
}
// GetScheduledMessages returns the list of scheduled messages based on params
func (api *Client) GetScheduledMessages(params *GetScheduledMessagesParameters) (channels []ScheduledMessage, nextCursor string, err error) {
return api.GetScheduledMessagesContext(context.Background(), params)
}
// GetScheduledMessagesContext returns the list of scheduled messages in a Slack team with a custom context
func (api *Client) GetScheduledMessagesContext(ctx context.Context, params *GetScheduledMessagesParameters) (channels []ScheduledMessage, nextCursor string, err error) {
values := url.Values{
"token": {api.token},
}
if params.Channel != "" {
values.Add("channel", params.Channel)
}
if params.Cursor != "" {
values.Add("cursor", params.Cursor)
}
if params.Limit != 0 {
values.Add("limit", strconv.Itoa(params.Limit))
}
if params.Latest != "" {
values.Add("latest", params.Latest)
}
if params.Oldest != "" {
values.Add("oldest", params.Oldest)
}
response := struct {
Messages []ScheduledMessage `json:"scheduled_messages"`
ResponseMetaData responseMetaData `json:"response_metadata"`
SlackResponse
}{}
err = api.postMethod(ctx, "chat.scheduledMessages.list", values, &response)
if err != nil {
return nil, "", err
}
return response.Messages, response.ResponseMetaData.NextCursor, response.Err()
}
type DeleteScheduledMessageParameters struct {
Channel string
ScheduledMessageID string
AsUser bool
}
// DeleteScheduledMessage returns the list of scheduled messages based on params
func (api *Client) DeleteScheduledMessage(params *DeleteScheduledMessageParameters) (bool, error) {
return api.DeleteScheduledMessageContext(context.Background(), params)
}
// DeleteScheduledMessageContext returns the list of scheduled messages in a Slack team with a custom context
func (api *Client) DeleteScheduledMessageContext(ctx context.Context, params *DeleteScheduledMessageParameters) (bool, error) {
values := url.Values{
"token": {api.token},
"channel": {params.Channel},
"scheduled_message_id": {params.ScheduledMessageID},
"as_user": {strconv.FormatBool(params.AsUser)},
}
response := struct {
SlackResponse
}{}
err := api.postMethod(ctx, "chat.deleteScheduledMessage", values, &response)
if err != nil {
return false, err
}
return response.Ok, response.Err()
}
slack-0.11.3/chat_test.go 0000664 0000000 0000000 00000021107 14307410331 0015173 0 ustar 00root root 0000000 0000000 package slack
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"testing"
)
func postMessageInvalidChannelHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(chatResponseFull{
SlackResponse: SlackResponse{Ok: false, Error: "channel_not_found"},
})
rw.Write(response)
}
func TestPostMessageInvalidChannel(t *testing.T) {
http.DefaultServeMux = new(http.ServeMux)
http.HandleFunc("/chat.postMessage", postMessageInvalidChannelHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
_, _, err := api.PostMessage("CXXXXXXXX", MsgOptionText("hello", false))
if err == nil {
t.Errorf("Expected error: channel_not_found; instead succeeded")
return
}
if err.Error() != "channel_not_found" {
t.Errorf("Expected error: channel_not_found; received: %s", err)
return
}
}
func TestGetPermalink(t *testing.T) {
channel := "C1H9RESGA"
timeStamp := "p135854651500008"
http.HandleFunc("/chat.getPermalink", func(rw http.ResponseWriter, r *http.Request) {
if got, want := r.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; got != want {
t.Errorf("request uses unexpected content type: got %s, want %s", got, want)
}
if got, want := r.URL.Query().Get("channel"), channel; got != want {
t.Errorf("request contains unexpected channel: got %s, want %s", got, want)
}
if got, want := r.URL.Query().Get("message_ts"), timeStamp; got != want {
t.Errorf("request contains unexpected message timestamp: got %s, want %s", got, want)
}
rw.Header().Set("Content-Type", "application/json")
response := []byte("{\"ok\": true, \"channel\": \"" + channel + "\", \"permalink\": \"https://ghostbusters.slack.com/archives/" + channel + "/" + timeStamp + "\"}")
rw.Write(response)
})
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
pp := PermalinkParameters{Channel: channel, Ts: timeStamp}
pl, err := api.GetPermalink(&pp)
if got, want := pl, "https://ghostbusters.slack.com/archives/C1H9RESGA/p135854651500008"; got != want {
t.Errorf("unexpected permalink: got %s, want %s", got, want)
}
if err != nil {
t.Errorf("unexpected error returned: %v", err)
}
}
func TestPostMessage(t *testing.T) {
type messageTest struct {
endpoint string
opt []MsgOption
expected url.Values
}
blocks := []Block{NewContextBlock("context", NewTextBlockObject(PlainTextType, "hello", false, false))}
blockStr := `[{"type":"context","block_id":"context","elements":[{"type":"plain_text","text":"hello"}]}]`
tests := map[string]messageTest{
"OnlyBasicProperties": {
endpoint: "/chat.postMessage",
opt: []MsgOption{},
expected: url.Values{
"channel": []string{"CXXX"},
"token": []string{"testing-token"},
},
},
"Blocks": {
endpoint: "/chat.postMessage",
opt: []MsgOption{
MsgOptionBlocks(blocks...),
MsgOptionText("text", false),
},
expected: url.Values{
"blocks": []string{blockStr},
"channel": []string{"CXXX"},
"text": []string{"text"},
"token": []string{"testing-token"},
},
},
"Attachment": {
endpoint: "/chat.postMessage",
opt: []MsgOption{
MsgOptionAttachments(
Attachment{
Blocks: Blocks{BlockSet: blocks},
}),
},
expected: url.Values{
"attachments": []string{`[{"blocks":` + blockStr + `}]`},
"channel": []string{"CXXX"},
"token": []string{"testing-token"},
},
},
"Metadata": {
endpoint: "/chat.postMessage",
opt: []MsgOption{
MsgOptionMetadata(
SlackMetadata{
EventType: "testing-event",
EventPayload: map[string]interface{}{
"id": 13,
"name": "testing-name",
},
}),
},
expected: url.Values{
"metadata": []string{`{"event_type":"testing-event","event_payload":{"id":13,"name":"testing-name"}}`},
"channel": []string{"CXXX"},
"token": []string{"testing-token"},
},
},
"Unfurl": {
endpoint: "/chat.unfurl",
opt: []MsgOption{
MsgOptionUnfurl("123", map[string]Attachment{"something": {Text: "attachment-test"}}),
},
expected: url.Values{
"channel": []string{"CXXX"},
"token": []string{"testing-token"},
"ts": []string{"123"},
"unfurls": []string{`{"something":{"text":"attachment-test","blocks":null}}`},
},
},
"UnfurlAuthURL": {
endpoint: "/chat.unfurl",
opt: []MsgOption{
MsgOptionUnfurlAuthURL("123", "https://auth-url.com"),
},
expected: url.Values{
"channel": []string{"CXXX"},
"token": []string{"testing-token"},
"ts": []string{"123"},
"user_auth_url": []string{"https://auth-url.com"},
},
},
"UnfurlAuthRequired": {
endpoint: "/chat.unfurl",
opt: []MsgOption{
MsgOptionUnfurlAuthRequired("123"),
},
expected: url.Values{
"channel": []string{"CXXX"},
"token": []string{"testing-token"},
"ts": []string{"123"},
"user_auth_required": []string{"true"},
},
},
"UnfurlAuthMessage": {
endpoint: "/chat.unfurl",
opt: []MsgOption{
MsgOptionUnfurlAuthMessage("123", "Please!"),
},
expected: url.Values{
"channel": []string{"CXXX"},
"token": []string{"testing-token"},
"ts": []string{"123"},
"user_auth_message": []string{"Please!"},
},
},
}
once.Do(startServer)
api := New(validToken, OptionAPIURL("http://"+serverAddr+"/"))
for name, test := range tests {
t.Run(name, func(t *testing.T) {
http.DefaultServeMux = new(http.ServeMux)
http.HandleFunc(test.endpoint, func(rw http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
actual, err := url.ParseQuery(string(body))
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if !reflect.DeepEqual(actual, test.expected) {
t.Errorf("\nexpected: %s\n actual: %s", test.expected, actual)
return
}
})
_, _, _ = api.PostMessage("CXXX", test.opt...)
})
}
}
func TestPostMessageWithBlocksWhenMsgOptionResponseURLApplied(t *testing.T) {
expectedBlocks := []Block{NewContextBlock("context", NewTextBlockObject(PlainTextType, "hello", false, false))}
http.DefaultServeMux = new(http.ServeMux)
http.HandleFunc("/response-url", func(rw http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
var msg Msg
if err := json.Unmarshal(body, &msg); err != nil {
t.Errorf("unexpected error: %v", err)
return
}
actualBlocks := msg.Blocks.BlockSet
if !reflect.DeepEqual(expectedBlocks, actualBlocks) {
t.Errorf("expected: %#v, got: %#v", expectedBlocks, actualBlocks)
return
}
})
once.Do(startServer)
api := New(validToken, OptionAPIURL("http://"+serverAddr+"/"))
responseURL := api.endpoint + "response-url"
_, _, _ = api.PostMessage("CXXX", MsgOptionBlocks(expectedBlocks...), MsgOptionText("text", false), MsgOptionResponseURL(responseURL, ResponseTypeInChannel))
}
func TestPostMessageWhenMsgOptionReplaceOriginalApplied(t *testing.T) {
http.DefaultServeMux = new(http.ServeMux)
http.HandleFunc("/response-url", func(rw http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
var msg Msg
if err := json.Unmarshal(body, &msg); err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if msg.ReplaceOriginal != true {
t.Errorf("expected: true, got: %v", msg.ReplaceOriginal)
return
}
})
once.Do(startServer)
api := New(validToken, OptionAPIURL("http://"+serverAddr+"/"))
responseURL := api.endpoint + "response-url"
_, _, _ = api.PostMessage("CXXX", MsgOptionText("text", false), MsgOptionReplaceOriginal(responseURL))
}
func TestPostMessageWhenMsgOptionDeleteOriginalApplied(t *testing.T) {
http.DefaultServeMux = new(http.ServeMux)
http.HandleFunc("/response-url", func(rw http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
var msg Msg
if err := json.Unmarshal(body, &msg); err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if msg.DeleteOriginal != true {
t.Errorf("expected: true, got: %v", msg.DeleteOriginal)
return
}
})
once.Do(startServer)
api := New(validToken, OptionAPIURL("http://"+serverAddr+"/"))
responseURL := api.endpoint + "response-url"
_, _, _ = api.PostMessage("CXXX", MsgOptionDeleteOriginal(responseURL))
}
slack-0.11.3/comment.go 0000664 0000000 0000000 00000000512 14307410331 0014654 0 ustar 00root root 0000000 0000000 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"`
}
slack-0.11.3/conversation.go 0000664 0000000 0000000 00000050476 14307410331 0015742 0 ustar 00root root 0000000 0000000 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 bool
Limit int
Types []string
TeamID 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},
}
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", strconv.FormatBool(params.ExcludeArchived))
}
if params.TeamID != "" {
values.Add("team_id", params.TeamID)
}
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
IncludeAllMetadata bool
}
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)
}
if params.IncludeAllMetadata {
values.Add("include_all_metadata", "1")
} else {
values.Add("include_all_metadata", "0")
}
response := GetConversationHistoryResponse{}
err := api.postMethod(ctx, "conversations.history", values, &response)
if err != nil {
return nil, err
}
return &response, response.Err()
}
// MarkConversation sets the read mark of a conversation to a specific point
func (api *Client) MarkConversation(channel, ts string) (err error) {
return api.MarkConversationContext(context.Background(), channel, ts)
}
// MarkConversationContext sets the read mark of a conversation to a specific point with a custom context
func (api *Client) MarkConversationContext(ctx context.Context, channel, ts string) error {
values := url.Values{
"token": {api.token},
"channel": {channel},
"ts": {ts},
}
response := &SlackResponse{}
err := api.postMethod(ctx, "conversations.mark", values, response)
if err != nil {
return err
}
return response.Err()
}
slack-0.11.3/conversation_test.go 0000664 0000000 0000000 00000037460 14307410331 0016777 0 ustar 00root root 0000000 0000000 package slack
import (
"encoding/json"
"net/http"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
// Channel
var simpleChannel = `{
"id": "C024BE91L",
"name": "fun",
"is_channel": true,
"created": 1360782804,
"creator": "U024BE7LH",
"is_archived": false,
"is_general": false,
"members": [
"U024BE7LH"
],
"topic": {
"value": "Fun times",
"creator": "U024BE7LV",
"last_set": 1369677212
},
"purpose": {
"value": "This channel is for fun",
"creator": "U024BE7LH",
"last_set": 1360782804
},
"is_member": true,
"last_read": "1401383885.000061",
"unread_count": 0,
"unread_count_display": 0
}`
func unmarshalChannel(j string) (*Channel, error) {
channel := &Channel{}
if err := json.Unmarshal([]byte(j), &channel); err != nil {
return nil, err
}
return channel, nil
}
func TestSimpleChannel(t *testing.T) {
channel, err := unmarshalChannel(simpleChannel)
assert.Nil(t, err)
assertSimpleChannel(t, channel)
}
func assertSimpleChannel(t *testing.T, channel *Channel) {
assert.NotNil(t, channel)
assert.Equal(t, "C024BE91L", channel.ID)
assert.Equal(t, "fun", channel.Name)
assert.Equal(t, true, channel.IsChannel)
assert.Equal(t, JSONTime(1360782804), channel.Created)
assert.Equal(t, "U024BE7LH", channel.Creator)
assert.Equal(t, false, channel.IsArchived)
assert.Equal(t, false, channel.IsGeneral)
assert.Equal(t, true, channel.IsMember)
assert.Equal(t, "1401383885.000061", channel.LastRead)
assert.Equal(t, 0, channel.UnreadCount)
assert.Equal(t, 0, channel.UnreadCountDisplay)
}
func TestCreateSimpleChannel(t *testing.T) {
channel := &Channel{}
channel.ID = "C024BE91L"
channel.Name = "fun"
channel.IsChannel = true
channel.Created = JSONTime(1360782804)
channel.Creator = "U024BE7LH"
channel.IsArchived = false
channel.IsGeneral = false
channel.IsMember = true
channel.LastRead = "1401383885.000061"
channel.UnreadCount = 0
channel.UnreadCountDisplay = 0
assertSimpleChannel(t, channel)
}
// Group
var simpleGroup = `{
"id": "G024BE91L",
"name": "secretplans",
"is_group": true,
"created": 1360782804,
"creator": "U024BE7LH",
"is_archived": false,
"members": [
"U024BE7LH"
],
"topic": {
"value": "Secret plans on hold",
"creator": "U024BE7LV",
"last_set": 1369677212
},
"purpose": {
"value": "Discuss secret plans that no-one else should know",
"creator": "U024BE7LH",
"last_set": 1360782804
},
"last_read": "1401383885.000061",
"unread_count": 0,
"unread_count_display": 0
}`
func unmarshalGroup(j string) (*Group, error) {
group := &Group{}
if err := json.Unmarshal([]byte(j), &group); err != nil {
return nil, err
}
return group, nil
}
func TestSimpleGroup(t *testing.T) {
group, err := unmarshalGroup(simpleGroup)
assert.Nil(t, err)
assertSimpleGroup(t, group)
}
func assertSimpleGroup(t *testing.T, group *Group) {
assert.NotNil(t, group)
assert.Equal(t, "G024BE91L", group.ID)
assert.Equal(t, "secretplans", group.Name)
assert.Equal(t, true, group.IsGroup)
assert.Equal(t, JSONTime(1360782804), group.Created)
assert.Equal(t, "U024BE7LH", group.Creator)
assert.Equal(t, false, group.IsArchived)
assert.Equal(t, "1401383885.000061", group.LastRead)
assert.Equal(t, 0, group.UnreadCount)
assert.Equal(t, 0, group.UnreadCountDisplay)
}
func TestCreateSimpleGroup(t *testing.T) {
group := &Group{}
group.ID = "G024BE91L"
group.Name = "secretplans"
group.IsGroup = true
group.Created = JSONTime(1360782804)
group.Creator = "U024BE7LH"
group.IsArchived = false
group.LastRead = "1401383885.000061"
group.UnreadCount = 0
group.UnreadCountDisplay = 0
assertSimpleGroup(t, group)
}
// IM
var simpleIM = `{
"id": "D024BFF1M",
"is_im": true,
"user": "U024BE7LH",
"created": 1360782804,
"is_user_deleted": false,
"is_open": true,
"last_read": "1401383885.000061",
"unread_count": 0,
"unread_count_display": 0
}`
func unmarshalIM(j string) (*IM, error) {
im := &IM{}
if err := json.Unmarshal([]byte(j), &im); err != nil {
return nil, err
}
return im, nil
}
func TestSimpleIM(t *testing.T) {
im, err := unmarshalIM(simpleIM)
assert.Nil(t, err)
assertSimpleIM(t, im)
}
func assertSimpleIM(t *testing.T, im *IM) {
assert.NotNil(t, im)
assert.Equal(t, "D024BFF1M", im.ID)
assert.Equal(t, true, im.IsIM)
assert.Equal(t, "U024BE7LH", im.User)
assert.Equal(t, JSONTime(1360782804), im.Created)
assert.Equal(t, false, im.IsUserDeleted)
assert.Equal(t, true, im.IsOpen)
assert.Equal(t, "1401383885.000061", im.LastRead)
assert.Equal(t, 0, im.UnreadCount)
assert.Equal(t, 0, im.UnreadCountDisplay)
}
func TestCreateSimpleIM(t *testing.T) {
im := &IM{}
im.ID = "D024BFF1M"
im.IsIM = true
im.User = "U024BE7LH"
im.Created = JSONTime(1360782804)
im.IsUserDeleted = false
im.IsOpen = true
im.LastRead = "1401383885.000061"
im.UnreadCount = 0
im.UnreadCountDisplay = 0
assertSimpleIM(t, im)
}
func getTestMembers() []string {
return []string{"test"}
}
func getUsersInConversation(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
SlackResponse
Members []string `json:"members"`
ResponseMetaData responseMetaData `json:"response_metadata"`
}{
SlackResponse: SlackResponse{Ok: true},
Members: getTestMembers(),
ResponseMetaData: responseMetaData{NextCursor: ""},
})
rw.Write(response)
}
func TestGetUsersInConversation(t *testing.T) {
http.HandleFunc("/conversations.members", getUsersInConversation)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
params := GetUsersInConversationParameters{
ChannelID: "CXXXXXXXX",
}
expectedMembers := getTestMembers()
members, _, err := api.GetUsersInConversation(¶ms)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
if !reflect.DeepEqual(expectedMembers, members) {
t.Fatal(ErrIncorrectResponse)
}
}
func TestArchiveConversation(t *testing.T) {
http.HandleFunc("/conversations.archive", okJSONHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
err := api.ArchiveConversation("CXXXXXXXX")
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
func TestUnArchiveConversation(t *testing.T) {
http.HandleFunc("/conversations.unarchive", okJSONHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
err := api.UnArchiveConversation("CXXXXXXXX")
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
func getTestChannel() *Channel {
return &Channel{
GroupConversation: GroupConversation{
Topic: Topic{
Value: "response topic",
},
Purpose: Purpose{
Value: "response purpose",
},
}}
}
func okChannelJsonHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
SlackResponse
Channel *Channel `json:"channel"`
}{
SlackResponse: SlackResponse{Ok: true},
Channel: getTestChannel(),
})
rw.Write(response)
}
func TestSetTopicOfConversation(t *testing.T) {
http.HandleFunc("/conversations.setTopic", okChannelJsonHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
inputChannel := getTestChannel()
channel, err := api.SetTopicOfConversation("CXXXXXXXX", inputChannel.Topic.Value)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
if channel.Topic.Value != inputChannel.Topic.Value {
t.Fatalf(`topic = '%s', want '%s'`, channel.Topic.Value, inputChannel.Topic.Value)
}
}
func TestSetPurposeOfConversation(t *testing.T) {
http.HandleFunc("/conversations.setPurpose", okChannelJsonHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
inputChannel := getTestChannel()
channel, err := api.SetPurposeOfConversation("CXXXXXXXX", inputChannel.Purpose.Value)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
if channel.Purpose.Value != inputChannel.Purpose.Value {
t.Fatalf(`purpose = '%s', want '%s'`, channel.Purpose.Value, inputChannel.Purpose.Value)
}
}
func TestRenameConversation(t *testing.T) {
http.HandleFunc("/conversations.rename", okChannelJsonHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
inputChannel := getTestChannel()
channel, err := api.RenameConversation("CXXXXXXXX", inputChannel.Name)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
if channel.Name != inputChannel.Name {
t.Fatalf(`channelName = '%s', want '%s'`, channel.Name, inputChannel.Name)
}
}
func TestInviteUsersToConversation(t *testing.T) {
http.HandleFunc("/conversations.invite", okChannelJsonHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
users := []string{"UXXXXXXX1", "UXXXXXXX2"}
channel, err := api.InviteUsersToConversation("CXXXXXXXX", users...)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
if channel == nil {
t.Error("channel should not be nil")
return
}
}
func TestKickUserFromConversation(t *testing.T) {
http.HandleFunc("/conversations.kick", okJSONHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
err := api.KickUserFromConversation("CXXXXXXXX", "UXXXXXXXX")
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
func closeConversationHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
SlackResponse
NoOp bool `json:"no_op"`
AlreadyClosed bool `json:"already_closed"`
}{
SlackResponse: SlackResponse{Ok: true}})
rw.Write(response)
}
func TestCloseConversation(t *testing.T) {
http.HandleFunc("/conversations.close", closeConversationHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
_, _, err := api.CloseConversation("CXXXXXXXX")
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
func TestCreateConversation(t *testing.T) {
http.HandleFunc("/conversations.create", okChannelJsonHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
channel, err := api.CreateConversation("CXXXXXXXX", false)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
if channel == nil {
t.Error("channel should not be nil")
return
}
}
func TestGetConversationInfo(t *testing.T) {
http.HandleFunc("/conversations.info", okChannelJsonHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
channel, err := api.GetConversationInfo("CXXXXXXXX", false)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
if channel == nil {
t.Error("channel should not be nil")
return
}
}
func leaveConversationHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
SlackResponse
NotInChannel bool `json:"not_in_channel"`
}{
SlackResponse: SlackResponse{Ok: true}})
rw.Write(response)
}
func TestLeaveConversation(t *testing.T) {
http.HandleFunc("/conversations.leave", leaveConversationHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
_, err := api.LeaveConversation("CXXXXXXXX")
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
func getConversationRepliesHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
SlackResponse
HasMore bool `json:"has_more"`
ResponseMetaData struct {
NextCursor string `json:"next_cursor"`
} `json:"response_metadata"`
Messages []Message `json:"messages"`
}{
SlackResponse: SlackResponse{Ok: true},
Messages: []Message{}})
rw.Write(response)
}
func TestGetConversationReplies(t *testing.T) {
http.HandleFunc("/conversations.replies", getConversationRepliesHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
params := GetConversationRepliesParameters{
ChannelID: "CXXXXXXXX",
Timestamp: "1234567890.123456",
}
_, _, _, err := api.GetConversationReplies(¶ms)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
func getConversationsHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
SlackResponse
ResponseMetaData struct {
NextCursor string `json:"next_cursor"`
} `json:"response_metadata"`
Channels []Channel `json:"channels"`
}{
SlackResponse: SlackResponse{Ok: true},
Channels: []Channel{}})
rw.Write(response)
}
func TestGetConversations(t *testing.T) {
http.HandleFunc("/conversations.list", getConversationsHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
params := GetConversationsParameters{}
_, _, err := api.GetConversations(¶ms)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
func openConversationHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
SlackResponse
NoOp bool `json:"no_op"`
AlreadyOpen bool `json:"already_open"`
Channel *Channel `json:"channel"`
}{
SlackResponse: SlackResponse{Ok: true}})
rw.Write(response)
}
func TestOpenConversation(t *testing.T) {
http.HandleFunc("/conversations.open", openConversationHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
params := OpenConversationParameters{ChannelID: "CXXXXXXXX"}
_, _, _, err := api.OpenConversation(¶ms)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
func joinConversationHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
Channel *Channel `json:"channel"`
Warning string `json:"warning"`
ResponseMetaData *struct {
Warnings []string `json:"warnings"`
} `json:"response_metadata"`
SlackResponse
}{
SlackResponse: SlackResponse{Ok: true}})
rw.Write(response)
}
func TestJoinConversation(t *testing.T) {
http.HandleFunc("/conversations.join", joinConversationHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
_, _, _, err := api.JoinConversation("CXXXXXXXX")
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
func getConversationHistoryHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(GetConversationHistoryResponse{
SlackResponse: SlackResponse{Ok: true}})
rw.Write(response)
}
func TestGetConversationHistory(t *testing.T) {
http.HandleFunc("/conversations.history", getConversationHistoryHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
params := GetConversationHistoryParameters{ChannelID: "CXXXXXXXX"}
_, err := api.GetConversationHistory(¶ms)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
func markConversationHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(GetConversationHistoryResponse{
SlackResponse: SlackResponse{Ok: true}})
w.Write(response)
}
func TestMarkConversation(t *testing.T) {
http.HandleFunc("/conversations.mark", markConversationHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
err := api.MarkConversation("CXXXXXXXX", "1401383885.000061")
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
}
slack-0.11.3/dialog.go 0000664 0000000 0000000 00000007436 14307410331 0014465 0 ustar 00root root 0000000 0000000 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 {
// NOTE: State is only used with the dialog_submission type.
// You should use InteractionCallback.BlockActionsState for block_actions type.
State string `json:"-"`
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()
}
slack-0.11.3/dialog_select.go 0000664 0000000 0000000 00000010153 14307410331 0016012 0 ustar 00root root 0000000 0000000 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,
}
}
// NewExternalSelectDialogInput constructor for a `external` datasource menu input
func NewExternalSelectDialogInput(name, label string, options []DialogSelectOption) *DialogInputSelect {
return &DialogInputSelect{
DialogInput: DialogInput{
Type: InputTypeSelect,
Name: name,
Label: label,
Optional: true,
},
DataSource: DialogDataSourceExternal,
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,
}
}
slack-0.11.3/dialog_select_test.go 0000664 0000000 0000000 00000007351 14307410331 0017057 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func selectOptionsFromArray(options ...string) []DialogSelectOption {
selectOptions := make([]DialogSelectOption, len(options))
for idx, value := range options {
selectOptions[idx] = DialogSelectOption{
Label: value,
Value: value,
}
}
return selectOptions
}
func selectOptionsFromMap(options map[string]string) []DialogSelectOption {
selectOptions := make([]DialogSelectOption, len(options))
idx := 0
var option DialogSelectOption
for key, value := range options {
option = DialogSelectOption{
Label: key,
Value: value,
}
selectOptions[idx] = option
idx++
}
return selectOptions
}
func TestSelectOptionsFromArray(t *testing.T) {
options := []string{"opt 1"}
expectedOptions := selectOptionsFromArray(options...)
assert.Equal(t, len(options), len(expectedOptions))
firstOption := expectedOptions[0]
assert.Equal(t, "opt 1", firstOption.Label)
assert.Equal(t, "opt 1", firstOption.Value)
}
func TestOptionsFromMap(t *testing.T) {
options := make(map[string]string)
options["key"] = "myValue"
selectOptions := selectOptionsFromMap(options)
assert.Equal(t, 1, len(selectOptions))
firstOption := selectOptions[0]
assert.Equal(t, "key", firstOption.Label)
assert.Equal(t, "myValue", firstOption.Value)
}
func TestStaticSelectFromArray(t *testing.T) {
name := "static select"
label := "Static Select Label"
expectedOptions := selectOptionsFromArray("opt 1", "opt 2", "opt 3")
selectInput := NewStaticSelectDialogInput(name, label, expectedOptions)
assert.Equal(t, name, selectInput.Name)
assert.Equal(t, label, selectInput.Label)
assert.Equal(t, expectedOptions, selectInput.Options)
}
func TestStaticSelectFromDictionary(t *testing.T) {
name := "static select"
label := "Static Select Label"
optionsMap := make(map[string]string)
optionsMap["option_1"] = "First"
optionsMap["option_2"] = "Second"
optionsMap["option_3"] = "Third"
expectedOptions := selectOptionsFromMap(optionsMap)
selectInput := NewStaticSelectDialogInput(name, label, expectedOptions)
assert.Equal(t, name, selectInput.Name)
assert.Equal(t, label, selectInput.Label)
assert.Equal(t, expectedOptions, selectInput.Options)
}
func TestNewDialogOptionGroup(t *testing.T) {
expectedOptions := selectOptionsFromArray("option_1", "option_2")
label := "GroupLabel"
optionGroup := NewDialogOptionGroup(label, expectedOptions...)
assert.Equal(t, label, optionGroup.Label)
assert.Equal(t, expectedOptions, optionGroup.Options)
}
func TestStaticGroupedSelect(t *testing.T) {
groupOpt1 := NewDialogOptionGroup("group1", selectOptionsFromArray("G1_01", "G1_02")...)
groupOpt2 := NewDialogOptionGroup("group2", selectOptionsFromArray("G2_01", "G2_02", "G2_03")...)
options := []DialogOptionGroup{groupOpt1, groupOpt2}
groupSelect := NewGroupedSelectDialogInput("groupSelect", "User Label", options)
assert.Equal(t, InputTypeSelect, groupSelect.Type)
assert.Equal(t, "groupSelect", groupSelect.Name)
assert.Equal(t, "User Label", groupSelect.Label)
assert.Nil(t, groupSelect.Options)
assert.NotNil(t, groupSelect.OptionGroups)
assert.Equal(t, 2, len(groupSelect.OptionGroups))
}
func TestConversationSelect(t *testing.T) {
convoSelect := NewConversationsSelect("", "")
assert.Equal(t, InputTypeSelect, convoSelect.Type)
assert.Equal(t, DialogDataSourceConversations, convoSelect.DataSource)
}
func TestChannelSelect(t *testing.T) {
convoSelect := NewChannelsSelect("", "")
assert.Equal(t, InputTypeSelect, convoSelect.Type)
assert.Equal(t, DialogDataSourceChannels, convoSelect.DataSource)
}
func TestUserSelect(t *testing.T) {
convoSelect := NewUsersSelect("", "")
assert.Equal(t, InputTypeSelect, convoSelect.Type)
assert.Equal(t, DialogDataSourceUsers, convoSelect.DataSource)
}
slack-0.11.3/dialog_test.go 0000664 0000000 0000000 00000023364 14307410331 0015522 0 ustar 00root root 0000000 0000000 package slack
import (
"encoding/json"
"fmt"
"testing"
"net/http"
"github.com/stretchr/testify/assert"
)
// Dialogs
var simpleDialog = `{
"callback_id":"ryde-46e2b0",
"title":"Request a Ride",
"submit_label":"Request",
"notify_on_cancel":true
}`
var simpleTextElement = `{
"label": "testing label",
"name": "testing name",
"type": "text",
"placeholder": "testing placeholder",
"optional": true,
"value": "testing value",
"max_length": 1000,
"min_length": 10,
"hint": "testing hint",
"subtype": "email"
}`
var simpleSelectElement = `{
"label": "testing label",
"name": "testing name",
"type": "select",
"placeholder": "testing placeholder",
"optional": true,
"value": "testing value",
"data_source": "users",
"selected_options": [],
"options": [{"label": "option 1", "value": "1"}],
"option_groups": []
}`
func unmarshalDialog() (*Dialog, error) {
dialog := &Dialog{}
// Unmarshall the simple dialog json
if err := json.Unmarshal([]byte(simpleDialog), &dialog); err != nil {
return nil, err
}
// Unmarshall and append the text element
textElement := &TextInputElement{}
if err := json.Unmarshal([]byte(simpleTextElement), &textElement); err != nil {
return nil, err
}
// Unmarshall and append the select element
selectElement := &DialogInputSelect{}
if err := json.Unmarshal([]byte(simpleSelectElement), &selectElement); err != nil {
return nil, err
}
dialog.Elements = []DialogElement{
textElement,
selectElement,
}
return dialog, nil
}
func TestSimpleDialog(t *testing.T) {
dialog, err := unmarshalDialog()
assert.Nil(t, err)
assertSimpleDialog(t, dialog)
}
func TestCreateSimpleDialog(t *testing.T) {
dialog := &Dialog{}
dialog.CallbackID = "ryde-46e2b0"
dialog.Title = "Request a Ride"
dialog.SubmitLabel = "Request"
dialog.NotifyOnCancel = true
textElement := &TextInputElement{}
textElement.Label = "testing label"
textElement.Name = "testing name"
textElement.Type = "text"
textElement.Placeholder = "testing placeholder"
textElement.Optional = true
textElement.Value = "testing value"
textElement.MaxLength = 1000
textElement.MinLength = 10
textElement.Hint = "testing hint"
textElement.Subtype = "email"
selectElement := &DialogInputSelect{}
selectElement.Label = "testing label"
selectElement.Name = "testing name"
selectElement.Type = "select"
selectElement.Placeholder = "testing placeholder"
selectElement.Optional = true
selectElement.Value = "testing value"
selectElement.DataSource = "users"
selectElement.SelectedOptions = []DialogSelectOption{}
selectElement.Options = []DialogSelectOption{
{Label: "option 1", Value: "1"},
}
selectElement.OptionGroups = []DialogOptionGroup{}
dialog.Elements = []DialogElement{
textElement,
selectElement,
}
assertSimpleDialog(t, dialog)
}
func assertSimpleDialog(t *testing.T, dialog *Dialog) {
assert.NotNil(t, dialog)
// Test the main dialog fields
assert.Equal(t, "ryde-46e2b0", dialog.CallbackID)
assert.Equal(t, "Request a Ride", dialog.Title)
assert.Equal(t, "Request", dialog.SubmitLabel)
assert.Equal(t, true, dialog.NotifyOnCancel)
// Test the text element is correctly parsed
textElement := dialog.Elements[0].(*TextInputElement)
assert.Equal(t, "testing label", textElement.Label)
assert.Equal(t, "testing name", textElement.Name)
assert.Equal(t, InputTypeText, textElement.Type)
assert.Equal(t, "testing placeholder", textElement.Placeholder)
assert.Equal(t, true, textElement.Optional)
assert.Equal(t, "testing value", textElement.Value)
assert.Equal(t, 1000, textElement.MaxLength)
assert.Equal(t, 10, textElement.MinLength)
assert.Equal(t, "testing hint", textElement.Hint)
assert.Equal(t, InputSubtypeEmail, textElement.Subtype)
// Test the select element is correctly parsed
selectElement := dialog.Elements[1].(*DialogInputSelect)
assert.Equal(t, "testing label", selectElement.Label)
assert.Equal(t, "testing name", selectElement.Name)
assert.Equal(t, InputTypeSelect, selectElement.Type)
assert.Equal(t, "testing placeholder", selectElement.Placeholder)
assert.Equal(t, true, selectElement.Optional)
assert.Equal(t, "testing value", selectElement.Value)
assert.Equal(t, DialogDataSourceUsers, selectElement.DataSource)
assert.Equal(t, []DialogSelectOption{}, selectElement.SelectedOptions)
assert.Equal(t, "option 1", selectElement.Options[0].Label)
assert.Equal(t, "1", selectElement.Options[0].Value)
assert.Equal(t, 0, len(selectElement.OptionGroups))
}
// Callbacks
var simpleCallback = `{
"type": "dialog_submission",
"submission": {
"name": "Sigourney Dreamweaver",
"email": "sigdre@example.com",
"phone": "+1 800-555-1212",
"meal": "burrito",
"comment": "No sour cream please",
"team_channel": "C0LFFBKPB",
"who_should_sing": "U0MJRG1AL"
},
"callback_id": "employee_offsite_1138b",
"team": {
"id": "T1ABCD2E12",
"domain": "coverbands"
},
"user": {
"id": "W12A3BCDEF",
"name": "dreamweaver"
},
"channel": {
"id": "C1AB2C3DE",
"name": "coverthon-1999"
},
"action_ts": "936893340.702759",
"token": "M1AqUUw3FqayAbqNtsGMch72",
"response_url": "https://hooks.slack.com/app/T012AB0A1/123456789/JpmK0yzoZDeRiqfeduTBYXWQ"
}`
func unmarshalCallback(j string) (*DialogCallback, error) {
callback := &DialogCallback{}
if err := json.Unmarshal([]byte(j), &callback); err != nil {
return nil, err
}
return callback, nil
}
func TestSimpleCallback(t *testing.T) {
callback, err := unmarshalCallback(simpleCallback)
assert.Nil(t, err)
assertSimpleCallback(t, callback)
}
func assertSimpleCallback(t *testing.T, callback *DialogCallback) {
assert.NotNil(t, callback)
assert.Equal(t, InteractionTypeDialogSubmission, callback.Type)
assert.Equal(t, "employee_offsite_1138b", callback.CallbackID)
assert.Equal(t, "T1ABCD2E12", callback.Team.ID)
assert.Equal(t, "coverbands", callback.Team.Domain)
assert.Equal(t, "C1AB2C3DE", callback.Channel.ID)
assert.Equal(t, "coverthon-1999", callback.Channel.Name)
assert.Equal(t, "W12A3BCDEF", callback.User.ID)
assert.Equal(t, "dreamweaver", callback.User.Name)
assert.Equal(t, "936893340.702759", callback.ActionTs)
assert.Equal(t, "M1AqUUw3FqayAbqNtsGMch72", callback.Token)
assert.Equal(t, "https://hooks.slack.com/app/T012AB0A1/123456789/JpmK0yzoZDeRiqfeduTBYXWQ", callback.ResponseURL)
assert.Equal(t, "Sigourney Dreamweaver", callback.Submission["name"])
assert.Equal(t, "sigdre@example.com", callback.Submission["email"])
assert.Equal(t, "+1 800-555-1212", callback.Submission["phone"])
assert.Equal(t, "burrito", callback.Submission["meal"])
assert.Equal(t, "No sour cream please", callback.Submission["comment"])
assert.Equal(t, "C0LFFBKPB", callback.Submission["team_channel"])
assert.Equal(t, "U0MJRG1AL", callback.Submission["who_should_sing"])
}
// Suggestion Callbacks
var simpleSuggestionCallback = `{
"type": "dialog_suggestion",
"token": "W3VDvuzi2nRLsiaDOsmJranO",
"action_ts": "1528203589.238335",
"team": {
"id": "T24BK35ML",
"domain": "hooli-hq"
},
"user": {
"id": "U900MV5U7",
"name": "gbelson"
},
"channel": {
"id": "C012AB3CD",
"name": "triage-platform"
},
"name": "external_data",
"value": "test",
"callback_id": "bugs"
}`
func unmarshalSuggestionCallback(j string) (*InteractionCallback, error) {
callback := &InteractionCallback{}
if err := json.Unmarshal([]byte(j), &callback); err != nil {
return nil, err
}
return callback, nil
}
func TestSimpleSuggestionCallback(t *testing.T) {
callback, err := unmarshalSuggestionCallback(simpleSuggestionCallback)
assert.Nil(t, err)
assertSimpleSuggestionCallback(t, callback)
}
func assertSimpleSuggestionCallback(t *testing.T, callback *InteractionCallback) {
assert.NotNil(t, callback)
assert.Equal(t, InteractionTypeDialogSuggestion, callback.Type)
assert.Equal(t, "W3VDvuzi2nRLsiaDOsmJranO", callback.Token)
assert.Equal(t, "1528203589.238335", callback.ActionTs)
assert.Equal(t, "T24BK35ML", callback.Team.ID)
assert.Equal(t, "hooli-hq", callback.Team.Domain)
assert.Equal(t, "U900MV5U7", callback.User.ID)
assert.Equal(t, "gbelson", callback.User.Name)
assert.Equal(t, "C012AB3CD", callback.Channel.ID)
assert.Equal(t, "triage-platform", callback.Channel.Name)
assert.Equal(t, "external_data", callback.Name)
assert.Equal(t, "test", callback.Value)
assert.Equal(t, "bugs", callback.CallbackID)
}
func openDialogHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(struct {
SlackResponse
}{
SlackResponse: SlackResponse{Ok: true},
})
rw.Write(response)
}
func TestOpenDialog(t *testing.T) {
http.HandleFunc("/dialog.open", openDialogHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
dialog, err := unmarshalDialog()
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
err = api.OpenDialog("TXXXXXXXX", *dialog)
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
err = api.OpenDialog("", *dialog)
if err == nil {
t.Errorf("Did not error with empty trigger, %s", err)
return
}
}
const (
triggerID = "trigger_xyz"
callbackID = "callback_xyz"
notifyOnCancel = false
title = "Dialog_title"
submitLabel = "Send"
token = "xoxa-123-123-123-213"
)
func _mocDialog() *Dialog {
triggerID := triggerID
callbackID := callbackID
notifyOnCancel := notifyOnCancel
title := title
submitLabel := submitLabel
return &Dialog{
TriggerID: triggerID,
CallbackID: callbackID,
NotifyOnCancel: notifyOnCancel,
Title: title,
SubmitLabel: submitLabel,
}
}
func TestDialogCreate(t *testing.T) {
dialog := _mocDialog()
if dialog == nil {
t.Errorf("Should be able to construct a dialog")
t.Fail()
}
}
func ExampleDialog() {
dialog := _mocDialog()
fmt.Println(*dialog)
// Output:
// {trigger_xyz callback_xyz Dialog_title Send false []}
}
slack-0.11.3/dialog_text.go 0000664 0000000 0000000 00000003133 14307410331 0015517 0 ustar 00root root 0000000 0000000 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,
}
}
slack-0.11.3/dialog_text_test.go 0000664 0000000 0000000 00000001370 14307410331 0016557 0 ustar 00root root 0000000 0000000 package slack
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewTextInput(t *testing.T) {
name := "internalName"
label := "Human Readable"
value := "Pre filled text"
textInput := NewTextInput(name, label, value)
assert.Equal(t, InputTypeText, textInput.Type)
assert.Equal(t, name, textInput.Name)
assert.Equal(t, label, textInput.Label)
assert.Equal(t, value, textInput.Value)
}
func TestNewTextAreaInput(t *testing.T) {
name := "internalName"
label := "Human Readable"
value := "Pre filled text"
textInput := NewTextAreaInput(name, label, value)
assert.Equal(t, InputTypeTextArea, textInput.Type)
assert.Equal(t, name, textInput.Name)
assert.Equal(t, label, textInput.Label)
assert.Equal(t, value, textInput.Value)
}
slack-0.11.3/dnd.go 0000664 0000000 0000000 00000010267 14307410331 0013767 0 ustar 00root root 0000000 0000000 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
}
slack-0.11.3/dnd_test.go 0000664 0000000 0000000 00000010550 14307410331 0015021 0 ustar 00root root 0000000 0000000 package slack
import (
"net/http"
"reflect"
"testing"
)
func TestSlack_EndDND(t *testing.T) {
http.HandleFunc("/dnd.endDnd", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{ "ok": true }`))
})
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
err := api.EndDND()
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
}
func TestSlack_EndSnooze(t *testing.T) {
http.HandleFunc("/dnd.endSnooze", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{ "ok": true,
"dnd_enabled": true,
"next_dnd_start_ts": 1450418400,
"next_dnd_end_ts": 1450454400,
"snooze_enabled": false }`))
})
state := DNDStatus{
Enabled: true,
NextStartTimestamp: 1450418400,
NextEndTimestamp: 1450454400,
SnoozeInfo: SnoozeInfo{SnoozeEnabled: false},
}
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
snoozeState, err := api.EndSnooze()
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
eq := reflect.DeepEqual(snoozeState, &state)
if !eq {
t.Errorf("got %v; want %v", snoozeState, &state)
}
}
func TestSlack_GetDNDInfo(t *testing.T) {
http.HandleFunc("/dnd.info", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{
"ok": true,
"dnd_enabled": true,
"next_dnd_start_ts": 1450416600,
"next_dnd_end_ts": 1450452600,
"snooze_enabled": true,
"snooze_endtime": 1450416600,
"snooze_remaining": 1196
}`))
})
userDNDInfo := DNDStatus{
Enabled: true,
NextStartTimestamp: 1450416600,
NextEndTimestamp: 1450452600,
SnoozeInfo: SnoozeInfo{
SnoozeEnabled: true,
SnoozeEndTime: 1450416600,
SnoozeRemaining: 1196,
},
}
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
userDNDInfoResponse, err := api.GetDNDInfo(nil)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
eq := reflect.DeepEqual(userDNDInfoResponse, &userDNDInfo)
if !eq {
t.Errorf("got %v; want %v", userDNDInfoResponse, &userDNDInfo)
}
}
func TestSlack_GetDNDTeamInfo(t *testing.T) {
http.HandleFunc("/dnd.teamInfo", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{
"ok": true,
"users": {
"U023BECGF": {
"dnd_enabled": true,
"next_dnd_start_ts": 1450387800,
"next_dnd_end_ts": 1450423800
},
"U058CJVAA": {
"dnd_enabled": false,
"next_dnd_start_ts": 1,
"next_dnd_end_ts": 1
}
}
}`))
})
usersDNDInfo := map[string]DNDStatus{
"U023BECGF": {
Enabled: true,
NextStartTimestamp: 1450387800,
NextEndTimestamp: 1450423800,
},
"U058CJVAA": {
Enabled: false,
NextStartTimestamp: 1,
NextEndTimestamp: 1,
},
}
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
usersDNDInfoResponse, err := api.GetDNDTeamInfo(nil)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
eq := reflect.DeepEqual(usersDNDInfoResponse, usersDNDInfo)
if !eq {
t.Errorf("got %v; want %v", usersDNDInfoResponse, usersDNDInfo)
}
}
func TestSlack_SetSnooze(t *testing.T) {
http.HandleFunc("/dnd.setSnooze", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{
"ok": true,
"dnd_enabled": true,
"snooze_endtime": 1450373897,
"snooze_remaining": 60
}`))
})
snooze := DNDStatus{
Enabled: true,
SnoozeInfo: SnoozeInfo{
SnoozeEndTime: 1450373897,
SnoozeRemaining: 60,
},
}
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
snoozeResponse, err := api.SetSnooze(60)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
eq := reflect.DeepEqual(snoozeResponse, &snooze)
if !eq {
t.Errorf("got %v; want %v", snoozeResponse, &snooze)
}
}
slack-0.11.3/emoji.go 0000664 0000000 0000000 00000001312 14307410331 0014314 0 ustar 00root root 0000000 0000000 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
}
slack-0.11.3/emoji_test.go 0000664 0000000 0000000 00000001772 14307410331 0015365 0 ustar 00root root 0000000 0000000 package slack
import (
"net/http"
"reflect"
"testing"
)
func getEmojiHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response := []byte(`{"ok": true, "emoji": {
"bowtie": "https://my.slack.com/emoji/bowtie/46ec6f2bb0.png",
"squirrel": "https://my.slack.com/emoji/squirrel/f35f40c0e0.png",
"shipit": "alias:squirrel"
}}`)
rw.Write(response)
}
func TestGetEmoji(t *testing.T) {
http.HandleFunc("/emoji.list", getEmojiHandler)
once.Do(startServer)
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/"))
emojisResponse := map[string]string{
"bowtie": "https://my.slack.com/emoji/bowtie/46ec6f2bb0.png",
"squirrel": "https://my.slack.com/emoji/squirrel/f35f40c0e0.png",
"shipit": "alias:squirrel",
}
emojis, err := api.GetEmoji()
if err != nil {
t.Errorf("Unexpected error: %s", err)
return
}
eq := reflect.DeepEqual(emojis, emojisResponse)
if !eq {
t.Errorf("got %v; want %v", emojis, emojisResponse)
}
}
slack-0.11.3/errors.go 0000664 0000000 0000000 00000001555 14307410331 0014536 0 ustar 00root root 0000000 0000000 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")
ErrBlockIDNotUnique = errorsx.String("Block ID needs to be unique")
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")
)
slack-0.11.3/examples/ 0000775 0000000 0000000 00000000000 14307410331 0014503 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/blocks/ 0000775 0000000 0000000 00000000000 14307410331 0015760 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/blocks/README.md 0000664 0000000 0000000 00000073755 14307410331 0017260 0 ustar 00root root 0000000 0000000 ### Block Examples
The examples provided replicate the template examples provided by slack. The template builder can be found at https://api.slack.com/tools/block-kit-builder.
Due to the nature how slack expects different components to be configured, building complex block using the provided functions can be very verbose, but allows for maximum flexibility.
The examples below should cover implementing most supported block elements.
For additional information on Blocks, see the [Block Kit website](https://api.slack.com/block-kit).
### Using examples with the Block Kit Builder website
When generating examples, they will be printed to the screen as a complete message that is meant to be sent back to slack as a direct response, or throuogh the ResponseURL provided. To test your examples in the Block Kit Builder, you must take the contents of the `blocks` property and paste the results into the builder.
For example, when printing a simple header, the output will be
```
{
"replace_original": false,
"delete_original": false,
"blocks": [
{
"type": "section",
"text": {
"type": "plain_text",
"text": "Example Header Text"
}
},
{
"type": "divider"
}
]
}
```
To preview this block on the builder website, you should copy just the contents of the blocks:
```
[
{
"type": "section",
"text": {
"type": "plain_text",
"text": "Example Header Text"
}
},
{
"type": "divider"
}
]
```
#### Example 1 - Approval
The first example demonstrates usage of Sections, Fields and Action buttons. You can view the [Approval Example](https://api.slack.com/tools/block-kit-builder?blocks=%5B%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22You%20have%20a%20new%20request%3A%5Cn*%3CfakeLink.toEmployeeProfile.com%7CFred%20Enriquez%20-%20New%20device%20request%3E*%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22fields%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*Type%3A*%5CnComputer%20(laptop)%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*When%3A*%5CnSubmitted%20Aut%2010%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*Last%20Update%3A*%5CnMar%2010%2C%202015%20(3%20years%2C%205%20months)%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*Reason%3A*%5CnAll%20vowel%20keys%20aren%27t%20working.%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*Specs%3A*%5Cn%5C%22Cheetah%20Pro%2015%5C%22%20-%20Fast%2C%20really%20fast%5C%22%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22actions%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Approve%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Deny%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%0A%5D) on the block kit builder website. This example can be generated with the function named `exampleOne`.
#### Example 2 - Approval - With Images
The secoond example adds additional complexity by introducing images as accessories to main blocks of text. You can view this [Approval Example with Images](https://api.slack.com/tools/block-kit-builder?blocks=%5B%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22You%20have%20a%20new%20request%3A%5Cn*%3Cgoogle.com%7CFred%20Enriquez%20-%20Time%20Off%20request%3E*%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Type%3A*%5CnPaid%20time%20off%5Cn*When%3A*%5CnAug%2010-Aug%2013%5Cn*Hours%3A*%2016.0%20(2%20days)%5Cn*Remaining%20balance%3A*%2032.0%20hours%20(4%20days)%5Cn*Comments%3A*%20%5C%22Family%20in%20town%2C%20going%20camping!%5C%22%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FapprovalsNewDevice.png%22%2C%0A%09%09%09%22alt_text%22%3A%20%22computer%20thumbnail%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22actions%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Approve%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Deny%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%0A%5D) on the block kit builder website. This example can be generated with the function named `exampleTwo`.
#### Example 3 - Notifications
This example shows how to add actions to your block that will trigger an interactive message to your application. You can view the rendered example for [Notifications](https://api.slack.com/tools/block-kit-builder?blocks=%5B%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%22text%22%3A%20%22Looks%20like%20you%20have%20a%20scheduling%20conflict%20with%20this%20event%3A%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3CfakeLink.toUserProfiles.com%7CIris%20%2F%20Zelda%201-1%3E*%5CnTuesday%2C%20January%2021%204%3A00-4%3A30pm%5CnBuilding%202%20-%20Havarti%20Cheese%20(3)%5Cn2%20guests%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2Fnotifications.png%22%2C%0A%09%09%09%22alt_text%22%3A%20%22calendar%20thumbnail%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22context%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FnotificationsWarningIcon.png%22%2C%0A%09%09%09%09%22alt_text%22%3A%20%22notifications%20warning%20icon%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22*Conflicts%20with%20Team%20Huddle%3A%204%3A15-4%3A30pm*%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Propose%20a%20new%20time%3A*%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Today%20-%204%3A30-5pm*%5CnEveryone%20is%20available%3A%20%40iris%2C%20%40zelda%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Choose%22%0A%09%09%09%7D%2C%0A%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Tomorrow%20-%204-4%3A30pm*%5CnEveryone%20is%20available%3A%20%40iris%2C%20%40zelda%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Choose%22%0A%09%09%09%7D%2C%0A%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Tomorrow%20-%206-6%3A30pm*%5CnSome%20people%20aren%27t%20available%3A%20%40iris%2C%20~%40zelda~%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Choose%22%0A%09%09%09%7D%2C%0A%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3Cfakelink.ToMoreTimes.com%7CShow%20more%20times%3E*%22%0A%09%09%7D%0A%09%7D%0A%5D) on the block builder website. Refer to the function `exampleThree` for details on how this block can be generated.
#### Example 4 - Polls
The Polls example displays results and allows the end user to vote, displaying a count and images of recent voters. You can view the rendered [Poll Example](https://api.slack.com/tools/block-kit-builder?blocks=%5B%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*Where%20should%20we%20order%20lunch%20from%3F*%20Poll%20by%20%3CfakeLink.toUser.com%7CMark%3E%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22%3Asushi%3A%20*Ace%20Wasabi%20Rock-n-Roll%20Sushi%20Bar*%5CnThe%20best%20landlocked%20sushi%20restaurant.%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Vote%22%0A%09%09%09%7D%2C%0A%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22context%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2Fprofile_1.png%22%2C%0A%09%09%09%09%22alt_text%22%3A%20%22Michael%20Scott%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2Fprofile_2.png%22%2C%0A%09%09%09%09%22alt_text%22%3A%20%22Dwight%20Schrute%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2Fprofile_3.png%22%2C%0A%09%09%09%09%22alt_text%22%3A%20%22Pam%20Beasely%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%223%20votes%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22%3Ahamburger%3A%20*Super%20Hungryman%20Hamburgers*%5CnOnly%20for%20the%20hungriest%20of%20the%20hungry.%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Vote%22%0A%09%09%09%7D%2C%0A%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22context%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2Fprofile_4.png%22%2C%0A%09%09%09%09%22alt_text%22%3A%20%22Angela%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2Fprofile_2.png%22%2C%0A%09%09%09%09%22alt_text%22%3A%20%22Dwight%20Schrute%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%222%20votes%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22%3Aramen%3A%20*Kagawa-Ya%20Udon%20Noodle%20Shop*%5CnDo%20you%20like%20to%20shop%20for%20noodles%3F%20We%20have%20noodles.%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Vote%22%0A%09%09%09%7D%2C%0A%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22context%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%09%22text%22%3A%20%22No%20votes%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22actions%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Add%20a%20suggestion%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%0A%5D) on the block kit builder website. Refer to the function named `exampleFour` for more information on generating this block type.
#### Example 5 - Search Results
This example introduces overflow elements, allowing you to populate a select style dropdown with fields. These fields can be static, loaded from an external source. You can view the rendered [Search Results Example](https://api.slack.com/tools/block-kit-builder?blocks=%5B%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22We%20found%20*205%20Hotels*%20in%20New%20Orleans%2C%20LA%20from%20*12%2F14%20to%2012%2F17*%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22overflow%22%2C%0A%09%09%09%22options%22%3A%20%5B%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Option%20One%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-0%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Option%20Two%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-1%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Option%20Three%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-2%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Option%20Four%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-3%22%0A%09%09%09%09%7D%0A%09%09%09%5D%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3CfakeLink.toHotelPage.com%7CWindsor%20Court%20Hotel%3E*%5Cn%E2%98%85%E2%98%85%E2%98%85%E2%98%85%E2%98%85%5Cn%24340%20per%20night%5CnRated%3A%209.4%20-%20Excellent%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FtripAgent_1.png%22%2C%0A%09%09%09%22alt_text%22%3A%20%22Windsor%20Court%20Hotel%20thumbnail%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22context%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FtripAgentLocationMarker.png%22%2C%0A%09%09%09%09%22alt_text%22%3A%20%22Location%20Pin%20Icon%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Location%3A%20Central%20Business%20District%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3CfakeLink.toHotelPage.com%7CThe%20Ritz-Carlton%20New%20Orleans%3E*%5Cn%E2%98%85%E2%98%85%E2%98%85%E2%98%85%E2%98%85%5Cn%24340%20per%20night%5CnRated%3A%209.1%20-%20Excellent%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FtripAgent_2.png%22%2C%0A%09%09%09%22alt_text%22%3A%20%22Ritz-Carlton%20New%20Orleans%20thumbnail%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22context%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FtripAgentLocationMarker.png%22%2C%0A%09%09%09%09%22alt_text%22%3A%20%22Location%20Pin%20Icon%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Location%3A%20French%20Quarter%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3CfakeLink.toHotelPage.com%7COmni%20Royal%20Orleans%20Hotel%3E*%5Cn%E2%98%85%E2%98%85%E2%98%85%E2%98%85%E2%98%85%5Cn%24419%20per%20night%5CnRated%3A%208.8%20-%20Excellent%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FtripAgent_3.png%22%2C%0A%09%09%09%22alt_text%22%3A%20%22Omni%20Royal%20Orleans%20Hotel%20thumbnail%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22context%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22image%22%2C%0A%09%09%09%09%22image_url%22%3A%20%22https%3A%2F%2Fapi.slack.com%2Fimg%2Fblocks%2Fbkb_template_images%2FtripAgentLocationMarker.png%22%2C%0A%09%09%09%09%22alt_text%22%3A%20%22Location%20Pin%20Icon%22%0A%09%09%09%7D%2C%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Location%3A%20French%20Quarter%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22actions%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Next%202%20Results%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%0A%5D) on the block kit builder website. Refer to the function named `exampleFive` for more information on generating this block.
#### Example 6 - Search Results with Options and Actions
Using a combination of overflow elements containing selectable options and actions, this examples allows you to prompt the user with multiple actions in a single response. You can view the rendered [Search Results with Actions](https://api.slack.com/tools/block-kit-builder?blocks=%5B%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22%3Amag%3A%20Search%20results%20for%20*Cata*%22%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3CfakeLink.toYourApp.com%7CUse%20Case%20Catalogue%3E*%5CnUse%20Case%20Catalogue%20for%20the%20following%20departments%2Froles...%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22static_select%22%2C%0A%09%09%09%22placeholder%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Manage%22%0A%09%09%09%7D%2C%0A%09%09%09%22options%22%3A%20%5B%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Edit%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-0%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Read%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-1%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Save%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-2%22%0A%09%09%09%09%7D%0A%09%09%09%5D%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3CfakeLink.toYourApp.com%7CCustomer%20Support%20-%20Workflow%20Diagram%20Catalogue%3E*%5CnThis%20resource%20was%20put%20together%20by%20members%20of...%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22static_select%22%2C%0A%09%09%09%22placeholder%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Manage%22%0A%09%09%09%7D%2C%0A%09%09%09%22options%22%3A%20%5B%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Manage%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-0%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Read%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-1%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Save%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-2%22%0A%09%09%09%09%7D%0A%09%09%09%5D%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3CfakeLink.toYourApp.com%7CSelf-Serve%20Learning%20Options%20Catalogue%3E*%5CnSee%20the%20learning%20and%20development%20options%20we...%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22static_select%22%2C%0A%09%09%09%22placeholder%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Manage%22%0A%09%09%09%7D%2C%0A%09%09%09%22options%22%3A%20%5B%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Manage%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-0%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Read%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-1%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Save%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-2%22%0A%09%09%09%09%7D%0A%09%09%09%5D%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3CfakeLink.toYourApp.com%7CUse%20Case%20Catalogue%20-%20CF%20Presentation%20-%20%5BJune%2012%2C%202018%5D%3E*%5CnThis%20is%20presentation%20will%20continue%20to%20be%20updated%20as...%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22static_select%22%2C%0A%09%09%09%22placeholder%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Manage%22%0A%09%09%09%7D%2C%0A%09%09%09%22options%22%3A%20%5B%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Manage%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-0%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Read%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-1%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Save%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-2%22%0A%09%09%09%09%7D%0A%09%09%09%5D%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22section%22%2C%0A%09%09%22text%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22mrkdwn%22%2C%0A%09%09%09%22text%22%3A%20%22*%3CfakeLink.toYourApp.com%7CComprehensive%20Benefits%20Catalogue%20-%202019%3E*%5CnInformation%20about%20all%20the%20benfits%20we%20offer%20is...%22%0A%09%09%7D%2C%0A%09%09%22accessory%22%3A%20%7B%0A%09%09%09%22type%22%3A%20%22static_select%22%2C%0A%09%09%09%22placeholder%22%3A%20%7B%0A%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%22text%22%3A%20%22Manage%22%0A%09%09%09%7D%2C%0A%09%09%09%22options%22%3A%20%5B%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Manage%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-0%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Read%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-1%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%7B%0A%09%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%09%22text%22%3A%20%22Save%20it%22%0A%09%09%09%09%09%7D%2C%0A%09%09%09%09%09%22value%22%3A%20%22value-2%22%0A%09%09%09%09%7D%0A%09%09%09%5D%0A%09%09%7D%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22divider%22%0A%09%7D%2C%0A%09%7B%0A%09%09%22type%22%3A%20%22actions%22%2C%0A%09%09%22elements%22%3A%20%5B%0A%09%09%09%7B%0A%09%09%09%09%22type%22%3A%20%22button%22%2C%0A%09%09%09%09%22text%22%3A%20%7B%0A%09%09%09%09%09%22type%22%3A%20%22plain_text%22%2C%0A%09%09%09%09%09%22emoji%22%3A%20true%2C%0A%09%09%09%09%09%22text%22%3A%20%22Next%205%20Results%22%0A%09%09%09%09%7D%2C%0A%09%09%09%09%22value%22%3A%20%22click_me_123%22%0A%09%09%09%7D%0A%09%09%5D%0A%09%7D%0A%5D) example on the block kit builder website. Refer to the function named `exampleSix` for more information on building this block. slack-0.11.3/examples/blocks/blocks.go 0000664 0000000 0000000 00000053767 14307410331 0017606 0 ustar 00root root 0000000 0000000 package main
import (
"encoding/json"
"fmt"
"github.com/slack-go/slack"
)
// The functions below mock the different templates slack has as examples on their website.
//
// Refer to README.md for more information on the examples and how to use them.
func main() {
fmt.Println("--- Begin Example One ---")
exampleOne()
fmt.Println("--- End Example One ---")
fmt.Println("--- Begin Example Two ---")
exampleTwo()
fmt.Println("--- End Example Two ---")
fmt.Println("--- Begin Example Three ---")
exampleThree()
fmt.Println("--- End Example Three ---")
fmt.Println("--- Begin Example Four ---")
exampleFour()
fmt.Println("--- End Example Four ---")
fmt.Println("--- Begin Example Five ---")
exampleFive()
fmt.Println("--- End Example Five ---")
fmt.Println("--- Begin Example Six ---")
exampleSix()
fmt.Println("--- End Example Six ---")
fmt.Println("--- Begin Example Unmarshalling ---")
unmarshalExample()
fmt.Println("--- End Example Unmarshalling ---")
}
// approvalRequest mocks the simple "Approval" template located on block kit builder website
func exampleOne() {
// Header Section
headerText := slack.NewTextBlockObject("mrkdwn", "You have a new request:\n**", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
// Fields
typeField := slack.NewTextBlockObject("mrkdwn", "*Type:*\nComputer (laptop)", false, false)
whenField := slack.NewTextBlockObject("mrkdwn", "*When:*\nSubmitted Aut 10", false, false)
lastUpdateField := slack.NewTextBlockObject("mrkdwn", "*Last Update:*\nMar 10, 2015 (3 years, 5 months)", false, false)
reasonField := slack.NewTextBlockObject("mrkdwn", "*Reason:*\nAll vowel keys aren't working.", false, false)
specsField := slack.NewTextBlockObject("mrkdwn", "*Specs:*\n\"Cheetah Pro 15\" - Fast, really fast\"", false, false)
fieldSlice := make([]*slack.TextBlockObject, 0)
fieldSlice = append(fieldSlice, typeField)
fieldSlice = append(fieldSlice, whenField)
fieldSlice = append(fieldSlice, lastUpdateField)
fieldSlice = append(fieldSlice, reasonField)
fieldSlice = append(fieldSlice, specsField)
fieldsSection := slack.NewSectionBlock(nil, fieldSlice, nil)
// Approve and Deny Buttons
approveBtnTxt := slack.NewTextBlockObject("plain_text", "Approve", false, false)
approveBtn := slack.NewButtonBlockElement("", "click_me_123", approveBtnTxt)
denyBtnTxt := slack.NewTextBlockObject("plain_text", "Deny", false, false)
denyBtn := slack.NewButtonBlockElement("", "click_me_123", denyBtnTxt)
actionBlock := slack.NewActionBlock("", approveBtn, denyBtn)
// Build Message with blocks created above
msg := slack.NewBlockMessage(
headerSection,
fieldsSection,
actionBlock,
)
b, err := json.MarshalIndent(msg, "", " ")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
// exampleTwo mocks the more complex "Approval" template located on block kit builder website
// which includes an accessory image next to the approval request
func exampleTwo() {
// Header Section
headerText := slack.NewTextBlockObject("mrkdwn", "You have a new request:\n**", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
approvalText := slack.NewTextBlockObject("mrkdwn", "*Type:*\nPaid time off\n*When:*\nAug 10-Aug 13\n*Hours:* 16.0 (2 days)\n*Remaining balance:* 32.0 hours (4 days)\n*Comments:* \"Family in town, going camping!\"", false, false)
approvalImage := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/approvalsNewDevice.png", "computer thumbnail")
fieldsSection := slack.NewSectionBlock(approvalText, nil, slack.NewAccessory(approvalImage))
// Approve and Deny Buttons
approveBtnTxt := slack.NewTextBlockObject("plain_text", "Approve", false, false)
approveBtn := slack.NewButtonBlockElement("", "click_me_123", approveBtnTxt)
denyBtnTxt := slack.NewTextBlockObject("plain_text", "Deny", false, false)
denyBtn := slack.NewButtonBlockElement("", "click_me_123", denyBtnTxt)
actionBlock := slack.NewActionBlock("", approveBtn, denyBtn)
// Build Message with blocks created above
msg := slack.NewBlockMessage(
headerSection,
fieldsSection,
actionBlock,
)
b, err := json.MarshalIndent(msg, "", " ")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
// exampleThree generates the notification example from the block kit builder website
func exampleThree() {
// Shared Assets for example
chooseBtnText := slack.NewTextBlockObject("plain_text", "Choose", true, false)
chooseBtnEle := slack.NewButtonBlockElement("", "click_me_123", chooseBtnText)
divSection := slack.NewDividerBlock()
// Header Section
headerText := slack.NewTextBlockObject("plain_text", "Looks like you have a scheduling conflict with this event:", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
// Schedule Info Section
scheduleText := slack.NewTextBlockObject("mrkdwn", "**\nTuesday, January 21 4:00-4:30pm\nBuilding 2 - Havarti Cheese (3)\n2 guests", false, false)
scheduleAccessory := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/notifications.png", "calendar thumbnail")
schedeuleSection := slack.NewSectionBlock(scheduleText, nil, slack.NewAccessory(scheduleAccessory))
// Conflict Section
conflictImage := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/notificationsWarningIcon.png", "notifications warning icon")
conflictText := slack.NewTextBlockObject("mrkdwn", "*Conflicts with Team Huddle: 4:15-4:30pm*", false, false)
conflictSection := slack.NewContextBlock(
"",
[]slack.MixedElement{conflictImage, conflictText}...,
)
// Proposese Text
proposeText := slack.NewTextBlockObject("mrkdwn", "*Propose a new time:*", false, false)
proposeSection := slack.NewSectionBlock(proposeText, nil, nil)
// Option 1
optionOneText := slack.NewTextBlockObject("mrkdwn", "*Today - 4:30-5pm*\nEveryone is available: @iris, @zelda", false, false)
optionOneSection := slack.NewSectionBlock(optionOneText, nil, slack.NewAccessory(chooseBtnEle))
// Option 2
optionTwoText := slack.NewTextBlockObject("mrkdwn", "*Tomorrow - 4-4:30pm*\nEveryone is available: @iris, @zelda", false, false)
optionTwoSection := slack.NewSectionBlock(optionTwoText, nil, slack.NewAccessory(chooseBtnEle))
// Option 3
optionThreeText := slack.NewTextBlockObject("mrkdwn", "*Tomorrow - 6-6:30pm*\nSome people aren't available: @iris, ~@zelda~", false, false)
optionThreeSection := slack.NewSectionBlock(optionThreeText, nil, slack.NewAccessory(chooseBtnEle))
// Show More Times Link
showMoreText := slack.NewTextBlockObject("mrkdwn", "**", false, false)
showMoreSection := slack.NewSectionBlock(showMoreText, nil, nil)
// Build Message with blocks created above
msg := slack.NewBlockMessage(
headerSection,
divSection,
schedeuleSection,
conflictSection,
divSection,
proposeSection,
optionOneSection,
optionTwoSection,
optionThreeSection,
showMoreSection,
)
b, err := json.MarshalIndent(msg, "", " ")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
// exampleFour profiles a poll example block
func exampleFour() {
// Shared Assets for example
divSection := slack.NewDividerBlock()
voteBtnText := slack.NewTextBlockObject("plain_text", "Vote", true, false)
voteBtnEle := slack.NewButtonBlockElement("", "click_me_123", voteBtnText)
profileOne := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/profile_1.png", "Michael Scott")
profileTwo := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/profile_2.png", "Dwight Schrute")
profileThree := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/profile_3.png", "Pam Beasely")
profileFour := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/profile_4.png", "Angela")
// Header Section
headerText := slack.NewTextBlockObject("mrkdwn", "*Where should we order lunch from?* Poll by ", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
// Option One Info
optOneText := slack.NewTextBlockObject("mrkdwn", ":sushi: *Ace Wasabi Rock-n-Roll Sushi Bar*\nThe best landlocked sushi restaurant.", false, false)
optOneSection := slack.NewSectionBlock(optOneText, nil, slack.NewAccessory(voteBtnEle))
// Option One Votes
optOneVoteText := slack.NewTextBlockObject("plain_text", "3 votes", true, false)
optOneContext := slack.NewContextBlock("", []slack.MixedElement{profileOne, profileTwo, profileThree, optOneVoteText}...)
// Option Two Info
optTwoText := slack.NewTextBlockObject("mrkdwn", ":hamburger: *Super Hungryman Hamburgers*\nOnly for the hungriest of the hungry.", false, false)
optTwoSection := slack.NewSectionBlock(optTwoText, nil, slack.NewAccessory(voteBtnEle))
// Option Two Votes
optTwoVoteText := slack.NewTextBlockObject("plain_text", "2 votes", true, false)
optTwoContext := slack.NewContextBlock("", []slack.MixedElement{profileFour, profileTwo, optTwoVoteText}...)
// Option Three Info
optThreeText := slack.NewTextBlockObject("mrkdwn", ":ramen: *Kagawa-Ya Udon Noodle Shop*\nDo you like to shop for noodles? We have noodles.", false, false)
optThreeSection := slack.NewSectionBlock(optThreeText, nil, slack.NewAccessory(voteBtnEle))
// Option Three Votes
optThreeVoteText := slack.NewTextBlockObject("plain_text", "No votes", true, false)
optThreeContext := slack.NewContextBlock("", []slack.MixedElement{optThreeVoteText}...)
// Suggestions Action
btnTxt := slack.NewTextBlockObject("plain_text", "Add a suggestion", false, false)
nextBtn := slack.NewButtonBlockElement("", "click_me_123", btnTxt)
actionBlock := slack.NewActionBlock("", nextBtn)
// Build Message with blocks created above
msg := slack.NewBlockMessage(
headerSection,
divSection,
optOneSection,
optOneContext,
optTwoSection,
optTwoContext,
optThreeSection,
optThreeContext,
divSection,
actionBlock,
)
b, err := json.MarshalIndent(msg, "", " ")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
func exampleFive() {
// Build Header Section Block, includes text and overflow menu
headerText := slack.NewTextBlockObject("mrkdwn", "We found *205 Hotels* in New Orleans, LA from *12/14 to 12/17*", false, false)
// Build Text Objects associated with each option
overflowOptionTextOne := slack.NewTextBlockObject("plain_text", "Option One", false, false)
overflowOptionTextTwo := slack.NewTextBlockObject("plain_text", "Option Two", false, false)
overflowOptionTextThree := slack.NewTextBlockObject("plain_text", "Option Three", false, false)
// Build each option, providing a value for the option
overflowOptionOne := slack.NewOptionBlockObject("value-0", overflowOptionTextOne, nil)
overflowOptionTwo := slack.NewOptionBlockObject("value-1", overflowOptionTextTwo, nil)
overflowOptionThree := slack.NewOptionBlockObject("value-2", overflowOptionTextThree, nil)
// Build overflow section
overflow := slack.NewOverflowBlockElement("", overflowOptionOne, overflowOptionTwo, overflowOptionThree)
// Create the header section
headerSection := slack.NewSectionBlock(headerText, nil, slack.NewAccessory(overflow))
// Shared Divider
divSection := slack.NewDividerBlock()
// Shared Objects
locationPinImage := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/tripAgentLocationMarker.png", "Location Pin Icon")
// First Hotel Listing
hotelOneInfo := slack.NewTextBlockObject("mrkdwn", "**\n★★★★★\n$340 per night\nRated: 9.4 - Excellent", false, false)
hotelOneImage := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/tripAgent_1.png", "Windsor Court Hotel thumbnail")
hotelOneLoc := slack.NewTextBlockObject("plain_text", "Location: Central Business District", true, false)
hotelOneSection := slack.NewSectionBlock(hotelOneInfo, nil, slack.NewAccessory(hotelOneImage))
hotelOneContext := slack.NewContextBlock("", []slack.MixedElement{locationPinImage, hotelOneLoc}...)
// Second Hotel Listing
hotelTwoInfo := slack.NewTextBlockObject("mrkdwn", "**\n★★★★★\n$340 per night\nRated: 9.1 - Excellent", false, false)
hotelTwoImage := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/tripAgent_2.png", "Ritz-Carlton New Orleans thumbnail")
hotelTwoLoc := slack.NewTextBlockObject("plain_text", "Location: French Quarter", true, false)
hotelTwoSection := slack.NewSectionBlock(hotelTwoInfo, nil, slack.NewAccessory(hotelTwoImage))
hotelTwoContext := slack.NewContextBlock("", []slack.MixedElement{locationPinImage, hotelTwoLoc}...)
// Third Hotel Listing
hotelThreeInfo := slack.NewTextBlockObject("mrkdwn", "**\n★★★★★\n$419 per night\nRated: 8.8 - Excellent", false, false)
hotelThreeImage := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/tripAgent_3.png", "https://api.slack.com/img/blocks/bkb_template_images/tripAgent_3.png")
hotelThreeLoc := slack.NewTextBlockObject("plain_text", "Location: French Quarter", true, false)
hotelThreeSection := slack.NewSectionBlock(hotelThreeInfo, nil, slack.NewAccessory(hotelThreeImage))
hotelThreeContext := slack.NewContextBlock("", []slack.MixedElement{locationPinImage, hotelThreeLoc}...)
// Action button
btnTxt := slack.NewTextBlockObject("plain_text", "Next 2 Results", false, false)
nextBtn := slack.NewButtonBlockElement("", "click_me_123", btnTxt)
actionBlock := slack.NewActionBlock("", nextBtn)
// Build Message with blocks created above
msg := slack.NewBlockMessage(
headerSection,
divSection,
hotelOneSection,
hotelOneContext,
divSection,
hotelTwoSection,
hotelTwoContext,
divSection,
hotelThreeSection,
hotelThreeContext,
divSection,
actionBlock,
)
b, err := json.MarshalIndent(msg, "", " ")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
func exampleSix() {
// Shared Assets for example
divSection := slack.NewDividerBlock()
// Shared Available Options
manageTxt := slack.NewTextBlockObject("plain_text", "Manage", true, false)
editTxt := slack.NewTextBlockObject("plain_text", "Edit it", false, false)
readTxt := slack.NewTextBlockObject("plain_text", "Read it", false, false)
saveTxt := slack.NewTextBlockObject("plain_text", "Save it", false, false)
editOpt := slack.NewOptionBlockObject("value-0", editTxt, nil)
readOpt := slack.NewOptionBlockObject("value-1", readTxt, nil)
saveOpt := slack.NewOptionBlockObject("value-2", saveTxt, nil)
availableOption := slack.NewOptionsSelectBlockElement("static_select", manageTxt, "", editOpt, readOpt, saveOpt)
// Header Section
headerText := slack.NewTextBlockObject("mrkdwn", ":mag: Search results for *Cata*", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
// Result One
resultOneTxt := slack.NewTextBlockObject("mrkdwn", "**\nUse Case Catalogue for the following departments/roles...", false, false)
resultOneSection := slack.NewSectionBlock(resultOneTxt, nil, slack.NewAccessory(availableOption))
// Result Two
resultTwoTxt := slack.NewTextBlockObject("mrkdwn", "**\nThis resource was put together by members of...", false, false)
resultTwoSection := slack.NewSectionBlock(resultTwoTxt, nil, slack.NewAccessory(availableOption))
// Result Three
resultThreeTxt := slack.NewTextBlockObject("mrkdwn", "**\nSee the learning and development options we...", false, false)
resultThreeSection := slack.NewSectionBlock(resultThreeTxt, nil, slack.NewAccessory(availableOption))
// Result Four
resultFourTxt := slack.NewTextBlockObject("mrkdwn", "**\nThis is presentation will continue to be updated as...", false, false)
resultFourSection := slack.NewSectionBlock(resultFourTxt, nil, slack.NewAccessory(availableOption))
// Result Five
resultFiveTxt := slack.NewTextBlockObject("mrkdwn", "**\nInformation about all the benfits we offer is...", false, false)
resultFiveSection := slack.NewSectionBlock(resultFiveTxt, nil, slack.NewAccessory(availableOption))
// Next Results Button
// Suggestions Action
btnTxt := slack.NewTextBlockObject("plain_text", "Next 5 Results", false, false)
nextBtn := slack.NewButtonBlockElement("", "click_me_123", btnTxt)
actionBlock := slack.NewActionBlock("", nextBtn)
// Build Message with blocks created above
msg := slack.NewBlockMessage(
headerSection,
divSection,
resultOneSection,
resultTwoSection,
resultThreeSection,
resultFourSection,
resultFiveSection,
divSection,
actionBlock,
)
b, err := json.MarshalIndent(msg, "", " ")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
func unmarshalExample() {
var msgBlocks []slack.Block
// Append ActionBlock for marshalling
btnTxt := slack.NewTextBlockObject("plain_text", "Add a suggestion", false, false)
nextBtn := slack.NewButtonBlockElement("", "click_me_123", btnTxt)
approveBtnTxt := slack.NewTextBlockObject("plain_text", "Approve", false, false)
approveBtn := slack.NewButtonBlockElement("", "click_me_123", approveBtnTxt)
msgBlocks = append(msgBlocks, slack.NewActionBlock("", nextBtn, approveBtn))
// Append ContextBlock for marshalling
profileOne := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/profile_1.png", "Michael Scott")
profileTwo := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/profile_2.png", "Dwight Schrute")
textBlockObj := slack.NewTextBlockObject("mrkdwn", "**\n★★★★★\n$419 per night\nRated: 8.8 - Excellent", false, false)
msgBlocks = append(msgBlocks, slack.NewContextBlock("", []slack.MixedElement{profileOne, profileTwo, textBlockObj}...))
// Append ImageBlock for marshalling
msgBlocks = append(msgBlocks, slack.NewImageBlock("https://api.slack.com/img/blocks/bkb_template_images/profile_2.png", "some profile", "image-block", textBlockObj))
// Append DividerBlock for marshalling
msgBlocks = append(msgBlocks, slack.NewDividerBlock())
// Append SectionBlock for marshalling
approvalText := slack.NewTextBlockObject("mrkdwn", "*Type:*\nPaid time off\n*When:*\nAug 10-Aug 13\n*Hours:* 16.0 (2 days)\n*Remaining balance:* 32.0 hours (4 days)\n*Comments:* \"Family in town, going camping!\"", false, false)
approvalImage := slack.NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/approvalsNewDevice.png", "computer thumbnail")
msgBlocks = append(msgBlocks, slack.NewSectionBlock(approvalText, nil, slack.NewAccessory(approvalImage)), nil)
// Build Message with blocks created above
msg := slack.NewBlockMessage(msgBlocks...)
b, err := json.Marshal(&msg)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
// Unmarshal message
m := slack.Message{}
if err := json.Unmarshal(b, &m); err != nil {
fmt.Println(err)
return
}
var respBlocks []slack.Block
for _, block := range m.Blocks.BlockSet {
// Need to implement a type switch to determine Block type since the
// response from Slack could include any/all types under "blocks" key
switch block.BlockType() {
case slack.MBTContext:
var respMixedElements []slack.MixedElement
contextElements := block.(*slack.ContextBlock).ContextElements.Elements
// Need to implement a type switch for ContextElements for same reason as Blocks
for _, elem := range contextElements {
switch elem.MixedElementType() {
case slack.MixedElementImage:
// Assert the block's type to manipulate/extract values
imageBlockElem := elem.(*slack.ImageBlockElement)
imageBlockElem.ImageURL = "https://api.slack.com/img/blocks/bkb_template_images/profile_1.png"
imageBlockElem.AltText = "MichaelScott"
respMixedElements = append(respMixedElements, imageBlockElem)
case slack.MixedElementText:
textBlockElem := elem.(*slack.TextBlockObject)
textBlockElem.Text = "go go go go go"
respMixedElements = append(respMixedElements, textBlockElem)
}
}
respBlocks = append(respBlocks, slack.NewContextBlock("new block", respMixedElements...))
case slack.MBTAction:
actionBlock := block.(*slack.ActionBlock)
// Need to implement a type switch for BlockElements for same reason as Blocks
for _, elem := range actionBlock.Elements.ElementSet {
switch elem.ElementType() {
case slack.METImage:
imageElem := elem.(*slack.ImageBlockElement)
fmt.Printf("do something with image block element: %v\n", imageElem)
case slack.METButton:
buttonElem := elem.(*slack.ButtonBlockElement)
fmt.Printf("do something with button block element: %v\n", buttonElem)
case slack.METOverflow:
overflowElem := elem.(*slack.OverflowBlockElement)
fmt.Printf("do something with overflow block element: %v\n", overflowElem)
case slack.METDatepicker:
datepickerElem := elem.(*slack.DatePickerBlockElement)
fmt.Printf("do something with datepicker block element: %v\n", datepickerElem)
case slack.METTimepicker:
timepickerElem := elem.(*slack.TimePickerBlockElement)
fmt.Printf("do something with timepicker block element: %v\n", timepickerElem)
}
}
respBlocks = append(respBlocks, block)
case slack.MBTImage:
// Simply re-append the block if you want to include it in the response
respBlocks = append(respBlocks, block)
case slack.MBTSection:
respBlocks = append(respBlocks, block)
case slack.MBTDivider:
respBlocks = append(respBlocks, block)
}
}
// Build new Message with Blocks obtained/edited from callback
respMsg := slack.NewBlockMessage(respBlocks...)
b, err = json.Marshal(&respMsg)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
slack-0.11.3/examples/buttons/ 0000775 0000000 0000000 00000000000 14307410331 0016201 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/buttons/buttons.go 0000664 0000000 0000000 00000003205 14307410331 0020226 0 ustar 00root root 0000000 0000000 package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"github.com/slack-go/slack"
)
func main() {
var token, channel string
var ok bool
token, ok = os.LookupEnv("SLACK_TOKEN")
if !ok {
fmt.Println("Missing SLACK_TOKEN in environment")
os.Exit(1)
}
channel, ok = os.LookupEnv("SLACK_CHANNEL")
if !ok {
fmt.Println("Missing SLACK_CHANNEL in environment")
os.Exit(1)
}
api := slack.New(token)
attachment := slack.Attachment{
Pretext: "pretext",
Fallback: "We don't currently support your client",
CallbackID: "accept_or_reject",
Color: "#3AA3E3",
Actions: []slack.AttachmentAction{
slack.AttachmentAction{
Name: "accept",
Text: "Accept",
Type: "button",
Value: "accept",
},
slack.AttachmentAction{
Name: "reject",
Text: "Reject",
Type: "button",
Value: "reject",
Style: "danger",
},
},
}
message := slack.MsgOptionAttachments(attachment)
channelID, timestamp, err := api.PostMessage(channel, slack.MsgOptionText("", false), message)
if err != nil {
fmt.Printf("Could not send message: %v", err)
}
fmt.Printf("Message with buttons sucessfully sent to channel %s at %s", channelID, timestamp)
http.HandleFunc("/actions", actionHandler)
http.ListenAndServe(":3000", nil)
}
func actionHandler(w http.ResponseWriter, r *http.Request) {
var payload slack.InteractionCallback
err := json.Unmarshal([]byte(r.FormValue("payload")), &payload)
if err != nil {
fmt.Printf("Could not parse action response JSON: %v", err)
}
fmt.Printf("Message button pressed by user %s with value %s", payload.User.Name, payload.ActionCallback.AttachmentActions[0].Value)
}
slack-0.11.3/examples/connparams/ 0000775 0000000 0000000 00000000000 14307410331 0016644 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/connparams/connparams.go 0000664 0000000 0000000 00000002754 14307410331 0021344 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"log"
"net/url"
"os"
"github.com/slack-go/slack"
)
func main() {
token, ok := os.LookupEnv("SLACK_TOKEN")
if !ok {
fmt.Println("Missing SLACK_TOKEN in environment")
os.Exit(1)
}
api := slack.New(
token,
slack.OptionDebug(true),
slack.OptionLog(log.New(os.Stdout, "slack-bot: ", log.Lshortfile|log.LstdFlags)),
)
// turn on the batch_presence_aware option
rtm := api.NewRTM(slack.RTMOptionConnParams(url.Values{
"batch_presence_aware": {"1"},
}))
go rtm.ManageConnection()
for msg := range rtm.IncomingEvents {
fmt.Print("Event Received: ")
switch ev := msg.Data.(type) {
case *slack.HelloEvent:
// Replace USER-ID-N here with your User IDs
rtm.SendMessage(rtm.NewSubscribeUserPresence([]string{
"USER-ID-1",
"USER-ID-2",
}))
case *slack.ConnectedEvent:
fmt.Println("Infos:", ev.Info)
fmt.Println("Connection counter:", ev.ConnectionCount)
// Replace C2147483705 with your Channel ID
rtm.SendMessage(rtm.NewOutgoingMessage("Hello world", "C2147483705"))
case *slack.MessageEvent:
fmt.Printf("Message: %v\n", ev)
case *slack.PresenceChangeEvent:
fmt.Printf("Presence Change: %v\n", ev)
case *slack.LatencyReport:
fmt.Printf("Current latency: %v\n", ev.Value)
case *slack.RTMError:
fmt.Printf("Error: %s\n", ev.Error())
case *slack.InvalidAuthEvent:
fmt.Printf("Invalid credentials")
return
default:
// Ignore other events..
// fmt.Printf("Unexpected: %v\n", msg.Data)
}
}
}
slack-0.11.3/examples/dialog/ 0000775 0000000 0000000 00000000000 14307410331 0015742 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/dialog/dialog.go 0000664 0000000 0000000 00000005072 14307410331 0017534 0 ustar 00root root 0000000 0000000 package main
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
"github.com/slack-go/slack"
)
var api = slack.New("YOUR_TOKEN")
var signingSecret = "YOUR_SIGNING_SECRET"
// You can open a dialog with a user interaction. (like pushing buttons, slash commands ...)
// https://api.slack.com/surfaces/modals
// https://api.slack.com/interactivity/entry-points
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":3000", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
// Read request body
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("[ERROR] Fail to read request body: %v", err)
return
}
// Verify signing secret
sv, err := slack.NewSecretsVerifier(r.Header, signingSecret)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
log.Printf("[ERROR] Fail to verify SigningSecret: %v", err)
return
}
sv.Write(body)
if err := sv.Ensure(); err != nil {
w.WriteHeader(http.StatusUnauthorized)
log.Printf("[ERROR] Fail to verify SigningSecret: %v", err)
return
}
// Parse request body
str, _ := url.QueryUnescape(string(body))
str = strings.Replace(str, "payload=", "", 1)
var message slack.InteractionCallback
if err := json.Unmarshal([]byte(str), &message); err != nil {
log.Printf("[ERROR] Fail to unmarshal json: %v", err)
return
}
switch message.Type {
case slack.InteractionTypeInteractionMessage:
// Make new dialog components and open a dialog.
// Component-Text
textInput := slack.NewTextInput("TextSample", "Sample label - Text", "Default value")
// Component-TextArea
textareaInput := slack.NewTextAreaInput("TexaAreaSample", "Sample label - TextArea", "Default value")
// Component-Select menu
option1 := slack.DialogSelectOption{
Label: "Display name 1",
Value: "Inner value 1",
}
option2 := slack.DialogSelectOption{
Label: "Display name 2",
Value: "Inner value 2",
}
options := []slack.DialogSelectOption{option1, option2}
selectInput := slack.NewStaticSelectDialogInput("SelectSample", "Sample label - Select", options)
// Open a dialog
elements := []slack.DialogElement{
textInput,
textareaInput,
selectInput,
}
dialog := slack.Dialog{
CallbackID: "Callback_ID",
Title: "Dialog title",
SubmitLabel: "Submit",
Elements: elements,
}
api.OpenDialog(message.TriggerID, dialog)
case slack.InteractionTypeDialogSubmission:
// Receive a notification of a dialog submission
log.Printf("Successfully receive a dialog submission.")
}
}
slack-0.11.3/examples/eventsapi/ 0000775 0000000 0000000 00000000000 14307410331 0016501 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/eventsapi/events.go 0000664 0000000 0000000 00000003250 14307410331 0020334 0 ustar 00root root 0000000 0000000 package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"github.com/slack-go/slack"
"github.com/slack-go/slack/slackevents"
)
// You more than likely want your "Bot User OAuth Access Token" which starts with "xoxb-"
var api = slack.New("TOKEN")
func main() {
signingSecret := os.Getenv("SLACK_SIGNING_SECRET")
http.HandleFunc("/events-endpoint", func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
sv, err := slack.NewSecretsVerifier(r.Header, signingSecret)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if _, err := sv.Write(body); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if err := sv.Ensure(); err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
eventsAPIEvent, err := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionNoVerifyToken())
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
if eventsAPIEvent.Type == slackevents.URLVerification {
var r *slackevents.ChallengeResponse
err := json.Unmarshal([]byte(body), &r)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text")
w.Write([]byte(r.Challenge))
}
if eventsAPIEvent.Type == slackevents.CallbackEvent {
innerEvent := eventsAPIEvent.InnerEvent
switch ev := innerEvent.Data.(type) {
case *slackevents.AppMentionEvent:
api.PostMessage(ev.Channel, slack.MsgOptionText("Yes, hello.", false))
}
}
})
fmt.Println("[INFO] Server listening")
http.ListenAndServe(":3000", nil)
}
slack-0.11.3/examples/files/ 0000775 0000000 0000000 00000000000 14307410331 0015605 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/files/example.txt 0000664 0000000 0000000 00000000047 14307410331 0020002 0 ustar 00root root 0000000 0000000 Nan Nan Nan Nan Nan Nan Nan Nan Batman
slack-0.11.3/examples/files/files.go 0000664 0000000 0000000 00000001102 14307410331 0017230 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"github.com/slack-go/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
params := slack.FileUploadParameters{
Title: "Batman Example",
//Filetype: "txt",
File: "example.txt",
//Content: "Nan Nan Nan Nan Nan Nan Nan Nan Batman",
}
file, err := api.UploadFile(params)
if err != nil {
fmt.Printf("%s\n", err)
return
}
fmt.Printf("Name: %s, URL: %s\n", file.Name, file.URL)
err = api.DeleteFile(file.ID)
if err != nil {
fmt.Printf("%s\n", err)
return
}
fmt.Printf("File %s deleted successfully.\n", file.Name)
}
slack-0.11.3/examples/messages/ 0000775 0000000 0000000 00000000000 14307410331 0016312 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/messages/messages.go 0000664 0000000 0000000 00000001471 14307410331 0020453 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"github.com/slack-go/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
attachment := slack.Attachment{
Pretext: "some pretext",
Text: "some text",
// Uncomment the following part to send a field too
/*
Fields: []slack.AttachmentField{
slack.AttachmentField{
Title: "a",
Value: "no",
},
},
*/
}
channelID, timestamp, err := api.PostMessage(
"CHANNEL_ID",
slack.MsgOptionText("Some text", false),
slack.MsgOptionAttachments(attachment),
slack.MsgOptionAsUser(true), // Add this if you want that the bot would post message as a user, otherwise it will send response using the default slackbot
)
if err != nil {
fmt.Printf("%s\n", err)
return
}
fmt.Printf("Message successfully sent to channel %s at %s", channelID, timestamp)
}
slack-0.11.3/examples/modal/ 0000775 0000000 0000000 00000000000 14307410331 0015577 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/modal/modal.go 0000664 0000000 0000000 00000012106 14307410331 0017222 0 ustar 00root root 0000000 0000000 // Modal example - How to respond to a slash command with an interactive modal and parse the response
// The flow of this example:
// 1. User trigers your app with a slash command (e.g. /modaltest) that will send a request to http://URL/slash and respond with a request to open a modal
// 2. User fills out fields first and last name in modal and hits submit
// 3. This will send a request to http://URL/modal and send a greeting message to the user
// Note: Within your slack app you will need to enable and provide a URL for "Interactivity & Shortcuts" and "Slash Commands"
// Note: Be sure to update YOUR_SIGNING_SECRET_HERE and YOUR_TOKEN_HERE
// You can use ngrok to test this example: https://api.slack.com/tutorials/tunneling-with-ngrok
// Helpful slack documentation to learn more: https://api.slack.com/interactivity/handling
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/slack-go/slack"
)
func generateModalRequest() slack.ModalViewRequest {
// Create a ModalViewRequest with a header and two inputs
titleText := slack.NewTextBlockObject("plain_text", "My App", false, false)
closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false)
headerText := slack.NewTextBlockObject("mrkdwn", "Please enter your name", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
firstNameText := slack.NewTextBlockObject("plain_text", "First Name", false, false)
firstNameHint := slack.NewTextBlockObject("plain_text", "First Name Hint", false, false)
firstNamePlaceholder := slack.NewTextBlockObject("plain_text", "Enter your first name", false, false)
firstNameElement := slack.NewPlainTextInputBlockElement(firstNamePlaceholder, "firstName")
// Notice that blockID is a unique identifier for a block
firstName := slack.NewInputBlock("First Name", firstNameText, firstNameHint, firstNameElement)
lastNameText := slack.NewTextBlockObject("plain_text", "Last Name", false, false)
lastNameHint := slack.NewTextBlockObject("plain_text", "Last Name Hint", false, false)
lastNamePlaceholder := slack.NewTextBlockObject("plain_text", "Enter your first name", false, false)
lastNameElement := slack.NewPlainTextInputBlockElement(lastNamePlaceholder, "lastName")
lastName := slack.NewInputBlock("Last Name", lastNameText, lastNameHint, lastNameElement)
blocks := slack.Blocks{
BlockSet: []slack.Block{
headerSection,
firstName,
lastName,
},
}
var modalRequest slack.ModalViewRequest
modalRequest.Type = slack.ViewType("modal")
modalRequest.Title = titleText
modalRequest.Close = closeText
modalRequest.Submit = submitText
modalRequest.Blocks = blocks
return modalRequest
}
// This was taken from the slash example
// https://github.com/slack-go/slack/blob/master/examples/slash/slash.go
func verifySigningSecret(r *http.Request) error {
signingSecret := "YOUR_SIGNING_SECRET_HERE"
verifier, err := slack.NewSecretsVerifier(r.Header, signingSecret)
if err != nil {
fmt.Println(err.Error())
return err
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Println(err.Error())
return err
}
// Need to use r.Body again when unmarshalling SlashCommand and InteractionCallback
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
verifier.Write(body)
if err = verifier.Ensure(); err != nil {
fmt.Println(err.Error())
return err
}
return nil
}
func handleSlash(w http.ResponseWriter, r *http.Request) {
err := verifySigningSecret(r)
if err != nil {
fmt.Printf(err.Error())
w.WriteHeader(http.StatusUnauthorized)
return
}
s, err := slack.SlashCommandParse(r)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Println(err.Error())
return
}
switch s.Command {
case "/humboldttest":
api := slack.New("YOUR_TOKEN_HERE")
modalRequest := generateModalRequest()
_, err = api.OpenView(s.TriggerID, modalRequest)
if err != nil {
fmt.Printf("Error opening view: %s", err)
}
default:
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func handleModal(w http.ResponseWriter, r *http.Request) {
err := verifySigningSecret(r)
if err != nil {
fmt.Printf(err.Error())
w.WriteHeader(http.StatusUnauthorized)
return
}
var i slack.InteractionCallback
err = json.Unmarshal([]byte(r.FormValue("payload")), &i)
if err != nil {
fmt.Printf(err.Error())
w.WriteHeader(http.StatusUnauthorized)
return
}
// Note there might be a better way to get this info, but I figured this structure out from looking at the json response
firstName := i.View.State.Values["First Name"]["firstName"].Value
lastName := i.View.State.Values["Last Name"]["lastName"].Value
msg := fmt.Sprintf("Hello %s %s, nice to meet you!", firstName, lastName)
api := slack.New("YOUR_TOKEN_HERE")
_, _, err = api.PostMessage(i.User.ID,
slack.MsgOptionText(msg, false),
slack.MsgOptionAttachments())
if err != nil {
fmt.Printf(err.Error())
w.WriteHeader(http.StatusUnauthorized)
return
}
}
func main() {
http.HandleFunc("/slash", handleSlash)
http.HandleFunc("/modal", handleModal)
http.ListenAndServe(":4390", nil)
}
slack-0.11.3/examples/modal_users/ 0000775 0000000 0000000 00000000000 14307410331 0017020 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/modal_users/users.go 0000664 0000000 0000000 00000013154 14307410331 0020514 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"github.com/slack-go/slack"
)
// An example how to open a modal with different kinds of input fields
func main() {
// Create a ModalViewRequest with a header and two inputs
titleText := slack.NewTextBlockObject(slack.PlainTextType, "Create channel demo", false, false)
closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false)
submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false)
contextText := slack.NewTextBlockObject(slack.MarkdownType, "This app demonstrates the use of different fields", false, false)
contextBlock := slack.NewContextBlock("context", contextText)
// Only the inputs in input blocks will be included in view_submission’s view.state.values: https://slack.dev/java-slack-sdk/guides/modals
// This means the inputs will not be interactive either because they do not trigger block_actions messages: https://api.slack.com/surfaces/modals/using#interactions
channelNameText := slack.NewTextBlockObject(slack.PlainTextType, "Channel Name", false, false)
channelNameHint := slack.NewTextBlockObject(slack.PlainTextType, "Channel names may only contain lowercase letters, numbers, hyphens, and underscores, and must be 80 characters or less", false, false)
channelPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "New channel name", false, false)
channelNameElement := slack.NewPlainTextInputBlockElement(channelPlaceholder, "channel_name")
// Slack channel names can be maximum 80 characters: https://api.slack.com/methods/conversations.create
channelNameElement.MaxLength = 80
channelNameBlock := slack.NewInputBlock("channel_name", channelNameText, channelNameHint, channelNameElement)
// Provide a static list of users to choose from, those provided now are just made up user IDs
// Get user IDs by right clicking on them in Slack, select "Copy link", and inspect the last part of the link
// The user ID should start with "U" followed by 8 random characters
memberOptions := createOptionBlockObjects([]string{"U9911MMAA", "U2233KKNN", "U00112233"}, true)
inviteeText := slack.NewTextBlockObject(slack.PlainTextType, "Invitee from static list", false, false)
inviteeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, nil, "invitee", memberOptions...)
inviteeBlock := slack.NewInputBlock("invitee", inviteeText, nil, inviteeOption)
// Section with users select - this input will not be included in the view_submission's view.state.values,
// but instead be sent as a "block_actions" request
additionalInviteeText := slack.NewTextBlockObject(slack.PlainTextType, "Invitee from complete list of users", false, false)
additionalInviteeHintText := slack.NewTextBlockObject(slack.PlainTextType, "", false, false)
additionalInviteeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeUser, additionalInviteeText, "user")
additionalInviteeSection := slack.NewSectionBlock(additionalInviteeText, nil, slack.NewAccessory(additionalInviteeOption))
// Input with users select - this input will be included in the view_submission's view.state.values
// It can be fetched as for example "payload.View.State.Values["user"]["user"].SelectedUser"
additionalInviteeBlock := slack.NewInputBlock("user", additionalInviteeText, additionalInviteeHintText, additionalInviteeOption)
checkboxTxt := slack.NewTextBlockObject(slack.PlainTextType, "Checkbox", false, false)
checkboxOptions := createOptionBlockObjects([]string{"option 1", "option 2", "option 3"}, false)
checkboxOptionsBlock := slack.NewCheckboxGroupsBlockElement("chkbox", checkboxOptions...)
checkboxBlock := slack.NewInputBlock("chkbox", checkboxTxt, nil, checkboxOptionsBlock)
summaryText := slack.NewTextBlockObject(slack.PlainTextType, "Summary", false, false)
summaryHint := slack.NewTextBlockObject(slack.PlainTextType, "Summary Hint", false, false)
summaryPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Summary of reason for creating channel", false, false)
summaryElement := slack.NewPlainTextInputBlockElement(summaryPlaceholder, "summary")
// Just set an arbitrary max length to avoid too prose summary
summaryElement.MaxLength = 200
summaryElement.Multiline = true
summaryBlock := slack.NewInputBlock("summary", summaryText, summaryHint, summaryElement)
blocks := slack.Blocks{
BlockSet: []slack.Block{
contextBlock,
channelNameBlock,
inviteeBlock,
additionalInviteeSection,
additionalInviteeBlock,
checkboxBlock,
summaryBlock,
},
}
var modalRequest slack.ModalViewRequest
modalRequest.Type = slack.ViewType("modal")
modalRequest.Title = titleText
modalRequest.Close = closeText
modalRequest.Submit = submitText
modalRequest.Blocks = blocks
modalRequest.CallbackID = "create_channel"
api := slack.New("YOUR_BOT_TOKEN_HERE")
// Using a trigger ID you can open a modal
// The trigger ID is provided through certain events and interactions
// More information can be found here: https://api.slack.com/interactivity/handling#modal_responses
_, err := api.OpenView("YOUR_TRIGGERID_HERE", modalRequest)
if err != nil {
fmt.Printf("Error opening view: %s", err)
}
}
// createOptionBlockObjects - utility function for generating option block objects
func createOptionBlockObjects(options []string, users bool) []*slack.OptionBlockObject {
optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options))
var text string
for _, o := range options {
if users {
text = fmt.Sprintf("<@%s>", o)
} else {
text = o
}
optionText := slack.NewTextBlockObject(slack.PlainTextType, text, false, false)
optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(o, optionText, nil))
}
return optionBlockObjects
}
slack-0.11.3/examples/pins/ 0000775 0000000 0000000 00000000000 14307410331 0015454 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/pins/pins.go 0000664 0000000 0000000 00000005471 14307410331 0016763 0 ustar 00root root 0000000 0000000 package main
import (
"flag"
"fmt"
"github.com/slack-go/slack"
)
/*
WARNING: This example is destructive in the sense that it create a channel called testpinning
*/
func main() {
var (
apiToken string
debug bool
)
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
flag.BoolVar(&debug, "debug", false, "Show JSON output")
flag.Parse()
api := slack.New(apiToken, slack.OptionDebug(debug))
var (
postAsUserName string
postAsUserID string
postToChannelID string
channels []slack.Channel
)
// Find the user to post as.
authTest, err := api.AuthTest()
if err != nil {
fmt.Printf("Error getting channels: %s\n", err)
return
}
channelName := "testpinning"
// Post as the authenticated user.
postAsUserName = authTest.User
postAsUserID = authTest.UserID
// Create a temporary channel
channel, err := api.CreateConversation(channelName, false)
if err != nil {
// If the channel exists, that means we just need to unarchive it
if err.Error() == "name_taken" {
err = nil
params := &slack.GetConversationsParameters{ExcludeArchived: false}
if channels, _, err = api.GetConversations(params); err != nil {
fmt.Println("Could not retrieve channels")
return
}
for _, archivedChannel := range channels {
if archivedChannel.Name == channelName {
if archivedChannel.IsArchived {
err = api.UnArchiveConversation(archivedChannel.ID)
if err != nil {
fmt.Printf("Could not unarchive %s: %s\n", archivedChannel.ID, err)
return
}
}
channel = &archivedChannel
break
}
}
}
if err != nil {
fmt.Printf("Error setting test channel for pinning: %s\n", err)
return
}
}
postToChannelID = channel.ID
fmt.Printf("Posting as %s (%s) in channel %s\n", postAsUserName, postAsUserID, postToChannelID)
// Post a message.
channelID, timestamp, err := api.PostMessage(postToChannelID, slack.MsgOptionText("Is this any good?", false))
if err != nil {
fmt.Printf("Error posting message: %s\n", err)
return
}
// Grab a reference to the message.
msgRef := slack.NewRefToMessage(channelID, timestamp)
// Add message pin to channel
if err = api.AddPin(channelID, msgRef); err != nil {
fmt.Printf("Error adding pin: %s\n", err)
return
}
// List all of the users pins.
listPins, _, err := api.ListPins(channelID)
if err != nil {
fmt.Printf("Error listing pins: %s\n", err)
return
}
fmt.Printf("\n")
fmt.Printf("All pins by %s...\n", authTest.User)
for _, item := range listPins {
fmt.Printf(" > Item type: %s\n", item.Type)
}
// Remove the pin.
err = api.RemovePin(channelID, msgRef)
if err != nil {
fmt.Printf("Error remove pin: %s\n", err)
return
}
if err = api.UnArchiveConversation(channelID); err != nil {
fmt.Printf("Error archiving channel: %s\n", err)
return
}
}
slack-0.11.3/examples/reactions/ 0000775 0000000 0000000 00000000000 14307410331 0016472 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/reactions/reactions.go 0000664 0000000 0000000 00000006174 14307410331 0021020 0 ustar 00root root 0000000 0000000 package main
import (
"flag"
"fmt"
"github.com/slack-go/slack"
)
func main() {
var (
apiToken string
debug bool
)
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
flag.BoolVar(&debug, "debug", false, "Show JSON output")
flag.Parse()
api := slack.New(apiToken, slack.OptionDebug(debug))
var (
postAsUserName string
postAsUserID string
postToUserName string
postToUserID string
postToChannelID string
)
// Find the user to post as.
authTest, err := api.AuthTest()
if err != nil {
fmt.Printf("Error getting channels: %s\n", err)
return
}
// Post as the authenticated user.
postAsUserName = authTest.User
postAsUserID = authTest.UserID
// Posting to DM with self causes a conversation with slackbot.
postToUserName = authTest.User
postToUserID = authTest.UserID
// Find the channel.
channel, _, _, err := api.OpenConversation(&slack.OpenConversationParameters{ChannelID: postToUserID})
if err != nil {
fmt.Printf("Error opening IM: %s\n", err)
return
}
postToChannelID = channel.ID
fmt.Printf("Posting as %s (%s) in DM with %s (%s), channel %s\n", postAsUserName, postAsUserID, postToUserName, postToUserID, postToChannelID)
// Post a message.
channelID, timestamp, err := api.PostMessage(postToChannelID, slack.MsgOptionText("Is this any good?", false))
if err != nil {
fmt.Printf("Error posting message: %s\n", err)
return
}
// Grab a reference to the message.
msgRef := slack.NewRefToMessage(channelID, timestamp)
// React with :+1:
if err = api.AddReaction("+1", msgRef); err != nil {
fmt.Printf("Error adding reaction: %s\n", err)
return
}
// React with :-1:
if err = api.AddReaction("cry", msgRef); err != nil {
fmt.Printf("Error adding reaction: %s\n", err)
return
}
// Get all reactions on the message.
msgReactions, err := api.GetReactions(msgRef, slack.NewGetReactionsParameters())
if err != nil {
fmt.Printf("Error getting reactions: %s\n", err)
return
}
fmt.Printf("\n")
fmt.Printf("%d reactions to message...\n", len(msgReactions))
for _, r := range msgReactions {
fmt.Printf(" %d users say %s\n", r.Count, r.Name)
}
// List all of the users reactions.
listReactions, _, err := api.ListReactions(slack.NewListReactionsParameters())
if err != nil {
fmt.Printf("Error listing reactions: %s\n", err)
return
}
fmt.Printf("\n")
fmt.Printf("All reactions by %s...\n", authTest.User)
for _, item := range listReactions {
fmt.Printf("%d on a %s...\n", len(item.Reactions), item.Type)
for _, r := range item.Reactions {
fmt.Printf(" %s (along with %d others)\n", r.Name, r.Count-1)
}
}
// Remove the :cry: reaction.
err = api.RemoveReaction("cry", msgRef)
if err != nil {
fmt.Printf("Error remove reaction: %s\n", err)
return
}
// Get all reactions on the message.
msgReactions, err = api.GetReactions(msgRef, slack.NewGetReactionsParameters())
if err != nil {
fmt.Printf("Error getting reactions: %s\n", err)
return
}
fmt.Printf("\n")
fmt.Printf("%d reactions to message after removing cry...\n", len(msgReactions))
for _, r := range msgReactions {
fmt.Printf(" %d users say %s\n", r.Count, r.Name)
}
}
slack-0.11.3/examples/remotefiles/ 0000775 0000000 0000000 00000000000 14307410331 0017021 5 ustar 00root root 0000000 0000000 slack-0.11.3/examples/remotefiles/remotefiles.go 0000664 0000000 0000000 00000003724 14307410331 0021674 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"os"
"github.com/slack-go/slack"
)
func main() {
api := slack.New("YOUR_TOKEN_HERE")
r, err := os.Open("slack-go.png")
if err != nil {
fmt.Printf("%s\n", err)
return
}
defer r.Close()
remotefile, err := api.AddRemoteFile(slack.RemoteFileParameters{
ExternalID: "slack-go",
ExternalURL: "https://github.com/slack-go/slack",
Title: "slack-go",
Filetype: "go",
IndexableFileContents: "golang, slack",
// PreviewImage: "slack-go.png",
PreviewImageReader: r,
})
if err != nil {
fmt.Printf("add remote file failed: %s\n", err)
return
}
fmt.Printf("remote file: %v\n", remotefile)
_, err = api.ShareRemoteFile([]string{"CPB8DC1CM"}, remotefile.ExternalID, "")
if err != nil {
fmt.Printf("share remote file failed: %s\n", err)
return
}
fmt.Printf("share remote file %s successfully.\n", remotefile.Name)
remotefiles, err := api.ListRemoteFiles(slack.ListRemoteFilesParameters{
Channel: "YOUR_CHANNEL_HERE",
})
if err != nil {
fmt.Printf("list remote files failed: %s\n", err)
return
}
fmt.Printf("remote files: %v\n", remotefiles)
remotefile, err = api.UpdateRemoteFile(remotefile.ID, slack.RemoteFileParameters{
ExternalID: "slack-go",
ExternalURL: "https://github.com/slack-go/slack",
Title: "slack-go",
Filetype: "go",
IndexableFileContents: "golang, slack, github",
})
if err != nil {
fmt.Printf("update remote file failed: %s\n", err)
return
}
fmt.Printf("remote file: %v\n", remotefile)
info, err := api.GetRemoteFileInfo(remotefile.ExternalID, "")
if err != nil {
fmt.Printf("get remote file info failed: %s\n", err)
return
}
fmt.Printf("remote file info: %v\n", info)
err = api.RemoveRemoteFile(remotefile.ExternalID, "")
if err != nil {
fmt.Printf("remove remote file failed: %s\n", err)
return
}
fmt.Printf("remote file %s deleted successfully.\n", remotefile.Name)
}
slack-0.11.3/examples/remotefiles/slack-go.png 0000664 0000000 0000000 00000451341 14307410331 0021237 0 ustar 00root root 0000000 0000000 ‰PNG
IHDR <