Merge branch 'v0.4.0'
* v0.4.0: Update version to 0.4.0 Set slack token as flag or env variable Implement configuration for desktop notifications Add desktop notifications Speed up process of loading channels Add loading screen Fix termui version Ignore reply events Limit resizing functionality Make default slack-term config file a dotfile Fix panic when pasting in normal mode Migrate to dep and update dependencies
This commit is contained in:
commit
02f91bf802
61
Gopkg.lock
generated
Normal file
61
Gopkg.lock
generated
Normal file
@ -0,0 +1,61 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/0xAX/notificator"
|
||||
packages = ["."]
|
||||
revision = "88d57ee9043ba88d6a62e437fa15dda1ca0d2b59"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/erroneousboat/termui"
|
||||
packages = ["."]
|
||||
revision = "80f245cdfa0488883a3e8602bf3f0c8a3c889a22"
|
||||
|
||||
[[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 = "6cdfd0125aad6371a6f4e75c7fc29507cee4a6001a6c68e06c7237066a31153a"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
50
Gopkg.toml
Normal file
50
Gopkg.toml
Normal 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"
|
||||
revision = "80f245cdfa0488883a3e8602bf3f0c8a3c889a22"
|
||||
|
||||
[[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
|
13
README.md
13
README.md
@ -1,4 +1,4 @@
|
||||
Slack-Term
|
||||
slack-term
|
||||
==========
|
||||
|
||||
A [Slack](https://slack.com) client for your terminal.
|
||||
@ -21,10 +21,12 @@ $ mv slack-term /usr/local/bin
|
||||
|
||||
#### Via Go
|
||||
|
||||
If you want you can also get `slack-term` via Go:
|
||||
If you want, you can also get `slack-term` via Go:
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/erroneousboat/slack-term
|
||||
$ cd $GOPATH/src/github.com/erroneousboat/slack-term
|
||||
$ go install .
|
||||
```
|
||||
|
||||
Setup
|
||||
@ -42,7 +44,12 @@ Setup
|
||||
"slack_token": "yourslacktokenhere",
|
||||
|
||||
// OPTIONAL: set the width of the sidebar (between 1 and 11), default is 1
|
||||
"sidebar_width": 3,
|
||||
"sidebar_width": 1,
|
||||
|
||||
// OPTIONAL: turn on desktop notifications for all incoming messages, set
|
||||
// the value as: "all", and for only mentions and im messages set the
|
||||
// value as: "mention", default is turned off: ""
|
||||
"notify": "",
|
||||
|
||||
// OPTIONAL: define custom key mappings, defaults are:
|
||||
"key_map": {
|
||||
|
@ -3,14 +3,21 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/erroneousboat/termui"
|
||||
)
|
||||
|
||||
const (
|
||||
NotifyAll = "all"
|
||||
NotifyMention = "mention"
|
||||
)
|
||||
|
||||
// Config is the definition of a Config struct
|
||||
type Config struct {
|
||||
SlackToken string `json:"slack_token"`
|
||||
Notify string `json:"notify"`
|
||||
SidebarWidth int `json:"sidebar_width"`
|
||||
MainWidth int `json:"-"`
|
||||
KeyMap map[string]keyMapping `json:"key_map"`
|
||||
@ -25,15 +32,11 @@ func NewConfig(filepath string) (*Config, error) {
|
||||
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return &cfg, err
|
||||
return &cfg, fmt.Errorf("couldn't find the slack-term config file: %v", err)
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(file).Decode(&cfg); err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
if cfg.SlackToken == "" {
|
||||
return &cfg, errors.New("couldn't find 'slack_token' parameter")
|
||||
return &cfg, fmt.Errorf("the slack-term config file isn't valid json: %v", err)
|
||||
}
|
||||
|
||||
if cfg.SidebarWidth < 1 || cfg.SidebarWidth > 11 {
|
||||
@ -42,6 +45,13 @@ func NewConfig(filepath string) (*Config, error) {
|
||||
|
||||
cfg.MainWidth = 12 - cfg.SidebarWidth
|
||||
|
||||
switch cfg.Notify {
|
||||
case NotifyAll, NotifyMention, "":
|
||||
break
|
||||
default:
|
||||
return &cfg, fmt.Errorf("unsupported setting for notify: %s", cfg.Notify)
|
||||
}
|
||||
|
||||
termui.ColorMap = map[string]termui.Attribute{
|
||||
"fg": termui.StringToAttribute(cfg.Theme.View.Fg),
|
||||
"bg": termui.StringToAttribute(cfg.Theme.View.Bg),
|
||||
@ -58,6 +68,7 @@ func getDefaultConfig() Config {
|
||||
return Config{
|
||||
SidebarWidth: 1,
|
||||
MainWidth: 11,
|
||||
Notify: "",
|
||||
KeyMap: map[string]keyMapping{
|
||||
"command": {
|
||||
"i": "mode-insert",
|
||||
|
@ -3,7 +3,9 @@ package context
|
||||
import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
|
||||
"github.com/0xAX/notificator"
|
||||
"github.com/erroneousboat/termui"
|
||||
termbox "github.com/nsf/termbox-go"
|
||||
|
||||
@ -26,23 +28,37 @@ type AppContext struct {
|
||||
Config *config.Config
|
||||
Debug bool
|
||||
Mode string
|
||||
Notify *notificator.Notificator
|
||||
}
|
||||
|
||||
// CreateAppContext creates an application context which can be passed
|
||||
// and referenced througout the application
|
||||
func CreateAppContext(flgConfig string, flgDebug bool) (*AppContext, error) {
|
||||
func CreateAppContext(flgConfig string, flgToken string, flgDebug bool) (*AppContext, error) {
|
||||
if flgDebug {
|
||||
go func() {
|
||||
http.ListenAndServe(":6060", nil)
|
||||
}()
|
||||
}
|
||||
|
||||
// Loading screen
|
||||
views.Loading()
|
||||
|
||||
// Load config
|
||||
config, err := config.NewConfig(flgConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// When slack token isn't set in the config file, we'll check
|
||||
// the command-line flag or the environment variable
|
||||
if config.SlackToken == "" {
|
||||
if flgToken != "" {
|
||||
config.SlackToken = flgToken
|
||||
} else {
|
||||
config.SlackToken = os.Getenv("SLACK_TOKEN")
|
||||
}
|
||||
}
|
||||
|
||||
// Create Service
|
||||
svc, err := service.NewSlackService(config)
|
||||
if err != nil {
|
||||
@ -89,5 +105,6 @@ func CreateAppContext(flgConfig string, flgDebug bool) (*AppContext, error) {
|
||||
Config: config,
|
||||
Debug: flgDebug,
|
||||
Mode: CommandMode,
|
||||
Notify: notificator.New(notificator.Options{AppName: "slack-term"}),
|
||||
}, nil
|
||||
}
|
||||
|
@ -6,15 +6,18 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/0xAX/notificator"
|
||||
"github.com/erroneousboat/termui"
|
||||
"github.com/nlopes/slack"
|
||||
termbox "github.com/nsf/termbox-go"
|
||||
|
||||
"github.com/erroneousboat/slack-term/config"
|
||||
"github.com/erroneousboat/slack-term/context"
|
||||
"github.com/erroneousboat/slack-term/views"
|
||||
)
|
||||
|
||||
var timer *time.Timer
|
||||
var scrollTimer *time.Timer
|
||||
var notifyTimer *time.Timer
|
||||
|
||||
// actionMap binds specific action names to the function counterparts,
|
||||
// these action names can then be used to bind them to specific keys
|
||||
@ -47,6 +50,7 @@ func RegisterEventHandlers(ctx *context.AppContext) {
|
||||
messageHandler(ctx)
|
||||
}
|
||||
|
||||
// eventHandler will handle events created by the user
|
||||
func eventHandler(ctx *context.AppContext) {
|
||||
go func() {
|
||||
for {
|
||||
@ -95,6 +99,7 @@ func handleMoreTermboxEvents(ctx *context.AppContext, ev termbox.Event) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// messageHandler will handle events created by the service
|
||||
func messageHandler(ctx *context.AppContext) {
|
||||
go func() {
|
||||
for {
|
||||
@ -102,13 +107,17 @@ func messageHandler(ctx *context.AppContext) {
|
||||
case msg := <-ctx.Service.RTM.IncomingEvents:
|
||||
switch ev := msg.Data.(type) {
|
||||
case *slack.MessageEvent:
|
||||
|
||||
// Construct message
|
||||
msg := ctx.Service.CreateMessageFromMessageEvent(ev)
|
||||
msg, err := ctx.Service.CreateMessageFromMessageEvent(ev)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add message to the selected channel
|
||||
if ev.Channel == ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID {
|
||||
|
||||
// reverse order of messages, mainly done
|
||||
// Reverse order of messages, mainly done
|
||||
// when attachments are added to message
|
||||
for i := len(msg) - 1; i >= 0; i-- {
|
||||
ctx.View.Chat.AddMessage(
|
||||
@ -128,7 +137,7 @@ func messageHandler(ctx *context.AppContext) {
|
||||
// window (tmux). But only create a notification when
|
||||
// it comes from someone else but the current user.
|
||||
if ev.User != ctx.Service.CurrentUserID {
|
||||
actionNewMessage(ctx, ev.Channel)
|
||||
actionNewMessage(ctx, ev)
|
||||
}
|
||||
case *slack.PresenceChangeEvent:
|
||||
actionSetPresence(ctx, ev.User, ev.Presence)
|
||||
@ -162,6 +171,12 @@ func actionKeyEvent(ctx *context.AppContext, ev termbox.Event) {
|
||||
}
|
||||
|
||||
func actionResizeEvent(ctx *context.AppContext, ev termbox.Event) {
|
||||
// When terminal window is too small termui will panic, here
|
||||
// we won't resize when the terminal window is too small.
|
||||
if termui.TermWidth() < 25 || termui.TermHeight() < 5 {
|
||||
return
|
||||
}
|
||||
|
||||
termui.Body.Width = termui.TermWidth()
|
||||
|
||||
// Vertical resize components
|
||||
@ -233,17 +248,21 @@ func actionSend(ctx *context.AppContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// actionSearch will search through the channels based on the users
|
||||
// input. A time is implemented to make sure the actual searching
|
||||
// and changing of channels is done when the user's typing is paused.
|
||||
func actionSearch(ctx *context.AppContext, key rune) {
|
||||
go func() {
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
actionInput(ctx.View, key)
|
||||
|
||||
timer = time.NewTimer(time.Second / 4)
|
||||
<-timer.C
|
||||
go func() {
|
||||
if scrollTimer != nil {
|
||||
scrollTimer.Stop()
|
||||
}
|
||||
|
||||
scrollTimer = time.NewTimer(time.Second / 4)
|
||||
<-scrollTimer.C
|
||||
|
||||
// Only actually search when the time expires
|
||||
term := ctx.View.Input.GetText()
|
||||
ctx.View.Channels.Search(term)
|
||||
actionChangeChannel(ctx)
|
||||
@ -291,19 +310,19 @@ func actionGetMessages(ctx *context.AppContext) {
|
||||
}
|
||||
|
||||
// actionMoveCursorUpChannels will execute the actionChangeChannel
|
||||
// function. A time is implemented to support fast scrolling through
|
||||
// function. A timer is implemented to support fast scrolling through
|
||||
// the list without executing the actionChangeChannel event
|
||||
func actionMoveCursorUpChannels(ctx *context.AppContext) {
|
||||
go func() {
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
if scrollTimer != nil {
|
||||
scrollTimer.Stop()
|
||||
}
|
||||
|
||||
ctx.View.Channels.MoveCursorUp()
|
||||
termui.Render(ctx.View.Channels)
|
||||
|
||||
timer = time.NewTimer(time.Second / 4)
|
||||
<-timer.C
|
||||
scrollTimer = time.NewTimer(time.Second / 4)
|
||||
<-scrollTimer.C
|
||||
|
||||
// Only actually change channel when the timer expires
|
||||
actionChangeChannel(ctx)
|
||||
@ -311,19 +330,19 @@ func actionMoveCursorUpChannels(ctx *context.AppContext) {
|
||||
}
|
||||
|
||||
// actionMoveCursorDownChannels will execute the actionChangeChannel
|
||||
// function. A time is implemented to support fast scrolling through
|
||||
// function. A timer is implemented to support fast scrolling through
|
||||
// the list without executing the actionChangeChannel event
|
||||
func actionMoveCursorDownChannels(ctx *context.AppContext) {
|
||||
go func() {
|
||||
if timer != nil {
|
||||
timer.Stop()
|
||||
if scrollTimer != nil {
|
||||
scrollTimer.Stop()
|
||||
}
|
||||
|
||||
ctx.View.Channels.MoveCursorDown()
|
||||
termui.Render(ctx.View.Channels)
|
||||
|
||||
timer = time.NewTimer(time.Second / 4)
|
||||
<-timer.C
|
||||
scrollTimer = time.NewTimer(time.Second / 4)
|
||||
<-scrollTimer.C
|
||||
|
||||
// Only actually change channel when the timer expires
|
||||
actionChangeChannel(ctx)
|
||||
@ -382,11 +401,24 @@ func actionChangeChannel(ctx *context.AppContext) {
|
||||
termui.Render(ctx.View.Chat)
|
||||
}
|
||||
|
||||
func actionNewMessage(ctx *context.AppContext, channelID string) {
|
||||
ctx.Service.MarkAsUnread(channelID)
|
||||
// actionNewMessage will set the new message indicator for a channel, and
|
||||
// if configured will also display a desktop notification
|
||||
func actionNewMessage(ctx *context.AppContext, ev *slack.MessageEvent) {
|
||||
ctx.Service.MarkAsUnread(ev.Channel)
|
||||
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
||||
termui.Render(ctx.View.Channels)
|
||||
|
||||
// Terminal bell
|
||||
fmt.Print("\a")
|
||||
|
||||
// Desktop notification
|
||||
if ctx.Config.Notify == config.NotifyMention {
|
||||
if ctx.Service.CheckNotifyMention(ev) {
|
||||
createNotifyMessage(ctx, ev)
|
||||
}
|
||||
} else if ctx.Config.Notify == config.NotifyAll {
|
||||
createNotifyMessage(ctx, ev)
|
||||
}
|
||||
}
|
||||
|
||||
func actionSetPresence(ctx *context.AppContext, channelID string, presence string) {
|
||||
@ -459,3 +491,21 @@ func getKeyString(e termbox.Event) string {
|
||||
ek = pre + mod + k
|
||||
return ek
|
||||
}
|
||||
|
||||
func createNotifyMessage(ctx *context.AppContext, ev *slack.MessageEvent) {
|
||||
go func() {
|
||||
if notifyTimer != nil {
|
||||
notifyTimer.Stop()
|
||||
}
|
||||
|
||||
notifyTimer = time.NewTimer(time.Second * 2)
|
||||
<-notifyTimer.C
|
||||
|
||||
// Only actually notify when time expires
|
||||
ctx.Notify.Push(
|
||||
"slack-term",
|
||||
ctx.Service.CreateNotifyMessage(ev.Channel), "",
|
||||
notificator.UR_NORMAL,
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
14
main.go
14
main.go
@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
VERSION = "v0.3.2"
|
||||
VERSION = "v0.4.0"
|
||||
USAGE = `NAME:
|
||||
slack-term - slack client for your terminal
|
||||
|
||||
@ -36,6 +36,7 @@ GLOBAL OPTIONS:
|
||||
|
||||
var (
|
||||
flgConfig string
|
||||
flgToken string
|
||||
flgDebug bool
|
||||
flgUsage bool
|
||||
)
|
||||
@ -51,10 +52,17 @@ func init() {
|
||||
flag.StringVar(
|
||||
&flgConfig,
|
||||
"config",
|
||||
path.Join(usr.HomeDir, "slack-term.json"),
|
||||
path.Join(usr.HomeDir, ".slack-term"),
|
||||
"location of config file",
|
||||
)
|
||||
|
||||
flag.StringVar(
|
||||
&flgToken,
|
||||
"token",
|
||||
"",
|
||||
"the slack token",
|
||||
)
|
||||
|
||||
flag.BoolVar(
|
||||
&flgDebug,
|
||||
"debug",
|
||||
@ -87,7 +95,7 @@ func main() {
|
||||
termui.DefaultEvtStream = customEvtStream
|
||||
|
||||
// Create context
|
||||
ctx, err := context.CreateAppContext(flgConfig, flgDebug)
|
||||
ctx, err := context.CreateAppContext(flgConfig, flgToken, flgDebug)
|
||||
if err != nil {
|
||||
termbox.Close()
|
||||
log.Println(err)
|
||||
|
145
service/slack.go
145
service/slack.go
@ -7,6 +7,7 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nlopes/slack"
|
||||
@ -46,7 +47,7 @@ func NewSlackService(config *config.Config) (*SlackService, error) {
|
||||
// arrives
|
||||
authTest, err := svc.Client.AuthTest()
|
||||
if err != nil {
|
||||
return nil, errors.New("not able to authorize client, check your connection and or slack-token")
|
||||
return nil, errors.New("not able to authorize client, check your connection and if your slack-token is set correctly")
|
||||
}
|
||||
svc.CurrentUserID = authTest.UserID
|
||||
|
||||
@ -81,12 +82,47 @@ func NewSlackService(config *config.Config) (*SlackService, error) {
|
||||
func (s *SlackService) GetChannels() []string {
|
||||
var chans []components.ChannelItem
|
||||
|
||||
// Channel
|
||||
slackChans, err := s.Client.GetChannels(true)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Channels
|
||||
wg.Add(1)
|
||||
var slackChans []slack.Channel
|
||||
go func() {
|
||||
var err error
|
||||
slackChans, err = s.Client.GetChannels(true)
|
||||
if err != nil {
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// Groups
|
||||
wg.Add(1)
|
||||
var slackGroups []slack.Group
|
||||
go func() {
|
||||
var err error
|
||||
slackGroups, err = s.Client.GetGroups(true)
|
||||
if err != nil {
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// IM
|
||||
wg.Add(1)
|
||||
var slackIM []slack.IM
|
||||
go func() {
|
||||
var err error
|
||||
slackIM, err = s.Client.GetIMChannels()
|
||||
if err != nil {
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Channels
|
||||
for _, chn := range slackChans {
|
||||
if chn.IsMember {
|
||||
s.SlackChannels = append(s.SlackChannels, chn)
|
||||
@ -106,10 +142,6 @@ func (s *SlackService) GetChannels() []string {
|
||||
}
|
||||
|
||||
// Groups
|
||||
slackGroups, err := s.Client.GetGroups(true)
|
||||
if err != nil {
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
for _, grp := range slackGroups {
|
||||
s.SlackChannels = append(s.SlackChannels, grp)
|
||||
chans = append(
|
||||
@ -127,15 +159,8 @@ func (s *SlackService) GetChannels() []string {
|
||||
}
|
||||
|
||||
// IM
|
||||
slackIM, err := s.Client.GetIMChannels()
|
||||
if err != nil {
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
for _, im := range slackIM {
|
||||
|
||||
// FIXME: err
|
||||
presence, _ := s.GetUserPresence(im.User)
|
||||
|
||||
// Uncover name, when we can't uncover name for
|
||||
// IM channel this is then probably a deleted
|
||||
// user, because we won't add deleted users
|
||||
@ -151,7 +176,7 @@ func (s *SlackService) GetChannels() []string {
|
||||
Topic: "",
|
||||
Type: components.ChannelTypeIM,
|
||||
UserID: im.User,
|
||||
Presence: presence,
|
||||
Presence: "",
|
||||
StylePrefix: s.Config.Theme.Channel.Prefix,
|
||||
StyleIcon: s.Config.Theme.Channel.Icon,
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
@ -163,10 +188,15 @@ func (s *SlackService) GetChannels() []string {
|
||||
|
||||
s.Channels = chans
|
||||
|
||||
// We set presence of IM channels here because we need to separately
|
||||
// issue an API call for every channel, this will speed up that process
|
||||
s.SetPresenceChannels()
|
||||
|
||||
var channels []string
|
||||
for _, chn := range s.Channels {
|
||||
channels = append(channels, chn.ToString())
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
@ -179,6 +209,26 @@ func (s *SlackService) ChannelsToString() []string {
|
||||
return channels
|
||||
}
|
||||
|
||||
// SetPresence will set presence for all IM channels
|
||||
func (s *SlackService) SetPresenceChannels() {
|
||||
var wg sync.WaitGroup
|
||||
for i, channel := range s.SlackChannels {
|
||||
|
||||
switch channel := channel.(type) {
|
||||
case slack.IM:
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
presence, _ := s.GetUserPresence(channel.User)
|
||||
s.Channels[i].Presence = presence
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// SetPresenceChannelEvent will set the presence of a IM channel
|
||||
func (s *SlackService) SetPresenceChannelEvent(userID string, presence string) {
|
||||
// Get the correct Channel from svc.Channels
|
||||
@ -256,8 +306,9 @@ func (s *SlackService) MarkAsRead(channelID int) {
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAsUnread will set the channel as unread
|
||||
func (s *SlackService) MarkAsUnread(channelID string) {
|
||||
// FindChannel will loop over s.Channels to find the index where the
|
||||
// channelID equals the ID
|
||||
func (s *SlackService) FindChannel(channelID string) int {
|
||||
var index int
|
||||
for i, channel := range s.Channels {
|
||||
if channel.ID == channelID {
|
||||
@ -265,9 +316,21 @@ func (s *SlackService) MarkAsUnread(channelID string) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// MarkAsUnread will set the channel as unread
|
||||
func (s *SlackService) MarkAsUnread(channelID string) {
|
||||
index := s.FindChannel(channelID)
|
||||
s.Channels[index].Notification = true
|
||||
}
|
||||
|
||||
// GetChannelName will return the name for a specific channelID
|
||||
func (s *SlackService) GetChannelName(channelID string) string {
|
||||
index := s.FindChannel(channelID)
|
||||
return s.Channels[index].Name
|
||||
}
|
||||
|
||||
// SendMessage will send a message to a particular channel
|
||||
func (s *SlackService) SendMessage(channelID int, message string) {
|
||||
|
||||
@ -397,15 +460,19 @@ func (s *SlackService) CreateMessage(message slack.Message) []components.Message
|
||||
return msgs
|
||||
}
|
||||
|
||||
func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent) []components.Message {
|
||||
func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent) ([]components.Message, error) {
|
||||
|
||||
var msgs []components.Message
|
||||
var name string
|
||||
|
||||
switch message.SubType {
|
||||
case "message_changed":
|
||||
// Append (edited) when an edited message is received
|
||||
if message.SubType == "message_changed" {
|
||||
message = &slack.MessageEvent{Msg: *message.SubMessage}
|
||||
message.Text = fmt.Sprintf("%s (edited)", message.Text)
|
||||
case "message_replied":
|
||||
// Ignore reply events
|
||||
return nil, errors.New("ignoring reply events")
|
||||
}
|
||||
|
||||
// Get username from cache
|
||||
@ -462,7 +529,45 @@ func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent
|
||||
|
||||
msgs = append(msgs, msg)
|
||||
|
||||
return msgs
|
||||
return msgs, nil
|
||||
}
|
||||
|
||||
// CheckNotifyMention check if the message event is either contains a
|
||||
// mention or is posted on an IM channel
|
||||
func (s *SlackService) CheckNotifyMention(ev *slack.MessageEvent) bool {
|
||||
channel := s.Channels[s.FindChannel(ev.Channel)]
|
||||
switch channel.Type {
|
||||
case ChannelTypeIM:
|
||||
return true
|
||||
}
|
||||
|
||||
// Mentions have the following format:
|
||||
// <@U12345|erroneousboat>
|
||||
// <@U12345>
|
||||
r := regexp.MustCompile(`\<@(\w+\|*\w+)\>`)
|
||||
matches := r.FindAllString(ev.Text, -1)
|
||||
for _, match := range matches {
|
||||
if strings.Contains(match, s.CurrentUserID) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *SlackService) CreateNotifyMessage(channelID string) string {
|
||||
channel := s.Channels[s.FindChannel(channelID)]
|
||||
|
||||
switch channel.Type {
|
||||
case ChannelTypeChannel:
|
||||
return fmt.Sprintf("Message received on channel: %s", channel.Name)
|
||||
case ChannelTypeGroup:
|
||||
return fmt.Sprintf("Message received in group: %s", channel.Name)
|
||||
case ChannelTypeIM:
|
||||
return fmt.Sprintf("Message received from: %s", channel.Name)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// parseMessage will parse a message string and find and replace:
|
||||
|
25
vendor/github.com/0xAX/notificator/.gitignore
generated
vendored
Normal file
25
vendor/github.com/0xAX/notificator/.gitignore
generated
vendored
Normal 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
|
||||
*.test
|
||||
|
||||
.idea
|
27
vendor/github.com/0xAX/notificator/LICENSE
generated
vendored
Normal file
27
vendor/github.com/0xAX/notificator/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2014, 0xAX
|
||||
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 the {organization} 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 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.
|
49
vendor/github.com/0xAX/notificator/README.md
generated
vendored
Normal file
49
vendor/github.com/0xAX/notificator/README.md
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
notificator
|
||||
===========================
|
||||
|
||||
Desktop notification with Golang for:
|
||||
|
||||
* Windows with `growlnotify`;
|
||||
* Mac OS X with `terminal-notifier` (if installed) or `osascript` (native, 10.9 Mavericks or Up.);
|
||||
* Linux with `notify-send` for Gnome and `kdialog` for Kde.
|
||||
|
||||
Usage
|
||||
------
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/0xAX/notificator"
|
||||
)
|
||||
|
||||
var notify *notificator.Notificator
|
||||
|
||||
func main() {
|
||||
|
||||
notify = notificator.New(notificator.Options{
|
||||
DefaultIcon: "icon/default.png",
|
||||
AppName: "My test App",
|
||||
})
|
||||
|
||||
notify.Push("title", "text", "/home/user/icon.png", notificator.UR_CRITICAL)
|
||||
}
|
||||
```
|
||||
|
||||
TODO
|
||||
-----
|
||||
|
||||
* Add more options for different notificators.
|
||||
|
||||
Сontribution
|
||||
------------
|
||||
|
||||
* Fork;
|
||||
* Make changes;
|
||||
* Send pull request;
|
||||
* Thank you.
|
||||
|
||||
author
|
||||
----------
|
||||
|
||||
[@0xAX](https://twitter.com/0xAX)
|
166
vendor/github.com/0xAX/notificator/notification.go
generated
vendored
Normal file
166
vendor/github.com/0xAX/notificator/notification.go
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
package notificator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
DefaultIcon string
|
||||
AppName string
|
||||
}
|
||||
|
||||
const (
|
||||
UR_NORMAL = "normal"
|
||||
UR_CRITICAL = "critical"
|
||||
)
|
||||
|
||||
type notifier interface {
|
||||
push(title string, text string, iconPath string) *exec.Cmd
|
||||
pushCritical(title string, text string, iconPath string) *exec.Cmd
|
||||
}
|
||||
|
||||
type Notificator struct {
|
||||
notifier notifier
|
||||
defaultIcon string
|
||||
}
|
||||
|
||||
func (n Notificator) Push(title string, text string, iconPath string, urgency string) error {
|
||||
icon := n.defaultIcon
|
||||
|
||||
if iconPath != "" {
|
||||
icon = iconPath
|
||||
}
|
||||
|
||||
if urgency == UR_CRITICAL {
|
||||
return n.notifier.pushCritical(title, text, icon).Run()
|
||||
}
|
||||
|
||||
return n.notifier.push(title, text, icon).Run()
|
||||
|
||||
}
|
||||
|
||||
type osxNotificator struct {
|
||||
AppName string
|
||||
}
|
||||
|
||||
func (o osxNotificator) push(title string, text string, iconPath string) *exec.Cmd {
|
||||
|
||||
// Checks if terminal-notifier exists, and is accessible.
|
||||
|
||||
term_notif := CheckTermNotif()
|
||||
os_version_check := CheckMacOSVersion()
|
||||
|
||||
// if terminal-notifier exists, use it.
|
||||
// else, fall back to osascript. (Mavericks and later.)
|
||||
|
||||
if term_notif == true {
|
||||
return exec.Command("terminal-notifier", "-title", o.AppName, "-message", text, "-subtitle", title, "-appIcon", iconPath)
|
||||
} else if os_version_check == true {
|
||||
title = strings.Replace(title, `"`, `\"`, -1)
|
||||
text = strings.Replace(text, `"`, `\"`, -1)
|
||||
|
||||
notification := fmt.Sprintf("display notification \"%s\" with title \"%s\" subtitle \"%s\"", text, o.AppName, title)
|
||||
return exec.Command("osascript", "-e", notification)
|
||||
}
|
||||
|
||||
// finally falls back to growlnotify.
|
||||
|
||||
return exec.Command("growlnotify", "-n", o.AppName, "--image", iconPath, "-m", title)
|
||||
}
|
||||
|
||||
// Causes the notification to stick around until clicked.
|
||||
func (o osxNotificator) pushCritical(title string, text string, iconPath string) *exec.Cmd {
|
||||
|
||||
// same function as above...
|
||||
|
||||
term_notif := CheckTermNotif()
|
||||
os_version_check := CheckMacOSVersion()
|
||||
|
||||
if term_notif == true {
|
||||
// timeout set to 30 seconds, to show the importance of the notification
|
||||
return exec.Command("terminal-notifier", "-title", o.AppName, "-message", text, "-subtitle", title, "-timeout", "30")
|
||||
} else if os_version_check == true {
|
||||
notification := fmt.Sprintf("display notification \"%s\" with title \"%s\" subtitle \"%s\"", text, o.AppName, title)
|
||||
return exec.Command("osascript", "-e", notification)
|
||||
}
|
||||
|
||||
return exec.Command("growlnotify", "-n", o.AppName, "--image", iconPath, "-m", title)
|
||||
|
||||
}
|
||||
|
||||
type linuxNotificator struct{}
|
||||
|
||||
func (l linuxNotificator) push(title string, text string, iconPath string) *exec.Cmd {
|
||||
return exec.Command("notify-send", "-i", iconPath, title, text)
|
||||
}
|
||||
|
||||
// Causes the notification to stick around until clicked.
|
||||
func (l linuxNotificator) pushCritical(title string, text string, iconPath string) *exec.Cmd {
|
||||
return exec.Command("notify-send", "-i", iconPath, title, text, "-u", "critical")
|
||||
}
|
||||
|
||||
type windowsNotificator struct{}
|
||||
|
||||
func (w windowsNotificator) push(title string, text string, iconPath string) *exec.Cmd {
|
||||
return exec.Command("growlnotify", "/i:", iconPath, "/t:", title, text)
|
||||
}
|
||||
|
||||
// Causes the notification to stick around until clicked.
|
||||
func (w windowsNotificator) pushCritical(title string, text string, iconPath string) *exec.Cmd {
|
||||
return exec.Command("notify-send", "-i", iconPath, title, text, "/s", "true", "/p", "2")
|
||||
}
|
||||
|
||||
func New(o Options) *Notificator {
|
||||
|
||||
var Notifier notifier
|
||||
|
||||
switch runtime.GOOS {
|
||||
|
||||
case "darwin":
|
||||
Notifier = osxNotificator{AppName: o.AppName}
|
||||
case "linux":
|
||||
Notifier = linuxNotificator{}
|
||||
case "windows":
|
||||
Notifier = windowsNotificator{}
|
||||
|
||||
}
|
||||
|
||||
return &Notificator{notifier: Notifier, defaultIcon: o.DefaultIcon}
|
||||
}
|
||||
|
||||
// Helper function for macOS
|
||||
|
||||
func CheckTermNotif() bool {
|
||||
// Checks if terminal-notifier exists, and is accessible.
|
||||
if err := exec.Command("which", "terminal-notifier").Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
// no error, so return true. (terminal-notifier exists)
|
||||
return true
|
||||
}
|
||||
|
||||
func CheckMacOSVersion() bool {
|
||||
// Checks if the version of macOS is 10.9 or Higher (osascript support for notifications.)
|
||||
|
||||
cmd := exec.Command("sw_vers", "-productVersion")
|
||||
check, _ := cmd.Output()
|
||||
|
||||
version := strings.Split(strings.TrimSpace(string(check)), ".")
|
||||
|
||||
// semantic versioning of macOS
|
||||
|
||||
major, _ := strconv.Atoi(version[0])
|
||||
minor, _ := strconv.Atoi(version[1])
|
||||
|
||||
if major < 10 {
|
||||
return false
|
||||
} else if major == 10 && minor < 9 {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
26
vendor/github.com/erroneousboat/termui/.gitignore
generated
vendored
Normal file
26
vendor/github.com/erroneousboat/termui/.gitignore
generated
vendored
Normal 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
6
vendor/github.com/erroneousboat/termui/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- tip
|
||||
|
||||
script: go test -v ./
|
25
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
Normal file
25
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
Normal 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
19
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
Normal 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
8
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal 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
22
vendor/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal 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
64
vendor/github.com/gorilla/websocket/README.md
generated
vendored
Normal 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
392
vendor/github.com/gorilla/websocket/client.go
generated
vendored
Normal 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
16
vendor/github.com/gorilla/websocket/client_clone.go
generated
vendored
Normal 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()
|
||||
}
|
38
vendor/github.com/gorilla/websocket/client_clone_legacy.go
generated
vendored
Normal file
38
vendor/github.com/gorilla/websocket/client_clone_legacy.go
generated
vendored
Normal 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
148
vendor/github.com/gorilla/websocket/compression.go
generated
vendored
Normal 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
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
18
vendor/github.com/gorilla/websocket/conn_read.go
generated
vendored
Normal 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
|
||||
}
|
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
Normal file
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
Normal 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
180
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
Normal 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
55
vendor/github.com/gorilla/websocket/json.go
generated
vendored
Normal 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
55
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
Normal 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
15
vendor/github.com/gorilla/websocket/mask_safe.go
generated
vendored
Normal 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
103
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
Normal 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
291
vendor/github.com/gorilla/websocket/server.go
generated
vendored
Normal 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
214
vendor/github.com/gorilla/websocket/util.go
generated
vendored
Normal 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
|
||||
}
|
37
vendor/github.com/maruel/panicparse/stack/source.go
generated
vendored
37
vendor/github.com/maruel/panicparse/stack/source.go
generated
vendored
@ -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
|
||||
|
12
vendor/github.com/maruel/panicparse/stack/stack.go
generated
vendored
12
vendor/github.com/maruel/panicparse/stack/stack.go
generated
vendored
@ -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
8
vendor/github.com/mattn/go-runewidth/.travis.yml
generated
vendored
Normal 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
|
1
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
1
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
@ -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
2
vendor/github.com/nlopes/slack/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.test
|
||||
*~
|
21
vendor/github.com/nlopes/slack/.travis.yml
generated
vendored
Normal file
21
vendor/github.com/nlopes/slack/.travis.yml
generated
vendored
Normal 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
|
2
vendor/github.com/nlopes/slack/README.md
generated
vendored
2
vendor/github.com/nlopes/slack/README.md
generated
vendored
@ -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.
|
||||
|
36
vendor/github.com/nlopes/slack/admin.go
generated
vendored
36
vendor/github.com/nlopes/slack/admin.go
generated
vendored
@ -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)
|
||||
}
|
||||
|
6
vendor/github.com/nlopes/slack/attachments.go
generated
vendored
6
vendor/github.com/nlopes/slack/attachments.go
generated
vendored
@ -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"`
|
||||
|
9
vendor/github.com/nlopes/slack/bots.go
generated
vendored
9
vendor/github.com/nlopes/slack/bots.go
generated
vendored
@ -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
|
||||
}
|
||||
|
234
vendor/github.com/nlopes/slack/channels.go
generated
vendored
234
vendor/github.com/nlopes/slack/channels.go
generated
vendored
@ -23,11 +23,12 @@ type Channel struct {
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
78
vendor/github.com/nlopes/slack/chat.go
generated
vendored
78
vendor/github.com/nlopes/slack/chat.go
generated
vendored
@ -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,14 +45,20 @@ 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,
|
||||
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,
|
||||
@ -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
|
||||
}
|
||||
@ -174,6 +212,7 @@ const (
|
||||
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
|
||||
}
|
||||
|
555
vendor/github.com/nlopes/slack/conversation.go
generated
vendored
555
vendor/github.com/nlopes/slack/conversation.go
generated
vendored
@ -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
|
||||
}
|
||||
|
28
vendor/github.com/nlopes/slack/dnd.go
generated
vendored
28
vendor/github.com/nlopes/slack/dnd.go
generated
vendored
@ -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
|
||||
}
|
||||
|
5
vendor/github.com/nlopes/slack/emoji.go
generated
vendored
5
vendor/github.com/nlopes/slack/emoji.go
generated
vendored
@ -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
|
||||
}
|
||||
|
65
vendor/github.com/nlopes/slack/files.go
generated
vendored
65
vendor/github.com/nlopes/slack/files.go
generated
vendored
@ -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
|
||||
}
|
||||
|
106
vendor/github.com/nlopes/slack/groups.go
generated
vendored
106
vendor/github.com/nlopes/slack/groups.go
generated
vendored
@ -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
29
vendor/github.com/nlopes/slack/im.go
generated
vendored
@ -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
53
vendor/github.com/nlopes/slack/logger.go
generated
vendored
Normal 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...))
|
||||
}
|
16
vendor/github.com/nlopes/slack/messages.go
generated
vendored
16
vendor/github.com/nlopes/slack/messages.go
generated
vendored
@ -3,6 +3,7 @@ package slack
|
||||
// OutgoingMessage is used for the realtime API, and seems incomplete.
|
||||
type OutgoingMessage struct {
|
||||
ID int `json:"id"`
|
||||
// channel ID
|
||||
Channel string `json:"channel,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
89
vendor/github.com/nlopes/slack/misc.go
generated
vendored
89
vendor/github.com/nlopes/slack/misc.go
generated
vendored
@ -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 json.Unmarshal(response, &intf)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
2
vendor/github.com/nlopes/slack/oauth.go
generated
vendored
2
vendor/github.com/nlopes/slack/oauth.go
generated
vendored
@ -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
|
||||
}
|
||||
|
15
vendor/github.com/nlopes/slack/pins.go
generated
vendored
15
vendor/github.com/nlopes/slack/pins.go
generated
vendored
@ -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
|
||||
}
|
||||
|
20
vendor/github.com/nlopes/slack/reactions.go
generated
vendored
20
vendor/github.com/nlopes/slack/reactions.go
generated
vendored
@ -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
|
||||
}
|
||||
|
42
vendor/github.com/nlopes/slack/rtm.go
generated
vendored
42
vendor/github.com/nlopes/slack/rtm.go
generated
vendored
@ -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),
|
||||
|
5
vendor/github.com/nlopes/slack/search.go
generated
vendored
5
vendor/github.com/nlopes/slack/search.go
generated
vendored
@ -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
|
||||
}
|
||||
|
72
vendor/github.com/nlopes/slack/slack.go
generated
vendored
72
vendor/github.com/nlopes/slack/slack.go
generated
vendored
@ -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
|
||||
}
|
||||
info Info
|
||||
debug bool
|
||||
httpclient HTTPRequester
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
49
vendor/github.com/nlopes/slack/slash.go
generated
vendored
Normal 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
|
||||
}
|
15
vendor/github.com/nlopes/slack/stars.go
generated
vendored
15
vendor/github.com/nlopes/slack/stars.go
generated
vendored
@ -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
|
||||
}
|
||||
|
29
vendor/github.com/nlopes/slack/team.go
generated
vendored
29
vendor/github.com/nlopes/slack/team.go
generated
vendored
@ -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)
|
||||
}
|
||||
|
32
vendor/github.com/nlopes/slack/usergroups.go
generated
vendored
32
vendor/github.com/nlopes/slack/usergroups.go
generated
vendored
@ -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
|
||||
}
|
||||
|
82
vendor/github.com/nlopes/slack/users.go
generated
vendored
82
vendor/github.com/nlopes/slack/users.go
generated
vendored
@ -19,6 +19,8 @@ type UserProfile struct {
|
||||
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"`
|
||||
@ -33,11 +35,13 @@ type UserProfile struct {
|
||||
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
|
||||
}
|
||||
|
||||
|
8
vendor/github.com/nlopes/slack/websocket.go
generated
vendored
8
vendor/github.com/nlopes/slack/websocket.go
generated
vendored
@ -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
|
||||
}
|
||||
|
7
vendor/github.com/nlopes/slack/websocket_internals.go
generated
vendored
7
vendor/github.com/nlopes/slack/websocket_internals.go
generated
vendored
@ -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
|
||||
|
39
vendor/github.com/nlopes/slack/websocket_managed_conn.go
generated
vendored
39
vendor/github.com/nlopes/slack/websocket_managed_conn.go
generated
vendored
@ -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,11 +339,20 @@ 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{fmt.Errorf("ack decode failure")}}
|
||||
}
|
||||
}
|
||||
|
||||
// handlePong handles an incoming 'PONG' message which should be in response to
|
||||
|
83
vendor/github.com/nlopes/slack/websocket_proxy.go
generated
vendored
83
vendor/github.com/nlopes/slack/websocket_proxy.go
generated
vendored
@ -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)
|
||||
}
|
20
vendor/github.com/nlopes/slack/websocket_utils.go
generated
vendored
20
vendor/github.com/nlopes/slack/websocket_utils.go
generated
vendored
@ -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
|
||||
}
|
6
vendor/github.com/nsf/termbox-go/README.md
generated
vendored
6
vendor/github.com/nsf/termbox-go/README.md
generated
vendored
@ -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/
|
||||
|
44
vendor/github.com/nsf/termbox-go/api.go
generated
vendored
44
vendor/github.com/nsf/termbox-go/api.go
generated
vendored
@ -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:
|
||||
|
2
vendor/github.com/nsf/termbox-go/api_common.go
generated
vendored
2
vendor/github.com/nsf/termbox-go/api_common.go
generated
vendored
@ -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
11
vendor/github.com/nsf/termbox-go/escwait.go
generated
vendored
Normal 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
9
vendor/github.com/nsf/termbox-go/escwait_darwin.go
generated
vendored
Normal 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
39
vendor/github.com/nsf/termbox-go/syscalls.go
generated
vendored
Normal 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
|
||||
)
|
40
vendor/github.com/nsf/termbox-go/termbox.go
generated
vendored
40
vendor/github.com/nsf/termbox-go/termbox.go
generated
vendored
@ -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) {
|
||||
|
7
vendor/github.com/nsf/termbox-go/terminfo.go
generated
vendored
7
vendor/github.com/nsf/termbox-go/terminfo.go
generated
vendored
@ -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)
|
||||
|
16
vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go
generated
vendored
16
vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go
generated
vendored
@ -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
27
vendor/golang.org/x/net/LICENSE
generated
vendored
@ -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
22
vendor/golang.org/x/net/PATENTS
generated
vendored
@ -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.
|
106
vendor/golang.org/x/net/websocket/client.go
generated
vendored
106
vendor/golang.org/x/net/websocket/client.go
generated
vendored
@ -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}
|
||||
}
|
24
vendor/golang.org/x/net/websocket/dial.go
generated
vendored
24
vendor/golang.org/x/net/websocket/dial.go
generated
vendored
@ -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
|
||||
}
|
583
vendor/golang.org/x/net/websocket/hybi.go
generated
vendored
583
vendor/golang.org/x/net/websocket/hybi.go
generated
vendored
@ -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)
|
||||
}
|
113
vendor/golang.org/x/net/websocket/server.go
generated
vendored
113
vendor/golang.org/x/net/websocket/server.go
generated
vendored
@ -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)
|
||||
}
|
448
vendor/golang.org/x/net/websocket/websocket.go
generated
vendored
448
vendor/golang.org/x/net/websocket/websocket.go
generated
vendored
@ -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
243
vendor/vendor.json
vendored
@ -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"
|
||||
}
|
21
views/load.go
Normal file
21
views/load.go
Normal file
@ -0,0 +1,21 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
termbox "github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
func Loading() {
|
||||
const loading string = "LOADING"
|
||||
|
||||
w, h := termbox.Size()
|
||||
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
||||
|
||||
offset := (w / 2) - (len(loading) / 2)
|
||||
y := h / 2
|
||||
|
||||
for x := 0; x < len(loading); x++ {
|
||||
termbox.SetCell(offset+x, y, rune(loading[x]), termbox.ColorDefault, termbox.ColorDefault)
|
||||
}
|
||||
|
||||
termbox.Flush()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user