Migrate to dep and update dependencies

Fixes #98
This commit is contained in:
erroneousboat 2018-03-23 11:16:06 +01:00
parent f30310efa6
commit 7d87ccef35
86 changed files with 4564 additions and 2180 deletions

56
Gopkg.lock generated Normal file
View File

@ -0,0 +1,56 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/erroneousboat/termui"
packages = ["."]
revision = "24acd523c756fd9728824cdfac66aad9d8982fb7"
version = "v2.2.0"
[[projects]]
name = "github.com/gorilla/websocket"
packages = ["."]
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
name = "github.com/maruel/panicparse"
packages = ["stack"]
revision = "ad661195ed0e88491e0f14be6613304e3b1141d6"
[[projects]]
name = "github.com/mattn/go-runewidth"
packages = ["."]
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
version = "v0.0.2"
[[projects]]
branch = "master"
name = "github.com/mitchellh/go-wordwrap"
packages = ["."]
revision = "ad45545899c7b13c020ea92b2072220eefad42b8"
[[projects]]
name = "github.com/nlopes/slack"
packages = ["."]
revision = "8ab4d0b364ef1e9af5d102531da20d5ec902b6c4"
version = "v0.2.0"
[[projects]]
branch = "master"
name = "github.com/nsf/termbox-go"
packages = ["."]
revision = "e2050e41c8847748ec5288741c0b19a8cb26d084"
[[projects]]
name = "github.com/renstrom/fuzzysearch"
packages = ["fuzzy"]
revision = "d4ca9dfccd55dc6b076f9880d49c35315922c1f4"
version = "v1.0.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "719f2440551009ce6f1e1a7f6e56db762acdadd9a0e05245ee427b7455e50233"
solver-name = "gps-cdcl"
solver-version = 1

50
Gopkg.toml Normal file
View File

@ -0,0 +1,50 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/erroneousboat/termui"
version = "2.2.0"
[[constraint]]
name = "github.com/mattn/go-runewidth"
version = "0.0.2"
[[constraint]]
name = "github.com/nlopes/slack"
version = "0.2.0"
[[constraint]]
branch = "master"
name = "github.com/nsf/termbox-go"
[[constraint]]
name = "github.com/renstrom/fuzzysearch"
version = "1.0.0"
[prune]
go-tests = true
unused-packages = true

26
vendor/github.com/erroneousboat/termui/.gitignore generated vendored Normal file
View File

@ -0,0 +1,26 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
.DS_Store
/vendor

6
vendor/github.com/erroneousboat/termui/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,6 @@
language: go
go:
- tip
script: go test -v ./

View File

@ -62,7 +62,7 @@ func (bc *BarChart) layout() {
}
//bc.max = bc.Data[0] // what if Data is nil? Sometimes when bar graph is nill it produces panic with panic: runtime error: index out of range
// Assign a negative value to get maxvalue auto-populates
// Asign a negative value to get maxvalue auto-populates
if bc.max == 0 {
bc.max = -1
}

View File

