Update vendor folder

This commit is contained in:
erroneousboat 2019-10-05 12:01:51 +02:00
parent 8ec19e2db2
commit 240deb0607
38 changed files with 688 additions and 245 deletions

9
go.mod
View File

@ -5,13 +5,16 @@ go 1.12
require (
github.com/0xAX/notificator v0.0.0-20171022182052-88d57ee9043b
github.com/erroneousboat/termui v0.0.0-20170923115141-80f245cdfa04
github.com/gorilla/websocket v1.4.0 // indirect
github.com/gorilla/websocket v1.4.1 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/maruel/panicparse v1.1.1 // indirect
github.com/mattn/go-runewidth v0.0.3
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/nlopes/slack v0.5.1-0.20190515005541-e2954b1409b0
github.com/nlopes/slack v0.6.0
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e
github.com/pkg/errors v0.8.1 // indirect
github.com/renstrom/fuzzysearch v1.0.1
github.com/stretchr/testify v1.3.0 // indirect
github.com/stretchr/testify v1.4.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.2.4 // indirect
)

24
go.sum
View File

@ -1,28 +1,42 @@
github.com/0xAX/notificator v0.0.0-20171022182052-88d57ee9043b h1:Sn+u6zpXFyfm2X7ruh+z6SJiUVyFg8YElh6HIOhrRCA=
github.com/0xAX/notificator v0.0.0-20171022182052-88d57ee9043b/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/erroneousboat/termui v0.0.0-20170923115141-80f245cdfa04 h1:DaFwoQC0Neeb2y2CVFxDPrS1BeyWAkKc4VVBDTZ0N98=
github.com/erroneousboat/termui v0.0.0-20170923115141-80f245cdfa04/go.mod h1:UPpsbgDrqmUayOFOkCGD7+xrBIml/1dA0dsqTRnZqac=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5IkRc=
github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/nlopes/slack v0.5.1-0.20190515005541-e2954b1409b0 h1:M89l6W6BKfWSn13BtDUcDeePI5bQE0frdE1a/n2bMc8=
github.com/nlopes/slack v0.5.1-0.20190515005541-e2954b1409b0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA=
github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY=
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/renstrom/fuzzysearch v1.0.1 h1:hnh2Fhqqa5I41Xgmm7UMAYgEIRn/iZwWItfwUHr1IWE=
github.com/renstrom/fuzzysearch v1.0.1/go.mod h1:SAEjPB4voP88qmWJXI7mA5m15uNlEnuHLx4Eu2mPGpQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -1,19 +0,0 @@
language: go
sudo: false
matrix:
include:
- go: 1.7.x
- go: 1.8.x
- go: 1.9.x
- go: 1.10.x
- go: 1.11.x
- go: tip
allow_failures:
- go: tip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...

View File

