Add nlopes/slack to vendor folder

This commit is contained in:
erroneousboat 2020-01-08 11:32:20 +01:00
parent 18512c9836
commit f12de7e34f
17 changed files with 258 additions and 55 deletions

View File

@ -28,6 +28,10 @@ matrix:
script: go test -v ./...
- go: "1.11.x"
script: go test -v -mod=vendor ./...
- go: "1.12.x"
script: go test -v -mod=vendor ./...
- go: "1.13.x"
script: go test -v -mod=vendor ./...
- go: "tip"
script: go test -v -mod=vendor ./...

View File

@ -61,7 +61,7 @@ type ConfirmationField struct {
// Attachment contains all the information for an attachment
type Attachment struct {
Color string `json:"color,omitempty"`
Fallback string `json:"fallback"`
Fallback string `json:"fallback,omitempty"`
CallbackID string `json:"callback_id,omitempty"`
ID int `json:"id,omitempty"`
@ -75,7 +75,7 @@ type Attachment struct {
Title string `json:"title,omitempty"`
TitleLink string `json:"title_link,omitempty"`
Pretext string `json:"pretext,omitempty"`
Text string `json:"text"`
Text string `json:"text,omitempty"`
ImageURL string `json:"image_url,omitempty"`
ThumbURL string `json:"thumb_url,omitempty"`
@ -84,6 +84,8 @@ type Attachment struct {
Actions []AttachmentAction `json:"actions,omitempty"`
MarkdownIn []string `json:"mrkdwn_in,omitempty"`
Blocks []Block `json:"blocks,omitempty"`
Footer string `json:"footer,omitempty"`
FooterIcon string `json:"footer_icon,omitempty"`

View File

@ -30,22 +30,23 @@ type Blocks struct {
// BlockAction is the action callback sent when a block is interacted with
type BlockAction struct {
ActionID string `json:"action_id"`
BlockID string `json:"block_id"`
Type actionType `json:"type"`
Text TextBlockObject `json:"text"`
Value string `json:"value"`
ActionTs string `json:"action_ts"`
SelectedOption OptionBlockObject `json:"selected_option"`
SelectedUser string `json:"selected_user"`
SelectedChannel string `json:"selected_channel"`
SelectedConversation string `json:"selected_conversation"`
SelectedDate string `json:"selected_date"`
InitialOption OptionBlockObject `json:"initial_option"`
InitialUser string `json:"initial_user"`
InitialChannel string `json:"initial_channel"`
InitialConversation string `json:"initial_conversation"`
InitialDate string `json:"initial_date"`
ActionID string `json:"action_id"`
BlockID string `json:"block_id"`
Type actionType `json:"type"`
Text TextBlockObject `json:"text"`
Value string `json:"value"`
ActionTs string `json:"action_ts"`
SelectedOption OptionBlockObject `json:"selected_option"`
SelectedOptions []OptionBlockObject `json:"selected_options"`
SelectedUser string `json:"selected_user"`
SelectedChannel string `json:"selected_channel"`
SelectedConversation string `json:"selected_conversation"`
SelectedDate string `json:"selected_date"`
InitialOption OptionBlockObject `json:"initial_option"`
InitialUser string `json:"initial_user"`
InitialChannel string `json:"initial_channel"`
InitialConversation string `json:"initial_conversation"`
InitialDate string `json:"initial_date"`
}
// actionType returns the type of the action

View File

@ -60,6 +60,9 @@ func (b *Blocks) UnmarshalJSON(data []byte) error {
block = &ImageBlock{}
case "section":
block = &SectionBlock{}
case "rich_text":
// for now ignore the (complex) content of rich_text blocks until we can fully support it
continue
default:
return errors.New("unsupported block type")
}

View File

@ -178,7 +178,7 @@ func NewConfirmationBlockObject(title, text, confirm, deny *TextBlockObject) *Co
type OptionBlockObject struct {
Text *TextBlockObject `json:"text"`
Value string `json:"value"`
URL string `json:"url"`
URL string `json:"url,omitempty"`
}
// NewOptionBlockObject returns an instance of a new Option Block Element

View File

@ -7,10 +7,13 @@ import (
// Bot contains information about a bot
type Bot struct {
ID string `json:"id"`
Name string `json:"name"`
Deleted bool `json:"deleted"`
Icons Icons `json:"icons"`
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 {

View File

@ -228,6 +228,7 @@ type sendConfig struct {
endpoint string
values url.Values
attachments []Attachment
blocks Blocks
responseType string
}
@ -242,6 +243,7 @@ func (t sendConfig) BuildRequest(token, channelID string) (req *http.Request, _
endpoint: t.endpoint,
values: t.values,
attachments: t.attachments,
blocks: t.blocks,
responseType: t.responseType,
}.BuildRequest()
default:
@ -265,6 +267,7 @@ type responseURLSender struct {
endpoint string
values url.Values
attachments []Attachment
blocks Blocks
responseType string
}
@ -273,6 +276,7 @@ func (t responseURLSender) BuildRequest() (*http.Request, func(*chatResponseFull
Text: t.values.Get("text"),
Timestamp: t.values.Get("ts"),
Attachments: t.attachments,
Blocks: t.blocks,
ResponseType: t.responseType,
})
return req, func(resp *chatResponseFull) responseParser {
@ -420,6 +424,8 @@ func MsgOptionBlocks(blocks ...Block) MsgOption {
return nil
}
config.blocks.BlockSet = append(config.blocks.BlockSet, blocks...)
blocks, err := json.Marshal(blocks)
if err == nil {
config.values.Set("blocks", string(blocks))

View File

@ -6,6 +6,8 @@ import "github.com/nlopes/slack/internal/errorsx"
const (
ErrAlreadyDisconnected = errorsx.String("Invalid call to Disconnect - Slack API is already disconnected")
ErrRTMDisconnected = errorsx.String("disconnect received while trying to connect")
ErrRTMGoodbye = errorsx.String("goodbye detected")
ErrRTMDeadman = errorsx.String("deadman switch triggered")
ErrParametersMissing = errorsx.String("received empty parameters")
ErrInvalidConfiguration = errorsx.String("invalid configuration")
ErrMissingHeaders = errorsx.String("missing headers")

View File

@ -1,6 +1,7 @@
package slack
import (
"bytes"
"encoding/json"
)
@ -53,6 +54,48 @@ type ActionCallbacks struct {
BlockActions []*BlockAction
}
// MarshalJSON implements the Marshaller interface in order to combine both
// action callback types back into a single array, like how the api responds.
// This makes Marshaling and Unmarshaling an InteractionCallback symmetrical
func (a ActionCallbacks) MarshalJSON() ([]byte, error) {
count := 0
length := len(a.AttachmentActions) + len(a.BlockActions)
buffer := bytes.NewBufferString("[")
f := func(obj interface{}) error {
js, err := json.Marshal(obj)
if err != nil {
return err
}
_, err = buffer.Write(js)
if err != nil {
return err
}
count++
if count < length {
_, err = buffer.WriteString(",")
return err
}
return nil
}
for _, act := range a.AttachmentActions {
err := f(act)
if err != nil {
return nil, err
}
}
for _, blk := range a.BlockActions {
err := f(blk)
if err != nil {
return nil, err
}
}
buffer.WriteString("]")
return buffer.Bytes(), nil
}
// UnmarshalJSON implements the Marshaller interface in order to delegate
// marshalling and allow for proper type assertion when decoding the response
func (a *ActionCallbacks) UnmarshalJSON(data []byte) error {

View File

@ -101,7 +101,7 @@ type Msg struct {
const (
// ResponseTypeInChannel in channel response for slash commands.
ResponseTypeInChannel = "in_channel"
// ResponseTypeEphemeral ephemeral respone for slash commands.
// ResponseTypeEphemeral ephemeral response for slash commands.
ResponseTypeEphemeral = "ephemeral"
)

View File

@ -2,7 +2,6 @@ package slack
import (
"context"
"encoding/json"
"net/url"
"sync"
"time"
@ -120,7 +119,6 @@ func (api *Client) NewRTM(options ...RTMOption) *RTM {
disconnected: make(chan struct{}),
disconnectedm: &sync.Once{},
forcePing: make(chan bool),
rawEvents: make(chan json.RawMessage),
idGen: NewSafeID(1),
mu: &sync.Mutex{},
}

View File

@ -23,8 +23,14 @@ type SearchParameters struct {
}
type CtxChannel struct {
ID string `json:"id"`
Name string `json:"name"`
ID string `json:"id"`
Name string `json:"name"`
IsExtShared bool `json:"is_ext_shared"`
IsMPIM bool `json:"is_mpim"`
ISOrgShared bool `json:"is_org_shared"`
IsPendingExtShared bool `json:"is_pending_ext_shared"`
IsPrivate bool `json:"is_private"`
IsShared bool `json:"is_shared"`
}
type CtxMessage struct {

View File

@ -4,6 +4,7 @@ import (
"context"
"net/url"
"strconv"
"time"
)
const (
@ -158,3 +159,105 @@ func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters
}
return starredItems, paging, nil
}
type listResponsePaginated struct {
Items []Item `json:"items"`
SlackResponse
Metadata ResponseMetadata `json:"response_metadata"`
}
// StarredItemPagination allows for paginating over the starred items
type StarredItemPagination struct {
Items []Item
limit int
previousResp *ResponseMetadata
c *Client
}
// ListStarsOption options for the GetUsers method call.
type ListStarsOption func(*StarredItemPagination)
// ListAllStars returns the complete list of starred items
func (api *Client) ListAllStars() ([]Item, error) {
return api.ListAllStarsContext(context.Background())
}
// ListAllStarsContext returns the list of users (with their detailed information) with a custom context
func (api *Client) ListAllStarsContext(ctx context.Context) (results []Item, err error) {
p := api.ListStarsPaginated()
for err == nil {
p, err = p.next(ctx)
if err == nil {
results = append(results, p.Items...)
} else if rateLimitedError, ok := err.(*RateLimitedError); ok {
select {
case <-ctx.Done():
err = ctx.Err()
case <-time.After(rateLimitedError.RetryAfter):
err = nil
}
}
}
return results, p.failure(err)
}
// ListStarsPaginated fetches users in a paginated fashion, see ListStarsPaginationContext for usage.
func (api *Client) ListStarsPaginated(options ...ListStarsOption) StarredItemPagination {
return newStarPagination(api, options...)
}
func newStarPagination(c *Client, options ...ListStarsOption) (sip StarredItemPagination) {
sip = StarredItemPagination{
c: c,
limit: 200, // per slack api documentation.
}
for _, opt := range options {
opt(&sip)
}
return sip
}
// done checks if the pagination has completed
func (StarredItemPagination) done(err error) bool {
return err == errPaginationComplete
}
// done checks if pagination failed.
func (t StarredItemPagination) failure(err error) error {
if t.done(err) {
return nil
}
return err
}
// next gets the next list of starred items based on the cursor value
func (t StarredItemPagination) next(ctx context.Context) (_ StarredItemPagination, err error) {
var (
resp *listResponsePaginated
)
if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") {
return t, errPaginationComplete
}
t.previousResp = t.previousResp.initialize()
values := url.Values{
"limit": {strconv.Itoa(t.limit)},
"token": {t.c.token},
"cursor": {t.previousResp.Cursor},
}
if err = t.c.postMethod(ctx, "stars.list", values, &resp); err != nil {
return t, err
}
t.previousResp = &resp.Metadata
t.Items = resp.Items
return t, nil
}

View File

@ -1,7 +1,6 @@
package slack
import (
"encoding/json"
"net/url"
"sync"
"time"
@ -35,7 +34,6 @@ type RTM struct {
disconnected chan struct{}
disconnectedm *sync.Once
forcePing chan bool
rawEvents chan json.RawMessage
// UserDetails upon connection
info *Info

View File

@ -0,0 +1,19 @@
package slack
// DesktopNotificationEvent represents the update event for Desktop Notification.
type DesktopNotificationEvent struct {
Type string `json:"type"`
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Message string `json:"msg"`
Timestamp string `json:"ts"`
Content string `json:"content"`
Channel string `json:"channel"`
LaunchURI string `json:"launchUri"`
AvatarImage string `json:"avatarImage"`
SsbFilename string `json:"ssbFilename"`
ImageURI string `json:"imageUri"`
IsShared bool `json:"is_shared"`
IsChannelInvite bool `json:"is_channel_invite"`
EventTimestamp string `json:"event_ts"`
}

View File

@ -58,15 +58,19 @@ func (rtm *RTM) ManageConnection() {
rtm.Debugf("RTM connection succeeded on try %d", connectionCount)
rawEvents := make(chan json.RawMessage)
// we're now connected so we can set up listeners
go rtm.handleIncomingEvents()
go rtm.handleIncomingEvents(rawEvents)
// this should be a blocking call until the connection has ended
rtm.handleEvents()
rtm.handleEvents(rawEvents)
select {
case <-rtm.disconnected:
// after handle events returns we need to check if we're disconnected
// when this happens we need to cleanup the newly created connection.
if err = conn.Close(); err != nil {
rtm.Debugln("failed to close conn on disconnected RTM", err)
}
return
default:
// otherwise continue and run the loop again to reconnect
@ -208,7 +212,7 @@ func (rtm *RTM) startRTMAndDial(useRTMStart bool) (info *Info, _ *websocket.Conn
// This should not be called directly! Instead a boolean value (true for
// intentional, false otherwise) should be sent to the killChannel on the RTM.
func (rtm *RTM) killConnection(intentional bool, cause error) (err error) {
rtm.Debugln("killing connection")
rtm.Debugln("killing connection", cause)
if rtm.conn != nil {
err = rtm.conn.Close()
@ -228,7 +232,7 @@ func (rtm *RTM) killConnection(intentional bool, cause error) (err error) {
// interval. This also sends outgoing messages that are received from the RTM's
// outgoingMessages channel. This also handles incoming raw events from the RTM
// rawEvents channel.
func (rtm *RTM) handleEvents() {
func (rtm *RTM) handleEvents(events chan json.RawMessage) {
ticker := time.NewTicker(rtm.pingInterval)
defer ticker.Stop()
for {
@ -239,7 +243,7 @@ func (rtm *RTM) handleEvents() {
return
// detect when the connection is dead.
case <-rtm.pingDeadman.C:
_ = rtm.killConnection(false, errorsx.String("deadman switch triggered"))
_ = rtm.killConnection(false, ErrRTMDeadman)
return
// send pings on ticker interval
case <-ticker.C:
@ -255,12 +259,17 @@ func (rtm *RTM) handleEvents() {
// listen for messages that need to be sent
case msg := <-rtm.outgoingMessages:
rtm.sendOutgoingMessage(msg)
// listen for incoming messages that need to be parsed
case rawEvent := <-rtm.rawEvents:
// listen for incoming messages that need to be parsed
case rawEvent := <-events:
switch rtm.handleRawEvent(rawEvent) {
case rtmEventTypeGoodbye:
_ = rtm.killConnection(false, errorsx.String("goodbye detected"))
return
// kill the connection, but DO NOT RETURN, a follow up kill signal will
// be sent that still needs to be processed. this duplication is because
// the event reader restarts once it emits the goodbye event.
// unlike the other cases in this function a final read will be triggered
// against the connection which will emit a kill signal. if we return early
// this kill signal will be processed by the next connection.
_ = rtm.killConnection(false, ErrRTMGoodbye)
default:
}
}
@ -268,13 +277,17 @@ func (rtm *RTM) handleEvents() {
}
// handleIncomingEvents monitors the RTM's opened websocket for any incoming
// events. It pushes the raw events onto the RTM channel rawEvents.
// events. It pushes the raw events into the channel.
//
// This will stop executing once the RTM's keepRunning channel has been closed
// or has anything sent to it.
func (rtm *RTM) handleIncomingEvents() {
// This will stop executing once the RTM's when a fatal error is detected, or
// a disconnect occurs.
func (rtm *RTM) handleIncomingEvents(events chan json.RawMessage) {
for {
if err := rtm.receiveIncomingEvent(); err != nil {
if err := rtm.receiveIncomingEvent(events); err != nil {
select {
case rtm.killChannel <- false:
case <-rtm.disconnected:
}
return
}
}
@ -336,9 +349,15 @@ func (rtm *RTM) ping() error {
// receiveIncomingEvent attempts to receive an event from the RTM's websocket.
// This will block until a frame is available from the websocket.
// If the read from the websocket results in a fatal error, this function will return non-nil.
func (rtm *RTM) receiveIncomingEvent() error {
func (rtm *RTM) receiveIncomingEvent(events chan json.RawMessage) error {
event := json.RawMessage{}
err := rtm.conn.ReadJSON(&event)
// check if the connection was closed.
if websocket.IsUnexpectedCloseError(err) {
return err
}
switch {
case err == io.ErrUnexpectedEOF:
// EOF's don't seem to signify a failed connection so instead we ignore
@ -357,22 +376,18 @@ func (rtm *RTM) receiveIncomingEvent() error {
ErrorObj: err,
}}
select {
case rtm.killChannel <- false:
case <-rtm.disconnected:
}
return err
case len(event) == 0:
rtm.Debugln("Received empty event")
default:
rtm.Debugln("Incoming Event:", string(event))
select {
case rtm.rawEvents <- event:
case events <- event:
case <-rtm.disconnected:
rtm.Debugln("disonnected while attempting to send raw event")
}
}
return nil
}
@ -396,8 +411,6 @@ func (rtm *RTM) handleRawEvent(rawEvent json.RawMessage) string {
rtm.handlePong(rawEvent)
case rtmEventTypeGoodbye:
// just return the event type up for goodbye, will be handled by caller.
case rtmEventTypeDesktopNotification:
rtm.Debugln("Received desktop notification, ignoring")
default:
rtm.handleEvent(event.Type, rawEvent)
}
@ -563,4 +576,6 @@ var EventMapping = map[string]interface{}{
"subteam_self_added": SubteamSelfAddedEvent{},
"subteam_self_removed": SubteamSelfRemovedEvent{},
"subteam_updated": SubteamUpdatedEvent{},
"desktop_notification": DesktopNotificationEvent{},
}

View File

@ -131,7 +131,7 @@ type MemberJoinedChannelEvent struct {
Inviter string `json:"inviter"`
}
// MemberJoinedChannelEvent, a user left a public or private channel
// MemberLeftChannelEvent a user left a public or private channel
type MemberLeftChannelEvent struct {
Type string `json:"type"`
User string `json:"user"`