@ -126,7 +126,7 @@ func hookTermboxEvt() {
e := termbox.PollEvent()
for _, c := range sysEvtChs {
func(ch chan Event) {
go func(ch chan Event) {
ch <- crtTermboxEvt(e)
}(c)
}
@ -221,7 +221,6 @@ func findMatch(mux map[string]func(Event), path string) string {
return pattern
}
// Remove all existing defined Handlers from the map
func (es *EvtStream) ResetHandlers() {
for Path, _ := range es.Handlers {
@ -244,7 +243,7 @@ func (es *EvtStream) Loop() {
case "/sig/stoploop":
return
}
func(a Event) {
go func(a Event) {
es.RLock()
defer es.RUnlock()
if pattern := es.match(a.Path); pattern != "" {
@ -274,10 +273,6 @@ func Handle(path string, handler func(Event)) {
DefaultEvtStream.Handle(path, handler)
}
func ResetHandlers() {
DefaultEvtStream.ResetHandlers()
}
func Loop() {
DefaultEvtStream.Loop()
}
@ -314,7 +309,7 @@ func NewTimerCh(du time.Duration) chan Event {
return t
}
var DefaultHandler = func(e Event) {
var DefualtHandler = func(e Event) {
}
var usrEvtCh = make(chan Event)

View File

@ -48,7 +48,7 @@ func (r *Row) assignWidth(w int) {
accW := 0 // acc span and offset
calcW := make([]int, len(r.Cols)) // calculated width
calcOftX := make([]int, len(r.Cols)) // computed start position of x
calcOftX := make([]int, len(r.Cols)) // computated start position of x
for i, c := range r.Cols {
accW += c.Span + c.Offset
@ -266,7 +266,7 @@ func (g *Grid) Align() {
}
}
// Buffer implements Bufferer interface.
// Buffer implments Bufferer interface.
func (g Grid) Buffer() Buffer {
buf := NewBuffer()

View File

@ -100,7 +100,7 @@ func charWidth(ch rune) int {
var whiteSpaceRegex = regexp.MustCompile(`\s`)
// StringToAttribute converts text to a termui attribute. You may specify more
// StringToAttribute converts text to a termui attribute. You may specifiy more
// then one attribute like that: "BLACK, BOLD, ...". All whitespaces
// are ignored.
func StringToAttribute(text string) Attribute {

View File

@ -35,7 +35,7 @@ var braillePatterns = map[[2]int]rune{
var lSingleBraille = [4]rune{'\u2840', '⠄', '⠂', '⠁'}
var rSingleBraille = [4]rune{'\u2880', '⠠', '⠐', '⠈'}
// LineChart has two modes: braille(default) and dot. Using braille gives 2x capacity as dot mode,
// LineChart has two modes: braille(default) and dot. Using braille gives 2x capicity as dot mode,
// because one braille char can represent two data points.
/*
lc := termui.NewLineChart()
@ -87,7 +87,7 @@ func NewLineChart() *LineChart {
}
// one cell contains two data points
// so the capacity is 2x as dot-mode
// so the capicity is 2x as dot-mode
func (lc *LineChart) renderBraille() Buffer {
buf := NewBuffer()

View File

@ -14,7 +14,7 @@ import "strings"
strs := []string{
"[0] github.com/gizak/termui",
"[1] editbox.go",
"[2] interrupt.go",
"[2] iterrupt.go",
"[3] keyboard.go",
"[4] output.go",
"[5] random_out.go",

View File

@ -8,7 +8,7 @@ import (
"fmt"
)
// This is the implementation of multi-colored or stacked bar graph. This is different from default barGraph which is implemented in bar.go
// This is the implemetation of multi-colored or stacked bar graph. This is different from default barGraph which is implemented in bar.go
// Multi-Colored-BarChart creates multiple bars in a widget:
/*
bc := termui.NewMBarChart()
@ -72,7 +72,7 @@ func (bc *MBarChart) layout() {
}
bc.numStack = DataLen
//We need to know what is the minimum size of data array data[0] could have 10 elements data[1] could have only 5, so we plot only 5 bar graphs
//We need to know what is the mimimum size of data array data[0] could have 10 elements data[1] could have only 5, so we plot only 5 bar graphs
for i := 0; i < DataLen; i++ {
if bc.minDataLen > len(bc.Data[i]) {
@ -90,13 +90,13 @@ func (bc *MBarChart) layout() {
for i := 0; i < bc.numStack; i++ {
bc.dataNum[i] = make([][]rune, len(bc.Data[i]))
//For each stack of bar calculate the rune
//For each stack of bar calcualte the rune
for j := 0; j < LabelLen && i < bc.numBar; j++ {
n := bc.Data[i][j]
s := fmt.Sprint(n)
bc.dataNum[i][j] = trimStr2Runes(s, bc.BarWidth)
}
//If color is not defined by default then populate a color that is different from the previous bar
//If color is not defined by default then populate a color that is different from the prevous bar
if bc.BarColor[i] == ColorDefault && bc.NumColor[i] == ColorDefault {
if i == 0 {
bc.BarColor[i] = ColorBlack

View File

@ -35,7 +35,7 @@ func Init() error {
}
sysEvtChs = make([]chan Event, 0)
// go hookTermboxEvt()
go hookTermboxEvt()
renderJobs = make(chan []Bufferer)
//renderLock = new(sync.RWMutex)
@ -51,7 +51,7 @@ func Init() error {
DefaultEvtStream.Merge("timer", NewTimerCh(time.Second))
DefaultEvtStream.Merge("custom", usrEvtCh)
DefaultEvtStream.Handle("/", DefaultHandler)
DefaultEvtStream.Handle("/", DefualtHandler)
DefaultEvtStream.Handle("/sys/wnd/resize", func(e Event) {
w := e.Data.(EvtWnd)
Body.Width = w.Width

View File

@ -51,7 +51,7 @@ func NewSparkline() Sparkline {
LineColor: ThemeAttr("sparkline.line.fg")}
}
// NewSparklines return a new *Sparklines with given Sparkline(s), you can always add a new Sparkline later.
// NewSparklines return a new *Spaklines with given Sparkline(s), you can always add a new Sparkline later.
func NewSparklines(ss ...Sparkline) *Sparklines {
s := &Sparklines{Block: *NewBlock(), Lines: ss}
return s

25
vendor/github.com/gorilla/websocket/.gitignore generated vendored Normal file
View File

@ -0,0 +1,25 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
.idea/
*.iml

19
vendor/github.com/gorilla/websocket/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
language: go
sudo: false
matrix:
include:
- go: 1.4
- go: 1.5
- go: 1.6
- go: 1.7
- go: 1.8
- 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 ./...

8
vendor/github.com/gorilla/websocket/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,8 @@
# This is the official list of Gorilla WebSocket authors for copyright
# purposes.
#
# Please keep the list sorted.
Gary Burd <gary@beagledreams.com>
Joachim Bauch <mail@joachim-bauch.de>

22
vendor/github.com/gorilla/websocket/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
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.

64
vendor/github.com/gorilla/websocket/README.md generated vendored Normal file
View File

@ -0,0 +1,64 @@
# 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)
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
### Status
The Gorilla WebSocket package provides a complete and tested implementation of
the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The
package API is stable.
### Installation
go get github.com/gorilla/websocket
### 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
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
### Gorilla WebSocket compared with other packages
<table>
<tr>
<th></th>
<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th>
<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th>
</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>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>
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
<tr><td colspan="3">Other Features</tr></td>
<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
</table>
Notes:
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
2. The application can get the type of a received data message by implementing
a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal)
function.
3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries.
Read returns when the input buffer is full or a frame boundary is
encountered. Each call to Write sends a single frame message. The Gorilla
io.Reader and io.WriteCloser operate on a single WebSocket message.

392
vendor/github.com/gorilla/websocket/client.go generated vendored Normal file
View File

@ -0,0 +1,392 @@
// Copyright 2013 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 (
"bufio"
"bytes"
"crypto/tls"
"encoding/base64"
"errors"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"time"
)
// ErrBadHandshake is returned when the server response to opening handshake is
// invalid.
var ErrBadHandshake = errors.New("websocket: bad handshake")
var errInvalidCompression = errors.New("websocket: invalid compression negotiation")
// NewClient creates a new client connection using the given net connection.
// The URL u specifies the host and request URI. Use requestHeader to specify
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies
// (Cookie). Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etc.
//
// Deprecated: Use Dialer instead.
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
d := Dialer{
ReadBufferSize: readBufSize,
WriteBufferSize: writeBufSize,
NetDial: func(net, addr string) (net.Conn, error) {
return netConn, nil
},
}
return d.Dial(u.String(), requestHeader)
}
// A Dialer contains options for connecting to WebSocket server.
type Dialer struct {
// NetDial specifies the dial function for creating TCP connections. If
// NetDial is nil, net.Dial is used.
NetDial func(network, addr string) (net.Conn, error)
// Proxy specifies a function to return a proxy for a given
// Request. If the function returns a non-nil error, the
// request is aborted with the provided error.
// If Proxy is nil or returns a nil *URL, no proxy is used.
Proxy func(*http.Request) (*url.URL, error)
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
// If nil, the default configuration is used.
TLSClientConfig *tls.Config
// HandshakeTimeout specifies the duration for the handshake to complete.
HandshakeTimeout time.Duration
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. 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
// Subprotocols specifies the client's requested subprotocols.
Subprotocols []string
// EnableCompression specifies if the client should attempt to negotiate
// per message compression (RFC 7692). Setting this value to true does not
// guarantee that compression will be supported. Currently only "no context
// takeover" modes are supported.
EnableCompression bool
// Jar specifies the cookie jar.
// If Jar is nil, cookies are not sent in requests and ignored
// in responses.
Jar http.CookieJar
}
var errMalformedURL = errors.New("malformed ws or wss URL")
// parseURL parses the URL.
//
// This function is a replacement for the standard library url.Parse function.
// In Go 1.4 and earlier, url.Parse loses information from the path.
func parseURL(s string) (*url.URL, error) {
// From the RFC:
//
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
var u url.URL
switch {
case strings.HasPrefix(s, "ws://"):
u.Scheme = "ws"
s = s[len("ws://"):]
case strings.HasPrefix(s, "wss://"):
u.Scheme = "wss"
s = s[len("wss://"):]
default:
return nil, errMalformedURL
}
if i := strings.Index(s, "?"); i >= 0 {
u.RawQuery = s[i+1:]
s = s[:i]
}
if i := strings.Index(s, "/"); i >= 0 {
u.Opaque = s[i:]
s = s[:i]
} else {
u.Opaque = "/"
}
u.Host = s
if strings.Contains(u.Host, "@") {
// Don't bother parsing user information because user information is
// not allowed in websocket URIs.
return nil, errMalformedURL
}
return &u, nil
}
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
hostPort = u.Host
hostNoPort = u.Host
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
hostNoPort = hostNoPort[:i]
} else {
switch u.Scheme {
case "wss":
hostPort += ":443"
case "https":
hostPort += ":443"
default:
hostPort += ":80"
}
}
return hostPort, hostNoPort
}
// DefaultDialer is a dialer with all fields set to the default zero values.
var DefaultDialer = &Dialer{
Proxy: http.ProxyFromEnvironment,
}
// Dial creates a new client connection. Use requestHeader to specify the
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
// Use the response.Header to get the selected subprotocol
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etcetera. The response body may not contain the entire response and does not
// need to be closed by the application.
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
if d == nil {
d = &Dialer{
Proxy: http.ProxyFromEnvironment,
}
}
challengeKey, err := generateChallengeKey()
if err != nil {
return nil, nil, err
}
u, err := parseURL(urlStr)
if err != nil {
return nil, nil, err
}
switch u.Scheme {
case "ws":
u.Scheme = "http"
case "wss":
u.Scheme = "https"
default:
return nil, nil, errMalformedURL
}
if u.User != nil {
// User name and password are not allowed in websocket URIs.
return nil, nil, errMalformedURL
}
req := &http.Request{
Method: "GET",
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Host: u.Host,
}
// Set the cookies present in the cookie jar of the dialer
if d.Jar != nil {
for _, cookie := range d.Jar.Cookies(u) {
req.AddCookie(cookie)
}
}
// Set the request headers using the capitalization for names and values in
// RFC examples. Although the capitalization shouldn't matter, there are
// servers that depend on it. The Header.Set method is not used because the
// method canonicalizes the header names.
req.Header["Upgrade"] = []string{"websocket"}
req.Header["Connection"] = []string{"Upgrade"}
req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
req.Header["Sec-WebSocket-Version"] = []string{"13"}
if len(d.Subprotocols) > 0 {
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
}
for k, vs := range requestHeader {
switch {
case k == "Host":
if len(vs) > 0 {
req.Host = vs[0]
}
case k == "Upgrade" ||
k == "Connection" ||
k == "Sec-Websocket-Key" ||
k == "Sec-Websocket-Version" ||
k == "Sec-Websocket-Extensions" ||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
default:
req.Header[k] = vs
}
}
if d.EnableCompression {
req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover")
}
hostPort, hostNoPort := hostPortNoPort(u)
var proxyURL *url.URL
// Check wether the proxy method has been configured
if d.Proxy != nil {
proxyURL, err = d.Proxy(req)
}
if err != nil {
return nil, nil, err
}
var targetHostPort string
if proxyURL != nil {
targetHostPort, _ = hostPortNoPort(proxyURL)
} else {
targetHostPort = hostPort
}
var deadline time.Time
if d.HandshakeTimeout != 0 {
deadline = time.Now().Add(d.HandshakeTimeout)
}
netDial := d.NetDial
if netDial == nil {
netDialer := &net.Dialer{Deadline: deadline}
netDial = netDialer.Dial
}
netConn, err := netDial("tcp", targetHostPort)
if err != nil {
return nil, nil, err
}
defer func() {
if netConn != nil {
netConn.Close()
}
}()
if err := netConn.SetDeadline(deadline); err != nil {
return nil, nil, err
}
if proxyURL != nil {
connectHeader := make(http.Header)
if user := proxyURL.User; user != nil {
proxyUser := user.Username()
if proxyPassword, passwordSet := user.Password(); passwordSet {
credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword))
connectHeader.Set("Proxy-Authorization", "Basic "+credential)
}
}
connectReq := &http.Request{
Method: "CONNECT",
URL: &url.URL{Opaque: hostPort},
Host: hostPort,
Header: connectHeader,
}
connectReq.Write(netConn)
// Read response.
// Okay to use and discard buffered reader here, because
// TLS server will not speak until spoken to.
br := bufio.NewReader(netConn)
resp, err := http.ReadResponse(br, connectReq)
if err != nil {
return nil, nil, err
}
if resp.StatusCode != 200 {
f := strings.SplitN(resp.Status, " ", 2)
return nil, nil, errors.New(f[1])
}
}
if u.Scheme == "https" {
cfg := cloneTLSConfig(d.TLSClientConfig)
if cfg.ServerName == "" {
cfg.ServerName = hostNoPort
}
tlsConn := tls.Client(netConn, cfg)
netConn = tlsConn
if err := tlsConn.Handshake(); err != nil {
return nil, nil, err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return nil, nil, err
}
}
}
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
if err := req.Write(netConn); err != nil {
return nil, nil, err
}
resp, err := http.ReadResponse(conn.br, req)
if err != nil {
return nil, nil, err
}
if d.Jar != nil {
if rc := resp.Cookies(); len(rc) > 0 {
d.Jar.SetCookies(u, rc)
}
}
if resp.StatusCode != 101 ||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
// Before closing the network connection on return from this
// function, slurp up some of the response to aid application
// debugging.
buf := make([]byte, 1024)
n, _ := io.ReadFull(resp.Body, buf)
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
return nil, resp, ErrBadHandshake
}
for _, ext := range parseExtensions(resp.Header) {
if ext[""] != "permessage-deflate" {
continue
}
_, snct := ext["server_no_context_takeover"]
_, cnct := ext["client_no_context_takeover"]
if !snct || !cnct {
return nil, resp, errInvalidCompression
}
conn.newCompressionWriter = compressNoContextTakeover
conn.newDecompressionReader = decompressNoContextTakeover
break
}
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
netConn.SetDeadline(time.Time{})
netConn = nil // to avoid close in defer.
return conn, resp, nil
}

16
vendor/github.com/gorilla/websocket/client_clone.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
// Copyright 2013 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.
// +build go1.8
package websocket
import "crypto/tls"
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}

View File

@ -0,0 +1,38 @@
// Copyright 2013 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.
// +build !go1.8
package websocket
import "crypto/tls"
// cloneTLSConfig clones all public fields except the fields
// SessionTicketsDisabled and SessionTicketKey. This avoids copying the
// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a
// config in active use.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
}
}

148
vendor/github.com/gorilla/websocket/compression.go generated vendored Normal file
View File

@ -0,0 +1,148 @@
// Copyright 2017 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 (
"compress/flate"
"errors"
"io"
"strings"
"sync"
)
const (
minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6
maxCompressionLevel = flate.BestCompression
defaultCompressionLevel = 1
)
var (
flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool
flateReaderPool = sync.Pool{New: func() interface{} {
return flate.NewReader(nil)
}}
)
func decompressNoContextTakeover(r io.Reader) io.ReadCloser {
const tail =
// Add four bytes as specified in RFC
"\x00\x00\xff\xff" +
// Add final block to squelch unexpected EOF error from flate reader.
"\x01\x00\x00\xff\xff"
fr, _ := flateReaderPool.Get().(io.ReadCloser)
fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil)
return &flateReadWrapper{fr}
}
func isValidCompressionLevel(level int) bool {
return minCompressionLevel <= level && level <= maxCompressionLevel
}
func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser {
p := &flateWriterPools[level-minCompressionLevel]
tw := &truncWriter{w: w}
fw, _ := p.Get().(*flate.Writer)
if fw == nil {
fw, _ = flate.NewWriter(tw, level)
} else {
fw.Reset(tw)
}
return &flateWriteWrapper{fw: fw, tw: tw, p: p}
}
// truncWriter is an io.Writer that writes all but the last four bytes of the
// stream to another io.Writer.
type truncWriter struct {
w io.WriteCloser
n int
p [4]byte
}
func (w *truncWriter) Write(p []byte) (int, error) {
n := 0
// fill buffer first for simplicity.
if w.n < len(w.p) {
n = copy(w.p[w.n:], p)
p = p[n:]
w.n += n
if len(p) == 0 {
return n, nil
}
}
m := len(p)
if m > len(w.p) {
m = len(w.p)
}
if nn, err := w.w.Write(w.p[:m]); err != nil {
return n + nn, err
}
copy(w.p[:], w.p[m:])
copy(w.p[len(w.p)-m:], p[len(p)-m:])
nn, err := w.w.Write(p[:len(p)-m])
return n + nn, err
}
type flateWriteWrapper struct {
fw *flate.Writer
tw *truncWriter
p *sync.Pool
}
func (w *flateWriteWrapper) Write(p []byte) (int, error) {
if w.fw == nil {
return 0, errWriteClosed
}
return w.fw.Write(p)
}
func (w *flateWriteWrapper) Close() error {
if w.fw == nil {
return errWriteClosed
}
err1 := w.fw.Flush()
w.p.Put(w.fw)
w.fw = nil
if w.tw.p != [4]byte{0, 0, 0xff, 0xff} {
return errors.New("websocket: internal error, unexpected bytes at end of flate stream")
}
err2 := w.tw.w.Close()
if err1 != nil {
return err1
}
return err2
}
type flateReadWrapper struct {
fr io.ReadCloser
}
func (r *flateReadWrapper) Read(p []byte) (int, error) {
if r.fr == nil {
return 0, io.ErrClosedPipe
}
n, err := r.fr.Read(p)
if err == io.EOF {
// Preemptively place the reader back in the pool. This helps with
// scenarios where the application does not call NextReader() soon after
// this final read.
r.Close()
}
return n, err
}
func (r *flateReadWrapper) Close() error {
if r.fr == nil {
return io.ErrClosedPipe
}
err := r.fr.Close()
flateReaderPool.Put(r.fr)
r.fr = nil
return err
}

1149
vendor/github.com/gorilla/websocket/conn.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

18
vendor/github.com/gorilla/websocket/conn_read.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
// Copyright 2016 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.
// +build go1.5
package websocket
import "io"
func (c *Conn) read(n int) ([]byte, error) {
p, err := c.br.Peek(n)
if err == io.EOF {
err = errUnexpectedEOF
}
c.br.Discard(len(p))
return p, err
}

View File

@ -0,0 +1,21 @@
// Copyright 2016 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.
// +build !go1.5
package websocket
import "io"
func (c *Conn) read(n int) ([]byte, error) {
p, err := c.br.Peek(n)
if err == io.EOF {
err = errUnexpectedEOF
}
if len(p) > 0 {
// advance over the bytes just read
io.ReadFull(c.br, p)
}
return p, err
}

180
vendor/github.com/gorilla/websocket/doc.go generated vendored Normal file
View File

@ -0,0 +1,180 @@
// Copyright 2013 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 implements the WebSocket protocol defined in RFC 6455.
//
// Overview
//
// The Conn type represents a WebSocket connection. A server application uses
// the Upgrade function from an Upgrader object with a HTTP request handler
// to get a pointer to a Conn:
//
// var upgrader = websocket.Upgrader{
// ReadBufferSize: 1024,
// WriteBufferSize: 1024,
// }
//
// func handler(w http.ResponseWriter, r *http.Request) {
// conn, err := upgrader.Upgrade(w, r, nil)
// if err != nil {
// log.Println(err)
// return
// }
// ... Use conn to send and receive messages.
// }
//
// Call the connection's WriteMessage and ReadMessage methods to send and
// receive messages as a slice of bytes. This snippet of code shows how to echo
// messages using these methods:
//
// for {
// messageType, p, err := conn.ReadMessage()
// if err != nil {
// return
// }
// if err = conn.WriteMessage(messageType, p); err != nil {
// return err
// }
// }
//
// In above snippet of code, p is a []byte and messageType is an int with value
// websocket.BinaryMessage or websocket.TextMessage.
//
// An application can also send and receive messages using the io.WriteCloser
// and io.Reader interfaces. To send a message, call the connection NextWriter
// method to get an io.WriteCloser, write the message to the writer and close
// the writer when done. To receive a message, call the connection NextReader
// method to get an io.Reader and read until io.EOF is returned. This snippet
// shows how to echo messages using the NextWriter and NextReader methods:
//
// for {
// messageType, r, err := conn.NextReader()
// if err != nil {
// return
// }
// w, err := conn.NextWriter(messageType)
// if err != nil {
// return err
// }
// if _, err := io.Copy(w, r); err != nil {
// return err
// }
// if err := w.Close(); err != nil {
// return err
// }
// }
//
// Data Messages
//
// The WebSocket protocol distinguishes between text and binary data messages.
// Text messages are interpreted as UTF-8 encoded text. The interpretation of
// binary messages is left to the application.
//
// This package uses the TextMessage and BinaryMessage integer constants to
// identify the two data message types. The ReadMessage and NextReader methods
// return the type of the received message. The messageType argument to the
// WriteMessage and NextWriter methods specifies the type of a sent message.
//
// It is the application's responsibility to ensure that text messages are
// valid UTF-8 encoded text.
//
// Control Messages
//
// The WebSocket protocol defines three types of control messages: close, ping
// and pong. Call the connection WriteControl, WriteMessage or NextWriter
// methods to send a control message to the peer.
//
// Connections handle received close messages by sending a close message to the
// peer and returning a *CloseError from the the NextReader, ReadMessage or the
// message Read method.
//
// Connections handle received ping and pong messages by invoking callback
// functions set with SetPingHandler and SetPongHandler methods. The callback
// functions are called from the NextReader, ReadMessage and the message Read
// methods.
//
// The default ping handler sends a pong to the peer. The application's reading
// goroutine can block for a short time while the handler writes the pong data
// to the connection.
//
// The application must read the connection to process ping, pong and close
// messages sent from the peer. If the application is not otherwise interested
// in messages from the peer, then the application should start a goroutine to
// read and discard messages from the peer. A simple example is:
//
// func readLoop(c *websocket.Conn) {
// for {
// if _, _, err := c.NextReader(); err != nil {
// c.Close()
// break
// }
// }
// }
//
// Concurrency
//
// Connections support one concurrent reader and one concurrent writer.
//
// Applications are responsible for ensuring that no more than one goroutine
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and
// that no more than one goroutine calls the read methods (NextReader,
// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler)
// concurrently.
//
// The Close and WriteControl methods can be called concurrently with all other
// methods.
//
// Origin Considerations
//
// Web browsers allow Javascript applications to open a WebSocket connection to
// any host. It's up to the server to enforce an origin policy using the Origin
// request header sent by the browser.
//
// The Upgrader calls the function specified in the CheckOrigin field to check
// the origin. If the CheckOrigin function returns false, then the Upgrade
// method fails the WebSocket handshake with HTTP status 403.
//
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail
// the handshake if the Origin request header is present and not equal to the
// Host request header.
//
// An application can allow connections from any origin by specifying a
// function that always returns true:
//
// var upgrader = websocket.Upgrader{
// CheckOrigin: func(r *http.Request) bool { return true },
// }
//
// The deprecated Upgrade function does not enforce an origin policy. It's the
// application's responsibility to check the Origin header before calling
// Upgrade.
//
// Compression EXPERIMENTAL
//
// Per message compression extensions (RFC 7692) are experimentally supported
// by this package in a limited capacity. Setting the EnableCompression option
// to true in Dialer or Upgrader will attempt to negotiate per message deflate
// support.
//
// var upgrader = websocket.Upgrader{
// EnableCompression: true,
// }
//
// If compression was successfully negotiated with the connection's peer, any
// message received in compressed form will be automatically decompressed.
// All Read methods will return uncompressed bytes.
//
// Per message compression of messages written to a connection can be enabled
// or disabled by calling the corresponding Conn method:
//
// conn.EnableWriteCompression(false)
//
// Currently this package does not support compression with "context takeover".
// This means that messages must be compressed and decompressed in isolation,
// without retaining sliding window or dictionary state across messages. For
// more details refer to RFC 7692.
//
// Use of compression is experimental and may result in decreased performance.
package websocket

55
vendor/github.com/gorilla/websocket/json.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2013 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 (
"encoding/json"
"io"
)
// WriteJSON is deprecated, use c.WriteJSON instead.
func WriteJSON(c *Conn, v interface{}) error {
return c.WriteJSON(v)
}
// WriteJSON writes the JSON encoding of v to the connection.
//
// See the documentation for encoding/json Marshal for details about the
// conversion of Go values to JSON.
func (c *Conn) WriteJSON(v interface{}) error {
w, err := c.NextWriter(TextMessage)
if err != nil {
return err
}
err1 := json.NewEncoder(w).Encode(v)
err2 := w.Close()
if err1 != nil {
return err1
}
return err2
}
// ReadJSON is deprecated, use c.ReadJSON instead.
func ReadJSON(c *Conn, v interface{}) error {
return c.ReadJSON(v)
}
// ReadJSON reads the next JSON-encoded message from the connection and stores
// it in the value pointed to by v.
//
// See the documentation for the encoding/json Unmarshal function for details
// about the conversion of JSON to a Go value.
func (c *Conn) ReadJSON(v interface{}) error {
_, r, err := c.NextReader()
if err != nil {
return err
}
err = json.NewDecoder(r).Decode(v)
if err == io.EOF {
// One value is expected in the message.
err = io.ErrUnexpectedEOF
}
return err
}

55
vendor/github.com/gorilla/websocket/mask.go generated vendored Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2016 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.
// +build !appengine
package websocket
import "unsafe"
const wordSize = int(unsafe.Sizeof(uintptr(0)))
func maskBytes(key [4]byte, pos int, b []byte) int {
// Mask one byte at a time for small buffers.
if len(b) < 2*wordSize {
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}
// Mask one byte at a time to word boundary.
if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 {
n = wordSize - n
for i := range b[:n] {
b[i] ^= key[pos&3]
pos++
}
b = b[n:]
}
// Create aligned word size key.
var k [wordSize]byte
for i := range k {
k[i] = key[(pos+i)&3]
}
kw := *(*uintptr)(unsafe.Pointer(&k))
// Mask one word at a time.
n := (len(b) / wordSize) * wordSize
for i := 0; i < n; i += wordSize {
*(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw
}
// Mask one byte at a time for remaining bytes.
b = b[n:]
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}

15
vendor/github.com/gorilla/websocket/mask_safe.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
// Copyright 2016 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.
// +build appengine
package websocket
func maskBytes(key [4]byte, pos int, b []byte) int {
for i := range b {
b[i] ^= key[pos&3]
pos++
}
return pos & 3
}

103
vendor/github.com/gorilla/websocket/prepared.go generated vendored Normal file
View File

@ -0,0 +1,103 @@
// Copyright 2017 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 (
"bytes"
"net"
"sync"
"time"
)
// PreparedMessage caches on the wire representations of a message payload.
// Use PreparedMessage to efficiently send a message payload to multiple
// connections. PreparedMessage is especially useful when compression is used
// because the CPU and memory expensive compression operation can be executed
// once for a given set of compression options.
type PreparedMessage struct {
messageType int
data []byte
err error
mu sync.Mutex
frames map[prepareKey]*preparedFrame
}
// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage.
type prepareKey struct {
isServer bool
compress bool
compressionLevel int
}
// preparedFrame contains data in wire representation.
type preparedFrame struct {
once sync.Once
data []byte
}
// NewPreparedMessage returns an initialized PreparedMessage. You can then send
// it to connection using WritePreparedMessage method. Valid wire
// representation will be calculated lazily only once for a set of current
// connection options.
func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) {
pm := &PreparedMessage{
messageType: messageType,
frames: make(map[prepareKey]*preparedFrame),
data: data,
}
// Prepare a plain server frame.
_, frameData, err := pm.frame(prepareKey{isServer: true, compress: false})
if err != nil {
return nil, err
}
// To protect against caller modifying the data argument, remember the data
// copied to the plain server frame.
pm.data = frameData[len(frameData)-len(data):]
return pm, nil
}
func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
pm.mu.Lock()
frame, ok := pm.frames[key]
if !ok {
frame = &preparedFrame{}
pm.frames[key] = frame
}
pm.mu.Unlock()
var err error
frame.once.Do(func() {
// Prepare a frame using a 'fake' connection.
// TODO: Refactor code in conn.go to allow more direct construction of
// the frame.
mu := make(chan bool, 1)
mu <- true
var nc prepareConn
c := &Conn{
conn: &nc,
mu: mu,
isServer: key.isServer,
compressionLevel: key.compressionLevel,
enableWriteCompression: true,
writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize),
}
if key.compress {
c.newCompressionWriter = compressNoContextTakeover
}
err = c.WriteMessage(pm.messageType, pm.data)
frame.data = nc.buf.Bytes()
})
return pm.messageType, frame.data, err
}
type prepareConn struct {
buf bytes.Buffer
net.Conn
}
func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) }
func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil }

291
vendor/github.com/gorilla/websocket/server.go generated vendored Normal file
View File

@ -0,0 +1,291 @@
// Copyright 2013 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 (
"bufio"
"errors"
"net"
"net/http"
"net/url"
"strings"
"time"
)
// HandshakeError describes an error with the handshake from the peer.
type HandshakeError struct {
message string
}
func (e HandshakeError) Error() string { return e.message }
// Upgrader specifies parameters for upgrading an HTTP connection to a
// WebSocket connection.
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
// 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.
ReadBufferSize, WriteBufferSize int
// Subprotocols specifies the server's supported protocols in order of
// preference. If this field is set, then the Upgrade method negotiates a
// subprotocol by selecting the first match in this list with a protocol
// requested by the client.
Subprotocols []string
// Error specifies the function for generating HTTP error responses. If Error
// is nil, then http.Error is used to generate the HTTP response.
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
// CheckOrigin returns true if the request Origin header is acceptable. If
// CheckOrigin is nil, the host in the Origin header must not be set or
// must match the host of the request.
CheckOrigin func(r *http.Request) bool
// EnableCompression specify if the server should attempt to negotiate per
// message compression (RFC 7692). Setting this value to true does not
// guarantee that compression will be supported. Currently only "no context
// takeover" modes are supported.
EnableCompression bool
}
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) {
err := HandshakeError{reason}
if u.Error != nil {
u.Error(w, r, status, err)
} else {
w.Header().Set("Sec-Websocket-Version", "13")
http.Error(w, http.StatusText(status), status)
}
return nil, err
}
// checkSameOrigin returns true if the origin is not set or is equal to the request host.
func checkSameOrigin(r *http.Request) bool {
origin := r.Header["Origin"]
if len(origin) == 0 {
return true
}
u, err := url.Parse(origin[0])
if err != nil {
return false
}
return u.Host == r.Host
}
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
if u.Subprotocols != nil {
clientProtocols := Subprotocols(r)
for _, serverProtocol := range u.Subprotocols {
for _, clientProtocol := range clientProtocols {
if clientProtocol == serverProtocol {
return clientProtocol
}
}
}
} else if responseHeader != nil {
return responseHeader.Get("Sec-Websocket-Protocol")
}
return ""
}
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
//
// The responseHeader is included in the response to the client's upgrade
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
// application negotiated subprotocol (Sec-Websocket-Protocol).
//
// If the upgrade fails, then Upgrade replies to the client with an HTTP error
// response.
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
if r.Method != "GET" {
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET")
}
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported")
}
if !tokenListContainsValue(r.Header, "Connection", "upgrade") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header")
}
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header")
}
if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") {
return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header")
}
checkOrigin := u.CheckOrigin
if checkOrigin == nil {
checkOrigin = checkSameOrigin
}
if !checkOrigin(r) {
return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed")
}
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")
}
subprotocol := u.selectSubprotocol(r, responseHeader)
// Negotiate PMCE
var compress bool
if u.EnableCompression {
for _, ext := range parseExtensions(r.Header) {
if ext[""] != "permessage-deflate" {
continue
}
compress = true
break
}
}
var (
netConn net.Conn
err error
)
h, ok := w.(http.Hijacker)
if !ok {
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
}
var brw *bufio.ReadWriter
netConn, brw, err = h.Hijack()
if err != nil {
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
}
if brw.Reader.Buffered() > 0 {
netConn.Close()
return nil, errors.New("websocket: client sent data before handshake is complete")
}
c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw)
c.subprotocol = subprotocol
if compress {
c.newCompressionWriter = compressNoContextTakeover
c.newDecompressionReader = decompressNoContextTakeover
}
p := c.writeBuf[:0]
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
p = append(p, computeAcceptKey(challengeKey)...)
p = append(p, "\r\n"...)
if c.subprotocol != "" {
p = append(p, "Sec-Websocket-Protocol: "...)
p = append(p, c.subprotocol...)
p = append(p, "\r\n"...)
}
if compress {
p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...)
}
for k, vs := range responseHeader {
if k == "Sec-Websocket-Protocol" {
continue
}
for _, v := range vs {
p = append(p, k...)
p = append(p, ": "...)
for i := 0; i < len(v); i++ {
b := v[i]
if b <= 31 {
// prevent response splitting.
b = ' '
}
p = append(p, b)
}
p = append(p, "\r\n"...)
}
}
p = append(p, "\r\n"...)
// Clear deadlines set by HTTP server.
netConn.SetDeadline(time.Time{})
if u.HandshakeTimeout > 0 {
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout))
}
if _, err = netConn.Write(p); err != nil {
netConn.Close()
return nil, err
}
if u.HandshakeTimeout > 0 {
netConn.SetWriteDeadline(time.Time{})
}
return c, nil
}
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
//
// This function is deprecated, use websocket.Upgrader instead.
//
// The application is responsible for checking the request origin before
// calling Upgrade. An example implementation of the same origin policy is:
//
// if req.Header.Get("Origin") != "http://"+req.Host {
// http.Error(w, "Origin not allowed", 403)
// return
// }
//
// If the endpoint supports subprotocols, then the application is responsible
// for negotiating the protocol used on the connection. Use the Subprotocols()
// function to get the subprotocols requested by the client. Use the
// Sec-Websocket-Protocol response header to specify the subprotocol selected
// by the application.
//
// The responseHeader is included in the response to the client's upgrade
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
// negotiated subprotocol (Sec-Websocket-Protocol).
//
// The connection buffers IO to the underlying network connection. The
// readBufSize and writeBufSize parameters specify the size of the buffers to
// use. Messages can be larger than the buffers.
//
// If the request is not a valid WebSocket handshake, then Upgrade returns an
// error of type HandshakeError. Applications should handle this error by
// replying to the client with an HTTP error response.
func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) {
u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize}
u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) {
// don't return errors to maintain backwards compatibility
}
u.CheckOrigin = func(r *http.Request) bool {
// allow all connections by default
return true
}
return u.Upgrade(w, r, responseHeader)
}
// Subprotocols returns the subprotocols requested by the client in the
// Sec-Websocket-Protocol header.
func Subprotocols(r *http.Request) []string {
h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol"))
if h == "" {
return nil
}
protocols := strings.Split(h, ",")
for i := range protocols {
protocols[i] = strings.TrimSpace(protocols[i])
}
return protocols
}
// IsWebSocketUpgrade returns true if the client requested upgrade to the
// WebSocket protocol.
func IsWebSocketUpgrade(r *http.Request) bool {
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
tokenListContainsValue(r.Header, "Upgrade", "websocket")
}

214
vendor/github.com/gorilla/websocket/util.go generated vendored Normal file
View File

@ -0,0 +1,214 @@
// Copyright 2013 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 (
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"io"
"net/http"
"strings"
)
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
func computeAcceptKey(challengeKey string) string {
h := sha1.New()
h.Write([]byte(challengeKey))
h.Write(keyGUID)
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func generateChallengeKey() (string, error) {
p := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, p); err != nil {
return "", err
}
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
}
}
func skipSpace(s string) (rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isSpaceOctet == 0 {
break
}
}
return s[i:]
}
func nextToken(s string) (token, rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isTokenOctet == 0 {
break
}
}
return s[:i], s[i:]
}
func nextTokenOrQuoted(s string) (value string, rest string) {
if !strings.HasPrefix(s, "\"") {
return nextToken(s)
}
s = s[1:]
for i := 0; i < len(s); i++ {
switch s[i] {
case '"':
return s[:i], s[i+1:]
case '\\':
p := make([]byte, len(s)-1)
j := copy(p, s[:i])
escape := true
for i = i + 1; i < len(s); i++ {
b := s[i]
switch {
case escape:
escape = false
p[j] = b
j += 1
case b == '\\':
escape = true
case b == '"':
return string(p[:j]), s[i+1:]
default:
p[j] = b
j += 1
}
}
return "", ""
}
}
return "", ""
}
// tokenListContainsValue returns true if the 1#token header with the given
// name contains token.
func tokenListContainsValue(header http.Header, name string, value string) bool {
headers:
for _, s := range header[name] {
for {
var t string
t, s = nextToken(skipSpace(s))
if t == "" {
continue headers
}
s = skipSpace(s)
if s != "" && s[0] != ',' {
continue headers
}
if strings.EqualFold(t, value) {
return true
}
if s == "" {
continue headers
}
s = s[1:]
}
}
return false
}
// parseExtensiosn parses WebSocket extensions from a header.
func parseExtensions(header http.Header) []map[string]string {
// From RFC 6455:
//
// Sec-WebSocket-Extensions = extension-list
// extension-list = 1#extension
// extension = extension-token *( ";" extension-param )
// extension-token = registered-token
// registered-token = token
// extension-param = token [ "=" (token | quoted-string) ]
// ;When using the quoted-string syntax variant, the value
// ;after quoted-string unescaping MUST conform to the
// ;'token' ABNF.
var result []map[string]string
headers:
for _, s := range header["Sec-Websocket-Extensions"] {
for {
var t string
t, s = nextToken(skipSpace(s))
if t == "" {
continue headers
}
ext := map[string]string{"": t}
for {
s = skipSpace(s)
if !strings.HasPrefix(s, ";") {
break
}
var k string
k, s = nextToken(skipSpace(s[1:]))
if k == "" {
continue headers
}
s = skipSpace(s)
var v string
if strings.HasPrefix(s, "=") {
v, s = nextTokenOrQuoted(skipSpace(s[1:]))
s = skipSpace(s)
}
if s != "" && s[0] != ',' && s[0] != ';' {
continue headers
}
ext[k] = v
}
if s != "" && s[0] != ',' {
continue headers
}
result = append(result, ext)
if s == "" {
continue headers
}
s = s[1:]
}
}
return result
}

View File

@ -53,7 +53,7 @@ func (c *cache) augmentGoroutine(goroutine *Goroutine) {
}
// Once all loaded, we can look at the next call when available.
for i := 0; i < len(goroutine.Stack.Calls)-1; i++ {
for i := 1; i < len(goroutine.Stack.Calls); i++ {
// Get the AST from the previous call and process the call line with it.
if f := c.getFuncAST(&goroutine.Stack.Calls[i]); f != nil {
processCall(&goroutine.Stack.Calls[i], f)
@ -115,15 +115,6 @@ type parsedFile struct {
// getFuncAST gets the callee site function AST representation for the code
// inside the function f at line l.
func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) {
if len(p.lineToByteOffset) <= l {
// The line number in the stack trace line does not exist in the file. That
// can only mean that the sources on disk do not match the sources used to
// build the binary.
// TODO(maruel): This should be surfaced, so that source parsing is
// completely ignored.
return
}
// Walk the AST to find the lineToByteOffset that fits the line number.
var lastFunc *ast.FuncDecl
var found ast.Node
@ -164,18 +155,20 @@ func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) {
}
func name(n ast.Node) string {
switch t := n.(type) {
case *ast.InterfaceType:
if _, ok := n.(*ast.InterfaceType); ok {
return "interface{}"
case *ast.Ident:
return t.Name
case *ast.SelectorExpr:
return t.Sel.Name
case *ast.StarExpr:
return "*" + name(t.X)
default:
return "<unknown>"
}
if i, ok := n.(*ast.Ident); ok {
return i.Name
}
if _, ok := n.(*ast.FuncType); ok {
return "func"
}
if s, ok := n.(*ast.SelectorExpr); ok {
return s.Sel.Name
}
// TODO(maruel): Implement anything missing.
return "<unknown>"
}
// fieldToType returns the type name and whether if it's an ellipsis.
@ -196,10 +189,6 @@ func fieldToType(f *ast.Field) (string, bool) {
return arg.Sel.Name, false
case *ast.StarExpr:
return "*" + name(arg.X), false
case *ast.MapType:
return fmt.Sprintf("map[%s]%s", name(arg.Key), name(arg.Value)), false
case *ast.ChanType:
return fmt.Sprintf("chan %s", name(arg.Value)), false
default:
// TODO(maruel): Implement anything missing.
return "<unknown>", false

View File

@ -35,7 +35,7 @@ var (
// - found next stack barrier at 0x123; expected
// - runtime: unexpected return pc for FUNC_NAME called from 0x123
reRoutineHeader = regexp.MustCompile("^goroutine (\\d+) \\[([^\\]]+)\\]\\:\r?\n$")
reRoutineHeader = regexp.MustCompile("^goroutine (\\d+) \\[([^\\]]+)\\]\\:\n$")
reMinutes = regexp.MustCompile("^(\\d+) minutes$")
reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable")
// See gentraceback() in src/runtime/traceback.go for more information.
@ -54,12 +54,12 @@ var (
// when a signal is not correctly handled. It is printed with m.throwing>0.
// These are discarded.
// - For cgo, the source file may be "??".
reFile = regexp.MustCompile("^(?:\t| +)(\\?\\?|\\<autogenerated\\>|.+\\.(?:c|go|s))\\:(\\d+)(?:| \\+0x[0-9a-f]+)(?:| fp=0x[0-9a-f]+ sp=0x[0-9a-f]+)\r?\n$")
reFile = regexp.MustCompile("^(?:\t| +)(\\?\\?|\\<autogenerated\\>|.+\\.(?:c|go|s))\\:(\\d+)(?:| \\+0x[0-9a-f]+)(?:| fp=0x[0-9a-f]+ sp=0x[0-9a-f]+)\n$")
// Sadly, it doesn't note the goroutine number so we could cascade them per
// parenthood.
reCreated = regexp.MustCompile("^created by (.+)\r?\n$")
reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\r?\n$")
reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\r?\n$")
reCreated = regexp.MustCompile("^created by (.+)\n$")
reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\n$")
reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\n$")
// Include frequent GOROOT value on Windows, distro provided and user
// installed path. This simplifies the user's life when processing a trace
// generated on another VM.
@ -656,7 +656,7 @@ func ParseDump(r io.Reader, out io.Writer) ([]Goroutine, error) {
firstLine := false
for scanner.Scan() {
line := scanner.Text()
if line == "\n" || line == "\r\n" {
if line == "\n" {
if goroutine != nil {
goroutine = nil
continue

8
vendor/github.com/mattn/go-runewidth/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,8 @@
language: go
go:
- tip
before_install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
script:
- $HOME/gopath/bin/goveralls -repotoken lAKAWPzcGsD3A8yBX3BGGtRUdJ6CaGERL

View File

@ -55,7 +55,6 @@ var private = table{
var nonprint = table{
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
{0x2028, 0x2029},
{0x202A, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
}

2
vendor/github.com/nlopes/slack/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
*.test
*~

21
vendor/github.com/nlopes/slack/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,21 @@
language: go
go:
- 1.7.x
- 1.8.x
- 1.9.x
- tip
before_install:
- export PATH=$HOME/gopath/bin:$PATH
script:
- go test -race ./...
- go test -cover ./...
matrix:
allow_failures:
- go: tip
git:
depth: 10

View File

@ -1,6 +1,8 @@
Slack API in Go [![GoDoc](https://godoc.org/github.com/nlopes/slack?status.svg)](https://godoc.org/github.com/nlopes/slack) [![Build Status](https://travis-ci.org/nlopes/slack.svg)](https://travis-ci.org/nlopes/slack)
===============
[![Join the chat at https://gitter.im/go-slack/Lobby](https://badges.gitter.im/go-slack/Lobby.svg)](https://gitter.im/go-slack/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
This library supports most if not all of the `api.slack.com` REST
calls, as well as the Real-Time Messaging protocol over websocket, in
a fully managed way.

View File

@ -12,9 +12,9 @@ type adminResponse struct {
Error string `json:"error"`
}
func adminRequest(ctx context.Context, method string, teamName string, values url.Values, debug bool) (*adminResponse, error) {
func adminRequest(ctx context.Context, client HTTPRequester, method string, teamName string, values url.Values, debug bool) (*adminResponse, error) {
adminResponse := &adminResponse{}
err := parseAdminResponse(ctx, method, teamName, values, adminResponse, debug)
err := parseAdminResponse(ctx, client, method, teamName, values, adminResponse, debug)
if err != nil {
return nil, err
}
@ -35,12 +35,12 @@ func (api *Client) DisableUser(teamName string, uid string) error {
func (api *Client) DisableUserContext(ctx context.Context, teamName string, uid string) error {
values := url.Values{
"user": {uid},
"token": {api.config.token},
"token": {api.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest(ctx, "setInactive", teamName, values, api.debug)
_, err := adminRequest(ctx, api.httpclient, "setInactive", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to disable user with id '%s': %s", uid, err)
}
@ -61,12 +61,12 @@ func (api *Client) InviteGuestContext(ctx context.Context, teamName, channel, fi
"first_name": {firstName},
"last_name": {lastName},
"ultra_restricted": {"1"},
"token": {api.config.token},
"token": {api.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest(ctx, "invite", teamName, values, api.debug)
_, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to invite single-channel guest: %s", err)
}
@ -87,12 +87,12 @@ func (api *Client) InviteRestrictedContext(ctx context.Context, teamName, channe
"first_name": {firstName},
"last_name": {lastName},
"restricted": {"1"},
"token": {api.config.token},
"token": {api.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest(ctx, "invite", teamName, values, api.debug)
_, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to restricted account: %s", err)
}
@ -111,12 +111,12 @@ func (api *Client) InviteToTeamContext(ctx context.Context, teamName, firstName,
"email": {emailAddress},
"first_name": {firstName},
"last_name": {lastName},
"token": {api.config.token},
"token": {api.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest(ctx, "invite", teamName, values, api.debug)
_, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to invite to team: %s", err)
}
@ -133,12 +133,12 @@ func (api *Client) SetRegular(teamName, user string) error {
func (api *Client) SetRegularContext(ctx context.Context, teamName, user string) error {
values := url.Values{
"user": {user},
"token": {api.config.token},
"token": {api.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest(ctx, "setRegular", teamName, values, api.debug)
_, err := adminRequest(ctx, api.httpclient, "setRegular", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to change the user (%s) to a regular user: %s", user, err)
}
@ -155,12 +155,12 @@ func (api *Client) SendSSOBindingEmail(teamName, user string) error {
func (api *Client) SendSSOBindingEmailContext(ctx context.Context, teamName, user string) error {
values := url.Values{
"user": {user},
"token": {api.config.token},
"token": {api.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest(ctx, "sendSSOBind", teamName, values, api.debug)
_, err := adminRequest(ctx, api.httpclient, "sendSSOBind", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to send SSO binding email for user (%s): %s", user, err)
}
@ -178,12 +178,12 @@ func (api *Client) SetUltraRestrictedContext(ctx context.Context, teamName, uid,
values := url.Values{
"user": {uid},
"channel": {channel},
"token": {api.config.token},
"token": {api.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest(ctx, "setUltraRestricted", teamName, values, api.debug)
_, err := adminRequest(ctx, api.httpclient, "setUltraRestricted", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to ultra-restrict account: %s", err)
}
@ -200,12 +200,12 @@ func (api *Client) SetRestricted(teamName, uid string) error {
func (api *Client) SetRestrictedContext(ctx context.Context, teamName, uid string) error {
values := url.Values{
"user": {uid},
"token": {api.config.token},
"token": {api.token},
"set_active": {"true"},
"_attempts": {"1"},
}
_, err := adminRequest(ctx, "setRestricted", teamName, values, api.debug)
_, err := adminRequest(ctx, api.httpclient, "setRestricted", teamName, values, api.debug)
if err != nil {
return fmt.Errorf("Failed to restrict account: %s", err)
}

View File

@ -25,6 +25,7 @@ type AttachmentAction struct {
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.
}
// AttachmentActionOption the individual option to appear in action menu.
@ -48,6 +49,9 @@ type AttachmentActionCallback struct {
Channel Channel `json:"channel"`
User User `json:"user"`
Name string `json:"name"`
Value string `json:"value"`
OriginalMessage Message `json:"original_message"`
ActionTs string `json:"action_ts"`
@ -55,6 +59,7 @@ type AttachmentActionCallback struct {
AttachmentID string `json:"attachment_id"`
Token string `json:"token"`
ResponseURL string `json:"response_url"`
TriggerID string `json:"trigger_id"`
}
// ConfirmationField are used to ask users to confirm actions
@ -71,6 +76,7 @@ type Attachment struct {
Fallback string `json:"fallback"`
CallbackID string `json:"callback_id,omitempty"`
ID int `json:"id,omitempty"`
AuthorName string `json:"author_name,omitempty"`
AuthorSubname string `json:"author_subname,omitempty"`

View File

@ -19,9 +19,9 @@ type botResponseFull struct {
SlackResponse
}
func botRequest(ctx context.Context, path string, values url.Values, debug bool) (*botResponseFull, error) {
func botRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*botResponseFull, error) {
response := &botResponseFull{}
err := post(ctx, path, values, response, debug)
err := post(ctx, client, path, values, response, debug)
if err != nil {
return nil, err
}
@ -39,10 +39,11 @@ func (api *Client) GetBotInfo(bot string) (*Bot, error) {
// 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.config.token},
"token": {api.token},
"bot": {bot},
}
response, err := botRequest(ctx, "bots.info", values, api.debug)
response, err := botRequest(ctx, api.httpclient, "bots.info", values, api.debug)
if err != nil {
return nil, err
}

View File

@ -20,14 +20,15 @@ type channelResponseFull struct {
// 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"`
IsChannel bool `json:"is_channel"`
IsGeneral bool `json:"is_general"`
IsMember bool `json:"is_member"`
Locale string `json:"locale"`
}
func channelRequest(ctx context.Context, path string, values url.Values, debug bool) (*channelResponseFull, error) {
func channelRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*channelResponseFull, error) {
response := &channelResponseFull{}
err := post(ctx, path, values, response, debug)
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
if err != nil {
return nil, err
}
@ -38,53 +39,62 @@ func channelRequest(ctx context.Context, path string, values url.Values, debug b
}
// ArchiveChannel archives the given channel
func (api *Client) ArchiveChannel(channel string) error {
return api.ArchiveChannelContext(context.Background(), channel)
// see https://api.slack.com/methods/channels.archive
func (api *Client) ArchiveChannel(channelID string) error {
return api.ArchiveChannelContext(context.Background(), channelID)
}
// ArchiveChannelContext archives the given channel with a custom context
func (api *Client) ArchiveChannelContext(ctx context.Context, channel string) error {
// see https://api.slack.com/methods/channels.archive
func (api *Client) ArchiveChannelContext(ctx context.Context, channelID string) (err error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
}
_, err := channelRequest(ctx, "channels.archive", values, api.debug)
if err != nil {
if _, err = channelRequest(ctx, api.httpclient, "channels.archive", values, api.debug); err != nil {
return err
}
return nil
}
// UnarchiveChannel unarchives the given channel
func (api *Client) UnarchiveChannel(channel string) error {
return api.UnarchiveChannelContext(context.Background(), channel)
// see https://api.slack.com/methods/channels.unarchive
func (api *Client) UnarchiveChannel(channelID string) error {
return api.UnarchiveChannelContext(context.Background(), channelID)
}
// UnarchiveChannelContext unarchives the given channel with a custom context
func (api *Client) UnarchiveChannelContext(ctx context.Context, channel string) error {
// see https://api.slack.com/methods/channels.unarchive
func (api *Client) UnarchiveChannelContext(ctx context.Context, channelID string) (err error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
}
_, err := channelRequest(ctx, "channels.unarchive", values, api.debug)
if err != nil {
if _, err = channelRequest(ctx, api.httpclient, "channels.unarchive", values, api.debug); err != nil {
return err
}
return nil
}
// CreateChannel creates a channel with the given name and returns a *Channel
func (api *Client) CreateChannel(channel string) (*Channel, error) {
return api.CreateChannelContext(context.Background(), channel)
// see https://api.slack.com/methods/channels.create
func (api *Client) CreateChannel(channelName string) (*Channel, error) {
return api.CreateChannelContext(context.Background(), channelName)
}
// CreateChannelContext creates a channel with the given name and returns a *Channel with a custom context
func (api *Client) CreateChannelContext(ctx context.Context, channel string) (*Channel, error) {
// see https://api.slack.com/methods/channels.create
func (api *Client) CreateChannelContext(ctx context.Context, channelName string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"name": {channel},
"token": {api.token},
"name": {channelName},
}
response, err := channelRequest(ctx, "channels.create", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.create", values, api.debug)
if err != nil {
return nil, err
}
@ -92,15 +102,17 @@ func (api *Client) CreateChannelContext(ctx context.Context, channel string) (*C
}
// GetChannelHistory retrieves the channel history
func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (*History, error) {
return api.GetChannelHistoryContext(context.Background(), channel, params)
// see https://api.slack.com/methods/channels.history
func (api *Client) GetChannelHistory(channelID string, params HistoryParameters) (*History, error) {
return api.GetChannelHistoryContext(context.Background(), channelID, params)
}
// GetChannelHistoryContext retrieves the channel history with a custom context
func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string, params HistoryParameters) (*History, error) {
// see https://api.slack.com/methods/channels.history
func (api *Client) GetChannelHistoryContext(ctx context.Context, channelID string, params HistoryParameters) (*History, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
}
if params.Latest != DEFAULT_HISTORY_LATEST {
values.Add("latest", params.Latest)
@ -118,6 +130,7 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string,
values.Add("inclusive", "0")
}
}
if params.Unreads != DEFAULT_HISTORY_UNREADS {
if params.Unreads {
values.Add("unreads", "1")
@ -125,7 +138,8 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string,
values.Add("unreads", "0")
}
}
response, err := channelRequest(ctx, "channels.history", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.history", values, api.debug)
if err != nil {
return nil, err
}
@ -133,17 +147,20 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string,
}
// GetChannelInfo retrieves the given channel
func (api *Client) GetChannelInfo(channel string) (*Channel, error) {
return api.GetChannelInfoContext(context.Background(), channel)
// see https://api.slack.com/methods/channels.info
func (api *Client) GetChannelInfo(channelID string) (*Channel, error) {
return api.GetChannelInfoContext(context.Background(), channelID)
}
// GetChannelInfoContext retrieves the given channel with a custom context
func (api *Client) GetChannelInfoContext(ctx context.Context, channel string) (*Channel, error) {
// see https://api.slack.com/methods/channels.info
func (api *Client) GetChannelInfoContext(ctx context.Context, channelID string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
}
response, err := channelRequest(ctx, "channels.info", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.info", values, api.debug)
if err != nil {
return nil, err
}
@ -151,18 +168,21 @@ func (api *Client) GetChannelInfoContext(ctx context.Context, channel string) (*
}
// InviteUserToChannel invites a user to a given channel and returns a *Channel
func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) {
return api.InviteUserToChannelContext(context.Background(), channel, user)
// see https://api.slack.com/methods/channels.invite
func (api *Client) InviteUserToChannel(channelID, user string) (*Channel, error) {
return api.InviteUserToChannelContext(context.Background(), channelID, user)
}
// InviteUserToChannelCustom invites a user to a given channel and returns a *Channel with a custom context
func (api *Client) InviteUserToChannelContext(ctx context.Context, channel, user string) (*Channel, error) {
// see https://api.slack.com/methods/channels.invite
func (api *Client) InviteUserToChannelContext(ctx context.Context, channelID, user string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
"user": {user},
}
response, err := channelRequest(ctx, "channels.invite", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.invite", values, api.debug)
if err != nil {
return nil, err
}
@ -170,17 +190,20 @@ func (api *Client) InviteUserToChannelContext(ctx context.Context, channel, user
}
// JoinChannel joins the currently authenticated user to a channel
func (api *Client) JoinChannel(channel string) (*Channel, error) {
return api.JoinChannelContext(context.Background(), channel)
// see https://api.slack.com/methods/channels.join
func (api *Client) JoinChannel(channelName string) (*Channel, error) {
return api.JoinChannelContext(context.Background(), channelName)
}
// JoinChannelContext joins the currently authenticated user to a channel with a custom context
func (api *Client) JoinChannelContext(ctx context.Context, channel string) (*Channel, error) {
// see https://api.slack.com/methods/channels.join
func (api *Client) JoinChannelContext(ctx context.Context, channelName string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"name": {channel},
"token": {api.token},
"name": {channelName},
}
response, err := channelRequest(ctx, "channels.join", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.join", values, api.debug)
if err != nil {
return nil, err
}
@ -188,59 +211,66 @@ func (api *Client) JoinChannelContext(ctx context.Context, channel string) (*Cha
}
// LeaveChannel makes the authenticated user leave the given channel
func (api *Client) LeaveChannel(channel string) (bool, error) {
return api.LeaveChannelContext(context.Background(), channel)
// see https://api.slack.com/methods/channels.leave
func (api *Client) LeaveChannel(channelID string) (bool, error) {
return api.LeaveChannelContext(context.Background(), channelID)
}
// LeaveChannelContext makes the authenticated user leave the given channel with a custom context
func (api *Client) LeaveChannelContext(ctx context.Context, channel string) (bool, error) {
// see https://api.slack.com/methods/channels.leave
func (api *Client) LeaveChannelContext(ctx context.Context, channelID string) (bool, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
}
response, err := channelRequest(ctx, "channels.leave", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.leave", values, api.debug)
if err != nil {
return false, err
}
if response.NotInChannel {
return response.NotInChannel, nil
}
return false, nil
return response.NotInChannel, nil
}
// KickUserFromChannel kicks a user from a given channel
func (api *Client) KickUserFromChannel(channel, user string) error {
return api.KickUserFromChannelContext(context.Background(), channel, user)
// see https://api.slack.com/methods/channels.kick
func (api *Client) KickUserFromChannel(channelID, user string) error {
return api.KickUserFromChannelContext(context.Background(), channelID, user)
}
// KickUserFromChannelContext kicks a user from a given channel with a custom context
func (api *Client) KickUserFromChannelContext(ctx context.Context, channel, user string) error {
// see https://api.slack.com/methods/channels.kick
func (api *Client) KickUserFromChannelContext(ctx context.Context, channelID, user string) (err error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
"user": {user},
}
_, err := channelRequest(ctx, "channels.kick", values, api.debug)
if err != nil {
if _, err = channelRequest(ctx, api.httpclient, "channels.kick", values, api.debug); err != nil {
return err
}
return nil
}
// GetChannels retrieves all the channels
// see https://api.slack.com/methods/channels.list
func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
return api.GetChannelsContext(context.Background(), excludeArchived)
}
// GetChannelsContext retrieves all the channels with a custom context
// see https://api.slack.com/methods/channels.list
func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool) ([]Channel, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if excludeArchived {
values.Add("exclude_archived", "1")
}
response, err := channelRequest(ctx, "channels.list", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.list", values, api.debug)
if err != nil {
return nil, err
}
@ -252,40 +282,46 @@ func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool)
// timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls
// (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A
// timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
func (api *Client) SetChannelReadMark(channel, ts string) error {
return api.SetChannelReadMarkContext(context.Background(), channel, ts)
// see https://api.slack.com/methods/channels.mark
func (api *Client) SetChannelReadMark(channelID, ts string) error {
return api.SetChannelReadMarkContext(context.Background(), channelID, ts)
}
// SetChannelReadMarkContext sets the read mark of a given channel to a specific point with a custom context
// For more details see SetChannelReadMark documentation
func (api *Client) SetChannelReadMarkContext(ctx context.Context, channel, ts string) error {
// see https://api.slack.com/methods/channels.mark
func (api *Client) SetChannelReadMarkContext(ctx context.Context, channelID, ts string) (err error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
"ts": {ts},
}
_, err := channelRequest(ctx, "channels.mark", values, api.debug)
if err != nil {
if _, err = channelRequest(ctx, api.httpclient, "channels.mark", values, api.debug); err != nil {
return err
}
return nil
}
// RenameChannel renames a given channel
func (api *Client) RenameChannel(channel, name string) (*Channel, error) {
return api.RenameChannelContext(context.Background(), channel, name)
// see https://api.slack.com/methods/channels.rename
func (api *Client) RenameChannel(channelID, name string) (*Channel, error) {
return api.RenameChannelContext(context.Background(), channelID, name)
}
// RenameChannelContext renames a given channel with a custom context
func (api *Client) RenameChannelContext(ctx context.Context, channel, name string) (*Channel, error) {
// see https://api.slack.com/methods/channels.rename
func (api *Client) RenameChannelContext(ctx context.Context, channelID, name string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
"name": {name},
}
// XXX: the created entry in this call returns a string instead of a number
// so I may have to do some workaround to solve it.
response, err := channelRequest(ctx, "channels.rename", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.rename", values, api.debug)
if err != nil {
return nil, err
}
@ -293,18 +329,21 @@ func (api *Client) RenameChannelContext(ctx context.Context, channel, name strin
}
// SetChannelPurpose sets the channel purpose and returns the purpose that was successfully set
func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) {
return api.SetChannelPurposeContext(context.Background(), channel, purpose)
// see https://api.slack.com/methods/channels.setPurpose
func (api *Client) SetChannelPurpose(channelID, purpose string) (string, error) {
return api.SetChannelPurposeContext(context.Background(), channelID, purpose)
}
// SetChannelPurposeContext sets the channel purpose and returns the purpose that was successfully set with a custom context
func (api *Client) SetChannelPurposeContext(ctx context.Context, channel, purpose string) (string, error) {
// see https://api.slack.com/methods/channels.setPurpose
func (api *Client) SetChannelPurposeContext(ctx context.Context, channelID, purpose string) (string, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
"purpose": {purpose},
}
response, err := channelRequest(ctx, "channels.setPurpose", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.setPurpose", values, api.debug)
if err != nil {
return "", err
}
@ -312,18 +351,21 @@ func (api *Client) SetChannelPurposeContext(ctx context.Context, channel, purpos
}
// SetChannelTopic sets the channel topic and returns the topic that was successfully set
func (api *Client) SetChannelTopic(channel, topic string) (string, error) {
return api.SetChannelTopicContext(context.Background(), channel, topic)
// see https://api.slack.com/methods/channels.setTopic
func (api *Client) SetChannelTopic(channelID, topic string) (string, error) {
return api.SetChannelTopicContext(context.Background(), channelID, topic)
}
// SetChannelTopicContext sets the channel topic and returns the topic that was successfully set with a custom context
func (api *Client) SetChannelTopicContext(ctx context.Context, channel, topic string) (string, error) {
// see https://api.slack.com/methods/channels.setTopic
func (api *Client) SetChannelTopicContext(ctx context.Context, channelID, topic string) (string, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
"topic": {topic},
}
response, err := channelRequest(ctx, "channels.setTopic", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.setTopic", values, api.debug)
if err != nil {
return "", err
}
@ -331,18 +373,20 @@ func (api *Client) SetChannelTopicContext(ctx context.Context, channel, topic st
}
// GetChannelReplies gets an entire thread (a message plus all the messages in reply to it).
func (api *Client) GetChannelReplies(channel, thread_ts string) ([]Message, error) {
return api.GetChannelRepliesContext(context.Background(), channel, thread_ts)
// see https://api.slack.com/methods/channels.replies
func (api *Client) GetChannelReplies(channelID, thread_ts string) ([]Message, error) {
return api.GetChannelRepliesContext(context.Background(), channelID, thread_ts)
}
// GetChannelRepliesContext gets an entire thread (a message plus all the messages in reply to it) with a custom context
func (api *Client) GetChannelRepliesContext(ctx context.Context, channel, thread_ts string) ([]Message, error) {
// see https://api.slack.com/methods/channels.replies
func (api *Client) GetChannelRepliesContext(ctx context.Context, channelID, thread_ts string) ([]Message, error) {
values := url.Values{
"token": {api.config.token},
"channel": {channel},
"token": {api.token},
"channel": {channelID},
"thread_ts": {thread_ts},
}
response, err := channelRequest(ctx, "channels.replies", values, api.debug)
response, err := channelRequest(ctx, api.httpclient, "channels.replies", values, api.debug)
if err != nil {
return nil, err
}

View File

@ -10,9 +10,10 @@ import (
const (
DEFAULT_MESSAGE_USERNAME = ""
DEFAULT_MESSAGE_THREAD_TIMESTAMP = ""
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
@ -31,11 +32,11 @@ type chatResponseFull struct {
// PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request
type PostMessageParameters struct {
Text string `json:"text"`
Username string `json:"user_name"`
AsUser bool `json:"as_user"`
Parse string `json:"parse"`
ThreadTimestamp string `json:"thread_ts"`
ReplyBroadcast bool `json:"reply_broadcast"`
LinkNames int `json:"link_names"`
Attachments []Attachment `json:"attachments"`
UnfurlLinks bool `json:"unfurl_links"`
@ -44,22 +45,28 @@ type PostMessageParameters struct {
IconEmoji string `json:"icon_emoji"`
Markdown bool `json:"mrkdwn,omitempty"`
EscapeText bool `json:"escape_text"`
// chat.postEphemeral support
Channel string `json:"channel"`
User string `json:"user"`
}
// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set
func NewPostMessageParameters() PostMessageParameters {
return PostMessageParameters{
Username: DEFAULT_MESSAGE_USERNAME,
AsUser: DEFAULT_MESSAGE_ASUSER,
Parse: DEFAULT_MESSAGE_PARSE,
LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
Attachments: nil,
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,
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,
Attachments: nil,
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,
}
}
@ -102,12 +109,43 @@ func (api *Client) PostMessageContext(ctx context.Context, channel, text string,
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(channel, userID string, options ...MsgOption) (string, error) {
options = append(options, MsgOptionPostEphemeral())
return api.PostEphemeralContext(
context.Background(),
channel,
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, channel, userID string, options ...MsgOption) (string, error) {
path, values, err := ApplyMsgOptions(api.token, channel, options...)
if err != nil {
return "", err
}
values.Add("user", userID)
response, err := chatRequest(ctx, api.httpclient, path, values, api.debug)
if err != nil {
return "", err
}
return response.Timestamp, nil
}
// UpdateMessage updates a message in a channel
func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
return api.UpdateMessageContext(context.Background(), channel, timestamp, text)
}
// UpdateMessage updates a message in a channel
// UpdateMessageContext updates a message in a channel
func (api *Client) UpdateMessageContext(ctx context.Context, channel, timestamp, text string) (string, string, string, error) {
return api.SendMessageContext(ctx, channel, MsgOptionUpdate(timestamp), MsgOptionText(text, true))
}
@ -119,12 +157,12 @@ 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, channel string, options ...MsgOption) (string, string, string, error) {
channel, values, err := ApplyMsgOptions(api.config.token, channel, options...)
channel, values, err := ApplyMsgOptions(api.token, channel, options...)
if err != nil {
return "", "", "", err
}
response, err := chatRequest(ctx, channel, values, api.debug)
response, err := chatRequest(ctx, api.httpclient, channel, values, api.debug)
if err != nil {
return "", "", "", err
}
@ -156,9 +194,9 @@ func escapeMessage(message string) string {
return replacer.Replace(message)
}
func chatRequest(ctx context.Context, path string, values url.Values, debug bool) (*chatResponseFull, error) {
func chatRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*chatResponseFull, error) {
response := &chatResponseFull{}
err := post(ctx, path, values, response, debug)
err := post(ctx, client, path, values, response, debug)
if err != nil {
return nil, err
}
@ -171,9 +209,10 @@ func chatRequest(ctx context.Context, path string, values url.Values, debug bool
type sendMode string
const (
chatUpdate sendMode = "chat.update"
chatPostMessage sendMode = "chat.postMessage"
chatDelete sendMode = "chat.delete"
chatUpdate sendMode = "chat.update"
chatPostMessage sendMode = "chat.postMessage"
chatDelete sendMode = "chat.delete"
chatPostEphemeral sendMode = "chat.postEphemeral"
)
type sendConfig struct {
@ -193,6 +232,15 @@ func MsgOptionPost() MsgOption {
}
}
// MsgOptionPostEphemeral posts an ephemeral message
func MsgOptionPostEphemeral() MsgOption {
return func(config *sendConfig) error {
config.mode = chatPostEphemeral
config.values.Del("ts")
return nil
}
}
// MsgOptionUpdate updates a message based on the timestamp.
func MsgOptionUpdate(timestamp string) MsgOption {
return func(config *sendConfig) error {
@ -256,6 +304,14 @@ func MsgOptionEnableLinkUnfurl() MsgOption {
}
}
// 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 {
@ -279,6 +335,11 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
config.values.Set("username", string(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)
@ -314,6 +375,9 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
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
}

View File

@ -1,5 +1,13 @@
package slack
import (
"context"
"errors"
"net/url"
"strconv"
"strings"
)
// Conversation is the foundation for IM and BaseGroupConversation
type conversation struct {
ID string `json:"id"`
@ -9,6 +17,20 @@ type conversation struct {
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"`
// TODO support pending_shared
// TODO support previous_names
}
// GroupConversation is the foundation for Group and Channel
@ -35,3 +57,536 @@ type Purpose struct {
Creator string `json:"creator"`
LastSet JSONTime `json:"last_set"`
}
type GetUsersInConversationParameters struct {
ChannelID string
Cursor string
Limit int
}
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", string(params.Limit))
}
response := struct {
Members []string `json:"members"`
ResponseMetaData responseMetaData `json:"response_metadata"`
SlackResponse
}{}
err := post(ctx, api.httpclient, "conversations.members", values, &response, api.debug)
if err != nil {
return nil, "", err
}
if !response.Ok {
return nil, "", errors.New(response.Error)
}
return response.Members, response.ResponseMetaData.NextCursor, nil
}
// 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 := post(ctx, api.httpclient, "conversations.archive", values, &response, api.debug)
if err != nil {
return err
}
if !response.Ok {
return errors.New(response.Error)
}
return nil
}
// 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 := post(ctx, api.httpclient, "conversations.unarchive", values, &response, api.debug)
if err != nil {
return err
}
if !response.Ok {
return errors.New(response.Error)
}
return nil
}
// 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 := post(ctx, api.httpclient, "conversations.setTopic", values, &response, api.debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response.Channel, nil
}
// 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 := post(ctx, api.httpclient, "conversations.setPurpose", values, &response, api.debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response.Channel, nil
}
// 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 := post(ctx, api.httpclient, "conversations.rename", values, &response, api.debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response.Channel, nil
}
// 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 := post(ctx, api.httpclient, "conversations.invite", values, &response, api.debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response.Channel, nil
}
// 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 := post(ctx, api.httpclient, "conversations.kick", values, &response, api.debug)
if err != nil {
return err
}
if !response.Ok {
return errors.New(response.Error)
}
return nil
}
// 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 = post(ctx, api.httpclient, "conversations.close", values, &response, api.debug)
if err != nil {
return false, false, err
}
if !response.Ok {
return false, false, errors.New(response.Error)
}
return response.NoOp, response.AlreadyClosed, nil
}
// 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 := channelRequest(
ctx, api.httpclient, "conversations.create", values, api.debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
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 := channelRequest(
ctx, api.httpclient, "conversations.info", values, api.debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return &response.Channel, nil
}
// 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 := channelRequest(ctx, api.httpclient, "conversations.leave", values, api.debug)
if err != nil {
return false, err
}
return response.NotInChannel, nil
}
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", string(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 = post(ctx, api.httpclient, "conversations.replies", values, &response, api.debug)
if err != nil {
return nil, false, "", err
}
if !response.Ok {
return nil, false, "", errors.New(response.Error)
}
return response.Messages, response.HasMore, response.ResponseMetaData.NextCursor, nil
}
type GetConversationsParameters struct {
Cursor string
ExcludeArchived string
Limit int
Types []string
}
// GetConversations returns the list of channels in a Slack team
func (api *Client) GetConversations(params *GetConversationsParameters) (channels []Channel, nextCursor string, err error) {
return api.GetConversationsContext(context.Background(), params)
}
// GetConversationsContext returns the list of channels in a Slack team with a custom context
func (api *Client) GetConversationsContext(ctx context.Context, params *GetConversationsParameters) (channels []Channel, nextCursor string, err error) {
values := url.Values{
"token": {api.token},
"exclude_archived": {params.ExcludeArchived},
}
if params.Cursor != "" {
values.Add("cursor", params.Cursor)
}
if params.Limit != 0 {
values.Add("limit", string(params.Limit))
}
if params.Types != nil {
values.Add("types", strings.Join(params.Types, ","))
}
response := struct {
Channels []Channel `json:"channels"`
ResponseMetaData responseMetaData `json:"response_metadata"`
SlackResponse
}{}
err = post(ctx, api.httpclient, "conversations.list", values, &response, api.debug)
if err != nil {
return nil, "", err
}
if !response.Ok {
return nil, "", errors.New(response.Error)
}
return response.Channels, response.ResponseMetaData.NextCursor, nil
}
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 := post(ctx, api.httpclient, "conversations.open", values, &response, api.debug)
if err != nil {
return nil, false, false, err
}
if !response.Ok {
return nil, false, false, errors.New(response.Error)
}
return response.Channel, response.NoOp, response.AlreadyOpen, nil
}
// 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 := post(ctx, api.httpclient, "conversations.join", values, &response, api.debug)
if err != nil {
return nil, "", nil, err
}
if !response.Ok {
return nil, "", nil, errors.New(response.Error)
}
var warnings []string
if response.ResponseMetaData != nil {
warnings = response.ResponseMetaData.Warnings
}
return response.Channel, response.Warning, warnings, nil
}
type GetConversationHistoryParameters struct {
ChannelID string
Cursor string
Inclusive bool
Latest string
Limit int
Oldest string
}
type GetConversationHistoryResponse struct {
SlackResponse
HasMore bool `json:"has_more"`
PinCount int `json:"pin_count"`
Latest string `json:"latest"`
ResponseMetaData struct {
NextCursor string `json:"next_cursor"`
} `json:"response_metadata"`
Messages []Message `json:"messages"`
}
// GetConversationHistory joins an existing conversation
func (api *Client) GetConversationHistory(params *GetConversationHistoryParameters) (*GetConversationHistoryResponse, error) {
return api.GetConversationHistoryContext(context.Background(), params)
}
// GetConversationHistoryContext joins an existing conversation with a custom context
func (api *Client) GetConversationHistoryContext(ctx context.Context, params *GetConversationHistoryParameters) (*GetConversationHistoryResponse, error) {
values := url.Values{"token": {api.token}, "channel": {params.ChannelID}}
if params.Cursor != "" {
values.Add("cursor", params.Cursor)
}
if params.Inclusive {
values.Add("inclusive", "1")
} else {
values.Add("inclusive", "0")
}
if params.Latest != "" {
values.Add("latest", params.Latest)
}
if params.Limit != 0 {
values.Add("limit", string(params.Limit))
}
if params.Oldest != "" {
values.Add("oldest", params.Oldest)
}
response := GetConversationHistoryResponse{}
err := post(ctx, api.httpclient, "conversations.history", values, &response, api.debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return &response, nil
}

View File

@ -36,9 +36,9 @@ type dndTeamInfoResponse struct {
SlackResponse
}
func dndRequest(ctx context.Context, path string, values url.Values, debug bool) (*dndResponseFull, error) {
func dndRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*dndResponseFull, error) {
response := &dndResponseFull{}
err := post(ctx, path, values, response, debug)
err := post(ctx, client, path, values, response, debug)
if err != nil {
return nil, err
}
@ -56,11 +56,12 @@ func (api *Client) EndDND() error {
// 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.config.token},
"token": {api.token},
}
response := &SlackResponse{}
if err := post(ctx, "dnd.endDnd", values, response, api.debug); err != nil {
if err := post(ctx, api.httpclient, "dnd.endDnd", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
@ -77,10 +78,10 @@ func (api *Client) EndSnooze() (*DNDStatus, error) {
// 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.config.token},
"token": {api.token},
}
response, err := dndRequest(ctx, "dnd.endSnooze", values, api.debug)
response, err := dndRequest(ctx, api.httpclient, "dnd.endSnooze", values, api.debug)
if err != nil {
return nil, err
}
@ -95,12 +96,13 @@ func (api *Client) GetDNDInfo(user *string) (*DNDStatus, error) {
// 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.config.token},
"token": {api.token},
}
if user != nil {
values.Set("user", *user)
}
response, err := dndRequest(ctx, "dnd.info", values, api.debug)
response, err := dndRequest(ctx, api.httpclient, "dnd.info", values, api.debug)
if err != nil {
return nil, err
}
@ -115,11 +117,12 @@ func (api *Client) GetDNDTeamInfo(users []string) (map[string]DNDStatus, error)
// 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.config.token},
"token": {api.token},
"users": {strings.Join(users, ",")},
}
response := &dndTeamInfoResponse{}
if err := post(ctx, "dnd.teamInfo", values, response, api.debug); err != nil {
if err := post(ctx, api.httpclient, "dnd.teamInfo", values, response, api.debug); err != nil {
return nil, err
}
if !response.Ok {
@ -139,10 +142,11 @@ func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) {
// For more information see the SetSnooze docs
func (api *Client) SetSnoozeContext(ctx context.Context, minutes int) (*DNDStatus, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"num_minutes": {strconv.Itoa(minutes)},
}
response, err := dndRequest(ctx, "dnd.setSnooze", values, api.debug)
response, err := dndRequest(ctx, api.httpclient, "dnd.setSnooze", values, api.debug)
if err != nil {
return nil, err
}

View File

@ -19,10 +19,11 @@ func (api *Client) GetEmoji() (map[string]string, error) {
// 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.config.token},
"token": {api.token},
}
response := &emojiResponseFull{}
err := post(ctx, "emoji.list", values, response, api.debug)
err := post(ctx, api.httpclient, "emoji.list", values, response, api.debug)
if err != nil {
return nil, err
}

View File

@ -136,9 +136,9 @@ func NewGetFilesParameters() GetFilesParameters {
}
}
func fileRequest(ctx context.Context, path string, values url.Values, debug bool) (*fileResponseFull, error) {
func fileRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*fileResponseFull, error) {
response := &fileResponseFull{}
err := post(ctx, path, values, response, debug)
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
if err != nil {
return nil, err
}
@ -156,12 +156,13 @@ func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment
// GetFileInfoContext retrieves a file and related comments with a custom context
func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count, page int) (*File, []Comment, *Paging, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"file": {fileID},
"count": {strconv.Itoa(count)},
"page": {strconv.Itoa(page)},
}
response, err := fileRequest(ctx, "files.info", values, api.debug)
response, err := fileRequest(ctx, api.httpclient, "files.info", values, api.debug)
if err != nil {
return nil, nil, nil, err
}
@ -176,7 +177,7 @@ func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error)
// GetFilesContext retrieves all files according to the parameters given with a custom context
func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if params.User != DEFAULT_FILES_USER {
values.Add("user", params.User)
@ -199,7 +200,8 @@ func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameter
if params.Page != DEFAULT_FILES_PAGE {
values.Add("page", strconv.Itoa(params.Page))
}
response, err := fileRequest(ctx, "files.list", values, api.debug)
response, err := fileRequest(ctx, api.httpclient, "files.list", values, api.debug)
if err != nil {
return nil, nil, err
}
@ -221,7 +223,7 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
}
response := &fileResponseFull{}
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if params.Filetype != "" {
values.Add("filetype", params.Filetype)
@ -240,11 +242,11 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
}
if params.Content != "" {
values.Add("content", params.Content)
err = post(ctx, "files.upload", values, response, api.debug)
err = postForm(ctx, api.httpclient, SLACK_API+"files.upload", values, response, api.debug)
} else if params.File != "" {
err = postLocalWithMultipartResponse(ctx, "files.upload", params.File, "file", values, response, api.debug)
err = postLocalWithMultipartResponse(ctx, api.httpclient, SLACK_API+"files.upload", params.File, "file", values, response, api.debug)
} else if params.Reader != nil {
err = postWithMultipartResponse(ctx, "files.upload", params.Filename, "file", values, params.Reader, response, api.debug)
err = postWithMultipartResponse(ctx, api.httpclient, SLACK_API+"files.upload", params.Filename, "file", values, params.Reader, response, api.debug)
}
if err != nil {
return nil, err
@ -255,23 +257,46 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
return &response.File, nil
}
// DeleteFileComment deletes a file's comment
func (api *Client) DeleteFileComment(commentID, fileID string) error {
return api.DeleteFileCommentContext(context.Background(), fileID, commentID)
}
// DeleteFileCommentContext deletes a file's comment with a custom context
func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, commentID string) (err error) {
if fileID == "" || commentID == "" {
return errors.New("received empty parameters")
}
values := url.Values{
"token": {api.token},
"file": {fileID},
"id": {commentID},
}
if _, err = fileRequest(ctx, api.httpclient, "files.comments.delete", values, api.debug); err != nil {
return err
}
return nil
}
// DeleteFile deletes a file
func (api *Client) DeleteFile(fileID string) error {
return api.DeleteFileContext(context.Background(), fileID)
}
// DeleteFileContext deletes a file with a custom context
func (api *Client) DeleteFileContext(ctx context.Context, fileID string) error {
func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"file": {fileID},
}
_, err := fileRequest(ctx, "files.delete", values, api.debug)
if err != nil {
if _, err = fileRequest(ctx, api.httpclient, "files.delete", values, api.debug); err != nil {
return err
}
return nil
return nil
}
// RevokeFilePublicURL disables public/external sharing for a file
@ -282,10 +307,11 @@ func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) {
// RevokeFilePublicURLContext disables public/external sharing for a file with a custom context
func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string) (*File, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"file": {fileID},
}
response, err := fileRequest(ctx, "files.revokePublicURL", values, api.debug)
response, err := fileRequest(ctx, api.httpclient, "files.revokePublicURL", values, api.debug)
if err != nil {
return nil, err
}
@ -300,10 +326,11 @@ func (api *Client) ShareFilePublicURL(fileID string) (*File, []Comment, *Paging,
// ShareFilePublicURLContext enabled public/external sharing for a file with a custom context
func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) (*File, []Comment, *Paging, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"file": {fileID},
}
response, err := fileRequest(ctx, "files.sharedPublicURL", values, api.debug)
response, err := fileRequest(ctx, api.httpclient, "files.sharedPublicURL", values, api.debug)
if err != nil {
return nil, nil, nil, err
}

View File

@ -28,9 +28,9 @@ type groupResponseFull struct {
SlackResponse
}
func groupRequest(ctx context.Context, path string, values url.Values, debug bool) (*groupResponseFull, error) {
func groupRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*groupResponseFull, error) {
response := &groupResponseFull{}
err := post(ctx, path, values, response, debug)
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
if err != nil {
return nil, err
}
@ -45,17 +45,18 @@ func (api *Client) ArchiveGroup(group string) error {
return api.ArchiveGroupContext(context.Background(), group)
}
// ArchiveGroup archives a private group
// ArchiveGroupContext archives a private group
func (api *Client) ArchiveGroupContext(ctx context.Context, group string) error {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
}
_, err := groupRequest(ctx, "groups.archive", values, api.debug)
_, err := groupRequest(ctx, api.httpclient, "groups.archive", values, api.debug)
if err != nil {
return err
}
return nil
return err
}
// UnarchiveGroup unarchives a private group
@ -63,13 +64,14 @@ func (api *Client) UnarchiveGroup(group string) error {
return api.UnarchiveGroupContext(context.Background(), group)
}
// UnarchiveGroup unarchives a private group
// UnarchiveGroupContext unarchives a private group
func (api *Client) UnarchiveGroupContext(ctx context.Context, group string) error {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
}
_, err := groupRequest(ctx, "groups.unarchive", values, api.debug)
_, err := groupRequest(ctx, api.httpclient, "groups.unarchive", values, api.debug)
if err != nil {
return err
}
@ -81,13 +83,14 @@ func (api *Client) CreateGroup(group string) (*Group, error) {
return api.CreateGroupContext(context.Background(), group)
}
// CreateGroup creates a private group
// CreateGroupContext creates a private group
func (api *Client) CreateGroupContext(ctx context.Context, group string) (*Group, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"name": {group},
}
response, err := groupRequest(ctx, "groups.create", values, api.debug)
response, err := groupRequest(ctx, api.httpclient, "groups.create", values, api.debug)
if err != nil {
return nil, err
}
@ -104,14 +107,15 @@ func (api *Client) CreateChildGroup(group string) (*Group, error) {
return api.CreateChildGroupContext(context.Background(), group)
}
// CreateChildGroup creates a new private group archiving the old one with a custom context
// CreateChildGroupContext creates a new private group archiving the old one with a custom context
// For more information see CreateChildGroup
func (api *Client) CreateChildGroupContext(ctx context.Context, group string) (*Group, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
}
response, err := groupRequest(ctx, "groups.createChild", values, api.debug)
response, err := groupRequest(ctx, api.httpclient, "groups.createChild", values, api.debug)
if err != nil {
return nil, err
}
@ -126,10 +130,11 @@ func (api *Client) CloseGroup(group string) (bool, bool, error) {
// CloseGroupContext closes a private group with a custom context
func (api *Client) CloseGroupContext(ctx context.Context, group string) (bool, bool, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
}
response, err := imRequest(ctx, "groups.close", values, api.debug)
response, err := imRequest(ctx, api.httpclient, "groups.close", values, api.debug)
if err != nil {
return false, false, err
}
@ -144,7 +149,7 @@ func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*His
// GetGroupHistoryContext fetches all the history for a private group with a custom context
func (api *Client) GetGroupHistoryContext(ctx context.Context, group string, params HistoryParameters) (*History, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
}
if params.Latest != DEFAULT_HISTORY_LATEST {
@ -170,7 +175,8 @@ func (api *Client) GetGroupHistoryContext(ctx context.Context, group string, par
values.Add("unreads", "0")
}
}
response, err := groupRequest(ctx, "groups.history", values, api.debug)
response, err := groupRequest(ctx, api.httpclient, "groups.history", values, api.debug)
if err != nil {
return nil, err
}
@ -185,11 +191,12 @@ func (api *Client) InviteUserToGroup(group, user string) (*Group, bool, error) {
// InviteUserToGroupContext invites a specific user to a private group with a custom context
func (api *Client) InviteUserToGroupContext(ctx context.Context, group, user string) (*Group, bool, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
"user": {user},
}
response, err := groupRequest(ctx, "groups.invite", values, api.debug)
response, err := groupRequest(ctx, api.httpclient, "groups.invite", values, api.debug)
if err != nil {
return nil, false, err
}
@ -202,15 +209,16 @@ func (api *Client) LeaveGroup(group string) error {
}
// LeaveGroupContext makes authenticated user leave the group with a custom context
func (api *Client) LeaveGroupContext(ctx context.Context, group string) error {
func (api *Client) LeaveGroupContext(ctx context.Context, group string) (err error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
}
_, err := groupRequest(ctx, "groups.leave", values, api.debug)
if err != nil {
if _, err = groupRequest(ctx, api.httpclient, "groups.leave", values, api.debug); err != nil {
return err
}
return nil
}
@ -220,16 +228,17 @@ func (api *Client) KickUserFromGroup(group, user string) error {
}
// KickUserFromGroupContext kicks a user from a group with a custom context
func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user string) error {
func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user string) (err error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
"user": {user},
}
_, err := groupRequest(ctx, "groups.kick", values, api.debug)
if err != nil {
if _, err = groupRequest(ctx, api.httpclient, "groups.kick", values, api.debug); err != nil {
return err
}
return nil
}
@ -241,12 +250,13 @@ func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) {
// GetGroupsContext retrieves all groups with a custom context
func (api *Client) GetGroupsContext(ctx context.Context, excludeArchived bool) ([]Group, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if excludeArchived {
values.Add("exclude_archived", "1")
}
response, err := groupRequest(ctx, "groups.list", values, api.debug)
response, err := groupRequest(ctx, api.httpclient, "groups.list", values, api.debug)
if err != nil {
return nil, err
}
@ -261,10 +271,11 @@ func (api *Client) GetGroupInfo(group string) (*Group, error) {
// GetGroupInfoContext retrieves the given group with a custom context
func (api *Client) GetGroupInfoContext(ctx context.Context, group string) (*Group, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
}
response, err := groupRequest(ctx, "groups.info", values, api.debug)
response, err := groupRequest(ctx, api.httpclient, "groups.info", values, api.debug)
if err != nil {
return nil, err
}
@ -282,16 +293,17 @@ func (api *Client) SetGroupReadMark(group, ts string) error {
// SetGroupReadMarkContext sets the read mark on a private group with a custom context
// For more details see SetGroupReadMark
func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string) error {
func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string) (err error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
"ts": {ts},
}
_, err := groupRequest(ctx, "groups.mark", values, api.debug)
if err != nil {
if _, err = groupRequest(ctx, api.httpclient, "groups.mark", values, api.debug); err != nil {
return err
}
return nil
}
@ -303,10 +315,11 @@ func (api *Client) OpenGroup(group string) (bool, bool, error) {
// OpenGroupContext opens a private group with a custom context
func (api *Client) OpenGroupContext(ctx context.Context, group string) (bool, bool, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
}
response, err := groupRequest(ctx, "groups.open", values, api.debug)
response, err := groupRequest(ctx, api.httpclient, "groups.open", values, api.debug)
if err != nil {
return false, false, err
}
@ -323,13 +336,14 @@ func (api *Client) RenameGroup(group, name string) (*Channel, error) {
// RenameGroupContext renames a group with a custom context
func (api *Client) RenameGroupContext(ctx context.Context, group, name string) (*Channel, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
"name": {name},
}
// XXX: the created entry in this call returns a string instead of a number
// so I may have to do some workaround to solve it.
response, err := groupRequest(ctx, "groups.rename", values, api.debug)
response, err := groupRequest(ctx, api.httpclient, "groups.rename", values, api.debug)
if err != nil {
return nil, err
}
@ -344,11 +358,12 @@ func (api *Client) SetGroupPurpose(group, purpose string) (string, error) {
// SetGroupPurposeContext sets the group purpose with a custom context
func (api *Client) SetGroupPurposeContext(ctx context.Context, group, purpose string) (string, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
"purpose": {purpose},
}
response, err := groupRequest(ctx, "groups.setPurpose", values, api.debug)
response, err := groupRequest(ctx, api.httpclient, "groups.setPurpose", values, api.debug)
if err != nil {
return "", err
}
@ -363,11 +378,12 @@ func (api *Client) SetGroupTopic(group, topic string) (string, error) {
// SetGroupTopicContext sets the group topic with a custom context
func (api *Client) SetGroupTopicContext(ctx context.Context, group, topic string) (string, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {group},
"topic": {topic},
}
response, err := groupRequest(ctx, "groups.setTopic", values, api.debug)
response, err := groupRequest(ctx, api.httpclient, "groups.setTopic", values, api.debug)
if err != nil {
return "", err
}

29
vendor/github.com/nlopes/slack/im.go generated vendored
View File

@ -29,9 +29,9 @@ type IM struct {
IsUserDeleted bool `json:"is_user_deleted"`
}
func imRequest(ctx context.Context, path string, values url.Values, debug bool) (*imResponseFull, error) {
func imRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*imResponseFull, error) {
response := &imResponseFull{}
err := post(ctx, path, values, response, debug)
err := post(ctx, client, path, values, response, debug)
if err != nil {
return nil, err
}
@ -49,10 +49,11 @@ func (api *Client) CloseIMChannel(channel string) (bool, bool, error) {
// CloseIMChannelContext closes the direct message channel with a custom context
func (api *Client) CloseIMChannelContext(ctx context.Context, channel string) (bool, bool, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {channel},
}
response, err := imRequest(ctx, "im.close", values, api.debug)
response, err := imRequest(ctx, api.httpclient, "im.close", values, api.debug)
if err != nil {
return false, false, err
}
@ -69,10 +70,11 @@ func (api *Client) OpenIMChannel(user string) (bool, bool, string, error) {
// Returns some status and the channel ID
func (api *Client) OpenIMChannelContext(ctx context.Context, user string) (bool, bool, string, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"user": {user},
}
response, err := imRequest(ctx, "im.open", values, api.debug)
response, err := imRequest(ctx, api.httpclient, "im.open", values, api.debug)
if err != nil {
return false, false, "", err
}
@ -87,11 +89,12 @@ func (api *Client) MarkIMChannel(channel, ts string) (err error) {
// MarkIMChannelContext sets the read mark of a direct message channel to a specific point with a custom context
func (api *Client) MarkIMChannelContext(ctx context.Context, channel, ts string) (err error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {channel},
"ts": {ts},
}
_, err = imRequest(ctx, "im.mark", values, api.debug)
_, err = imRequest(ctx, api.httpclient, "im.mark", values, api.debug)
if err != nil {
return err
}
@ -106,7 +109,7 @@ func (api *Client) GetIMHistory(channel string, params HistoryParameters) (*Hist
// GetIMHistoryContext retrieves the direct message channel history with a custom context
func (api *Client) GetIMHistoryContext(ctx context.Context, channel string, params HistoryParameters) (*History, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"channel": {channel},
}
if params.Latest != DEFAULT_HISTORY_LATEST {
@ -132,7 +135,8 @@ func (api *Client) GetIMHistoryContext(ctx context.Context, channel string, para
values.Add("unreads", "0")
}
}
response, err := imRequest(ctx, "im.history", values, api.debug)
response, err := imRequest(ctx, api.httpclient, "im.history", values, api.debug)
if err != nil {
return nil, err
}
@ -147,9 +151,10 @@ func (api *Client) GetIMChannels() ([]IM, error) {
// GetIMChannelsContext returns the list of direct message channels with a custom context
func (api *Client) GetIMChannelsContext(ctx context.Context) ([]IM, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
response, err := imRequest(ctx, "im.list", values, api.debug)
response, err := imRequest(ctx, api.httpclient, "im.list", values, api.debug)
if err != nil {
return nil, err
}

53
vendor/github.com/nlopes/slack/logger.go generated vendored Normal file
View File

@ -0,0 +1,53 @@
package slack
import (
"fmt"
"sync"
)
// SetLogger let's library users supply a logger, so that api debugging
// can be logged along with the application's debugging info.
func SetLogger(l logProvider) {
loggerMutex.Lock()
logger = ilogger{logProvider: l}
loggerMutex.Unlock()
}
var (
loggerMutex = new(sync.Mutex)
logger logInternal // A logger that can be set by consumers
)
// logProvider is a logger interface compatible with both stdlib and some
// 3rd party loggers such as logrus.
type logProvider interface {
Output(int, string) error
}
// logInternal represents the internal logging api we use.
type logInternal interface {
Print(...interface{})
Printf(string, ...interface{})
Println(...interface{})
Output(int, string) error
}
// ilogger implements the additional methods used by our internal logging.
type ilogger struct {
logProvider
}
// Println replicates the behaviour of the standard logger.
func (t ilogger) Println(v ...interface{}) {
t.Output(2, fmt.Sprintln(v...))
}
// Printf replicates the behaviour of the standard logger.
func (t ilogger) Printf(format string, v ...interface{}) {
t.Output(2, fmt.Sprintf(format, v...))
}
// Print replicates the behaviour of the standard logger.
func (t ilogger) Print(v ...interface{}) {
t.Output(2, fmt.Sprint(v...))
}

View File

@ -2,7 +2,8 @@ package slack
// OutgoingMessage is used for the realtime API, and seems incomplete.
type OutgoingMessage struct {
ID int `json:"id"`
ID int `json:"id"`
// channel ID
Channel string `json:"channel,omitempty"`
Text string `json:"text,omitempty"`
Type string `json:"type,omitempty"`
@ -28,6 +29,9 @@ type Msg struct {
PinnedTo []string `json:"pinned_to, omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
Edited *Edited `json:"edited,omitempty"`
LastRead string `json:"last_read,omitempty"`
Subscribed bool `json:"subscribed,omitempty"`
UnreadCount int `json:"unread_count,omitempty"`
// Message Subtypes
SubType string `json:"subtype,omitempty"`
@ -81,6 +85,10 @@ type Msg struct {
// reactions
Reactions []ItemReaction `json:"reactions,omitempty"`
// slash commands and interactive messages
ResponseType string `json:"response_type,omitempty"`
ReplaceOriginal bool `json:"replace_original,omitempty"`
}
// Icon is used for bot messages
@ -121,12 +129,12 @@ type Pong struct {
// NewOutgoingMessage prepares an OutgoingMessage that the user can
// use to send a message. Use this function to properly set the
// messageID.
func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage {
func (rtm *RTM) NewOutgoingMessage(text string, channelID string) *OutgoingMessage {
id := rtm.idGen.Next()
return &OutgoingMessage{
ID: id,
Type: "message",
Channel: channel,
Channel: channelID,
Text: text,
}
}
@ -134,11 +142,11 @@ func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage
// NewTypingMessage prepares an OutgoingMessage that the user can
// use to send as a typing indicator. Use this function to properly set the
// messageID.
func (rtm *RTM) NewTypingMessage(channel string) *OutgoingMessage {
func (rtm *RTM) NewTypingMessage(channelID string) *OutgoingMessage {
id := rtm.idGen.Next()
return &OutgoingMessage{
ID: id,
Type: "typing",
Channel: channel,
Channel: channelID,
}
}

View File

@ -13,24 +13,11 @@ import (
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
// HTTPRequester defines the minimal interface needed for an http.Client to be implemented.
//
// Use it in conjunction with the SetHTTPClient function to allow for other capabilities
// like a tracing http.Client
type HTTPRequester interface {
Do(*http.Request) (*http.Response, error)
}
var customHTTPClient HTTPRequester
// HTTPClient sets a custom http.Client
// deprecated: in favor of SetHTTPClient()
var HTTPClient = &http.Client{}
type WebResponse struct {
Ok bool `json:"ok"`
Error *WebError `json:"error"`
@ -42,6 +29,14 @@ func (s WebError) Error() string {
return string(s)
}
type RateLimitedError struct {
RetryAfter time.Duration
}
func (e *RateLimitedError) Error() string {
return fmt.Sprintf("Slack rate limit exceeded, retry after %s", e.RetryAfter)
}
func fileUploadReq(ctx context.Context, path, fieldname, filename string, values url.Values, r io.Reader) (*http.Request, error) {
body := &bytes.Buffer{}
wr := multipart.NewWriter(body)
@ -79,15 +74,10 @@ func parseResponseBody(body io.ReadCloser, intf *interface{}, debug bool) error
logger.Printf("parseResponseBody: %s\n", string(response))
}
err = json.Unmarshal(response, &intf)
if err != nil {
return err
}
return nil
return json.Unmarshal(response, &intf)
}
func postLocalWithMultipartResponse(ctx context.Context, path, fpath, fieldname string, values url.Values, intf interface{}, debug bool) error {
func postLocalWithMultipartResponse(ctx context.Context, client HTTPRequester, path, fpath, fieldname string, values url.Values, intf interface{}, debug bool) error {
fullpath, err := filepath.Abs(fpath)
if err != nil {
return err
@ -97,23 +87,31 @@ func postLocalWithMultipartResponse(ctx context.Context, path, fpath, fieldname
return err
}
defer file.Close()
return postWithMultipartResponse(ctx, path, filepath.Base(fpath), fieldname, values, file, intf, debug)
return postWithMultipartResponse(ctx, client, path, filepath.Base(fpath), fieldname, values, file, intf, debug)
}
func postWithMultipartResponse(ctx context.Context, path, name, fieldname string, values url.Values, r io.Reader, intf interface{}, debug bool) error {
func postWithMultipartResponse(ctx context.Context, client HTTPRequester, path, name, fieldname string, values url.Values, r io.Reader, intf interface{}, debug bool) error {
req, err := fileUploadReq(ctx, SLACK_API+path, fieldname, name, values, r)
if err != nil {
return err
}
req = req.WithContext(ctx)
resp, err := getHTTPClient().Do(req)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64)
if err != nil {
return err
}
return &RateLimitedError{time.Duration(retry) * time.Second}
}
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
if resp.StatusCode != 200 {
if resp.StatusCode != http.StatusOK {
logResponse(resp, debug)
return fmt.Errorf("Slack server error: %s.", resp.Status)
}
@ -121,7 +119,7 @@ func postWithMultipartResponse(ctx context.Context, path, name, fieldname string
return parseResponseBody(resp.Body, &intf, debug)
}
func postForm(ctx context.Context, endpoint string, values url.Values, intf interface{}, debug bool) error {
func postForm(ctx context.Context, client HTTPRequester, endpoint string, values url.Values, intf interface{}, debug bool) error {
reqBody := strings.NewReader(values.Encode())
req, err := http.NewRequest("POST", endpoint, reqBody)
if err != nil {
@ -130,14 +128,22 @@ func postForm(ctx context.Context, endpoint string, values url.Values, intf inte
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req = req.WithContext(ctx)
resp, err := getHTTPClient().Do(req)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64)
if err != nil {
return err
}
return &RateLimitedError{time.Duration(retry) * time.Second}
}
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
if resp.StatusCode != 200 {
if resp.StatusCode != http.StatusOK {
logResponse(resp, debug)
return fmt.Errorf("Slack server error: %s.", resp.Status)
}
@ -145,13 +151,13 @@ func postForm(ctx context.Context, endpoint string, values url.Values, intf inte
return parseResponseBody(resp.Body, &intf, debug)
}
func post(ctx context.Context, path string, values url.Values, intf interface{}, debug bool) error {
return postForm(ctx, SLACK_API+path, values, intf, debug)
func post(ctx context.Context, client HTTPRequester, path string, values url.Values, intf interface{}, debug bool) error {
return postForm(ctx, client, SLACK_API+path, values, intf, debug)
}
func parseAdminResponse(ctx context.Context, method string, teamName string, values url.Values, intf interface{}, debug bool) error {
func parseAdminResponse(ctx context.Context, client HTTPRequester, method string, teamName string, values url.Values, intf interface{}, debug bool) error {
endpoint := fmt.Sprintf(SLACK_WEB_API_FORMAT, teamName, method, time.Now().Unix())
return postForm(ctx, endpoint, values, intf, debug)
return postForm(ctx, client, endpoint, values, intf, debug)
}
func logResponse(resp *http.Response, debug bool) error {
@ -167,17 +173,10 @@ func logResponse(resp *http.Response, debug bool) error {
return nil
}
func getHTTPClient() HTTPRequester {
if customHTTPClient != nil {
return customHTTPClient
}
return HTTPClient
}
// SetHTTPClient allows you to specify a custom http.Client
// Use this instead of the package level HTTPClient variable if you want to use a custom client like the
// Stackdriver Trace HTTPClient https://godoc.org/cloud.google.com/go/trace#HTTPClient
func SetHTTPClient(client HTTPRequester) {
customHTTPClient = client
func okJsonHandler(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "application/json")
response, _ := json.Marshal(SlackResponse{
Ok: true,
})
rw.Write(response)
}

View File

@ -55,7 +55,7 @@ func GetOAuthResponseContext(ctx context.Context, clientID, clientSecret, code,
"redirect_uri": {redirectURI},
}
response := &OAuthResponse{}
err = post(ctx, "oauth.access", values, response, debug)
err = post(ctx, customHTTPClient, "oauth.access", values, response, debug)
if err != nil {
return nil, err
}

View File

@ -21,7 +21,7 @@ func (api *Client) AddPin(channel string, item ItemRef) error {
func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemRef) error {
values := url.Values{
"channel": {channel},
"token": {api.config.token},
"token": {api.token},
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
@ -32,8 +32,9 @@ func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemR
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post(ctx, "pins.add", values, response, api.debug); err != nil {
if err := post(ctx, api.httpclient, "pins.add", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
@ -51,7 +52,7 @@ func (api *Client) RemovePin(channel string, item ItemRef) error {
func (api *Client) RemovePinContext(ctx context.Context, channel string, item ItemRef) error {
values := url.Values{
"channel": {channel},
"token": {api.config.token},
"token": {api.token},
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
@ -62,8 +63,9 @@ func (api *Client) RemovePinContext(ctx context.Context, channel string, item It
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post(ctx, "pins.remove", values, response, api.debug); err != nil {
if err := post(ctx, api.httpclient, "pins.remove", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
@ -81,10 +83,11 @@ func (api *Client) ListPins(channel string) ([]Item, *Paging, error) {
func (api *Client) ListPinsContext(ctx context.Context, channel string) ([]Item, *Paging, error) {
values := url.Values{
"channel": {channel},
"token": {api.config.token},
"token": {api.token},
}
response := &listPinsResponseFull{}
err := post(ctx, "pins.list", values, response, api.debug)
err := post(ctx, api.httpclient, "pins.list", values, response, api.debug)
if err != nil {
return nil, nil, err
}

View File

@ -136,7 +136,7 @@ func (api *Client) AddReaction(name string, item ItemRef) error {
// AddReactionContext adds a reaction emoji to a message, file or file comment with a custom context.
func (api *Client) AddReactionContext(ctx context.Context, name string, item ItemRef) error {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if name != "" {
values.Set("name", name)
@ -153,8 +153,9 @@ func (api *Client) AddReactionContext(ctx context.Context, name string, item Ite
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post(ctx, "reactions.add", values, response, api.debug); err != nil {
if err := post(ctx, api.httpclient, "reactions.add", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
@ -171,7 +172,7 @@ func (api *Client) RemoveReaction(name string, item ItemRef) error {
// RemoveReactionContext removes a reaction emoji from a message, file or file comment with a custom context.
func (api *Client) RemoveReactionContext(ctx context.Context, name string, item ItemRef) error {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if name != "" {
values.Set("name", name)
@ -188,8 +189,9 @@ func (api *Client) RemoveReactionContext(ctx context.Context, name string, item
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post(ctx, "reactions.remove", values, response, api.debug); err != nil {
if err := post(ctx, api.httpclient, "reactions.remove", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
@ -206,7 +208,7 @@ func (api *Client) GetReactions(item ItemRef, params GetReactionsParameters) ([]
// GetReactionsContext returns details about the reactions on an item with a custom context
func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if item.Channel != "" {
values.Set("channel", string(item.Channel))
@ -223,8 +225,9 @@ func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params
if params.Full != DEFAULT_REACTIONS_FULL {
values.Set("full", strconv.FormatBool(params.Full))
}
response := &getReactionsResponseFull{}
if err := post(ctx, "reactions.get", values, response, api.debug); err != nil {
if err := post(ctx, api.httpclient, "reactions.get", values, response, api.debug); err != nil {
return nil, err
}
if !response.Ok {
@ -241,7 +244,7 @@ func (api *Client) ListReactions(params ListReactionsParameters) ([]ReactedItem,
// ListReactionsContext returns information about the items a user reacted to with a custom context.
func (api *Client) ListReactionsContext(ctx context.Context, params ListReactionsParameters) ([]ReactedItem, *Paging, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if params.User != DEFAULT_REACTIONS_USER {
values.Add("user", params.User)
@ -255,8 +258,9 @@ func (api *Client) ListReactionsContext(ctx context.Context, params ListReaction
if params.Full != DEFAULT_REACTIONS_FULL {
values.Add("full", strconv.FormatBool(params.Full))
}
response := &listReactionsResponseFull{}
err := post(ctx, "reactions.list", values, response, api.debug)
err := post(ctx, api.httpclient, "reactions.list", values, response, api.debug)
if err != nil {
return nil, nil, err
}

View File

@ -8,11 +8,18 @@ import (
"time"
)
const (
websocketDefaultTimeout = 10 * time.Second
)
// StartRTM calls the "rtm.start" endpoint and returns the provided URL and the full Info block.
//
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
func (api *Client) StartRTM() (info *Info, websocketURL string, err error) {
return api.StartRTMContext(context.Background())
ctx, cancel := context.WithTimeout(context.Background(), websocketDefaultTimeout)
defer cancel()
return api.StartRTMContext(ctx)
}
// StartRTMContext calls the "rtm.start" endpoint and returns the provided URL and the full Info block with a custom context.
@ -20,31 +27,25 @@ func (api *Client) StartRTM() (info *Info, websocketURL string, err error) {
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
func (api *Client) StartRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) {
response := &infoResponseFull{}
err = post(ctx, "rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
err = post(ctx, api.httpclient, "rtm.start", url.Values{"token": {api.token}}, response, api.debug)
if err != nil {
return nil, "", fmt.Errorf("post: %s", err)
}
if !response.Ok {
return nil, "", response.Error
}
// websocket.Dial does not accept url without the port (yet)
// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
// but slack returns the address with no port, so we have to fix it
api.Debugln("Using URL:", response.Info.URL)
websocketURL, err = websocketizeURLPort(response.Info.URL)
if err != nil {
return nil, "", fmt.Errorf("parsing response URL: %s", err)
}
return &response.Info, websocketURL, nil
return &response.Info, response.Info.URL, nil
}
// ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block.
//
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
func (api *Client) ConnectRTM() (info *Info, websocketURL string, err error) {
return api.ConnectRTMContext(context.Background())
ctx, cancel := context.WithTimeout(context.Background(), websocketDefaultTimeout)
defer cancel()
return api.ConnectRTMContext(ctx)
}
// ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block with a custom context.
@ -52,24 +53,16 @@ func (api *Client) ConnectRTM() (info *Info, websocketURL string, err error) {
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
func (api *Client) ConnectRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) {
response := &infoResponseFull{}
err = post(ctx, "rtm.connect", url.Values{"token": {api.config.token}}, response, api.debug)
err = post(ctx, api.httpclient, "rtm.connect", url.Values{"token": {api.token}}, response, api.debug)
if err != nil {
api.Debugf("Failed to connect to RTM: %s", err)
return nil, "", fmt.Errorf("post: %s", err)
}
if !response.Ok {
return nil, "", response.Error
}
// websocket.Dial does not accept url without the port (yet)
// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
// but slack returns the address with no port, so we have to fix it
api.Debugln("Using URL:", response.Info.URL)
websocketURL, err = websocketizeURLPort(response.Info.URL)
if err != nil {
return nil, "", fmt.Errorf("parsing response URL: %s", err)
}
return &response.Info, websocketURL, nil
return &response.Info, response.Info.URL, nil
}
// NewRTM returns a RTM, which provides a fully managed connection to
@ -90,6 +83,7 @@ func (api *Client) NewRTMWithOptions(options *RTMOptions) *RTM {
isConnected: false,
wasIntentional: true,
killChannel: make(chan bool),
disconnected: make(chan struct{}),
forcePing: make(chan bool),
rawEvents: make(chan json.RawMessage),
idGen: NewSafeID(1),

View File

@ -83,7 +83,7 @@ func NewSearchParameters() SearchParameters {
func (api *Client) _search(ctx context.Context, path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"query": {query},
}
if params.Sort != DEFAULT_SEARCH_SORT {
@ -101,8 +101,9 @@ func (api *Client) _search(ctx context.Context, path, query string, params Searc
if params.Page != DEFAULT_SEARCH_PAGE {
values.Add("page", strconv.Itoa(params.Page))
}
response = &searchResponseFull{}
err := post(ctx, path, values, response, api.debug)
err := post(ctx, api.httpclient, path, values, response, api.debug)
if err != nil {
return nil, err
}

View File

@ -3,18 +3,38 @@ package slack
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"os"
)
var logger *log.Logger // A logger that can be set by consumers
/*
Added as a var so that we can change this for testing purposes
*/
// Added as a var so that we can change this for testing purposes
var SLACK_API string = "https://slack.com/api/"
var SLACK_WEB_API_FORMAT string = "https://%s.slack.com/api/users.admin.%s?t=%s"
// HTTPClient sets a custom http.Client
// deprecated: in favor of SetHTTPClient()
var HTTPClient = &http.Client{}
var customHTTPClient HTTPRequester = HTTPClient
// HTTPRequester defines the minimal interface needed for an http.Client to be implemented.
//
// Use it in conjunction with the SetHTTPClient function to allow for other capabilities
// like a tracing http.Client
type HTTPRequester interface {
Do(*http.Request) (*http.Response, error)
}
// SetHTTPClient allows you to specify a custom http.Client
// Use this instead of the package level HTTPClient variable if you want to use a custom client like the
// Stackdriver Trace HTTPClient https://godoc.org/cloud.google.com/go/trace#HTTPClient
func SetHTTPClient(client HTTPRequester) {
customHTTPClient = client
}
type SlackResponse struct {
Ok bool `json:"ok"`
Error string `json:"error"`
@ -34,22 +54,33 @@ type authTestResponseFull struct {
}
type Client struct {
config struct {
token string
token string
info Info
debug bool
httpclient HTTPRequester
}
// Option defines an option for a Client
type Option func(*Client)
// OptionHTTPClient - provide a custom http client to the slack client.
func OptionHTTPClient(c HTTPRequester) func(*Client) {
return func(s *Client) {
s.httpclient = c
}
info Info
debug bool
}
// SetLogger let's library users supply a logger, so that api debugging
// can be logged along with the application's debugging info.
func SetLogger(l *log.Logger) {
logger = l
}
// New builds a slack client from the provided token and options.
func New(token string, options ...Option) *Client {
s := &Client{
token: token,
httpclient: customHTTPClient,
}
for _, opt := range options {
opt(s)
}
func New(token string) *Client {
s := &Client{}
s.config.token = token
return s
}
@ -60,14 +91,19 @@ func (api *Client) AuthTest() (response *AuthTestResponse, error error) {
// AuthTestContext tests if the user is able to do authenticated requests or not with a custom context
func (api *Client) AuthTestContext(ctx context.Context) (response *AuthTestResponse, error error) {
api.Debugf("Challenging auth...")
responseFull := &authTestResponseFull{}
err := post(ctx, "auth.test", url.Values{"token": {api.config.token}}, responseFull, api.debug)
err := post(ctx, api.httpclient, "auth.test", url.Values{"token": {api.token}}, responseFull, api.debug)
if err != nil {
api.Debugf("failed to test for auth: %s", err)
return nil, err
}
if !responseFull.Ok {
api.Debugf("auth response was not Ok: %s", responseFull.Error)
return nil, errors.New(responseFull.Error)
}
api.Debugf("Auth challenge was successful with response %+v", responseFull.AuthTestResponse)
return &responseFull.AuthTestResponse, nil
}
@ -77,18 +113,20 @@ func (api *Client) AuthTestContext(ctx context.Context) (response *AuthTestRespo
func (api *Client) SetDebug(debug bool) {
api.debug = debug
if debug && logger == nil {
logger = log.New(os.Stdout, "nlopes/slack", log.LstdFlags|log.Lshortfile)
SetLogger(log.New(os.Stdout, "nlopes/slack", log.LstdFlags|log.Lshortfile))
}
}
// Debugf print a formatted debug line.
func (api *Client) Debugf(format string, v ...interface{}) {
if api.debug {
logger.Printf(format, v...)
logger.Output(2, fmt.Sprintf(format, v...))
}
}
// Debugln print a debug line.
func (api *Client) Debugln(v ...interface{}) {
if api.debug {
logger.Println(v...)
logger.Output(2, fmt.Sprintln(v...))
}
}

49
vendor/github.com/nlopes/slack/slash.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package slack
import (
"net/http"
)
// SlashCommand contains information about a request of the slash command
type SlashCommand struct {
Token string `json:"token"`
TeamID string `json:"team_id"`
TeamDomain string `json:"team_domain"`
ChannelID string `json:"channel_id"`
ChannelName string `json:"channel_name"`
UserID string `json:"user_id"`
UserName string `json:"user_name"`
Command string `json:"command"`
Text string `json:"text"`
ResponseURL string `json:"response_url"`
TriggerID string `json:"trigger_id"`
}
// SlashCommandParse will parse the request of the slash command
func SlashCommandParse(r *http.Request) (s SlashCommand, err error) {
if err = r.ParseForm(); err != nil {
return s, err
}
s.Token = r.PostForm.Get("token")
s.TeamID = r.PostForm.Get("team_id")
s.TeamDomain = r.PostForm.Get("team_domain")
s.ChannelID = r.PostForm.Get("channel_id")
s.ChannelName = r.PostForm.Get("channel_name")
s.UserID = r.PostForm.Get("user_id")
s.UserName = r.PostForm.Get("user_name")
s.Command = r.PostForm.Get("command")
s.Text = r.PostForm.Get("text")
s.ResponseURL = r.PostForm.Get("response_url")
s.TriggerID = r.PostForm.Get("trigger_id")
return s, nil
}
// ValidateToken validates verificationTokens
func (s SlashCommand) ValidateToken(verificationTokens ...string) bool {
for _, token := range verificationTokens {
if s.Token == token {
return true
}
}
return false
}

View File

@ -45,7 +45,7 @@ func (api *Client) AddStar(channel string, item ItemRef) error {
func (api *Client) AddStarContext(ctx context.Context, channel string, item ItemRef) error {
values := url.Values{
"channel": {channel},
"token": {api.config.token},
"token": {api.token},
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
@ -56,8 +56,9 @@ func (api *Client) AddStarContext(ctx context.Context, channel string, item Item
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post(ctx, "stars.add", values, response, api.debug); err != nil {
if err := post(ctx, api.httpclient, "stars.add", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
@ -75,7 +76,7 @@ func (api *Client) RemoveStar(channel string, item ItemRef) error {
func (api *Client) RemoveStarContext(ctx context.Context, channel string, item ItemRef) error {
values := url.Values{
"channel": {channel},
"token": {api.config.token},
"token": {api.token},
}
if item.Timestamp != "" {
values.Set("timestamp", string(item.Timestamp))
@ -86,8 +87,9 @@ func (api *Client) RemoveStarContext(ctx context.Context, channel string, item I
if item.Comment != "" {
values.Set("file_comment", string(item.Comment))
}
response := &SlackResponse{}
if err := post(ctx, "stars.remove", values, response, api.debug); err != nil {
if err := post(ctx, api.httpclient, "stars.remove", values, response, api.debug); err != nil {
return err
}
if !response.Ok {
@ -104,7 +106,7 @@ func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
// ListStarsContext returns information about the stars a user added with a custom context
func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) ([]Item, *Paging, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if params.User != DEFAULT_STARS_USER {
values.Add("user", params.User)
@ -115,8 +117,9 @@ func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters)
if params.Page != DEFAULT_STARS_PAGE {
values.Add("page", strconv.Itoa(params.Page))
}
response := &listResponseFull{}
err := post(ctx, "stars.list", values, response, api.debug)
err := post(ctx, api.httpclient, "stars.list", values, response, api.debug)
if err != nil {
return nil, nil, err
}

View File

@ -67,9 +67,9 @@ func NewAccessLogParameters() AccessLogParameters {
}
}
func teamRequest(ctx context.Context, path string, values url.Values, debug bool) (*TeamResponse, error) {
func teamRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*TeamResponse, error) {
response := &TeamResponse{}
err := post(ctx, path, values, response, debug)
err := post(ctx, client, path, values, response, debug)
if err != nil {
return nil, err
}
@ -81,9 +81,9 @@ func teamRequest(ctx context.Context, path string, values url.Values, debug bool
return response, nil
}
func billableInfoRequest(ctx context.Context, path string, values url.Values, debug bool) (map[string]BillingActive, error) {
func billableInfoRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (map[string]BillingActive, error) {
response := &BillableInfoResponse{}
err := post(ctx, path, values, response, debug)
err := post(ctx, client, path, values, response, debug)
if err != nil {
return nil, err
}
@ -95,9 +95,9 @@ func billableInfoRequest(ctx context.Context, path string, values url.Values, de
return response.BillableInfo, nil
}
func accessLogsRequest(ctx context.Context, path string, values url.Values, debug bool) (*LoginResponse, error) {
func accessLogsRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*LoginResponse, error) {
response := &LoginResponse{}
err := post(ctx, path, values, response, debug)
err := post(ctx, client, path, values, response, debug)
if err != nil {
return nil, err
}
@ -115,10 +115,10 @@ func (api *Client) GetTeamInfo() (*TeamInfo, error) {
// GetTeamInfoContext gets the Team Information of the user with a custom context
func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
response, err := teamRequest(ctx, "team.info", values, api.debug)
response, err := teamRequest(ctx, api.httpclient, "team.info", values, api.debug)
if err != nil {
return nil, err
}
@ -133,7 +133,7 @@ func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging,
// GetAccessLogsContext retrieves a page of logins according to the parameters given with a custom context
func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogParameters) ([]Login, *Paging, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if params.Count != DEFAULT_LOGINS_COUNT {
values.Add("count", strconv.Itoa(params.Count))
@ -141,7 +141,8 @@ func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogPar
if params.Page != DEFAULT_LOGINS_PAGE {
values.Add("page", strconv.Itoa(params.Page))
}
response, err := accessLogsRequest(ctx, "team.accessLogs", values, api.debug)
response, err := accessLogsRequest(ctx, api.httpclient, "team.accessLogs", values, api.debug)
if err != nil {
return nil, nil, err
}
@ -154,11 +155,11 @@ func (api *Client) GetBillableInfo(user string) (map[string]BillingActive, error
func (api *Client) GetBillableInfoContext(ctx context.Context, user string) (map[string]BillingActive, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"user": {user},
}
return billableInfoRequest(ctx, "team.billableInfo", values, api.debug)
return billableInfoRequest(ctx, api.httpclient, "team.billableInfo", values, api.debug)
}
// GetBillableInfoForTeam returns the billing_active status of all users on the team.
@ -169,8 +170,8 @@ func (api *Client) GetBillableInfoForTeam() (map[string]BillingActive, error) {
// GetBillableInfoForTeamContext returns the billing_active status of all users on the team with a custom context
func (api *Client) GetBillableInfoForTeamContext(ctx context.Context) (map[string]BillingActive, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
return billableInfoRequest(ctx, "team.billableInfo", values, api.debug)
return billableInfoRequest(ctx, api.httpclient, "team.billableInfo", values, api.debug)
}

View File

@ -40,9 +40,9 @@ type userGroupResponseFull struct {
SlackResponse
}
func userGroupRequest(ctx context.Context, path string, values url.Values, debug bool) (*userGroupResponseFull, error) {
func userGroupRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*userGroupResponseFull, error) {
response := &userGroupResponseFull{}
err := post(ctx, path, values, response, debug)
err := post(ctx, client, path, values, response, debug)
if err != nil {
return nil, err
}
@ -60,7 +60,7 @@ func (api *Client) CreateUserGroup(userGroup UserGroup) (UserGroup, error) {
// CreateUserGroupContext creates a new user group with a custom context
func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"name": {userGroup.Name},
}
@ -76,7 +76,7 @@ func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGro
values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")}
}
response, err := userGroupRequest(ctx, "usergroups.create", values, api.debug)
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.create", values, api.debug)
if err != nil {
return UserGroup{}, err
}
@ -91,11 +91,11 @@ func (api *Client) DisableUserGroup(userGroup string) (UserGroup, error) {
// DisableUserGroupContext disables an existing user group with a custom context
func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"usergroup": {userGroup},
}
response, err := userGroupRequest(ctx, "usergroups.disable", values, api.debug)
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.disable", values, api.debug)
if err != nil {
return UserGroup{}, err
}
@ -110,11 +110,11 @@ func (api *Client) EnableUserGroup(userGroup string) (UserGroup, error) {
// EnableUserGroupContext enables an existing user group with a custom context
func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"usergroup": {userGroup},
}
response, err := userGroupRequest(ctx, "usergroups.enable", values, api.debug)
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.enable", values, api.debug)
if err != nil {
return UserGroup{}, err
}
@ -129,10 +129,10 @@ func (api *Client) GetUserGroups() ([]UserGroup, error) {
// GetUserGroupsContext returns a list of user groups for the team with a custom context
func (api *Client) GetUserGroupsContext(ctx context.Context) ([]UserGroup, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
response, err := userGroupRequest(ctx, "usergroups.list", values, api.debug)
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.list", values, api.debug)
if err != nil {
return nil, err
}
@ -147,7 +147,7 @@ func (api *Client) UpdateUserGroup(userGroup UserGroup) (UserGroup, error) {
// UpdateUserGroupContext will update an existing user group with a custom context
func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"usergroup": {userGroup.ID},
}
@ -163,7 +163,7 @@ func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGro
values["description"] = []string{userGroup.Description}
}
response, err := userGroupRequest(ctx, "usergroups.update", values, api.debug)
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.update", values, api.debug)
if err != nil {
return UserGroup{}, err
}
@ -178,11 +178,11 @@ func (api *Client) GetUserGroupMembers(userGroup string) ([]string, error) {
// GetUserGroupMembersContext will retrieve the current list of users in a group with a custom context
func (api *Client) GetUserGroupMembersContext(ctx context.Context, userGroup string) ([]string, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"usergroup": {userGroup},
}
response, err := userGroupRequest(ctx, "usergroups.users.list", values, api.debug)
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.users.list", values, api.debug)
if err != nil {
return []string{}, err
}
@ -197,12 +197,12 @@ func (api *Client) UpdateUserGroupMembers(userGroup string, members string) (Use
// UpdateUserGroupMembersContext will update the members of an existing user group with a custom context
func (api *Client) UpdateUserGroupMembersContext(ctx context.Context, userGroup string, members string) (UserGroup, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"usergroup": {userGroup},
"users": {members},
}
response, err := userGroupRequest(ctx, "usergroups.users.update", values, api.debug)
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.users.update", values, api.debug)
if err != nil {
return UserGroup{}, err
}

View File

@ -15,29 +15,33 @@ const (
// UserProfile contains all the information details of a given user
type UserProfile struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
RealName string `json:"real_name"`
RealNameNormalized string `json:"real_name_normalized"`
Email string `json:"email"`
Skype string `json:"skype"`
Phone string `json:"phone"`
Image24 string `json:"image_24"`
Image32 string `json:"image_32"`
Image48 string `json:"image_48"`
Image72 string `json:"image_72"`
Image192 string `json:"image_192"`
ImageOriginal string `json:"image_original"`
Title string `json:"title"`
BotID string `json:"bot_id,omitempty"`
ApiAppID string `json:"api_app_id,omitempty"`
StatusText string `json:"status_text,omitempty"`
StatusEmoji string `json:"status_emoji,omitempty"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
RealName string `json:"real_name"`
RealNameNormalized string `json:"real_name_normalized"`
DisplayName string `json:"display_name"`
DisplayNameNormalized string `json:"display_name_normalized"`
Email string `json:"email"`
Skype string `json:"skype"`
Phone string `json:"phone"`
Image24 string `json:"image_24"`
Image32 string `json:"image_32"`
Image48 string `json:"image_48"`
Image72 string `json:"image_72"`
Image192 string `json:"image_192"`
ImageOriginal string `json:"image_original"`
Title string `json:"title"`
BotID string `json:"bot_id,omitempty"`
ApiAppID string `json:"api_app_id,omitempty"`
StatusText string `json:"status_text,omitempty"`
StatusEmoji string `json:"status_emoji,omitempty"`
Team string `json:"team"`
}
// User contains all the information of a user
type User struct {
ID string `json:"id"`
TeamID string `json:"team_id"`
Name string `json:"name"`
Deleted bool `json:"deleted"`
Color string `json:"color"`
@ -52,9 +56,12 @@ type User struct {
IsPrimaryOwner bool `json:"is_primary_owner"`
IsRestricted bool `json:"is_restricted"`
IsUltraRestricted bool `json:"is_ultra_restricted"`
IsStranger bool `json:"is_stranger"`
IsAppUser bool `json:"is_app_user"`
Has2FA bool `json:"has_2fa"`
HasFiles bool `json:"has_files"`
Presence string `json:"presence"`
Locale string `json:"locale"`
}
// UserPresence contains details about a user online status
@ -121,9 +128,9 @@ func NewUserSetPhotoParams() UserSetPhotoParams {
}
}
func userRequest(ctx context.Context, path string, values url.Values, debug bool) (*userResponseFull, error) {
func userRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*userResponseFull, error) {
response := &userResponseFull{}
err := post(ctx, path, values, response, debug)
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
if err != nil {
return nil, err
}
@ -141,10 +148,11 @@ func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
// GetUserPresenceContext will retrieve the current presence status of given user with a custom context.
func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*UserPresence, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"user": {user},
}
response, err := userRequest(ctx, "users.getPresence", values, api.debug)
response, err := userRequest(ctx, api.httpclient, "users.getPresence", values, api.debug)
if err != nil {
return nil, err
}
@ -159,10 +167,11 @@ func (api *Client) GetUserInfo(user string) (*User, error) {
// GetUserInfoContext will retrieve the complete user information with a custom context
func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"user": {user},
}
response, err := userRequest(ctx, "users.info", values, api.debug)
response, err := userRequest(ctx, api.httpclient, "users.info", values, api.debug)
if err != nil {
return nil, err
}
@ -177,30 +186,50 @@ 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) ([]User, error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"presence": {"1"},
}
response, err := userRequest(ctx, "users.list", values, api.debug)
response, err := userRequest(ctx, api.httpclient, "users.list", values, api.debug)
if err != nil {
return nil, err
}
return response.Members, nil
}
// GetUserByEmail will retrieve the complete user information by email
func (api *Client) GetUserByEmail(email string) (*User, error) {
return api.GetUserByEmailContext(context.Background(), email)
}
// GetUserByEmailContext will retrieve the complete user information by email with a custom context
func (api *Client) GetUserByEmailContext(ctx context.Context, email string) (*User, error) {
values := url.Values{
"token": {api.token},
"email": {email},
}
response, err := userRequest(ctx, api.httpclient, "users.lookupByEmail", values, api.debug)
if err != nil {
return nil, err
}
return &response.User, nil
}
// SetUserAsActive marks the currently authenticated user as active
func (api *Client) SetUserAsActive() error {
return api.SetUserAsActiveContext(context.Background())
}
// SetUserAsActiveContext marks the currently authenticated user as active with a custom context
func (api *Client) SetUserAsActiveContext(ctx context.Context) error {
func (api *Client) SetUserAsActiveContext(ctx context.Context) (err error) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
_, err := userRequest(ctx, "users.setActive", values, api.debug)
if err != nil {
if _, err := userRequest(ctx, api.httpclient, "users.setActive", values, api.debug); err != nil {
return err
}
return nil
}
@ -212,10 +241,11 @@ func (api *Client) SetUserPresence(presence string) error {
// SetUserPresenceContext changes the currently authenticated user presence with a custom context
func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) error {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"presence": {presence},
}
_, err := userRequest(ctx, "users.setPresence", values, api.debug)
_, err := userRequest(ctx, api.httpclient, "users.setPresence", values, api.debug)
if err != nil {
return err
}
@ -231,10 +261,11 @@ 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) {
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
response := &UserIdentityResponse{}
err := post(ctx, "users.identity", values, response, api.debug)
err := postForm(ctx, api.httpclient, SLACK_API+"users.identity", values, response, api.debug)
if err != nil {
return nil, err
}
@ -245,15 +276,15 @@ func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityRes
}
// SetUserPhoto changes the currently authenticated user's profile image
func (api *Client) SetUserPhoto(ctx context.Context, image string, params UserSetPhotoParams) error {
return api.SetUserPhoto(context.Background(), image, params)
func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error {
return api.SetUserPhotoContext(context.Background(), image, params)
}
// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) error {
response := &SlackResponse{}
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
if params.CropX != DEFAULT_USER_PHOTO_CROP_X {
values.Add("crop_x", string(params.CropX))
@ -264,7 +295,8 @@ func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params
if params.CropW != DEFAULT_USER_PHOTO_CROP_W {
values.Add("crop_w", string(params.CropW))
}
err := postLocalWithMultipartResponse(ctx, "users.setPhoto", image, "image", values, response, api.debug)
err := postLocalWithMultipartResponse(ctx, api.httpclient, SLACK_API+"users.setPhoto", image, "image", values, response, api.debug)
if err != nil {
return err
}
@ -283,9 +315,10 @@ func (api *Client) DeleteUserPhoto() error {
func (api *Client) DeleteUserPhotoContext(ctx context.Context) error {
response := &SlackResponse{}
values := url.Values{
"token": {api.config.token},
"token": {api.token},
}
err := post(ctx, "users.deletePhoto", values, response, api.debug)
err := postForm(ctx, api.httpclient, SLACK_API+"users.deletePhoto", values, response, api.debug)
if err != nil {
return err
}
@ -332,13 +365,12 @@ func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, s
}
values := url.Values{
"token": {api.config.token},
"token": {api.token},
"profile": {string(profile)},
}
response := &userResponseFull{}
if err = post(ctx, "users.profile.set", values, response, api.debug); err != nil {
if err = postForm(ctx, api.httpclient, SLACK_API+"users.profile.set", values, response, api.debug); err != nil {
return err
}

View File

@ -5,7 +5,7 @@ import (
"errors"
"time"
"golang.org/x/net/websocket"
"github.com/gorilla/websocket"
)
const (
@ -27,6 +27,7 @@ type RTM struct {
IncomingEvents chan RTMEvent
outgoingMessages chan OutgoingMessage
killChannel chan bool
disconnected chan struct{} // disconnected is closed when Disconnect is invoked, regardless of connection state. Allows for ManagedConnection to not leak.
forcePing chan bool
rawEvents chan json.RawMessage
wasIntentional bool
@ -59,9 +60,14 @@ type RTMOptions struct {
// Disconnect and wait, blocking until a successful disconnection.
func (rtm *RTM) Disconnect() error {
// this channel is always closed on disconnect. lets the ManagedConnection() function
// properly clean up.
close(rtm.disconnected)
if !rtm.isConnected {
return errors.New("Invalid call to Disconnect - Slack API is already disconnected")
}
rtm.killChannel <- true
return nil
}

View File

@ -63,6 +63,13 @@ func (m *MessageTooLongEvent) Error() string {
return fmt.Sprintf("Message too long (max %d characters)", m.MaxLength)
}
// RateLimitEvent is used when Slack warns that rate-limits are being hit.
type RateLimitEvent struct{}
func (e *RateLimitEvent) Error() string {
return "Messages are being sent too fast."
}
// OutgoingErrorEvent contains information in case there were errors sending messages
type OutgoingErrorEvent struct {
Message OutgoingMessage

View File

@ -4,10 +4,11 @@ import (
"encoding/json"
"fmt"
"io"
"net/http"
"reflect"
"time"
"golang.org/x/net/websocket"
"github.com/gorilla/websocket"
)
// ManageConnection can be called on a Slack RTM instance returned by the
@ -33,6 +34,7 @@ func (rtm *RTM) ManageConnection() {
// if err != nil then the connection is sucessful - otherwise it is
// fatal
if err != nil {
rtm.Debugf("Failed to connect with RTM on try %d: %s", connectionCount, err)
return
}
rtm.info = info
@ -44,6 +46,8 @@ func (rtm *RTM) ManageConnection() {
rtm.conn = conn
rtm.isConnected = true
rtm.Debugf("RTM connection succeeded on try %d", connectionCount)
keepRunning := make(chan bool)
// we're now connected (or have failed fatally) so we can set up
// listeners
@ -89,6 +93,7 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke
}
// check for fatal errors - currently only invalid_auth
if sErr, ok := err.(*WebError); ok && (sErr.Error() == "invalid_auth" || sErr.Error() == "account_inactive") {
rtm.Debugf("Invalid auth when connecting with RTM: %s", err)
rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}}
return nil, nil, sErr
}
@ -99,6 +104,15 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke
Attempt: boff.attempts,
ErrorObj: err,
}}
// check if Disconnect() has been invoked.
select {
case _ = <-rtm.disconnected:
rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{Intentional: true}}
return nil, nil, fmt.Errorf("disconnect received while trying to connect")
default:
}
// get time we should wait before attempting to connect again
dur := boff.Duration()
rtm.Debugf("reconnection %d failed: %s", boff.attempts+1, err)
@ -116,16 +130,24 @@ func (rtm *RTM) startRTMAndDial(useRTMStart bool) (*Info, *websocket.Conn, error
var err error
if useRTMStart {
rtm.Debugf("Starting RTM")
info, url, err = rtm.StartRTM()
} else {
rtm.Debugf("Connecting to RTM")
info, url, err = rtm.ConnectRTM()
}
if err != nil {
rtm.Debugf("Failed to start or connect to RTM: %s", err)
return nil, nil, err
}
conn, err := websocketProxyDial(url, "http://api.slack.com")
rtm.Debugf("Dialing to websocket on url %s", url)
// Only use HTTPS for connections to prevent MITM attacks on the connection.
upgradeHeader := http.Header{}
upgradeHeader.Add("Origin", "https://api.slack.com")
conn, _, err := websocket.DefaultDialer.Dial(url, upgradeHeader)
if err != nil {
rtm.Debugf("Failed to dial to the websocket: %s", err)
return nil, nil, err
}
return info, conn, err
@ -208,7 +230,7 @@ func (rtm *RTM) sendWithDeadline(msg interface{}) error {
if err := rtm.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)); err != nil {
return err
}
if err := websocket.JSON.Send(rtm.conn, msg); err != nil {
if err := rtm.conn.WriteJSON(msg); err != nil {
return err
}
// remove write deadline
@ -263,7 +285,7 @@ func (rtm *RTM) ping() error {
// This will block until a frame is available from the websocket.
func (rtm *RTM) receiveIncomingEvent() {
event := json.RawMessage{}
err := websocket.JSON.Receive(rtm.conn, &event)
err := rtm.conn.ReadJSON(&event)
if err == io.EOF {
// EOF's don't seem to signify a failed connection so instead we ignore
// them here and detect a failed connection upon attempting to send a
@ -317,10 +339,19 @@ func (rtm *RTM) handleAck(event json.RawMessage) {
rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
return
}
if ack.Ok {
rtm.IncomingEvents <- RTMEvent{"ack", ack}
} else if ack.RTMResponse.Error != nil {
// As there is no documentation for RTM error-codes, this
// identification of a rate-limit warning is very brittle.
if ack.RTMResponse.Error.Code == -1 && ack.RTMResponse.Error.Msg == "slow down, too many messages..." {
rtm.IncomingEvents <- RTMEvent{"ack_error", &RateLimitEvent{}}
} else {
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
}
} else {
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{fmt.Errorf("ack decode failure")}}
}
}

View File

@ -1,83 +0,0 @@
package slack
import (
"crypto/tls"
"errors"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"golang.org/x/net/websocket"
)
// Taken and reworked from: https://gist.github.com/madmo/8548738
func websocketHTTPConnect(proxy, urlString string) (net.Conn, error) {
p, err := net.Dial("tcp", proxy)
if err != nil {
return nil, err
}
turl, err := url.Parse(urlString)
if err != nil {
return nil, err
}
req := http.Request{
Method: "CONNECT",
URL: &url.URL{},
Host: turl.Host,
}
cc := httputil.NewProxyClientConn(p, nil)
cc.Do(&req)
if err != nil && err != httputil.ErrPersistEOF {
return nil, err
}
rwc, _ := cc.Hijack()
return rwc, nil
}
func websocketProxyDial(urlString, origin string) (ws *websocket.Conn, err error) {
if os.Getenv("HTTP_PROXY") == "" {
return websocket.Dial(urlString, "", origin)
}
purl, err := url.Parse(os.Getenv("HTTP_PROXY"))
if err != nil {
return nil, err
}
config, err := websocket.NewConfig(urlString, origin)
if err != nil {
return nil, err
}
client, err := websocketHTTPConnect(purl.Host, urlString)
if err != nil {
return nil, err
}
switch config.Location.Scheme {
case "ws":
case "wss":
tlsClient := tls.Client(client, &tls.Config{
ServerName: strings.Split(config.Location.Host, ":")[0],
})
err := tlsClient.Handshake()
if err != nil {
tlsClient.Close()
return nil, err
}
client = tlsClient
default:
return nil, errors.New("invalid websocket schema")
}
return websocket.NewClient(config, client)
}

View File

@ -1,20 +0,0 @@
package slack
import (
"net"
"net/url"
)
var portMapping = map[string]string{"ws": "80", "wss": "443"}
func websocketizeURLPort(orig string) (string, error) {
urlObj, err := url.ParseRequestURI(orig)
if err != nil {
return "", err
}
_, _, err = net.SplitHostPort(urlObj.Host)
if err != nil {
return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
}
return orig, nil
}

View File

@ -1,3 +1,5 @@
[![GoDoc](https://godoc.org/github.com/nsf/termbox-go?status.svg)](http://godoc.org/github.com/nsf/termbox-go)
## Termbox
Termbox is a library that provides a minimalistic API which allows the programmer to write text-based user interfaces. The library is crossplatform and has both terminal-based implementations on *nix operating systems and a winapi console based implementation for windows operating systems. The basic idea is an abstraction of the greatest common subset of features available on all major terminals and other terminal-like APIs in a minimalistic fashion. Small API means it is easy to implement, test, maintain and learn it, that's what makes the termbox a distinct library in its area.
@ -33,6 +35,4 @@ There are also some interesting projects using termbox-go:
- [jv](https://github.com/maxzender/jv) helps you view JSON on the command-line.
- [pinger](https://github.com/hirose31/pinger) helps you to monitor numerous hosts using ICMP ECHO_REQUEST.
- [vixl44](https://github.com/sebashwa/vixl44) lets you create pixel art inside your terminal using vim movements
### API reference
[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go)
- [zterm](https://github.com/varunrau/zterm) is a typing game inspired by http://zty.pe/

View File

@ -8,6 +8,7 @@ import "os"
import "os/signal"
import "syscall"
import "runtime"
import "time"
// public API
@ -253,8 +254,8 @@ func CellBuffer() []Cell {
// NOTE: This API is experimental and may change in future.
func ParseEvent(data []byte) Event {
event := Event{Type: EventKey}
ok := extract_event(data, &event)
if !ok {
status := extract_event(data, &event, false)
if status != event_extracted {
return Event{Type: EventNone, N: event.N}
}
return event
@ -303,34 +304,65 @@ func PollRawEvent(data []byte) Event {
// Wait for an event and return it. This is a blocking function call.
func PollEvent() Event {
// Constant governing macOS specific behavior. See https://github.com/nsf/termbox-go/issues/132
// This is an arbitrary delay which hopefully will be enough time for any lagging
// partial escape sequences to come through.
const esc_wait_delay = 100 * time.Millisecond
var event Event
var esc_wait_timer *time.Timer
var esc_timeout <-chan time.Time
// try to extract event from input buffer, return on success
event.Type = EventKey
ok := extract_event(inbuf, &event)
status := extract_event(inbuf, &event, true)
if event.N != 0 {
copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N]
}
if ok {
if status == event_extracted {
return event
} else if status == esc_wait {
esc_wait_timer = time.NewTimer(esc_wait_delay)
esc_timeout = esc_wait_timer.C
}
for {
select {
case ev := <-input_comm:
if esc_wait_timer != nil {
if !esc_wait_timer.Stop() {
<-esc_wait_timer.C
}
esc_wait_timer = nil
}
if ev.err != nil {
return Event{Type: EventError, Err: ev.err}
}
inbuf = append(inbuf, ev.data...)
input_comm <- ev
ok := extract_event(inbuf, &event)
status := extract_event(inbuf, &event, true)
if event.N != 0 {
copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N]
}
if ok {
if status == event_extracted {
return event
} else if status == esc_wait {
esc_wait_timer = time.NewTimer(esc_wait_delay)
esc_timeout = esc_wait_timer.C
}
case <-esc_timeout:
esc_wait_timer = nil
status := extract_event(inbuf, &event, false)
if event.N != 0 {
copy(inbuf, inbuf[event.N:])
inbuf = inbuf[:len(inbuf)-event.N]
}
if status == event_extracted {
return event
}
case <-interrupt_comm:

View File

@ -148,7 +148,7 @@ const (
// using bitwise OR ('|'). Although, colors cannot be combined. But you can
// combine attributes and a single color.
//
// It's worth mentioning that some platforms don't support certain attibutes.
// It's worth mentioning that some platforms don't support certain attributes.
// For example windows console doesn't support AttrUnderline. And on some
// terminals applying AttrBold to background may result in blinking text. Use
// them with caution and test your code on various terminals.

11
vendor/github.com/nsf/termbox-go/escwait.go generated vendored Normal file
View File

@ -0,0 +1,11 @@
// +build !darwin
package termbox
// On all systems other than macOS, disable behavior which will wait before
// deciding that the escape key was pressed, to account for partially send
// escape sequences, especially with regard to lengthy mouse sequences.
// See https://github.com/nsf/termbox-go/issues/132
func enable_wait_for_escape_sequence() bool {
return false
}

9
vendor/github.com/nsf/termbox-go/escwait_darwin.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
package termbox
// On macOS, enable behavior which will wait before deciding that the escape
// key was pressed, to account for partially send escape sequences, especially
// with regard to lengthy mouse sequences.
// See https://github.com/nsf/termbox-go/issues/132
func enable_wait_for_escape_sequence() bool {
return true
}

39
vendor/github.com/nsf/termbox-go/syscalls.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
// +build ignore
package termbox
/*
#include <termios.h>
#include <sys/ioctl.h>
*/
import "C"
type syscall_Termios C.struct_termios
const (
syscall_IGNBRK = C.IGNBRK
syscall_BRKINT = C.BRKINT
syscall_PARMRK = C.PARMRK
syscall_ISTRIP = C.ISTRIP
syscall_INLCR = C.INLCR
syscall_IGNCR = C.IGNCR
syscall_ICRNL = C.ICRNL
syscall_IXON = C.IXON
syscall_OPOST = C.OPOST
syscall_ECHO = C.ECHO
syscall_ECHONL = C.ECHONL
syscall_ICANON = C.ICANON
syscall_ISIG = C.ISIG
syscall_IEXTEN = C.IEXTEN
syscall_CSIZE = C.CSIZE
syscall_PARENB = C.PARENB
syscall_CS8 = C.CS8
syscall_VMIN = C.VMIN
syscall_VTIME = C.VTIME
// on darwin change these to (on *bsd too?):
// C.TIOCGETA
// C.TIOCSETA
syscall_TCGETS = C.TCGETS
syscall_TCSETS = C.TCSETS
)

View File

@ -41,6 +41,14 @@ type input_event struct {
err error
}
type extract_event_res int
const (
event_not_extracted extract_event_res = iota
event_extracted
esc_wait
)
var (
// term specific sequences
keys []string
@ -417,7 +425,7 @@ func parse_escape_sequence(event *Event, buf []byte) (int, bool) {
}
}
// if none of the keys match, let's try mouse seqences
// if none of the keys match, let's try mouse sequences
return parse_mouse_event(event, bufstr)
}
@ -440,17 +448,27 @@ func extract_raw_event(data []byte, event *Event) bool {
return true
}
func extract_event(inbuf []byte, event *Event) bool {
func extract_event(inbuf []byte, event *Event, allow_esc_wait bool) extract_event_res {
if len(inbuf) == 0 {
event.N = 0
return false
return event_not_extracted
}
if inbuf[0] == '\033' {
// possible escape sequence
if n, ok := parse_escape_sequence(event, inbuf); n != 0 {
event.N = n
return ok
if ok {
return event_extracted
} else {
return event_not_extracted
}
}
// possible partially read escape sequence; trigger a wait if appropriate
if enable_wait_for_escape_sequence() && allow_esc_wait {
event.N = 0
return esc_wait
}
// it's not escape sequence, then it's Alt or Esc, check input_mode
@ -461,17 +479,17 @@ func extract_event(inbuf []byte, event *Event) bool {
event.Key = KeyEsc
event.Mod = 0
event.N = 1
return true
return event_extracted
case input_mode&InputAlt != 0:
// if we're in alt mode, set Alt modifier to event and redo parsing
event.Mod = ModAlt
ok := extract_event(inbuf[1:], event)
if ok {
status := extract_event(inbuf[1:], event, false)
if status == event_extracted {
event.N++
} else {
event.N = 0
}
return ok
return status
default:
panic("unreachable")
}
@ -486,7 +504,7 @@ func extract_event(inbuf []byte, event *Event) bool {
event.Ch = 0
event.Key = Key(inbuf[0])
event.N = 1
return true
return event_extracted
}
// the only possible option is utf8 rune
@ -494,10 +512,10 @@ func extract_event(inbuf []byte, event *Event) bool {
event.Ch = r
event.Key = 0
event.N = n
return true
return event_extracted
}
return false
return event_not_extracted
}
func fcntl(fd int, cmd int, arg int) (val int, err error) {

View File

@ -151,11 +151,16 @@ func setup_term() (err error) {
return
}
number_sec_len := int16(2)
if header[0] == 542 { // doc says it should be octal 0542, but what I see it terminfo files is 542, learn to program please... thank you..
number_sec_len = 4
}
if (header[1]+header[2])%2 != 0 {
// old quirk to align everything on word boundaries
header[2] += 1
}
str_offset = ti_header_length + header[1] + header[2] + 2*header[3]
str_offset = ti_header_length + header[1] + header[2] + number_sec_len*header[3]
table_offset = str_offset + 2*header[4]
keys = make([]string, 0xFFFF-key_min)

View File

@ -122,8 +122,8 @@ Outer:
// RankFind is similar to Find, except it will also rank all matches using
// Levenshtein distance.
func RankFind(source string, targets []string) Ranks {
var r Ranks
func RankFind(source string, targets []string) ranks {
var r ranks
for _, target := range find(source, targets, noop) {
distance := LevenshteinDistance(source, target)
r = append(r, Rank{source, target, distance})
@ -132,8 +132,8 @@ func RankFind(source string, targets []string) Ranks {
}
// RankFindFold is a case-insensitive version of RankFind.
func RankFindFold(source string, targets []string) Ranks {
var r Ranks
func RankFindFold(source string, targets []string) ranks {
var r ranks
for _, target := range find(source, targets, unicode.ToLower) {
distance := LevenshteinDistance(source, target)
r = append(r, Rank{source, target, distance})
@ -152,16 +152,16 @@ type Rank struct {
Distance int
}
type Ranks []Rank
type ranks []Rank
func (r Ranks) Len() int {
func (r ranks) Len() int {
return len(r)
}
func (r Ranks) Swap(i, j int) {
func (r ranks) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
func (r Ranks) Less(i, j int) bool {
func (r ranks) Less(i, j int) bool {
return r[i].Distance < r[j].Distance
}

27
vendor/golang.org/x/net/LICENSE generated vendored
View File

@ -1,27 +0,0 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
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
OWNER 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.

22
vendor/golang.org/x/net/PATENTS generated vendored
View File

@ -1,22 +0,0 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

View File

@ -1,106 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bufio"
"io"
"net"
"net/http"
"net/url"
)
// DialError is an error that occurs while dialling a websocket server.
type DialError struct {
*Config
Err error
}
func (e *DialError) Error() string {
return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error()
}
// NewConfig creates a new WebSocket config for client connection.
func NewConfig(server, origin string) (config *Config, err error) {
config = new(Config)
config.Version = ProtocolVersionHybi13
config.Location, err = url.ParseRequestURI(server)
if err != nil {
return
}
config.Origin, err = url.ParseRequestURI(origin)
if err != nil {
return
}
config.Header = http.Header(make(map[string][]string))
return
}
// NewClient creates a new WebSocket client connection over rwc.
func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) {
br := bufio.NewReader(rwc)
bw := bufio.NewWriter(rwc)
err = hybiClientHandshake(config, br, bw)
if err != nil {
return
}
buf := bufio.NewReadWriter(br, bw)
ws = newHybiClientConn(config, buf, rwc)
return
}
// Dial opens a new client connection to a WebSocket.
func Dial(url_, protocol, origin string) (ws *Conn, err error) {
config, err := NewConfig(url_, origin)
if err != nil {
return nil, err
}
if protocol != "" {
config.Protocol = []string{protocol}
}
return DialConfig(config)
}
var portMap = map[string]string{
"ws": "80",
"wss": "443",
}
func parseAuthority(location *url.URL) string {
if _, ok := portMap[location.Scheme]; ok {
if _, _, err := net.SplitHostPort(location.Host); err != nil {
return net.JoinHostPort(location.Host, portMap[location.Scheme])
}
}
return location.Host
}
// DialConfig opens a new client connection to a WebSocket with a config.
func DialConfig(config *Config) (ws *Conn, err error) {
var client net.Conn
if config.Location == nil {
return nil, &DialError{config, ErrBadWebSocketLocation}
}
if config.Origin == nil {
return nil, &DialError{config, ErrBadWebSocketOrigin}
}
dialer := config.Dialer
if dialer == nil {
dialer = &net.Dialer{}
}
client, err = dialWithDialer(dialer, config)
if err != nil {
goto Error
}
ws, err = NewClient(config, client)
if err != nil {
client.Close()
goto Error
}
return
Error:
return nil, &DialError{config, err}
}

View File

@ -1,24 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"crypto/tls"
"net"
)
func dialWithDialer(dialer *net.Dialer, config *Config) (conn net.Conn, err error) {
switch config.Location.Scheme {
case "ws":
conn, err = dialer.Dial("tcp", parseAuthority(config.Location))
case "wss":
conn, err = tls.DialWithDialer(dialer, "tcp", parseAuthority(config.Location), config.TlsConfig)
default:
err = ErrBadScheme
}
return
}

View File

@ -1,583 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
// This file implements a protocol of hybi draft.
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
import (
"bufio"
"bytes"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
const (
websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
closeStatusNormal = 1000
closeStatusGoingAway = 1001
closeStatusProtocolError = 1002
closeStatusUnsupportedData = 1003
closeStatusFrameTooLarge = 1004
closeStatusNoStatusRcvd = 1005
closeStatusAbnormalClosure = 1006
closeStatusBadMessageData = 1007
closeStatusPolicyViolation = 1008
closeStatusTooBigData = 1009
closeStatusExtensionMismatch = 1010
maxControlFramePayloadLength = 125
)
var (
ErrBadMaskingKey = &ProtocolError{"bad masking key"}
ErrBadPongMessage = &ProtocolError{"bad pong message"}
ErrBadClosingStatus = &ProtocolError{"bad closing status"}
ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"}
ErrNotImplemented = &ProtocolError{"not implemented"}
handshakeHeader = map[string]bool{
"Host": true,
"Upgrade": true,
"Connection": true,
"Sec-Websocket-Key": true,
"Sec-Websocket-Origin": true,
"Sec-Websocket-Version": true,
"Sec-Websocket-Protocol": true,
"Sec-Websocket-Accept": true,
}
)
// A hybiFrameHeader is a frame header as defined in hybi draft.
type hybiFrameHeader struct {
Fin bool
Rsv [3]bool
OpCode byte
Length int64
MaskingKey []byte
data *bytes.Buffer
}
// A hybiFrameReader is a reader for hybi frame.
type hybiFrameReader struct {
reader io.Reader
header hybiFrameHeader
pos int64
length int
}
func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) {
n, err = frame.reader.Read(msg)
if frame.header.MaskingKey != nil {
for i := 0; i < n; i++ {
msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4]
frame.pos++
}
}
return n, err
}
func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode }
func (frame *hybiFrameReader) HeaderReader() io.Reader {
if frame.header.data == nil {
return nil
}
if frame.header.data.Len() == 0 {
return nil
}
return frame.header.data
}
func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil }
func (frame *hybiFrameReader) Len() (n int) { return frame.length }
// A hybiFrameReaderFactory creates new frame reader based on its frame type.
type hybiFrameReaderFactory struct {
*bufio.Reader
}
// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
// See Section 5.2 Base Framing protocol for detail.
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) {
hybiFrame := new(hybiFrameReader)
frame = hybiFrame
var header []byte
var b byte
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
b, err = buf.ReadByte()
if err != nil {
return
}
header = append(header, b)
hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0
for i := 0; i < 3; i++ {
j := uint(6 - i)
hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0
}
hybiFrame.header.OpCode = header[0] & 0x0f
// Second byte. Mask/Payload len(7bits)
b, err = buf.ReadByte()
if err != nil {
return
}
header = append(header, b)
mask := (b & 0x80) != 0
b &= 0x7f
lengthFields := 0
switch {
case b <= 125: // Payload length 7bits.
hybiFrame.header.Length = int64(b)
case b == 126: // Payload length 7+16bits
lengthFields = 2
case b == 127: // Payload length 7+64bits
lengthFields = 8
}
for i := 0; i < lengthFields; i++ {
b, err = buf.ReadByte()
if err != nil {
return
}
if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits
b &= 0x7f
}
header = append(header, b)
hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
}
if mask {
// Masking key. 4 bytes.
for i := 0; i < 4; i++ {
b, err = buf.ReadByte()
if err != nil {
return
}
header = append(header, b)
hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b)
}
}
hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length)
hybiFrame.header.data = bytes.NewBuffer(header)
hybiFrame.length = len(header) + int(hybiFrame.header.Length)
return
}
// A HybiFrameWriter is a writer for hybi frame.
type hybiFrameWriter struct {
writer *bufio.Writer
header *hybiFrameHeader
}
func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) {
var header []byte
var b byte
if frame.header.Fin {
b |= 0x80
}
for i := 0; i < 3; i++ {
if frame.header.Rsv[i] {
j := uint(6 - i)
b |= 1 << j
}
}
b |= frame.header.OpCode
header = append(header, b)
if frame.header.MaskingKey != nil {
b = 0x80
} else {
b = 0
}
lengthFields := 0
length := len(msg)
switch {
case length <= 125:
b |= byte(length)
case length < 65536:
b |= 126
lengthFields = 2
default:
b |= 127
lengthFields = 8
}
header = append(header, b)
for i := 0; i < lengthFields; i++ {
j := uint((lengthFields - i - 1) * 8)
b = byte((length >> j) & 0xff)
header = append(header, b)
}
if frame.header.MaskingKey != nil {
if len(frame.header.MaskingKey) != 4 {
return 0, ErrBadMaskingKey
}
header = append(header, frame.header.MaskingKey...)
frame.writer.Write(header)
data := make([]byte, length)
for i := range data {
data[i] = msg[i] ^ frame.header.MaskingKey[i%4]
}
frame.writer.Write(data)
err = frame.writer.Flush()
return length, err
}
frame.writer.Write(header)
frame.writer.Write(msg)
err = frame.writer.Flush()
return length, err
}
func (frame *hybiFrameWriter) Close() error { return nil }
type hybiFrameWriterFactory struct {
*bufio.Writer
needMaskingKey bool
}
func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType}
if buf.needMaskingKey {
frameHeader.MaskingKey, err = generateMaskingKey()
if err != nil {
return nil, err
}
}
return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil
}
type hybiFrameHandler struct {
conn *Conn
payloadType byte
}
func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) {
if handler.conn.IsServerConn() {
// The client MUST mask all frames sent to the server.
if frame.(*hybiFrameReader).header.MaskingKey == nil {
handler.WriteClose(closeStatusProtocolError)
return nil, io.EOF
}
} else {
// The server MUST NOT mask all frames.
if frame.(*hybiFrameReader).header.MaskingKey != nil {
handler.WriteClose(closeStatusProtocolError)
return nil, io.EOF
}
}
if header := frame.HeaderReader(); header != nil {
io.Copy(ioutil.Discard, header)
}
switch frame.PayloadType() {
case ContinuationFrame:
frame.(*hybiFrameReader).header.OpCode = handler.payloadType
case TextFrame, BinaryFrame:
handler.payloadType = frame.PayloadType()
case CloseFrame:
return nil, io.EOF
case PingFrame, PongFrame:
b := make([]byte, maxControlFramePayloadLength)
n, err := io.ReadFull(frame, b)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
return nil, err
}
io.Copy(ioutil.Discard, frame)
if frame.PayloadType() == PingFrame {
if _, err := handler.WritePong(b[:n]); err != nil {
return nil, err
}
}
return nil, nil
}
return frame, nil
}
func (handler *hybiFrameHandler) WriteClose(status int) (err error) {
handler.conn.wio.Lock()
defer handler.conn.wio.Unlock()
w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame)
if err != nil {
return err
}
msg := make([]byte, 2)
binary.BigEndian.PutUint16(msg, uint16(status))
_, err = w.Write(msg)
w.Close()
return err
}
func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) {
handler.conn.wio.Lock()
defer handler.conn.wio.Unlock()
w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame)
if err != nil {
return 0, err
}
n, err = w.Write(msg)
w.Close()
return n, err
}
// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
if buf == nil {
br := bufio.NewReader(rwc)
bw := bufio.NewWriter(rwc)
buf = bufio.NewReadWriter(br, bw)
}
ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
frameReaderFactory: hybiFrameReaderFactory{buf.Reader},
frameWriterFactory: hybiFrameWriterFactory{
buf.Writer, request == nil},
PayloadType: TextFrame,
defaultCloseStatus: closeStatusNormal}
ws.frameHandler = &hybiFrameHandler{conn: ws}
return ws
}
// generateMaskingKey generates a masking key for a frame.
func generateMaskingKey() (maskingKey []byte, err error) {
maskingKey = make([]byte, 4)
if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil {
return
}
return
}
// generateNonce generates a nonce consisting of a randomly selected 16-byte
// value that has been base64-encoded.
func generateNonce() (nonce []byte) {
key := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
panic(err)
}
nonce = make([]byte, 24)
base64.StdEncoding.Encode(nonce, key)
return
}
// removeZone removes IPv6 zone identifer from host.
// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080"
func removeZone(host string) string {
if !strings.HasPrefix(host, "[") {
return host
}
i := strings.LastIndex(host, "]")
if i < 0 {
return host
}
j := strings.LastIndex(host[:i], "%")
if j < 0 {
return host
}
return host[:j] + host[i:]
}
// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
func getNonceAccept(nonce []byte) (expected []byte, err error) {
h := sha1.New()
if _, err = h.Write(nonce); err != nil {
return
}
if _, err = h.Write([]byte(websocketGUID)); err != nil {
return
}
expected = make([]byte, 28)
base64.StdEncoding.Encode(expected, h.Sum(nil))
return
}
// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17
func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
// According to RFC 6874, an HTTP client, proxy, or other
// intermediary must remove any IPv6 zone identifier attached
// to an outgoing URI.
bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")
bw.WriteString("Upgrade: websocket\r\n")
bw.WriteString("Connection: Upgrade\r\n")
nonce := generateNonce()
if config.handshakeData != nil {
nonce = []byte(config.handshakeData["key"])
}
bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")
bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
if config.Version != ProtocolVersionHybi13 {
return ErrBadProtocolVersion
}
bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n")
if len(config.Protocol) > 0 {
bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n")
}
// TODO(ukai): send Sec-WebSocket-Extensions.
err = config.Header.WriteSubset(bw, handshakeHeader)
if err != nil {
return err
}
bw.WriteString("\r\n")
if err = bw.Flush(); err != nil {
return err
}
resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
if err != nil {
return err
}
if resp.StatusCode != 101 {
return ErrBadStatus
}
if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" ||
strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
return ErrBadUpgrade
}
expectedAccept, err := getNonceAccept(nonce)
if err != nil {
return err
}
if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) {
return ErrChallengeResponse
}
if resp.Header.Get("Sec-WebSocket-Extensions") != "" {
return ErrUnsupportedExtensions
}
offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol")
if offeredProtocol != "" {
protocolMatched := false
for i := 0; i < len(config.Protocol); i++ {
if config.Protocol[i] == offeredProtocol {
protocolMatched = true
break
}
}
if !protocolMatched {
return ErrBadWebSocketProtocol
}
config.Protocol = []string{offeredProtocol}
}
return nil
}
// newHybiClientConn creates a client WebSocket connection after handshake.
func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
return newHybiConn(config, buf, rwc, nil)
}
// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
type hybiServerHandshaker struct {
*Config
accept []byte
}
func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
c.Version = ProtocolVersionHybi13
if req.Method != "GET" {
return http.StatusMethodNotAllowed, ErrBadRequestMethod
}
// HTTP version can be safely ignored.
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
return http.StatusBadRequest, ErrNotWebSocket
}
key := req.Header.Get("Sec-Websocket-Key")
if key == "" {
return http.StatusBadRequest, ErrChallengeResponse
}
version := req.Header.Get("Sec-Websocket-Version")
switch version {
case "13":
c.Version = ProtocolVersionHybi13
default:
return http.StatusBadRequest, ErrBadWebSocketVersion
}
var scheme string
if req.TLS != nil {
scheme = "wss"
} else {
scheme = "ws"
}
c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
if err != nil {
return http.StatusBadRequest, err
}
protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
if protocol != "" {
protocols := strings.Split(protocol, ",")
for i := 0; i < len(protocols); i++ {
c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
}
}
c.accept, err = getNonceAccept([]byte(key))
if err != nil {
return http.StatusInternalServerError, err
}
return http.StatusSwitchingProtocols, nil
}
// Origin parses the Origin header in req.
// If the Origin header is not set, it returns nil and nil.
func Origin(config *Config, req *http.Request) (*url.URL, error) {
var origin string
switch config.Version {
case ProtocolVersionHybi13:
origin = req.Header.Get("Origin")
}
if origin == "" {
return nil, nil
}
return url.ParseRequestURI(origin)
}
func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
if len(c.Protocol) > 0 {
if len(c.Protocol) != 1 {
// You need choose a Protocol in Handshake func in Server.
return ErrBadWebSocketProtocol
}
}
buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
buf.WriteString("Upgrade: websocket\r\n")
buf.WriteString("Connection: Upgrade\r\n")
buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n")
if len(c.Protocol) > 0 {
buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
}
// TODO(ukai): send Sec-WebSocket-Extensions.
if c.Header != nil {
err := c.Header.WriteSubset(buf, handshakeHeader)
if err != nil {
return err
}
}
buf.WriteString("\r\n")
return buf.Flush()
}
func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
return newHybiServerConn(c.Config, buf, rwc, request)
}
// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
return newHybiConn(config, buf, rwc, request)
}

View File

@ -1,113 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package websocket
import (
"bufio"
"fmt"
"io"
"net/http"
)
func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) {
var hs serverHandshaker = &hybiServerHandshaker{Config: config}
code, err := hs.ReadHandshake(buf.Reader, req)
if err == ErrBadWebSocketVersion {
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion)
buf.WriteString("\r\n")
buf.WriteString(err.Error())
buf.Flush()
return
}
if err != nil {
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
buf.WriteString("\r\n")
buf.WriteString(err.Error())
buf.Flush()
return
}
if handshake != nil {
err = handshake(config, req)
if err != nil {
code = http.StatusForbidden
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
buf.WriteString("\r\n")
buf.Flush()
return
}
}
err = hs.AcceptHandshake(buf.Writer)
if err != nil {
code = http.StatusBadRequest
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
buf.WriteString("\r\n")
buf.Flush()
return
}
conn = hs.NewServerConn(buf, rwc, req)
return
}
// Server represents a server of a WebSocket.
type Server struct {
// Config is a WebSocket configuration for new WebSocket connection.
Config
// Handshake is an optional function in WebSocket handshake.
// For example, you can check, or don't check Origin header.
// Another example, you can select config.Protocol.
Handshake func(*Config, *http.Request) error
// Handler handles a WebSocket connection.
Handler
}
// ServeHTTP implements the http.Handler interface for a WebSocket
func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
s.serveWebSocket(w, req)
}
func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
rwc, buf, err := w.(http.Hijacker).Hijack()
if err != nil {
panic("Hijack failed: " + err.Error())
}
// The server should abort the WebSocket connection if it finds
// the client did not send a handshake that matches with protocol
// specification.
defer rwc.Close()
conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
if err != nil {
return
}
if conn == nil {
panic("unexpected nil conn")
}
s.Handler(conn)
}
// Handler is a simple interface to a WebSocket browser client.
// It checks if Origin header is valid URL by default.
// You might want to verify websocket.Conn.Config().Origin in the func.
// If you use Server instead of Handler, you could call websocket.Origin and
// check the origin in your Handshake func. So, if you want to accept
// non-browser clients, which do not send an Origin header, set a
// Server.Handshake that does not check the origin.
type Handler func(*Conn)
func checkOrigin(config *Config, req *http.Request) (err error) {
config.Origin, err = Origin(config, req)
if err == nil && config.Origin == nil {
return fmt.Errorf("null origin")
}
return err
}
// ServeHTTP implements the http.Handler interface for a WebSocket
func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
s := Server{Handler: h, Handshake: checkOrigin}
s.serveWebSocket(w, req)
}

View File

@ -1,448 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package websocket implements a client and server for the WebSocket protocol
// as specified in RFC 6455.
//
// This package currently lacks some features found in an alternative
// and more actively maintained WebSocket package:
//
// https://godoc.org/github.com/gorilla/websocket
//
package websocket // import "golang.org/x/net/websocket"
import (
"bufio"
"crypto/tls"
"encoding/json"
"errors"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"sync"
"time"
)
const (
ProtocolVersionHybi13 = 13
ProtocolVersionHybi = ProtocolVersionHybi13
SupportedProtocolVersion = "13"
ContinuationFrame = 0
TextFrame = 1
BinaryFrame = 2
CloseFrame = 8
PingFrame = 9
PongFrame = 10
UnknownFrame = 255
DefaultMaxPayloadBytes = 32 << 20 // 32MB
)
// ProtocolError represents WebSocket protocol errors.
type ProtocolError struct {
ErrorString string
}
func (err *ProtocolError) Error() string { return err.ErrorString }
var (
ErrBadProtocolVersion = &ProtocolError{"bad protocol version"}
ErrBadScheme = &ProtocolError{"bad scheme"}
ErrBadStatus = &ProtocolError{"bad status"}
ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"}
ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"}
ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"}
ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"}
ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"}
ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"}
ErrBadFrame = &ProtocolError{"bad frame"}
ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"}
ErrNotWebSocket = &ProtocolError{"not websocket protocol"}
ErrBadRequestMethod = &ProtocolError{"bad method"}
ErrNotSupported = &ProtocolError{"not supported"}
)
// ErrFrameTooLarge is returned by Codec's Receive method if payload size
// exceeds limit set by Conn.MaxPayloadBytes
var ErrFrameTooLarge = errors.New("websocket: frame payload size exceeds limit")
// Addr is an implementation of net.Addr for WebSocket.
type Addr struct {
*url.URL
}
// Network returns the network type for a WebSocket, "websocket".
func (addr *Addr) Network() string { return "websocket" }
// Config is a WebSocket configuration
type Config struct {
// A WebSocket server address.
Location *url.URL
// A Websocket client origin.
Origin *url.URL
// WebSocket subprotocols.
Protocol []string
// WebSocket protocol version.
Version int
// TLS config for secure WebSocket (wss).
TlsConfig *tls.Config
// Additional header fields to be sent in WebSocket opening handshake.
Header http.Header
// Dialer used when opening websocket connections.
Dialer *net.Dialer
handshakeData map[string]string
}
// serverHandshaker is an interface to handle WebSocket server side handshake.
type serverHandshaker interface {
// ReadHandshake reads handshake request message from client.
// Returns http response code and error if any.
ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error)
// AcceptHandshake accepts the client handshake request and sends
// handshake response back to client.
AcceptHandshake(buf *bufio.Writer) (err error)
// NewServerConn creates a new WebSocket connection.
NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn)
}
// frameReader is an interface to read a WebSocket frame.
type frameReader interface {
// Reader is to read payload of the frame.
io.Reader
// PayloadType returns payload type.
PayloadType() byte
// HeaderReader returns a reader to read header of the frame.
HeaderReader() io.Reader
// TrailerReader returns a reader to read trailer of the frame.
// If it returns nil, there is no trailer in the frame.
TrailerReader() io.Reader
// Len returns total length of the frame, including header and trailer.
Len() int
}
// frameReaderFactory is an interface to creates new frame reader.
type frameReaderFactory interface {
NewFrameReader() (r frameReader, err error)
}
// frameWriter is an interface to write a WebSocket frame.
type frameWriter interface {
// Writer is to write payload of the frame.
io.WriteCloser
}
// frameWriterFactory is an interface to create new frame writer.
type frameWriterFactory interface {
NewFrameWriter(payloadType byte) (w frameWriter, err error)
}
type frameHandler interface {
HandleFrame(frame frameReader) (r frameReader, err error)
WriteClose(status int) (err error)
}
// Conn represents a WebSocket connection.
//
// Multiple goroutines may invoke methods on a Conn simultaneously.
type Conn struct {
config *Config
request *http.Request
buf *bufio.ReadWriter
rwc io.ReadWriteCloser
rio sync.Mutex
frameReaderFactory
frameReader
wio sync.Mutex
frameWriterFactory
frameHandler
PayloadType byte
defaultCloseStatus int
// MaxPayloadBytes limits the size of frame payload received over Conn
// by Codec's Receive method. If zero, DefaultMaxPayloadBytes is used.
MaxPayloadBytes int
}
// Read implements the io.Reader interface:
// it reads data of a frame from the WebSocket connection.
// if msg is not large enough for the frame data, it fills the msg and next Read
// will read the rest of the frame data.
// it reads Text frame or Binary frame.
func (ws *Conn) Read(msg []byte) (n int, err error) {
ws.rio.Lock()
defer ws.rio.Unlock()
again:
if ws.frameReader == nil {
frame, err := ws.frameReaderFactory.NewFrameReader()
if err != nil {
return 0, err
}
ws.frameReader, err = ws.frameHandler.HandleFrame(frame)
if err != nil {
return 0, err
}
if ws.frameReader == nil {
goto again
}
}
n, err = ws.frameReader.Read(msg)
if err == io.EOF {
if trailer := ws.frameReader.TrailerReader(); trailer != nil {
io.Copy(ioutil.Discard, trailer)
}
ws.frameReader = nil
goto again
}
return n, err
}
// Write implements the io.Writer interface:
// it writes data as a frame to the WebSocket connection.
func (ws *Conn) Write(msg []byte) (n int, err error) {
ws.wio.Lock()
defer ws.wio.Unlock()
w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType)
if err != nil {
return 0, err
}
n, err = w.Write(msg)
w.Close()
return n, err
}
// Close implements the io.Closer interface.
func (ws *Conn) Close() error {
err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
err1 := ws.rwc.Close()
if err != nil {
return err
}
return err1
}
func (ws *Conn) IsClientConn() bool { return ws.request == nil }
func (ws *Conn) IsServerConn() bool { return ws.request != nil }
// LocalAddr returns the WebSocket Origin for the connection for client, or
// the WebSocket location for server.
func (ws *Conn) LocalAddr() net.Addr {
if ws.IsClientConn() {
return &Addr{ws.config.Origin}
}
return &Addr{ws.config.Location}
}
// RemoteAddr returns the WebSocket location for the connection for client, or
// the Websocket Origin for server.
func (ws *Conn) RemoteAddr() net.Addr {
if ws.IsClientConn() {
return &Addr{ws.config.Location}
}
return &Addr{ws.config.Origin}
}
var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn")
// SetDeadline sets the connection's network read & write deadlines.
func (ws *Conn) SetDeadline(t time.Time) error {
if conn, ok := ws.rwc.(net.Conn); ok {
return conn.SetDeadline(t)
}
return errSetDeadline
}
// SetReadDeadline sets the connection's network read deadline.
func (ws *Conn) SetReadDeadline(t time.Time) error {
if conn, ok := ws.rwc.(net.Conn); ok {
return conn.SetReadDeadline(t)
}
return errSetDeadline
}
// SetWriteDeadline sets the connection's network write deadline.
func (ws *Conn) SetWriteDeadline(t time.Time) error {
if conn, ok := ws.rwc.(net.Conn); ok {
return conn.SetWriteDeadline(t)
}
return errSetDeadline
}
// Config returns the WebSocket config.
func (ws *Conn) Config() *Config { return ws.config }
// Request returns the http request upgraded to the WebSocket.
// It is nil for client side.
func (ws *Conn) Request() *http.Request { return ws.request }
// Codec represents a symmetric pair of functions that implement a codec.
type Codec struct {
Marshal func(v interface{}) (data []byte, payloadType byte, err error)
Unmarshal func(data []byte, payloadType byte, v interface{}) (err error)
}
// Send sends v marshaled by cd.Marshal as single frame to ws.
func (cd Codec) Send(ws *Conn, v interface{}) (err error) {
data, payloadType, err := cd.Marshal(v)
if err != nil {
return err
}
ws.wio.Lock()
defer ws.wio.Unlock()
w, err := ws.frameWriterFactory.NewFrameWriter(payloadType)
if err != nil {
return err
}
_, err = w.Write(data)
w.Close()
return err
}
// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores
// in v. The whole frame payload is read to an in-memory buffer; max size of
// payload is defined by ws.MaxPayloadBytes. If frame payload size exceeds
// limit, ErrFrameTooLarge is returned; in this case frame is not read off wire
// completely. The next call to Receive would read and discard leftover data of
// previous oversized frame before processing next frame.
func (cd Codec) Receive(ws *Conn, v interface{}) (err error) {
ws.rio.Lock()
defer ws.rio.Unlock()
if ws.frameReader != nil {
_, err = io.Copy(ioutil.Discard, ws.frameReader)
if err != nil {
return err
}
ws.frameReader = nil
}
again:
frame, err := ws.frameReaderFactory.NewFrameReader()
if err != nil {
return err
}
frame, err = ws.frameHandler.HandleFrame(frame)
if err != nil {
return err
}
if frame == nil {
goto again
}
maxPayloadBytes := ws.MaxPayloadBytes
if maxPayloadBytes == 0 {
maxPayloadBytes = DefaultMaxPayloadBytes
}
if hf, ok := frame.(*hybiFrameReader); ok && hf.header.Length > int64(maxPayloadBytes) {
// payload size exceeds limit, no need to call Unmarshal
//
// set frameReader to current oversized frame so that
// the next call to this function can drain leftover
// data before processing the next frame
ws.frameReader = frame
return ErrFrameTooLarge
}
payloadType := frame.PayloadType()
data, err := ioutil.ReadAll(frame)
if err != nil {
return err
}
return cd.Unmarshal(data, payloadType, v)
}
func marshal(v interface{}) (msg []byte, payloadType byte, err error) {
switch data := v.(type) {
case string:
return []byte(data), TextFrame, nil
case []byte:
return data, BinaryFrame, nil
}
return nil, UnknownFrame, ErrNotSupported
}
func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
switch data := v.(type) {
case *string:
*data = string(msg)
return nil
case *[]byte:
*data = msg
return nil
}
return ErrNotSupported
}
/*
Message is a codec to send/receive text/binary data in a frame on WebSocket connection.
To send/receive text frame, use string type.
To send/receive binary frame, use []byte type.
Trivial usage:
import "websocket"
// receive text frame
var message string
websocket.Message.Receive(ws, &message)
// send text frame
message = "hello"
websocket.Message.Send(ws, message)
// receive binary frame
var data []byte
websocket.Message.Receive(ws, &data)
// send binary frame
data = []byte{0, 1, 2}
websocket.Message.Send(ws, data)
*/
var Message = Codec{marshal, unmarshal}
func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) {
msg, err = json.Marshal(v)
return msg, TextFrame, err
}
func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
return json.Unmarshal(msg, v)
}
/*
JSON is a codec to send/receive JSON data in a frame from a WebSocket connection.
Trivial usage:
import "websocket"
type T struct {
Msg string
Count int
}
// receive JSON type T
var data T
websocket.JSON.Receive(ws, &data)
// send JSON type T
websocket.JSON.Send(ws, data)
*/
var JSON = Codec{jsonMarshal, jsonUnmarshal}

243
vendor/vendor.json vendored
View File

@ -1,243 +0,0 @@
{
"comment": "",
"ignore": "test",
"package": [
{
"path": "bufio",
"revision": ""
},
{
"path": "bytes",
"revision": ""
},
{
"path": "context",
"revision": ""
},
{
"path": "crypto/rand",
"revision": ""
},
{
"path": "crypto/sha1",
"revision": ""
},
{
"path": "crypto/tls",
"revision": ""
},
{
"path": "encoding/base64",
"revision": ""
},
{
"path": "encoding/binary",
"revision": ""
},
{
"path": "encoding/hex",
"revision": ""
},
{
"path": "encoding/json",
"revision": ""
},
{
"path": "errors",
"revision": ""
},
{
"path": "flag",
"revision": ""
},
{
"path": "fmt",
"revision": ""
},
{
"checksumSHA1": "CbpC2ha+GTTuROMyyLVd/L3O+8Y=",
"path": "github.com/erroneousboat/termui",
"revision": "80f245cdfa0488883a3e8602bf3f0c8a3c889a22",
"revisionTime": "2017-09-23T11:51:41Z"
},
{
"checksumSHA1": "zpFCi2nWiwR5F2INAJOvQqsj7lY=",
"path": "github.com/maruel/panicparse/stack",
"revision": "766956aceb8ff49664065ae50bef0ae8a0a83ec4",
"revisionTime": "2017-11-29T15:16:18Z"
},
{
"checksumSHA1": "cJE7dphDlam/i7PhnsyosNWtbd4=",
"path": "github.com/mattn/go-runewidth",
"revision": "97311d9f7767e3d6f422ea06661bc2c7a19e8a5d",
"revisionTime": "2017-05-10T07:48:58Z"
},
{
"checksumSHA1": "L3leymg2RT8hFl5uL+5KP/LpBkg=",
"path": "github.com/mitchellh/go-wordwrap",
"revision": "ad45545899c7b13c020ea92b2072220eefad42b8",
"revisionTime": "2015-03-14T17:03:34Z"
},
{
"checksumSHA1": "HYgTWn4FgVbvSBYVO4DxUPWfCz0=",
"path": "github.com/nlopes/slack",
"revision": "5cde21b8b96a43fc3435a1f514123d14fd7eabdc",
"revisionTime": "2017-07-25T12:17:30Z"
},
{
"checksumSHA1": "Zi8hWUMkKtii1fc6YaGgoYAssIw=",
"path": "github.com/nsf/termbox-go",
"revision": "aa4a75b1c20a2b03751b1a9f7e41d58bd6f71c43",
"revisionTime": "2017-11-04T16:23:16Z"
},
{
"checksumSHA1": "DF3jZEw4lCq/SEaC7DIl/R+7S70=",
"path": "github.com/renstrom/fuzzysearch/fuzzy",
"revision": "2d205ac6ec17a839a94bdbfd16d2fa6c6dada2e0",
"revisionTime": "2016-03-31T20:48:55Z"
},
{
"path": "go/ast",
"revision": ""
},
{
"path": "go/parser",
"revision": ""
},
{
"path": "go/token",
"revision": ""
},
{
"checksumSHA1": "7EZyXN0EmZLgGxZxK01IJua4c8o=",
"path": "golang.org/x/net/websocket",
"revision": "a8b9294777976932365dabb6640cf1468d95c70f",
"revisionTime": "2017-11-29T19:21:16Z"
},
{
"path": "html",
"revision": ""
},
{
"path": "image",
"revision": ""
},
{
"path": "io",
"revision": ""
},
{
"path": "io/ioutil",
"revision": ""
},
{
"path": "log",
"revision": ""
},
{
"path": "math",
"revision": ""
},
{
"path": "math/rand",
"revision": ""
},
{
"path": "mime/multipart",
"revision": ""
},
{
"path": "net",
"revision": ""
},
{
"path": "net/http",
"revision": ""
},
{
"path": "net/http/httputil",
"revision": ""
},
{
"path": "net/url",
"revision": ""
},
{
"path": "os",
"revision": ""
},
{
"path": "os/signal",
"revision": ""
},
{
"path": "os/user",
"revision": ""
},
{
"path": "path",
"revision": ""
},
{
"path": "path/filepath",
"revision": ""
},
{
"path": "reflect",
"revision": ""
},
{
"path": "regexp",
"revision": ""
},
{
"path": "runtime",
"revision": ""
},
{
"path": "runtime/debug",
"revision": ""
},
{
"path": "sort",
"revision": ""
},
{
"path": "strconv",
"revision": ""
},
{
"path": "strings",
"revision": ""
},
{
"path": "sync",
"revision": ""
},
{
"path": "syscall",
"revision": ""
},
{
"path": "time",
"revision": ""
},
{
"path": "unicode",
"revision": ""
},
{
"path": "unicode/utf16",
"revision": ""
},
{
"path": "unicode/utf8",
"revision": ""
},
{
"path": "unsafe",
"revision": ""
}
],
"rootPath": "github.com/erroneousboat/slack-term"
}