@ -1,11 +1,11 @@
# Gorilla WebSocket
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
[![CircleCI](https://circleci.com/gh/gorilla/websocket.svg?style=svg)](https://circleci.com/gh/gorilla/websocket)
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket)
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
### Documentation
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
@ -27,7 +27,7 @@ package API is stable.
### Protocol Compliance
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
### Gorilla WebSocket compared with other packages
@ -40,7 +40,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn
</tr>
<tr>
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
<tr><td>Passes <a href="https://github.com/crossbario/autobahn-testsuite">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>

View File

@ -70,7 +70,7 @@ type Dialer struct {
// HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
// size is zero, then a useful default size is used. The I/O buffer sizes
// do not limit the size of the messages that can be sent or received.
ReadBufferSize, WriteBufferSize int
@ -140,7 +140,7 @@ var nilDialer = *DefaultDialer
// Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// The context will be used in the request and in the Dialer
// The context will be used in the request and in the Dialer.
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,

View File

@ -260,10 +260,12 @@ type Conn struct {
newCompressionWriter func(io.WriteCloser, int) io.WriteCloser
// Read fields
reader io.ReadCloser // the current reader returned to the application
readErr error
br *bufio.Reader
readRemaining int64 // bytes remaining in current frame.
reader io.ReadCloser // the current reader returned to the application
readErr error
br *bufio.Reader
// bytes remaining in current frame.
// set setReadRemaining to safely update this value and prevent overflow
readRemaining int64
readFinal bool // true the current message has more frames.
readLength int64 // Message size.
readLimit int64 // Maximum message size.
@ -320,6 +322,17 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int,
return c
}
// setReadRemaining tracks the number of bytes remaining on the connection. If n
// overflows, an ErrReadLimit is returned.
func (c *Conn) setReadRemaining(n int64) error {
if n < 0 {
return ErrReadLimit
}
c.readRemaining = n
return nil
}
// Subprotocol returns the negotiated protocol for the connection.
func (c *Conn) Subprotocol() string {
return c.subprotocol
@ -451,7 +464,8 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
return err
}
func (c *Conn) prepWrite(messageType int) error {
// beginMessage prepares a connection and message writer for a new message.
func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
// Close previous writer if not already closed by the application. It's
// probably better to return an error in this situation, but we cannot
// change this without breaking existing applications.
@ -471,6 +485,10 @@ func (c *Conn) prepWrite(messageType int) error {
return err
}
mw.c = c
mw.frameType = messageType
mw.pos = maxFrameHeaderSize
if c.writeBuf == nil {
wpd, ok := c.writePool.Get().(writePoolData)
if ok {
@ -491,16 +509,11 @@ func (c *Conn) prepWrite(messageType int) error {
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
// PongMessage) are supported.
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
if err := c.prepWrite(messageType); err != nil {
var mw messageWriter
if err := c.beginMessage(&mw, messageType); err != nil {
return nil, err
}
mw := &messageWriter{
c: c,
frameType: messageType,
pos: maxFrameHeaderSize,
}
c.writer = mw
c.writer = &mw
if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
w := c.newCompressionWriter(c.writer, c.compressionLevel)
mw.compress = true
@ -517,10 +530,16 @@ type messageWriter struct {
err error
}
func (w *messageWriter) fatal(err error) error {
func (w *messageWriter) endMessage(err error) error {
if w.err != nil {
w.err = err
w.c.writer = nil
return err
}
c := w.c
w.err = err
c.writer = nil
if c.writePool != nil {
c.writePool.Put(writePoolData{buf: c.writeBuf})
c.writeBuf = nil
}
return err
}
@ -534,7 +553,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
// Check for invalid control frames.
if isControl(w.frameType) &&
(!final || length > maxControlFramePayloadSize) {
return w.fatal(errInvalidControlFrame)
return w.endMessage(errInvalidControlFrame)
}
b0 := byte(w.frameType)
@ -579,7 +598,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])
if len(extra) > 0 {
return c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))
return w.endMessage(c.writeFatal(errors.New("websocket: internal error, extra used in client mode")))
}
}
@ -600,15 +619,11 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
c.isWriting = false
if err != nil {
return w.fatal(err)
return w.endMessage(err)
}
if final {
c.writer = nil
if c.writePool != nil {
c.writePool.Put(writePoolData{buf: c.writeBuf})
c.writeBuf = nil
}
w.endMessage(errWriteClosed)
return nil
}
@ -706,11 +721,7 @@ func (w *messageWriter) Close() error {
if w.err != nil {
return w.err
}
if err := w.flushFrame(true, nil); err != nil {
return err
}
w.err = errWriteClosed
return nil
return w.flushFrame(true, nil)
}
// WritePreparedMessage writes prepared message into connection.
@ -742,10 +753,10 @@ func (c *Conn) WriteMessage(messageType int, data []byte) error {
if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
// Fast path with no allocations and single frame.
if err := c.prepWrite(messageType); err != nil {
var mw messageWriter
if err := c.beginMessage(&mw, messageType); err != nil {
return err
}
mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
n := copy(c.writeBuf[mw.pos:], data)
mw.pos += n
data = data[n:]
@ -792,7 +803,7 @@ func (c *Conn) advanceFrame() (int, error) {
final := p[0]&finalBit != 0
frameType := int(p[0] & 0xf)
mask := p[1]&maskBit != 0
c.readRemaining = int64(p[1] & 0x7f)
c.setReadRemaining(int64(p[1] & 0x7f))
c.readDecompress = false
if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 {
@ -826,7 +837,17 @@ func (c *Conn) advanceFrame() (int, error) {
return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType))
}
// 3. Read and parse frame length.
// 3. Read and parse frame length as per
// https://tools.ietf.org/html/rfc6455#section-5.2
//
// The length of the "Payload data", in bytes: if 0-125, that is the payload
// length.
// - If 126, the following 2 bytes interpreted as a 16-bit unsigned
// integer are the payload length.
// - If 127, the following 8 bytes interpreted as
// a 64-bit unsigned integer (the most significant bit MUST be 0) are the
// payload length. Multibyte length quantities are expressed in network byte
// order.
switch c.readRemaining {
case 126:
@ -834,13 +855,19 @@ func (c *Conn) advanceFrame() (int, error) {
if err != nil {
return noFrame, err
}
c.readRemaining = int64(binary.BigEndian.Uint16(p))
if err := c.setReadRemaining(int64(binary.BigEndian.Uint16(p))); err != nil {
return noFrame, err
}
case 127:
p, err := c.read(8)
if err != nil {
return noFrame, err
}
c.readRemaining = int64(binary.BigEndian.Uint64(p))
if err := c.setReadRemaining(int64(binary.BigEndian.Uint64(p))); err != nil {
return noFrame, err
}
}
// 4. Handle frame masking.
@ -863,6 +890,12 @@ func (c *Conn) advanceFrame() (int, error) {
if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage {
c.readLength += c.readRemaining
// Don't allow readLength to overflow in the presence of a large readRemaining
// counter.
if c.readLength < 0 {
return noFrame, ErrReadLimit
}
if c.readLimit > 0 && c.readLength > c.readLimit {
c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait))
return noFrame, ErrReadLimit
@ -876,7 +909,7 @@ func (c *Conn) advanceFrame() (int, error) {
var payload []byte
if c.readRemaining > 0 {
payload, err = c.read(int(c.readRemaining))
c.readRemaining = 0
c.setReadRemaining(0)
if err != nil {
return noFrame, err
}
@ -949,6 +982,7 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
c.readErr = hideTempErr(err)
break
}
if frameType == TextMessage || frameType == BinaryMessage {
c.messageReader = &messageReader{c}
c.reader = c.messageReader
@ -989,7 +1023,9 @@ func (r *messageReader) Read(b []byte) (int, error) {
if c.isServer {
c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
}
c.readRemaining -= int64(n)
rem := c.readRemaining
rem -= int64(n)
c.setReadRemaining(rem)
if c.readRemaining > 0 && c.readErr == io.EOF {
c.readErr = errUnexpectedEOF
}
@ -1041,7 +1077,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
// SetReadLimit sets the maximum size for a message read from the peer. If a
// SetReadLimit sets the maximum size in bytes for a message read from the peer. If a
// message exceeds the limit, the connection sends a close message to the peer
// and returns ErrReadLimit to the application.
func (c *Conn) SetReadLimit(limit int64) {

View File

@ -151,6 +151,53 @@
// checking. The application is responsible for checking the Origin header
// before calling the Upgrade function.
//
// Buffers
//
// Connections buffer network input and output to reduce the number
// of system calls when reading or writing messages.
//
// Write buffers are also used for constructing WebSocket frames. See RFC 6455,
// Section 5 for a discussion of message framing. A WebSocket frame header is
// written to the network each time a write buffer is flushed to the network.
// Decreasing the size of the write buffer can increase the amount of framing
// overhead on the connection.
//
// The buffer sizes in bytes are specified by the ReadBufferSize and
// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default
// size of 4096 when a buffer size field is set to zero. The Upgrader reuses
// buffers created by the HTTP server when a buffer size field is set to zero.
// The HTTP server buffers have a size of 4096 at the time of this writing.
//
// The buffer sizes do not limit the size of a message that can be read or
// written by a connection.
//
// Buffers are held for the lifetime of the connection by default. If the
// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the
// write buffer only when writing a message.
//
// Applications should tune the buffer sizes to balance memory use and
// performance. Increasing the buffer size uses more memory, but can reduce the
// number of system calls to read or write the network. In the case of writing,
// increasing the buffer size can reduce the number of frame headers written to
// the network.
//
// Some guidelines for setting buffer parameters are:
//
// Limit the buffer sizes to the maximum expected message size. Buffers larger
// than the largest message do not provide any benefit.
//
// Depending on the distribution of message sizes, setting the buffer size to
// to a value less than the maximum expected message size can greatly reduce
// memory use with a small impact on performance. Here's an example: If 99% of
// the messages are smaller than 256 bytes and the maximum message size is 512
// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls
// than a buffer size of 512 bytes. The memory savings is 50%.
//
// A write buffer pool is useful when the application has a modest number
// writes over a large number of connections. when buffers are pooled, a larger
// buffer size has a reduced impact on total memory use and has the benefit of
// reducing system calls and frame overhead.
//
// Compression EXPERIMENTAL
//
// Per message compression extensions (RFC 7692) are experimentally supported

3
vendor/github.com/gorilla/websocket/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/gorilla/websocket
go 1.12

2
vendor/github.com/gorilla/websocket/go.sum generated vendored Normal file
View File

@ -0,0 +1,2 @@
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=

42
vendor/github.com/gorilla/websocket/join.go generated vendored Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"io"
"strings"
)
// JoinMessages concatenates received messages to create a single io.Reader.
// The string term is appended to each message. The returned reader does not
// support concurrent calls to the Read method.
func JoinMessages(c *Conn, term string) io.Reader {
return &joinReader{c: c, term: term}
}
type joinReader struct {
c *Conn
term string
r io.Reader
}
func (r *joinReader) Read(p []byte) (int, error) {
if r.r == nil {
var err error
_, r.r, err = r.c.NextReader()
if err != nil {
return 0, err
}
if r.term != "" {
r.r = io.MultiReader(r.r, strings.NewReader(r.term))
}
}
n, err := r.r.Read(p)
if err == io.EOF {
err = nil
r.r = nil
}
return n, err
}

View File

@ -22,18 +22,18 @@ func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
func init() {
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil
return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil
})
}
type httpProxyDialer struct {
proxyURL *url.URL
fowardDial func(network, addr string) (net.Conn, error)
proxyURL *url.URL
forwardDial func(network, addr string) (net.Conn, error)
}
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
hostPort, _ := hostPortNoPort(hpd.proxyURL)
conn, err := hpd.fowardDial(network, hostPort)
conn, err := hpd.forwardDial(network, hostPort)
if err != nil {
return nil, err
}

View File

@ -27,7 +27,7 @@ type Upgrader struct {
// HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
// size is zero, then buffers allocated by the HTTP server are used. The
// I/O buffer sizes do not limit the size of the messages that can be sent
// or received.
@ -153,7 +153,7 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
challengeKey := r.Header.Get("Sec-Websocket-Key")
if challengeKey == "" {
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank")
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank")
}
subprotocol := u.selectSubprotocol(r, responseHeader)

View File

@ -31,68 +31,113 @@ func generateChallengeKey() (string, error) {
return base64.StdEncoding.EncodeToString(p), nil
}
// Octet types from RFC 2616.
var octetTypes [256]byte
const (
isTokenOctet = 1 << iota
isSpaceOctet
)
func init() {
// From RFC 2616
//
// OCTET = <any 8-bit sequence of data>
// CHAR = <any US-ASCII character (octets 0 - 127)>
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
// CR = <US-ASCII CR, carriage return (13)>
// LF = <US-ASCII LF, linefeed (10)>
// SP = <US-ASCII SP, space (32)>
// HT = <US-ASCII HT, horizontal-tab (9)>
// <"> = <US-ASCII double-quote mark (34)>
// CRLF = CR LF
// LWS = [CRLF] 1*( SP | HT )
// TEXT = <any OCTET except CTLs, but including LWS>
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
// token = 1*<any CHAR except CTLs or separators>
// qdtext = <any TEXT except <">>
for c := 0; c < 256; c++ {
var t byte
isCtl := c <= 31 || c == 127
isChar := 0 <= c && c <= 127
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
t |= isSpaceOctet
}
if isChar && !isCtl && !isSeparator {
t |= isTokenOctet
}
octetTypes[c] = t
}
// Token octets per RFC 2616.
var isTokenOctet = [256]bool{
'!': true,
'#': true,
'$': true,
'%': true,
'&': true,
'\'': true,
'*': true,
'+': true,
'-': true,
'.': true,
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
'7': true,
'8': true,
'9': true,
'A': true,
'B': true,
'C': true,
'D': true,
'E': true,
'F': true,
'G': true,
'H': true,
'I': true,
'J': true,
'K': true,
'L': true,
'M': true,
'N': true,
'O': true,
'P': true,
'Q': true,
'R': true,
'S': true,
'T': true,
'U': true,
'W': true,
'V': true,
'X': true,
'Y': true,
'Z': true,
'^': true,
'_': true,
'`': true,
'a': true,
'b': true,
'c': true,
'd': true,
'e': true,
'f': true,
'g': true,
'h': true,
'i': true,
'j': true,
'k': true,
'l': true,
'm': true,
'n': true,
'o': true,
'p': true,
'q': true,
'r': true,
's': true,
't': true,
'u': true,
'v': true,
'w': true,
'x': true,
'y': true,
'z': true,
'|': true,
'~': true,
}
// skipSpace returns a slice of the string s with all leading RFC 2616 linear
// whitespace removed.
func skipSpace(s string) (rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isSpaceOctet == 0 {
if b := s[i]; b != ' ' && b != '\t' {
break
}
}
return s[i:]
}
// nextToken returns the leading RFC 2616 token of s and the string following
// the token.
func nextToken(s string) (token, rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isTokenOctet == 0 {
if !isTokenOctet[s[i]] {
break
}
}
return s[:i], s[i:]
}
// nextTokenOrQuoted returns the leading token or quoted string per RFC 2616
// and the string following the token or quoted string.
func nextTokenOrQuoted(s string) (value string, rest string) {
if !strings.HasPrefix(s, "\"") {
return nextToken(s)
@ -128,7 +173,8 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
return "", ""
}
// equalASCIIFold returns true if s is equal to t with ASCII case folding.
// equalASCIIFold returns true if s is equal to t with ASCII case folding as
// defined in RFC 4790.
func equalASCIIFold(s, t string) bool {
for s != "" && t != "" {
sr, size := utf8.DecodeRuneInString(s)

View File

@ -1,3 +1,20 @@
### v0.6.0 - August 31, 2019
full differences can be viewed using `git log --oneline --decorate --color v0.5.0..v0.6.0`
thanks to everyone who has contributed since January!
#### Breaking Changes:
- Info struct has had fields removed related to deprecated functionality by slack.
- minor adjustments to some structs.
- some internal default values have changed, usually to be more inline with slack defaults or to correct inability to set a particular value. (Message Parse for example.)
##### Highlights:
- new slacktest package easy mocking for slack client. use, enjoy, please submit PRs for improvements and default behaviours! shamelessly taken from the [slack-test repo](https://github.com/lusis/slack-test) thank you lusis for letting us use it and bring it into the slack repo.
- blocks, blocks, blocks.
- RTM ManagedConnection has undergone a significant cleanup.
in particular handles backoffs gracefully, removed many deadlocks,
and Disconnect is now much more responsive.
### v0.5.0 - January 20, 2019
full differences can be viewed using `git log --oneline --decorate --color v0.4.0..v0.5.0`
- Breaking changes: various old struct fields have been removed or updated to match slack's api.

View File

@ -30,12 +30,22 @@ 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"`
Text TextBlockObject `json:"text"`
Value string `json:"value"`
Type actionType `json:"type"`
ActionTs string `json:"action_ts"`
ActionID string `json:"action_id"`
BlockID string `json:"block_id"`
Type actionType `json:"type"`
Text TextBlockObject `json:"text"`
Value string `json:"value"`
ActionTs string `json:"action_ts"`
SelectedOption OptionBlockObject `json:"selected_option"`
SelectedUser string `json:"selected_user"`
SelectedChannel string `json:"selected_channel"`
SelectedConversation string `json:"selected_conversation"`
SelectedDate string `json:"selected_date"`
InitialOption OptionBlockObject `json:"initial_option"`
InitialUser string `json:"initial_user"`
InitialChannel string `json:"initial_channel"`
InitialConversation string `json:"initial_conversation"`
InitialDate string `json:"initial_date"`
}
// actionType returns the type of the action

View File

@ -124,7 +124,7 @@ func (b *BlockElements) UnmarshalJSON(data []byte) error {
blockElement = &OverflowBlockElement{}
case "datepicker":
blockElement = &DatePickerBlockElement{}
case "static_select":
case "static_select", "external_select", "users_select", "conversations_select", "channels_select":
blockElement = &SelectBlockElement{}
default:
return errors.New("unsupported block element type")

View File

@ -139,13 +139,17 @@ func NewButtonBlockElement(actionID, value string, text *TextBlockObject) *Butto
//
// 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"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
Type string `json:"type,omitempty"`
Placeholder *TextBlockObject `json:"placeholder,omitempty"`
ActionID string `json:"action_id,omitempty"`
Options []*OptionBlockObject `json:"options,omitempty"`
OptionGroups []*OptionGroupBlockObject `json:"option_groups,omitempty"`
InitialOption *OptionBlockObject `json:"initial_option,omitempty"`
InitialUser string `json:"initial_user,omitempty"`
InitialConversation string `json:"initial_conversation,omitempty"`
InitialChannel string `json:"initial_channel,omitempty"`
MinQueryLength int `json:"min_query_length,omitempty"`
Confirm *ConfirmationBlockObject `json:"confirm,omitempty"`
}
// ElementType returns the type of the Element

View File

@ -178,6 +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"`
}
// NewOptionBlockObject returns an instance of a new Option Block Element
@ -197,7 +198,7 @@ func (s OptionBlockObject) validateType() MessageObjectType {
//
// More Information: https://api.slack.com/reference/messaging/composition-objects#option-group
type OptionGroupBlockObject struct {
Label *TextBlockObject `json:"label"`
Label *TextBlockObject `json:"label,omitempty"`
Options []*OptionBlockObject `json:"options"`
}

View File

@ -16,12 +16,27 @@ 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) *SectionBlock {
return &SectionBlock{
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
}

View File

@ -41,7 +41,10 @@ func (api *Client) GetBotInfo(bot string) (*Bot, error) {
func (api *Client) GetBotInfoContext(ctx context.Context, bot string) (*Bot, error) {
values := url.Values{
"token": {api.token},
"bot": {bot},
}
if bot != "" {
values.Add("bot", bot)
}
response, err := api.botRequest(ctx, "bots.info", values)

View File

@ -32,11 +32,7 @@ func (api *Client) channelRequest(ctx context.Context, path string, values url.V
return nil, err
}
if err := response.Err(); err != nil {
return nil, err
}
return response, nil
return response, response.Err()
}
type channelsConfig struct {
@ -284,6 +280,7 @@ func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool,
"token": {api.token},
},
}
if excludeArchived {
options = append(options, GetChannelsOptionExcludeArchived())
}

View File

@ -3,6 +3,7 @@ package slack
import (
"context"
"encoding/json"
"net/http"
"net/url"
"github.com/nlopes/slack/slackutilsx"
@ -25,7 +26,7 @@ const (
type chatResponseFull struct {
Channel string `json:"channel"`
Timestamp string `json:"ts"` //Regualr message timestamp
Timestamp string `json:"ts"` //Regular message timestamp
MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp
Text string `json:"text"`
SlackResponse
@ -156,17 +157,18 @@ func (api *Client) SendMessage(channel string, options ...MsgOption) (string, st
}
// SendMessageContext more flexible method for configuring messages with a custom context.
func (api *Client) SendMessageContext(ctx context.Context, channelID string, options ...MsgOption) (channel string, timestamp string, text string, err error) {
func (api *Client) SendMessageContext(ctx context.Context, channelID string, options ...MsgOption) (_channel string, _timestamp string, _text string, err error) {
var (
config sendConfig
req *http.Request
parser func(*chatResponseFull) responseParser
response chatResponseFull
)
if config, err = applyMsgOptions(api.token, channelID, api.endpoint, options...); err != nil {
if req, parser, err = buildSender(api.endpoint, options...).BuildRequest(api.token, channelID); err != nil {
return "", "", "", err
}
if err = postForm(ctx, api.httpclient, config.endpoint, config.values, &response, api); err != nil {
if err = doPost(ctx, api.httpclient, req, parser(&response), api); err != nil {
return "", "", "", err
}
@ -200,6 +202,13 @@ func applyMsgOptions(token, channel, apiurl string, options ...MsgOption) (sendC
return config, nil
}
func buildSender(apiurl string, options ...MsgOption) sendConfig {
return sendConfig{
apiurl: apiurl,
options: options,
}
}
type sendMode string
const (
@ -207,16 +216,70 @@ const (
chatPostMessage sendMode = "chat.postMessage"
chatDelete sendMode = "chat.delete"
chatPostEphemeral sendMode = "chat.postEphemeral"
chatResponse sendMode = "chat.responseURL"
chatMeMessage sendMode = "chat.meMessage"
chatUnfurl sendMode = "chat.unfurl"
)
type sendConfig struct {
apiurl string
apiurl string
options []MsgOption
mode sendMode
endpoint string
values url.Values
attachments []Attachment
responseType string
}
func (t sendConfig) BuildRequest(token, channelID string) (req *http.Request, _ func(*chatResponseFull) responseParser, err error) {
if t, err = applyMsgOptions(token, channelID, t.apiurl, t.options...); err != nil {
return nil, nil, err
}
switch t.mode {
case chatResponse:
return responseURLSender{
endpoint: t.endpoint,
values: t.values,
attachments: t.attachments,
responseType: t.responseType,
}.BuildRequest()
default:
return formSender{endpoint: t.endpoint, values: t.values}.BuildRequest()
}
}
type formSender struct {
endpoint string
values url.Values
}
func (t formSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) {
req, err := formReq(t.endpoint, t.values)
return req, func(resp *chatResponseFull) responseParser {
return newJSONParser(resp)
}, err
}
type responseURLSender struct {
endpoint string
values url.Values
attachments []Attachment
responseType string
}
func (t responseURLSender) BuildRequest() (*http.Request, func(*chatResponseFull) responseParser, error) {
req, err := jsonReq(t.endpoint, Msg{
Text: t.values.Get("text"),
Timestamp: t.values.Get("ts"),
Attachments: t.attachments,
ResponseType: t.responseType,
})
return req, func(resp *chatResponseFull) responseParser {
return newContentTypeParser(resp)
}, err
}
// MsgOption option provided when sending a message.
type MsgOption func(*sendConfig) error
@ -279,6 +342,17 @@ func MsgOptionUnfurl(timestamp string, unfurls map[string]Attachment) MsgOption
}
}
// MsgOptionResponseURL supplies a url to use as the endpoint.
func MsgOptionResponseURL(url string, rt string) MsgOption {
return func(config *sendConfig) error {
config.mode = chatResponse
config.endpoint = url
config.responseType = rt
config.values.Del("ts")
return nil
}
}
// MsgOptionAsUser whether or not to send the message as the user.
func MsgOptionAsUser(b bool) MsgOption {
return func(config *sendConfig) error {
@ -324,10 +398,17 @@ func MsgOptionAttachments(attachments ...Attachment) MsgOption {
return nil
}
attachments, err := json.Marshal(attachments)
config.attachments = attachments
// FIXME: We are setting the attachments on the message twice: above for
// the json version, and below for the html version. The marshalled bytes
// we put into config.values below don't work directly in the Msg version.
attachmentBytes, err := json.Marshal(attachments)
if err == nil {
config.values.Set("attachments", string(attachments))
config.values.Set("attachments", string(attachmentBytes))
}
return err
}
}

View File

@ -99,6 +99,7 @@ func (api *Client) GetUsersInConversationContext(ctx context.Context, params *Ge
ResponseMetaData responseMetaData `json:"response_metadata"`
SlackResponse
}{}
err := api.postMethod(ctx, "conversations.members", values, &response)
if err != nil {
return nil, "", err
@ -160,6 +161,7 @@ func (api *Client) ArchiveConversationContext(ctx context.Context, channelID str
"token": {api.token},
"channel": {channelID},
}
response := SlackResponse{}
err := api.postMethod(ctx, "conversations.archive", values, &response)
if err != nil {
@ -229,6 +231,7 @@ func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelI
SlackResponse
Channel *Channel `json:"channel"`
}{}
err := api.postMethod(ctx, "conversations.setPurpose", values, &response)
if err != nil {
return nil, err
@ -253,6 +256,7 @@ func (api *Client) RenameConversationContext(ctx context.Context, channelID, cha
SlackResponse
Channel *Channel `json:"channel"`
}{}
err := api.postMethod(ctx, "conversations.rename", values, &response)
if err != nil {
return nil, err
@ -277,6 +281,7 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel
SlackResponse
Channel *Channel `json:"channel"`
}{}
err := api.postMethod(ctx, "conversations.invite", values, &response)
if err != nil {
return nil, err
@ -297,6 +302,7 @@ func (api *Client) KickUserFromConversationContext(ctx context.Context, channelI
"channel": {channelID},
"user": {user},
}
response := SlackResponse{}
err := api.postMethod(ctx, "conversations.kick", values, &response)
if err != nil {
@ -479,6 +485,7 @@ func (api *Client) GetConversationsContext(ctx context.Context, params *GetConve
ResponseMetaData responseMetaData `json:"response_metadata"`
SlackResponse
}{}
err = api.postMethod(ctx, "conversations.list", values, &response)
if err != nil {
return nil, "", err
@ -516,6 +523,7 @@ func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConv
AlreadyOpen bool `json:"already_open"`
SlackResponse
}{}
err := api.postMethod(ctx, "conversations.open", values, &response)
if err != nil {
return nil, false, false, err
@ -540,6 +548,7 @@ func (api *Client) JoinConversationContext(ctx context.Context, channelID string
} `json:"response_metadata"`
SlackResponse
}{}
err := api.postMethod(ctx, "conversations.join", values, &response)
if err != nil {
return nil, "", nil, err

View File

@ -21,7 +21,7 @@ type DialogInputSelect struct {
DialogInput
Value string `json:"value,omitempty"` //Optional.
DataSource SelectDataSource `json:"data_source,omitempty"` //Optional. Allowed values: "users", "channels", "conversations", "external".
SelectedOptions string `json:"selected_options,omitempty"` //Optional. Default value for "external" only
SelectedOptions []DialogSelectOption `json:"selected_options,omitempty"` //Optional. May hold at most one element, for use with "external" only.
Options []DialogSelectOption `json:"options,omitempty"` //One of options or option_groups is required.
OptionGroups []DialogOptionGroup `json:"option_groups,omitempty"` //Provide up to 100 options.
MinQueryLength int `json:"min_query_length,omitempty"` //Optional. minimum characters before query is sent.

View File

@ -3,6 +3,9 @@ package slack
// TextInputSubtype Accepts email, number, tel, or url. In some form factors, optimized input is provided for this subtype.
type TextInputSubtype string
// TextInputOption handle to extra inputs options.
type TextInputOption func(*TextInputElement)
const (
// InputSubtypeEmail email keyboard
InputSubtypeEmail TextInputSubtype = "email"
@ -26,8 +29,8 @@ type TextInputElement struct {
}
// NewTextInput constructor for a `text` input
func NewTextInput(name, label, text string) *TextInputElement {
return &TextInputElement{
func NewTextInput(name, label, text string, options ...TextInputOption) *TextInputElement {
t := &TextInputElement{
DialogInput: DialogInput{
Type: InputTypeText,
Name: name,
@ -35,6 +38,12 @@ func NewTextInput(name, label, text string) *TextInputElement {
},
Value: text,
}
for _, opt := range options {
opt(t)
}
return t
}
// NewTextAreaInput constructor for a `textarea` input

View File

@ -90,7 +90,8 @@ type File struct {
}
type Share struct {
Public map[string][]ShareFileInfo `json:"public"`
Public map[string][]ShareFileInfo `json:"public"`
Private map[string][]ShareFileInfo `json:"private"`
}
type ShareFileInfo struct {
@ -133,11 +134,21 @@ type GetFilesParameters struct {
Page int
}
// ListFilesParameters contains all the parameters necessary (including the optional ones) for a ListFiles() request
type ListFilesParameters struct {
Limit int
User string
Channel string
Types string
Cursor string
}
type fileResponseFull struct {
File `json:"file"`
Paging `json:"paging"`
Comments []Comment `json:"comments"`
Files []File `json:"files"`
Comments []Comment `json:"comments"`
Files []File `json:"files"`
Metadata ResponseMetadata `json:"response_metadata"`
SlackResponse
}
@ -196,6 +207,40 @@ func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error)
return api.GetFilesContext(context.Background(), params)
}
// ListFiles retrieves all files according to the parameters given. Uses cursor based pagination.
func (api *Client) ListFiles(params ListFilesParameters) ([]File, *ListFilesParameters, error) {
return api.ListFilesContext(context.Background(), params)
}
// ListFilesContext retrieves all files according to the parameters given with a custom context. Uses cursor based pagination.
func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParameters) ([]File, *ListFilesParameters, error) {
values := url.Values{
"token": {api.token},
}
if params.User != DEFAULT_FILES_USER {
values.Add("user", params.User)
}
if params.Channel != DEFAULT_FILES_CHANNEL {
values.Add("channel", params.Channel)
}
if params.Limit != DEFAULT_FILES_COUNT {
values.Add("limit", strconv.Itoa(params.Limit))
}
if params.Cursor != "" {
values.Add("cursor", params.Cursor)
}
response, err := api.fileRequest(ctx, "files.list", values)
if err != nil {
return nil, nil, err
}
params.Cursor = response.Metadata.Cursor
return response.Files, &params, nil
}
// GetFilesContext retrieves all files according to the parameters given with a custom context
func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) {
values := url.Values{
@ -243,9 +288,6 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
if err != nil {
return nil, err
}
if params.Filename == "" {
return nil, fmt.Errorf("files.upload: FileUploadParameters.Filename is mandatory")
}
response := &fileResponseFull{}
values := url.Values{
"token": {api.token},
@ -274,8 +316,12 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
} else if params.File != "" {
err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.File, "file", values, response, api)
} else if params.Reader != nil {
if params.Filename == "" {
return nil, fmt.Errorf("files.upload: FileUploadParameters.Filename is mandatory when using FileUploadParameters.Reader")
}
err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.Filename, "file", values, params.Reader, response, api)
}
if err != nil {
return nil, err
}

View File

@ -156,17 +156,12 @@ type Icons struct {
Image72 string `json:"image_72,omitempty"`
}
// Info contains various details about Users, Channels, Bots and the authenticated user.
// Info contains various details about the authenticated user and team.
// It is returned by StartRTM or included in the "ConnectedEvent" RTM event.
type Info struct {
URL string `json:"url,omitempty"`
User *UserDetails `json:"self,omitempty"`
Team *Team `json:"team,omitempty"`
Users []User `json:"users,omitempty"`
Channels []Channel `json:"channels,omitempty"`
Groups []Group `json:"groups,omitempty"`
Bots []Bot `json:"bots,omitempty"`
IMs []IM `json:"ims,omitempty"`
URL string `json:"url,omitempty"`
User *UserDetails `json:"self,omitempty"`
Team *Team `json:"team,omitempty"`
}
type infoResponseFull struct {
@ -174,52 +169,27 @@ type infoResponseFull struct {
SlackResponse
}
// GetBotByID returns a bot given a bot id
// GetBotByID is deprecated and returns nil
func (info Info) GetBotByID(botID string) *Bot {
for _, bot := range info.Bots {
if bot.ID == botID {
return &bot
}
}
return nil
}
// GetUserByID returns a user given a user id
// GetUserByID is deprecated and returns nil
func (info Info) GetUserByID(userID string) *User {
for _, user := range info.Users {
if user.ID == userID {
return &user
}
}
return nil
}
// GetChannelByID returns a channel given a channel id
// GetChannelByID is deprecated and returns nil
func (info Info) GetChannelByID(channelID string) *Channel {
for _, channel := range info.Channels {
if channel.ID == channelID {
return &channel
}
}
return nil
}
// GetGroupByID returns a group given a group id
// GetGroupByID is deprecated and returns nil
func (info Info) GetGroupByID(groupID string) *Group {
for _, group := range info.Groups {
if group.ID == groupID {
return &group
}
}
return nil
}
// GetIMByID returns an IM given an IM id
// GetIMByID is deprecated and returns nil
func (info Info) GetIMByID(imID string) *IM {
for _, im := range info.IMs {
if im.ID == imID {
return &im
}
}
return nil
}

View File

@ -22,6 +22,7 @@ const (
InteractionTypeDialogSuggestion = InteractionType("dialog_suggestion")
InteractionTypeInteractionMessage = InteractionType("interactive_message")
InteractionTypeMessageAction = InteractionType("message_action")
InteractionTypeBlockActions = InteractionType("block_actions")
)
// InteractionCallback is sent from slack when a user interactions with a button or dialog.

View File

@ -98,6 +98,13 @@ type Msg struct {
Blocks Blocks `json:"blocks,omitempty"`
}
const (
// ResponseTypeInChannel in channel response for slash commands.
ResponseTypeInChannel = "in_channel"
// ResponseTypeEphemeral ephemeral respone for slash commands.
ResponseTypeEphemeral = "ephemeral"
)
// Icon is used for bot messages
type Icon struct {
IconURL string `json:"icon_url,omitempty"`

View File

@ -8,6 +8,7 @@ import (
"fmt"
"io"
"io/ioutil"
"mime"
"mime/multipart"
"net/http"
"net/http/httputil"
@ -80,8 +81,8 @@ func fileUploadReq(ctx context.Context, path string, values url.Values, r io.Rea
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
req = req.WithContext(ctx)
req.URL.RawQuery = (values).Encode()
return req, nil
}
@ -117,6 +118,29 @@ func downloadFile(client httpClient, token string, downloadURL string, writer io
return err
}
func formReq(endpoint string, values url.Values) (req *http.Request, err error) {
if req, err = http.NewRequest("POST", endpoint, strings.NewReader(values.Encode())); err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return req, nil
}
func jsonReq(endpoint string, body interface{}) (req *http.Request, err error) {
buffer := bytes.NewBuffer([]byte{})
if err = json.NewEncoder(buffer).Encode(body); err != nil {
return nil, err
}
if req, err = http.NewRequest("POST", endpoint, buffer); err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
return req, nil
}
func parseResponseBody(body io.ReadCloser, intf interface{}, d debug) error {
response, err := ioutil.ReadAll(body)
if err != nil {
@ -130,7 +154,7 @@ func parseResponseBody(body io.ReadCloser, intf interface{}, d debug) error {
return json.Unmarshal(response, intf)
}
func postLocalWithMultipartResponse(ctx context.Context, client httpClient, path, fpath, fieldname string, values url.Values, intf interface{}, d debug) error {
func postLocalWithMultipartResponse(ctx context.Context, client httpClient, method, fpath, fieldname string, values url.Values, intf interface{}, d debug) error {
fullpath, err := filepath.Abs(fpath)
if err != nil {
return err
@ -140,7 +164,8 @@ func postLocalWithMultipartResponse(ctx context.Context, client httpClient, path
return err
}
defer file.Close()
return postWithMultipartResponse(ctx, client, path, filepath.Base(fpath), fieldname, values, file, intf, d)
return postWithMultipartResponse(ctx, client, method, filepath.Base(fpath), fieldname, values, file, intf, d)
}
func postWithMultipartResponse(ctx context.Context, client httpClient, path, name, fieldname string, values url.Values, r io.Reader, intf interface{}, d debug) error {
@ -186,11 +211,11 @@ func postWithMultipartResponse(ctx context.Context, client httpClient, path, nam
case err = <-errc:
return err
default:
return parseResponseBody(resp.Body, intf, d)
return newJSONParser(intf)(resp)
}
}
func doPost(ctx context.Context, client httpClient, req *http.Request, intf interface{}, d debug) error {
func doPost(ctx context.Context, client httpClient, req *http.Request, parser responseParser, d debug) error {
req = req.WithContext(ctx)
resp, err := client.Do(req)
if err != nil {
@ -203,7 +228,7 @@ func doPost(ctx context.Context, client httpClient, req *http.Request, intf inte
return err
}
return parseResponseBody(resp.Body, intf, d)
return parser(resp)
}
// post JSON.
@ -215,7 +240,8 @@ func postJSON(ctx context.Context, client httpClient, endpoint, token string, js
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
return doPost(ctx, client, req, intf, d)
return doPost(ctx, client, req, newJSONParser(intf), d)
}
// post a url encoded form.
@ -226,7 +252,7 @@ func postForm(ctx context.Context, client httpClient, endpoint string, values ur
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return doPost(ctx, client, req, intf, d)
return doPost(ctx, client, req, newJSONParser(intf), d)
}
func getResource(ctx context.Context, client httpClient, endpoint string, values url.Values, intf interface{}, d debug) error {
@ -237,7 +263,7 @@ func getResource(ctx context.Context, client httpClient, endpoint string, values
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.URL.RawQuery = values.Encode()
return doPost(ctx, client, req, intf, d)
return doPost(ctx, client, req, newJSONParser(intf), d)
}
func parseAdminResponse(ctx context.Context, client httpClient, method string, teamName string, values url.Values, intf interface{}, d debug) error {
@ -290,3 +316,45 @@ func checkStatusCode(resp *http.Response, d debug) error {
return nil
}
type responseParser func(*http.Response) error
func newJSONParser(dst interface{}) responseParser {
return func(resp *http.Response) error {
return json.NewDecoder(resp.Body).Decode(dst)
}
}
func newTextParser(dst interface{}) responseParser {
return func(resp *http.Response) error {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if !bytes.Equal(b, []byte("ok")) {
return errors.New(string(b))
}
return nil
}
}
func newContentTypeParser(dst interface{}) responseParser {
return func(req *http.Response) (err error) {
var (
ctype string
)
if ctype, _, err = mime.ParseMediaType(req.Header.Get("Content-Type")); err != nil {
return err
}
switch ctype {
case "application/json":
return newJSONParser(dst)(req)
default:
return newTextParser(dst)(req)
}
}
}

View File

@ -41,6 +41,7 @@ type SearchMessage struct {
User string `json:"user"`
Username string `json:"username"`
Timestamp string `json:"ts"`
Blocks Blocks `json:"blocks,omitempty"`
Text string `json:"text"`
Permalink string `json:"permalink"`
Attachments []Attachment `json:"attachments"`

View File

@ -51,6 +51,8 @@ type authTestResponseFull struct {
}
// Client for the slack api.
type ParamOption func(*url.Values)
type Client struct {
token string
endpoint string

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"net/url"
"strconv"
"time"
)
const (
@ -117,6 +118,7 @@ type User struct {
IsUltraRestricted bool `json:"is_ultra_restricted"`
IsStranger bool `json:"is_stranger"`
IsAppUser bool `json:"is_app_user"`
IsInvitedUser bool `json:"is_invited_user"`
Has2FA bool `json:"has_2fa"`
HasFiles bool `json:"has_files"`
Presence string `json:"presence"`
@ -345,12 +347,19 @@ func (api *Client) GetUsers() ([]User, error) {
// GetUsersContext returns the list of users (with their detailed information) with a custom context
func (api *Client) GetUsersContext(ctx context.Context) (results []User, err error) {
var (
p UserPagination
)
for p = api.GetUsersPaginated(); !p.Done(err); p, err = p.Next(ctx) {
results = append(results, p.Users...)
p := api.GetUsersPaginated()
for err == nil {
p, err = p.Next(ctx)
if err == nil {
results = append(results, p.Users...)
} else if rateLimitedError, ok := err.(*RateLimitedError); ok {
select {
case <-ctx.Done():
err = ctx.Err()
case <-time.After(rateLimitedError.RetryAfter):
err = nil
}
}
}
return results, p.Failure(err)
@ -411,13 +420,13 @@ func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) {
}
// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context
func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityResponse, error) {
func (api *Client) GetUserIdentityContext(ctx context.Context) (response *UserIdentityResponse, err error) {
values := url.Values{
"token": {api.token},
}
response := &UserIdentityResponse{}
response = &UserIdentityResponse{}
err := api.postMethod(ctx, "users.identity", values, response)
err = api.postMethod(ctx, "users.identity", values, response)
if err != nil {
return nil, err
}
@ -435,7 +444,7 @@ func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error {
}
// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) error {
func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) (err error) {
response := &SlackResponse{}
values := url.Values{
"token": {api.token},
@ -450,7 +459,7 @@ func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params
values.Add("crop_w", strconv.Itoa(params.CropW))
}
err := postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"users.setPhoto", image, "image", values, response, api)
err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"users.setPhoto", image, "image", values, response, api)
if err != nil {
return err
}
@ -464,13 +473,13 @@ func (api *Client) DeleteUserPhoto() error {
}
// DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context
func (api *Client) DeleteUserPhotoContext(ctx context.Context) error {
func (api *Client) DeleteUserPhotoContext(ctx context.Context) (err error) {
response := &SlackResponse{}
values := url.Values{
"token": {api.token},
}
err := api.postMethod(ctx, "users.deletePhoto", values, response)
err = api.postMethod(ctx, "users.deletePhoto", values, response)
if err != nil {
return err
}
@ -484,13 +493,27 @@ func (api *Client) DeleteUserPhotoContext(ctx context.Context) error {
// the Slack API will unset the custom status/emoji. If statusExpiration is set to 0
// the status will not expire.
func (api *Client) SetUserCustomStatus(statusText, statusEmoji string, statusExpiration int64) error {
return api.SetUserCustomStatusContext(context.Background(), statusText, statusEmoji, statusExpiration)
return api.SetUserCustomStatusContextWithUser(context.Background(), "", statusText, statusEmoji, statusExpiration)
}
// SetUserCustomStatusContext will set a custom status and emoji for the currently authenticated user with a custom context
//
// For more information see SetUserCustomStatus
func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, statusEmoji string, statusExpiration int64) error {
return api.SetUserCustomStatusContextWithUser(context.Background(), "", statusText, statusEmoji, statusExpiration)
}
// SetUserCustomStatusWithUser will set a custom status and emoji for the provided user.
//
// For more information see SetUserCustomStatus
func (api *Client) SetUserCustomStatusWithUser(user, statusText, statusEmoji string, statusExpiration int64) error {
return api.SetUserCustomStatusContextWithUser(context.Background(), user, statusText, statusEmoji, statusExpiration)
}
// SetUserCustomStatusContextWithUser will set a custom status and emoji for the provided user with a custom context
//
// For more information see SetUserCustomStatus
func (api *Client) SetUserCustomStatusContextWithUser(ctx context.Context, user, statusText, statusEmoji string, statusExpiration int64) error {
// XXX(theckman): this anonymous struct is for making requests to the Slack
// API for setting and unsetting a User's Custom Status/Emoji. To change
// these values we must provide a JSON document as the profile POST field.
@ -518,6 +541,7 @@ func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, s
}
values := url.Values{
"user": {user},
"token": {api.token},
"profile": {string(profile)},
}

View File

@ -20,13 +20,17 @@ type WebhookMessage struct {
}
func PostWebhook(url string, msg *WebhookMessage) error {
return PostWebhookCustomHTTP(url, http.DefaultClient, msg)
}
func PostWebhookCustomHTTP(url string, httpClient *http.Client, msg *WebhookMessage) error {
raw, err := json.Marshal(msg)
if err != nil {
return errors.Wrap(err, "marshal failed")
}
response, err := http.Post(url, "application/json", bytes.NewReader(raw))
response, err := httpClient.Post(url, "application/json", bytes.NewReader(raw))
if err != nil {
return errors.Wrap(err, "failed to post webhook")

View File

@ -78,9 +78,8 @@ func (rtm *RTM) Disconnect() error {
}
// GetInfo returns the info structure received when calling
// "startrtm", holding all channels, groups and other metadata needed
// to implement a full chat client. It will be non-nil after a call to
// StartRTM().
// "startrtm", holding metadata needed to implement a full
// chat client. It will be non-nil after a call to StartRTM().
func (rtm *RTM) GetInfo() *Info {
return rtm.info
}

View File

@ -35,6 +35,7 @@ type ConnectingEvent struct {
// DisconnectedEvent contains information about how we disconnected
type DisconnectedEvent struct {
Intentional bool
Cause error
}
// LatencyReport contains information about connection latency

View File

@ -10,6 +10,7 @@ import (
"time"
"github.com/gorilla/websocket"
"github.com/nlopes/slack/internal/errorsx"
"github.com/nlopes/slack/internal/timex"
)
@ -138,21 +139,22 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke
ErrorObj: err,
}}
// check if Disconnect() has been invoked.
// get time we should wait before attempting to connect again
rtm.Debugf("reconnection %d failed: %s reconnecting in %v\n", boff.attempts, err, backoff)
// wait for one of the following to occur,
// backoff duration has elapsed, killChannel is signalled, or
// the rtm finishes disconnecting.
select {
case <-time.After(backoff): // retry after the backoff.
case intentional := <-rtm.killChannel:
if intentional {
rtm.killConnection(intentional)
rtm.killConnection(intentional, ErrRTMDisconnected)
return nil, nil, ErrRTMDisconnected
}
case <-rtm.disconnected:
return nil, nil, ErrRTMDisconnected
default:
}
// get time we should wait before attempting to connect again
rtm.Debugf("reconnection %d failed: %s reconnecting in %v\n", boff.attempts, err, backoff)
time.Sleep(backoff)
}
}
@ -205,14 +207,14 @@ 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) (err error) {
func (rtm *RTM) killConnection(intentional bool, cause error) (err error) {
rtm.Debugln("killing connection")
if rtm.conn != nil {
err = rtm.conn.Close()
}
rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{intentional}}
rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{Intentional: intentional, Cause: cause}}
if intentional {
rtm.disconnect()
@ -233,22 +235,21 @@ func (rtm *RTM) handleEvents() {
select {
// catch "stop" signal on channel close
case intentional := <-rtm.killChannel:
_ = rtm.killConnection(intentional)
_ = rtm.killConnection(intentional, errorsx.String("signaled"))
return
// detect when the connection is dead.
case <-rtm.pingDeadman.C:
rtm.Debugln("deadman switch trigger disconnecting")
_ = rtm.killConnection(false)
_ = rtm.killConnection(false, errorsx.String("deadman switch triggered"))
return
// send pings on ticker interval
case <-ticker.C:
if err := rtm.ping(); err != nil {
_ = rtm.killConnection(false)
_ = rtm.killConnection(false, err)
return
}
case <-rtm.forcePing:
if err := rtm.ping(); err != nil {
_ = rtm.killConnection(false)
_ = rtm.killConnection(false, err)
return
}
// listen for messages that need to be sent
@ -258,7 +259,7 @@ func (rtm *RTM) handleEvents() {
case rawEvent := <-rtm.rawEvents:
switch rtm.handleRawEvent(rawEvent) {
case rtmEventTypeGoodbye:
_ = rtm.killConnection(false)
_ = rtm.killConnection(false, errorsx.String("goodbye detected"))
return
default:
}
@ -310,7 +311,6 @@ func (rtm *RTM) sendOutgoingMessage(msg OutgoingMessage) {
Message: msg,
ErrorObj: err,
}}
// TODO force ping?
}
}

4
vendor/modules.txt vendored
View File

@ -2,7 +2,7 @@
github.com/0xAX/notificator
# github.com/erroneousboat/termui v0.0.0-20170923115141-80f245cdfa04
github.com/erroneousboat/termui
# github.com/gorilla/websocket v1.4.0
# github.com/gorilla/websocket v1.4.1
github.com/gorilla/websocket
# github.com/maruel/panicparse v1.1.1
github.com/maruel/panicparse/stack
@ -10,7 +10,7 @@ github.com/maruel/panicparse/stack
github.com/mattn/go-runewidth
# github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7
github.com/mitchellh/go-wordwrap
# github.com/nlopes/slack v0.5.1-0.20190515005541-e2954b1409b0
# github.com/nlopes/slack v0.6.0
github.com/nlopes/slack
github.com/nlopes/slack/internal/errorsx
github.com/nlopes/slack/internal/timex