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.
|
A [Slack](https://slack.com) client for your terminal.
|
||||||
@ -21,10 +21,12 @@ $ mv slack-term /usr/local/bin
|
|||||||
|
|
||||||
#### Via Go
|
#### 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
|
```bash
|
||||||
$ go get -u github.com/erroneousboat/slack-term
|
$ go get -u github.com/erroneousboat/slack-term
|
||||||
|
$ cd $GOPATH/src/github.com/erroneousboat/slack-term
|
||||||
|
$ go install .
|
||||||
```
|
```
|
||||||
|
|
||||||
Setup
|
Setup
|
||||||
@ -42,7 +44,12 @@ Setup
|
|||||||
"slack_token": "yourslacktokenhere",
|
"slack_token": "yourslacktokenhere",
|
||||||
|
|
||||||
// OPTIONAL: set the width of the sidebar (between 1 and 11), default is 1
|
// 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:
|
// OPTIONAL: define custom key mappings, defaults are:
|
||||||
"key_map": {
|
"key_map": {
|
||||||
|
@ -3,14 +3,21 @@ package config
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/erroneousboat/termui"
|
"github.com/erroneousboat/termui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
NotifyAll = "all"
|
||||||
|
NotifyMention = "mention"
|
||||||
|
)
|
||||||
|
|
||||||
// Config is the definition of a Config struct
|
// Config is the definition of a Config struct
|
||||||
type Config struct {
|
type Config struct {
|
||||||
SlackToken string `json:"slack_token"`
|
SlackToken string `json:"slack_token"`
|
||||||
|
Notify string `json:"notify"`
|
||||||
SidebarWidth int `json:"sidebar_width"`
|
SidebarWidth int `json:"sidebar_width"`
|
||||||
MainWidth int `json:"-"`
|
MainWidth int `json:"-"`
|
||||||
KeyMap map[string]keyMapping `json:"key_map"`
|
KeyMap map[string]keyMapping `json:"key_map"`
|
||||||
@ -25,15 +32,11 @@ func NewConfig(filepath string) (*Config, error) {
|
|||||||
|
|
||||||
file, err := os.Open(filepath)
|
file, err := os.Open(filepath)
|
||||||
if err != nil {
|
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 {
|
if err := json.NewDecoder(file).Decode(&cfg); err != nil {
|
||||||
return &cfg, err
|
return &cfg, fmt.Errorf("the slack-term config file isn't valid json: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.SlackToken == "" {
|
|
||||||
return &cfg, errors.New("couldn't find 'slack_token' parameter")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.SidebarWidth < 1 || cfg.SidebarWidth > 11 {
|
if cfg.SidebarWidth < 1 || cfg.SidebarWidth > 11 {
|
||||||
@ -42,6 +45,13 @@ func NewConfig(filepath string) (*Config, error) {
|
|||||||
|
|
||||||
cfg.MainWidth = 12 - cfg.SidebarWidth
|
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{
|
termui.ColorMap = map[string]termui.Attribute{
|
||||||
"fg": termui.StringToAttribute(cfg.Theme.View.Fg),
|
"fg": termui.StringToAttribute(cfg.Theme.View.Fg),
|
||||||
"bg": termui.StringToAttribute(cfg.Theme.View.Bg),
|
"bg": termui.StringToAttribute(cfg.Theme.View.Bg),
|
||||||
@ -58,6 +68,7 @@ func getDefaultConfig() Config {
|
|||||||
return Config{
|
return Config{
|
||||||
SidebarWidth: 1,
|
SidebarWidth: 1,
|
||||||
MainWidth: 11,
|
MainWidth: 11,
|
||||||
|
Notify: "",
|
||||||
KeyMap: map[string]keyMapping{
|
KeyMap: map[string]keyMapping{
|
||||||
"command": {
|
"command": {
|
||||||
"i": "mode-insert",
|
"i": "mode-insert",
|
||||||
|
@ -3,7 +3,9 @@ package context
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/0xAX/notificator"
|
||||||
"github.com/erroneousboat/termui"
|
"github.com/erroneousboat/termui"
|
||||||
termbox "github.com/nsf/termbox-go"
|
termbox "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
@ -26,23 +28,37 @@ type AppContext struct {
|
|||||||
Config *config.Config
|
Config *config.Config
|
||||||
Debug bool
|
Debug bool
|
||||||
Mode string
|
Mode string
|
||||||
|
Notify *notificator.Notificator
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAppContext creates an application context which can be passed
|
// CreateAppContext creates an application context which can be passed
|
||||||
// and referenced througout the application
|
// 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 {
|
if flgDebug {
|
||||||
go func() {
|
go func() {
|
||||||
http.ListenAndServe(":6060", nil)
|
http.ListenAndServe(":6060", nil)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loading screen
|
||||||
|
views.Loading()
|
||||||
|
|
||||||
// Load config
|
// Load config
|
||||||
config, err := config.NewConfig(flgConfig)
|
config, err := config.NewConfig(flgConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Create Service
|
||||||
svc, err := service.NewSlackService(config)
|
svc, err := service.NewSlackService(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -89,5 +105,6 @@ func CreateAppContext(flgConfig string, flgDebug bool) (*AppContext, error) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
Debug: flgDebug,
|
Debug: flgDebug,
|
||||||
Mode: CommandMode,
|
Mode: CommandMode,
|
||||||
|
Notify: notificator.New(notificator.Options{AppName: "slack-term"}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,18 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/0xAX/notificator"
|
||||||
"github.com/erroneousboat/termui"
|
"github.com/erroneousboat/termui"
|
||||||
"github.com/nlopes/slack"
|
"github.com/nlopes/slack"
|
||||||
termbox "github.com/nsf/termbox-go"
|
termbox "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
|
"github.com/erroneousboat/slack-term/config"
|
||||||
"github.com/erroneousboat/slack-term/context"
|
"github.com/erroneousboat/slack-term/context"
|
||||||
"github.com/erroneousboat/slack-term/views"
|
"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,
|
// actionMap binds specific action names to the function counterparts,
|
||||||
// these action names can then be used to bind them to specific keys
|
// these action names can then be used to bind them to specific keys
|
||||||
@ -47,6 +50,7 @@ func RegisterEventHandlers(ctx *context.AppContext) {
|
|||||||
messageHandler(ctx)
|
messageHandler(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eventHandler will handle events created by the user
|
||||||
func eventHandler(ctx *context.AppContext) {
|
func eventHandler(ctx *context.AppContext) {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
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) {
|
func messageHandler(ctx *context.AppContext) {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
@ -102,13 +107,17 @@ func messageHandler(ctx *context.AppContext) {
|
|||||||
case msg := <-ctx.Service.RTM.IncomingEvents:
|
case msg := <-ctx.Service.RTM.IncomingEvents:
|
||||||
switch ev := msg.Data.(type) {
|
switch ev := msg.Data.(type) {
|
||||||
case *slack.MessageEvent:
|
case *slack.MessageEvent:
|
||||||
|
|
||||||
// Construct message
|
// Construct message
|
||||||
msg := ctx.Service.CreateMessageFromMessageEvent(ev)
|
msg, err := ctx.Service.CreateMessageFromMessageEvent(ev)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Add message to the selected channel
|
// Add message to the selected channel
|
||||||
if ev.Channel == ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID {
|
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
|
// when attachments are added to message
|
||||||
for i := len(msg) - 1; i >= 0; i-- {
|
for i := len(msg) - 1; i >= 0; i-- {
|
||||||
ctx.View.Chat.AddMessage(
|
ctx.View.Chat.AddMessage(
|
||||||
@ -128,7 +137,7 @@ func messageHandler(ctx *context.AppContext) {
|
|||||||
// window (tmux). But only create a notification when
|
// window (tmux). But only create a notification when
|
||||||
// it comes from someone else but the current user.
|
// it comes from someone else but the current user.
|
||||||
if ev.User != ctx.Service.CurrentUserID {
|
if ev.User != ctx.Service.CurrentUserID {
|
||||||
actionNewMessage(ctx, ev.Channel)
|
actionNewMessage(ctx, ev)
|
||||||
}
|
}
|
||||||
case *slack.PresenceChangeEvent:
|
case *slack.PresenceChangeEvent:
|
||||||
actionSetPresence(ctx, ev.User, ev.Presence)
|
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) {
|
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()
|
termui.Body.Width = termui.TermWidth()
|
||||||
|
|
||||||
// Vertical resize components
|
// 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) {
|
func actionSearch(ctx *context.AppContext, key rune) {
|
||||||
|
actionInput(ctx.View, key)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if timer != nil {
|
if scrollTimer != nil {
|
||||||
timer.Stop()
|
scrollTimer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
actionInput(ctx.View, key)
|
scrollTimer = time.NewTimer(time.Second / 4)
|
||||||
|
<-scrollTimer.C
|
||||||
timer = time.NewTimer(time.Second / 4)
|
|
||||||
<-timer.C
|
|
||||||
|
|
||||||
|
// Only actually search when the time expires
|
||||||
term := ctx.View.Input.GetText()
|
term := ctx.View.Input.GetText()
|
||||||
ctx.View.Channels.Search(term)
|
ctx.View.Channels.Search(term)
|
||||||
actionChangeChannel(ctx)
|
actionChangeChannel(ctx)
|
||||||
@ -291,19 +310,19 @@ func actionGetMessages(ctx *context.AppContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// actionMoveCursorUpChannels will execute the actionChangeChannel
|
// 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
|
// the list without executing the actionChangeChannel event
|
||||||
func actionMoveCursorUpChannels(ctx *context.AppContext) {
|
func actionMoveCursorUpChannels(ctx *context.AppContext) {
|
||||||
go func() {
|
go func() {
|
||||||
if timer != nil {
|
if scrollTimer != nil {
|
||||||
timer.Stop()
|
scrollTimer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.View.Channels.MoveCursorUp()
|
ctx.View.Channels.MoveCursorUp()
|
||||||
termui.Render(ctx.View.Channels)
|
termui.Render(ctx.View.Channels)
|
||||||
|
|
||||||
timer = time.NewTimer(time.Second / 4)
|
scrollTimer = time.NewTimer(time.Second / 4)
|
||||||
<-timer.C
|
<-scrollTimer.C
|
||||||
|
|
||||||
// Only actually change channel when the timer expires
|
// Only actually change channel when the timer expires
|
||||||
actionChangeChannel(ctx)
|
actionChangeChannel(ctx)
|
||||||
@ -311,19 +330,19 @@ func actionMoveCursorUpChannels(ctx *context.AppContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// actionMoveCursorDownChannels will execute the actionChangeChannel
|
// 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
|
// the list without executing the actionChangeChannel event
|
||||||
func actionMoveCursorDownChannels(ctx *context.AppContext) {
|
func actionMoveCursorDownChannels(ctx *context.AppContext) {
|
||||||
go func() {
|
go func() {
|
||||||
if timer != nil {
|
if scrollTimer != nil {
|
||||||
timer.Stop()
|
scrollTimer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.View.Channels.MoveCursorDown()
|
ctx.View.Channels.MoveCursorDown()
|
||||||
termui.Render(ctx.View.Channels)
|
termui.Render(ctx.View.Channels)
|
||||||
|
|
||||||
timer = time.NewTimer(time.Second / 4)
|
scrollTimer = time.NewTimer(time.Second / 4)
|
||||||
<-timer.C
|
<-scrollTimer.C
|
||||||
|
|
||||||
// Only actually change channel when the timer expires
|
// Only actually change channel when the timer expires
|
||||||
actionChangeChannel(ctx)
|
actionChangeChannel(ctx)
|
||||||
@ -382,11 +401,24 @@ func actionChangeChannel(ctx *context.AppContext) {
|
|||||||
termui.Render(ctx.View.Chat)
|
termui.Render(ctx.View.Chat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func actionNewMessage(ctx *context.AppContext, channelID string) {
|
// actionNewMessage will set the new message indicator for a channel, and
|
||||||
ctx.Service.MarkAsUnread(channelID)
|
// 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())
|
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
||||||
termui.Render(ctx.View.Channels)
|
termui.Render(ctx.View.Channels)
|
||||||
|
|
||||||
|
// Terminal bell
|
||||||
fmt.Print("\a")
|
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) {
|
func actionSetPresence(ctx *context.AppContext, channelID string, presence string) {
|
||||||
@ -459,3 +491,21 @@ func getKeyString(e termbox.Event) string {
|
|||||||
ek = pre + mod + k
|
ek = pre + mod + k
|
||||||
return ek
|
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 (
|
const (
|
||||||
VERSION = "v0.3.2"
|
VERSION = "v0.4.0"
|
||||||
USAGE = `NAME:
|
USAGE = `NAME:
|
||||||
slack-term - slack client for your terminal
|
slack-term - slack client for your terminal
|
||||||
|
|
||||||
@ -36,6 +36,7 @@ GLOBAL OPTIONS:
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
flgConfig string
|
flgConfig string
|
||||||
|
flgToken string
|
||||||
flgDebug bool
|
flgDebug bool
|
||||||
flgUsage bool
|
flgUsage bool
|
||||||
)
|
)
|
||||||
@ -51,10 +52,17 @@ func init() {
|
|||||||
flag.StringVar(
|
flag.StringVar(
|
||||||
&flgConfig,
|
&flgConfig,
|
||||||
"config",
|
"config",
|
||||||
path.Join(usr.HomeDir, "slack-term.json"),
|
path.Join(usr.HomeDir, ".slack-term"),
|
||||||
"location of config file",
|
"location of config file",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
flag.StringVar(
|
||||||
|
&flgToken,
|
||||||
|
"token",
|
||||||
|
"",
|
||||||
|
"the slack token",
|
||||||
|
)
|
||||||
|
|
||||||
flag.BoolVar(
|
flag.BoolVar(
|
||||||
&flgDebug,
|
&flgDebug,
|
||||||
"debug",
|
"debug",
|
||||||
@ -87,7 +95,7 @@ func main() {
|
|||||||
termui.DefaultEvtStream = customEvtStream
|
termui.DefaultEvtStream = customEvtStream
|
||||||
|
|
||||||
// Create context
|
// Create context
|
||||||
ctx, err := context.CreateAppContext(flgConfig, flgDebug)
|
ctx, err := context.CreateAppContext(flgConfig, flgToken, flgDebug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
termbox.Close()
|
termbox.Close()
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
153
service/slack.go
153
service/slack.go
@ -7,6 +7,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nlopes/slack"
|
"github.com/nlopes/slack"
|
||||||
@ -46,7 +47,7 @@ func NewSlackService(config *config.Config) (*SlackService, error) {
|
|||||||
// arrives
|
// arrives
|
||||||
authTest, err := svc.Client.AuthTest()
|
authTest, err := svc.Client.AuthTest()
|
||||||
if err != nil {
|
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
|
svc.CurrentUserID = authTest.UserID
|
||||||
|
|
||||||
@ -81,12 +82,47 @@ func NewSlackService(config *config.Config) (*SlackService, error) {
|
|||||||
func (s *SlackService) GetChannels() []string {
|
func (s *SlackService) GetChannels() []string {
|
||||||
var chans []components.ChannelItem
|
var chans []components.ChannelItem
|
||||||
|
|
||||||
// Channel
|
var wg sync.WaitGroup
|
||||||
slackChans, err := s.Client.GetChannels(true)
|
|
||||||
if err != nil {
|
|
||||||
chans = append(chans, components.ChannelItem{})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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 {
|
for _, chn := range slackChans {
|
||||||
if chn.IsMember {
|
if chn.IsMember {
|
||||||
s.SlackChannels = append(s.SlackChannels, chn)
|
s.SlackChannels = append(s.SlackChannels, chn)
|
||||||
@ -106,10 +142,6 @@ func (s *SlackService) GetChannels() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Groups
|
// Groups
|
||||||
slackGroups, err := s.Client.GetGroups(true)
|
|
||||||
if err != nil {
|
|
||||||
chans = append(chans, components.ChannelItem{})
|
|
||||||
}
|
|
||||||
for _, grp := range slackGroups {
|
for _, grp := range slackGroups {
|
||||||
s.SlackChannels = append(s.SlackChannels, grp)
|
s.SlackChannels = append(s.SlackChannels, grp)
|
||||||
chans = append(
|
chans = append(
|
||||||
@ -127,15 +159,8 @@ func (s *SlackService) GetChannels() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IM
|
// IM
|
||||||
slackIM, err := s.Client.GetIMChannels()
|
|
||||||
if err != nil {
|
|
||||||
chans = append(chans, components.ChannelItem{})
|
|
||||||
}
|
|
||||||
for _, im := range slackIM {
|
for _, im := range slackIM {
|
||||||
|
|
||||||
// FIXME: err
|
|
||||||
presence, _ := s.GetUserPresence(im.User)
|
|
||||||
|
|
||||||
// Uncover name, when we can't uncover name for
|
// Uncover name, when we can't uncover name for
|
||||||
// IM channel this is then probably a deleted
|
// IM channel this is then probably a deleted
|
||||||
// user, because we won't add deleted users
|
// user, because we won't add deleted users
|
||||||
@ -151,7 +176,7 @@ func (s *SlackService) GetChannels() []string {
|
|||||||
Topic: "",
|
Topic: "",
|
||||||
Type: components.ChannelTypeIM,
|
Type: components.ChannelTypeIM,
|
||||||
UserID: im.User,
|
UserID: im.User,
|
||||||
Presence: presence,
|
Presence: "",
|
||||||
StylePrefix: s.Config.Theme.Channel.Prefix,
|
StylePrefix: s.Config.Theme.Channel.Prefix,
|
||||||
StyleIcon: s.Config.Theme.Channel.Icon,
|
StyleIcon: s.Config.Theme.Channel.Icon,
|
||||||
StyleText: s.Config.Theme.Channel.Text,
|
StyleText: s.Config.Theme.Channel.Text,
|
||||||
@ -163,10 +188,15 @@ func (s *SlackService) GetChannels() []string {
|
|||||||
|
|
||||||
s.Channels = chans
|
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
|
var channels []string
|
||||||
for _, chn := range s.Channels {
|
for _, chn := range s.Channels {
|
||||||
channels = append(channels, chn.ToString())
|
channels = append(channels, chn.ToString())
|
||||||
}
|
}
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,6 +209,26 @@ func (s *SlackService) ChannelsToString() []string {
|
|||||||
return channels
|
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
|
// SetPresenceChannelEvent will set the presence of a IM channel
|
||||||
func (s *SlackService) SetPresenceChannelEvent(userID string, presence string) {
|
func (s *SlackService) SetPresenceChannelEvent(userID string, presence string) {
|
||||||
// Get the correct Channel from svc.Channels
|
// Get the correct Channel from svc.Channels
|
||||||
@ -256,8 +306,9 @@ func (s *SlackService) MarkAsRead(channelID int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkAsUnread will set the channel as unread
|
// FindChannel will loop over s.Channels to find the index where the
|
||||||
func (s *SlackService) MarkAsUnread(channelID string) {
|
// channelID equals the ID
|
||||||
|
func (s *SlackService) FindChannel(channelID string) int {
|
||||||
var index int
|
var index int
|
||||||
for i, channel := range s.Channels {
|
for i, channel := range s.Channels {
|
||||||
if channel.ID == channelID {
|
if channel.ID == channelID {
|
||||||
@ -265,9 +316,21 @@ func (s *SlackService) MarkAsUnread(channelID string) {
|
|||||||
break
|
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
|
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
|
// SendMessage will send a message to a particular channel
|
||||||
func (s *SlackService) SendMessage(channelID int, message string) {
|
func (s *SlackService) SendMessage(channelID int, message string) {
|
||||||
|
|
||||||
@ -397,15 +460,19 @@ func (s *SlackService) CreateMessage(message slack.Message) []components.Message
|
|||||||
return msgs
|
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 msgs []components.Message
|
||||||
var name string
|
var name string
|
||||||
|
|
||||||
// Append (edited) when an edited message is received
|
switch message.SubType {
|
||||||
if message.SubType == "message_changed" {
|
case "message_changed":
|
||||||
|
// Append (edited) when an edited message is received
|
||||||
message = &slack.MessageEvent{Msg: *message.SubMessage}
|
message = &slack.MessageEvent{Msg: *message.SubMessage}
|
||||||
message.Text = fmt.Sprintf("%s (edited)", message.Text)
|
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
|
// Get username from cache
|
||||||
@ -462,7 +529,45 @@ func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent
|
|||||||
|
|
||||||
msgs = append(msgs, msg)
|
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:
|
// 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.
|
// 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.
|
// Get the AST from the previous call and process the call line with it.
|
||||||
if f := c.getFuncAST(&goroutine.Stack.Calls[i]); f != nil {
|
if f := c.getFuncAST(&goroutine.Stack.Calls[i]); f != nil {
|
||||||
processCall(&goroutine.Stack.Calls[i], f)
|
processCall(&goroutine.Stack.Calls[i], f)
|
||||||
@ -115,15 +115,6 @@ type parsedFile struct {
|
|||||||
// getFuncAST gets the callee site function AST representation for the code
|
// getFuncAST gets the callee site function AST representation for the code
|
||||||
// inside the function f at line l.
|
// inside the function f at line l.
|
||||||
func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) {
|
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.
|
// Walk the AST to find the lineToByteOffset that fits the line number.
|
||||||
var lastFunc *ast.FuncDecl
|
var lastFunc *ast.FuncDecl
|
||||||
var found ast.Node
|
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 {
|
func name(n ast.Node) string {
|
||||||
switch t := n.(type) {
|
if _, ok := n.(*ast.InterfaceType); ok {
|
||||||
case *ast.InterfaceType:
|
|
||||||
return "interface{}"
|
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.
|
// 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
|
return arg.Sel.Name, false
|
||||||
case *ast.StarExpr:
|
case *ast.StarExpr:
|
||||||
return "*" + name(arg.X), false
|
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:
|
default:
|
||||||
// TODO(maruel): Implement anything missing.
|
// TODO(maruel): Implement anything missing.
|
||||||
return "<unknown>", false
|
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
|
// - found next stack barrier at 0x123; expected
|
||||||
// - runtime: unexpected return pc for FUNC_NAME called from 0x123
|
// - 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$")
|
reMinutes = regexp.MustCompile("^(\\d+) minutes$")
|
||||||
reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable")
|
reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable")
|
||||||
// See gentraceback() in src/runtime/traceback.go for more information.
|
// 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.
|
// when a signal is not correctly handled. It is printed with m.throwing>0.
|
||||||
// These are discarded.
|
// These are discarded.
|
||||||
// - For cgo, the source file may be "??".
|
// - 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
|
// Sadly, it doesn't note the goroutine number so we could cascade them per
|
||||||
// parenthood.
|
// parenthood.
|
||||||
reCreated = regexp.MustCompile("^created by (.+)\r?\n$")
|
reCreated = regexp.MustCompile("^created by (.+)\n$")
|
||||||
reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\r?\n$")
|
reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\n$")
|
||||||
reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\r?\n$")
|
reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\n$")
|
||||||
// Include frequent GOROOT value on Windows, distro provided and user
|
// Include frequent GOROOT value on Windows, distro provided and user
|
||||||
// installed path. This simplifies the user's life when processing a trace
|
// installed path. This simplifies the user's life when processing a trace
|
||||||
// generated on another VM.
|
// generated on another VM.
|
||||||
@ -656,7 +656,7 @@ func ParseDump(r io.Reader, out io.Writer) ([]Goroutine, error) {
|
|||||||
firstLine := false
|
firstLine := false
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if line == "\n" || line == "\r\n" {
|
if line == "\n" {
|
||||||
if goroutine != nil {
|
if goroutine != nil {
|
||||||
goroutine = nil
|
goroutine = nil
|
||||||
continue
|
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{
|
var nonprint = table{
|
||||||
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
|
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
|
||||||
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
|
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
|
||||||
{0x2028, 0x2029},
|
|
||||||
{0x202A, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
|
{0x202A, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
|
||||||
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
|
{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)
|
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
|
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
|
calls, as well as the Real-Time Messaging protocol over websocket, in
|
||||||
a fully managed way.
|
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"`
|
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{}
|
adminResponse := &adminResponse{}
|
||||||
err := parseAdminResponse(ctx, method, teamName, values, adminResponse, debug)
|
err := parseAdminResponse(ctx, client, method, teamName, values, adminResponse, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
func (api *Client) DisableUserContext(ctx context.Context, teamName string, uid string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"user": {uid},
|
"user": {uid},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"set_active": {"true"},
|
"set_active": {"true"},
|
||||||
"_attempts": {"1"},
|
"_attempts": {"1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := adminRequest(ctx, "setInactive", teamName, values, api.debug)
|
_, err := adminRequest(ctx, api.httpclient, "setInactive", teamName, values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to disable user with id '%s': %s", uid, err)
|
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},
|
"first_name": {firstName},
|
||||||
"last_name": {lastName},
|
"last_name": {lastName},
|
||||||
"ultra_restricted": {"1"},
|
"ultra_restricted": {"1"},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"set_active": {"true"},
|
"set_active": {"true"},
|
||||||
"_attempts": {"1"},
|
"_attempts": {"1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := adminRequest(ctx, "invite", teamName, values, api.debug)
|
_, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to invite single-channel guest: %s", err)
|
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},
|
"first_name": {firstName},
|
||||||
"last_name": {lastName},
|
"last_name": {lastName},
|
||||||
"restricted": {"1"},
|
"restricted": {"1"},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"set_active": {"true"},
|
"set_active": {"true"},
|
||||||
"_attempts": {"1"},
|
"_attempts": {"1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := adminRequest(ctx, "invite", teamName, values, api.debug)
|
_, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to restricted account: %s", err)
|
return fmt.Errorf("Failed to restricted account: %s", err)
|
||||||
}
|
}
|
||||||
@ -111,12 +111,12 @@ func (api *Client) InviteToTeamContext(ctx context.Context, teamName, firstName,
|
|||||||
"email": {emailAddress},
|
"email": {emailAddress},
|
||||||
"first_name": {firstName},
|
"first_name": {firstName},
|
||||||
"last_name": {lastName},
|
"last_name": {lastName},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"set_active": {"true"},
|
"set_active": {"true"},
|
||||||
"_attempts": {"1"},
|
"_attempts": {"1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := adminRequest(ctx, "invite", teamName, values, api.debug)
|
_, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to invite to team: %s", err)
|
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 {
|
func (api *Client) SetRegularContext(ctx context.Context, teamName, user string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"user": {user},
|
"user": {user},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"set_active": {"true"},
|
"set_active": {"true"},
|
||||||
"_attempts": {"1"},
|
"_attempts": {"1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := adminRequest(ctx, "setRegular", teamName, values, api.debug)
|
_, err := adminRequest(ctx, api.httpclient, "setRegular", teamName, values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to change the user (%s) to a regular user: %s", user, err)
|
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 {
|
func (api *Client) SendSSOBindingEmailContext(ctx context.Context, teamName, user string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"user": {user},
|
"user": {user},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"set_active": {"true"},
|
"set_active": {"true"},
|
||||||
"_attempts": {"1"},
|
"_attempts": {"1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := adminRequest(ctx, "sendSSOBind", teamName, values, api.debug)
|
_, err := adminRequest(ctx, api.httpclient, "sendSSOBind", teamName, values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to send SSO binding email for user (%s): %s", user, err)
|
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{
|
values := url.Values{
|
||||||
"user": {uid},
|
"user": {uid},
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"set_active": {"true"},
|
"set_active": {"true"},
|
||||||
"_attempts": {"1"},
|
"_attempts": {"1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := adminRequest(ctx, "setUltraRestricted", teamName, values, api.debug)
|
_, err := adminRequest(ctx, api.httpclient, "setUltraRestricted", teamName, values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to ultra-restrict account: %s", err)
|
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 {
|
func (api *Client) SetRestrictedContext(ctx context.Context, teamName, uid string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"user": {uid},
|
"user": {uid},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"set_active": {"true"},
|
"set_active": {"true"},
|
||||||
"_attempts": {"1"},
|
"_attempts": {"1"},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := adminRequest(ctx, "setRestricted", teamName, values, api.debug)
|
_, err := adminRequest(ctx, api.httpclient, "setRestricted", teamName, values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to restrict account: %s", err)
|
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.
|
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.
|
OptionGroups []AttachmentActionOptionGroup `json:"option_groups,omitempty"` // Optional.
|
||||||
Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
|
Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
|
||||||
|
URL string `json:"url,omitempty"` // Optional.
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachmentActionOption the individual option to appear in action menu.
|
// AttachmentActionOption the individual option to appear in action menu.
|
||||||
@ -48,6 +49,9 @@ type AttachmentActionCallback struct {
|
|||||||
Channel Channel `json:"channel"`
|
Channel Channel `json:"channel"`
|
||||||
User User `json:"user"`
|
User User `json:"user"`
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
|
||||||
OriginalMessage Message `json:"original_message"`
|
OriginalMessage Message `json:"original_message"`
|
||||||
|
|
||||||
ActionTs string `json:"action_ts"`
|
ActionTs string `json:"action_ts"`
|
||||||
@ -55,6 +59,7 @@ type AttachmentActionCallback struct {
|
|||||||
AttachmentID string `json:"attachment_id"`
|
AttachmentID string `json:"attachment_id"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
ResponseURL string `json:"response_url"`
|
ResponseURL string `json:"response_url"`
|
||||||
|
TriggerID string `json:"trigger_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfirmationField are used to ask users to confirm actions
|
// ConfirmationField are used to ask users to confirm actions
|
||||||
@ -71,6 +76,7 @@ type Attachment struct {
|
|||||||
Fallback string `json:"fallback"`
|
Fallback string `json:"fallback"`
|
||||||
|
|
||||||
CallbackID string `json:"callback_id,omitempty"`
|
CallbackID string `json:"callback_id,omitempty"`
|
||||||
|
ID int `json:"id,omitempty"`
|
||||||
|
|
||||||
AuthorName string `json:"author_name,omitempty"`
|
AuthorName string `json:"author_name,omitempty"`
|
||||||
AuthorSubname string `json:"author_subname,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
|
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{}
|
response := &botResponseFull{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := post(ctx, client, path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// GetBotInfoContext will retrieve the complete bot information using a custom context
|
||||||
func (api *Client) GetBotInfoContext(ctx context.Context, bot string) (*Bot, error) {
|
func (api *Client) GetBotInfoContext(ctx context.Context, bot string) (*Bot, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"bot": {bot},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
242
vendor/github.com/nlopes/slack/channels.go
generated
vendored
242
vendor/github.com/nlopes/slack/channels.go
generated
vendored
@ -20,14 +20,15 @@ type channelResponseFull struct {
|
|||||||
// Channel contains information about the channel
|
// Channel contains information about the channel
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
groupConversation
|
groupConversation
|
||||||
IsChannel bool `json:"is_channel"`
|
IsChannel bool `json:"is_channel"`
|
||||||
IsGeneral bool `json:"is_general"`
|
IsGeneral bool `json:"is_general"`
|
||||||
IsMember bool `json:"is_member"`
|
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{}
|
response := &channelResponseFull{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -38,53 +39,62 @@ func channelRequest(ctx context.Context, path string, values url.Values, debug b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ArchiveChannel archives the given channel
|
// ArchiveChannel archives the given channel
|
||||||
func (api *Client) ArchiveChannel(channel string) error {
|
// see https://api.slack.com/methods/channels.archive
|
||||||
return api.ArchiveChannelContext(context.Background(), channel)
|
func (api *Client) ArchiveChannel(channelID string) error {
|
||||||
|
return api.ArchiveChannelContext(context.Background(), channelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ArchiveChannelContext archives the given channel with a custom context
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnarchiveChannel unarchives the given channel
|
// UnarchiveChannel unarchives the given channel
|
||||||
func (api *Client) UnarchiveChannel(channel string) error {
|
// see https://api.slack.com/methods/channels.unarchive
|
||||||
return api.UnarchiveChannelContext(context.Background(), channel)
|
func (api *Client) UnarchiveChannel(channelID string) error {
|
||||||
|
return api.UnarchiveChannelContext(context.Background(), channelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnarchiveChannelContext unarchives the given channel with a custom context
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateChannel creates a channel with the given name and returns a *Channel
|
// CreateChannel creates a channel with the given name and returns a *Channel
|
||||||
func (api *Client) CreateChannel(channel string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.create
|
||||||
return api.CreateChannelContext(context.Background(), channel)
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"name": {channel},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -92,15 +102,17 @@ func (api *Client) CreateChannelContext(ctx context.Context, channel string) (*C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelHistory retrieves the channel history
|
// GetChannelHistory retrieves the channel history
|
||||||
func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (*History, error) {
|
// see https://api.slack.com/methods/channels.history
|
||||||
return api.GetChannelHistoryContext(context.Background(), channel, params)
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
}
|
}
|
||||||
if params.Latest != DEFAULT_HISTORY_LATEST {
|
if params.Latest != DEFAULT_HISTORY_LATEST {
|
||||||
values.Add("latest", params.Latest)
|
values.Add("latest", params.Latest)
|
||||||
@ -118,6 +130,7 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string,
|
|||||||
values.Add("inclusive", "0")
|
values.Add("inclusive", "0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Unreads != DEFAULT_HISTORY_UNREADS {
|
if params.Unreads != DEFAULT_HISTORY_UNREADS {
|
||||||
if params.Unreads {
|
if params.Unreads {
|
||||||
values.Add("unreads", "1")
|
values.Add("unreads", "1")
|
||||||
@ -125,7 +138,8 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string,
|
|||||||
values.Add("unreads", "0")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -133,17 +147,20 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelInfo retrieves the given channel
|
// GetChannelInfo retrieves the given channel
|
||||||
func (api *Client) GetChannelInfo(channel string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.info
|
||||||
return api.GetChannelInfoContext(context.Background(), channel)
|
func (api *Client) GetChannelInfo(channelID string) (*Channel, error) {
|
||||||
|
return api.GetChannelInfoContext(context.Background(), channelID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelInfoContext retrieves the given channel with a custom context
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// InviteUserToChannel invites a user to a given channel and returns a *Channel
|
||||||
func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.invite
|
||||||
return api.InviteUserToChannelContext(context.Background(), channel, user)
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"user": {user},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// JoinChannel joins the currently authenticated user to a channel
|
||||||
func (api *Client) JoinChannel(channel string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.join
|
||||||
return api.JoinChannelContext(context.Background(), channel)
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"name": {channel},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// LeaveChannel makes the authenticated user leave the given channel
|
||||||
func (api *Client) LeaveChannel(channel string) (bool, error) {
|
// see https://api.slack.com/methods/channels.leave
|
||||||
return api.LeaveChannelContext(context.Background(), channel)
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"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 {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if response.NotInChannel {
|
|
||||||
return response.NotInChannel, nil
|
return response.NotInChannel, nil
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// KickUserFromChannel kicks a user from a given channel
|
// KickUserFromChannel kicks a user from a given channel
|
||||||
func (api *Client) KickUserFromChannel(channel, user string) error {
|
// see https://api.slack.com/methods/channels.kick
|
||||||
return api.KickUserFromChannelContext(context.Background(), channel, user)
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"user": {user},
|
"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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChannels retrieves all the channels
|
// GetChannels retrieves all the channels
|
||||||
|
// see https://api.slack.com/methods/channels.list
|
||||||
func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
|
func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
|
||||||
return api.GetChannelsContext(context.Background(), excludeArchived)
|
return api.GetChannelsContext(context.Background(), excludeArchived)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetChannelsContext retrieves all the channels with a custom context
|
// 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) {
|
func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool) ([]Channel, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if excludeArchived {
|
if excludeArchived {
|
||||||
values.Add("exclude_archived", "1")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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
|
// (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.
|
// 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 {
|
// see https://api.slack.com/methods/channels.mark
|
||||||
return api.SetChannelReadMarkContext(context.Background(), channel, ts)
|
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
|
// SetChannelReadMarkContext sets the read mark of a given channel to a specific point with a custom context
|
||||||
// For more details see SetChannelReadMark documentation
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"ts": {ts},
|
"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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameChannel renames a given channel
|
// RenameChannel renames a given channel
|
||||||
func (api *Client) RenameChannel(channel, name string) (*Channel, error) {
|
// see https://api.slack.com/methods/channels.rename
|
||||||
return api.RenameChannelContext(context.Background(), channel, name)
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"name": {name},
|
"name": {name},
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: the created entry in this call returns a string instead of a number
|
// 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.
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// SetChannelPurpose sets the channel purpose and returns the purpose that was successfully set
|
||||||
func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) {
|
// see https://api.slack.com/methods/channels.setPurpose
|
||||||
return api.SetChannelPurposeContext(context.Background(), channel, purpose)
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"purpose": {purpose},
|
"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 {
|
if err != nil {
|
||||||
return "", err
|
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
|
// SetChannelTopic sets the channel topic and returns the topic that was successfully set
|
||||||
func (api *Client) SetChannelTopic(channel, topic string) (string, error) {
|
// see https://api.slack.com/methods/channels.setTopic
|
||||||
return api.SetChannelTopicContext(context.Background(), channel, topic)
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"topic": {topic},
|
"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 {
|
if err != nil {
|
||||||
return "", err
|
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).
|
// 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) {
|
// see https://api.slack.com/methods/channels.replies
|
||||||
return api.GetChannelRepliesContext(context.Background(), channel, thread_ts)
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"channel": {channelID},
|
||||||
"thread_ts": {thread_ts},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
106
vendor/github.com/nlopes/slack/chat.go
generated
vendored
106
vendor/github.com/nlopes/slack/chat.go
generated
vendored
@ -10,9 +10,10 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DEFAULT_MESSAGE_USERNAME = ""
|
DEFAULT_MESSAGE_USERNAME = ""
|
||||||
DEFAULT_MESSAGE_THREAD_TIMESTAMP = ""
|
DEFAULT_MESSAGE_REPLY_BROADCAST = false
|
||||||
DEFAULT_MESSAGE_ASUSER = false
|
DEFAULT_MESSAGE_ASUSER = false
|
||||||
DEFAULT_MESSAGE_PARSE = ""
|
DEFAULT_MESSAGE_PARSE = ""
|
||||||
|
DEFAULT_MESSAGE_THREAD_TIMESTAMP = ""
|
||||||
DEFAULT_MESSAGE_LINK_NAMES = 0
|
DEFAULT_MESSAGE_LINK_NAMES = 0
|
||||||
DEFAULT_MESSAGE_UNFURL_LINKS = false
|
DEFAULT_MESSAGE_UNFURL_LINKS = false
|
||||||
DEFAULT_MESSAGE_UNFURL_MEDIA = true
|
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
|
// PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request
|
||||||
type PostMessageParameters struct {
|
type PostMessageParameters struct {
|
||||||
Text string `json:"text"`
|
|
||||||
Username string `json:"user_name"`
|
Username string `json:"user_name"`
|
||||||
AsUser bool `json:"as_user"`
|
AsUser bool `json:"as_user"`
|
||||||
Parse string `json:"parse"`
|
Parse string `json:"parse"`
|
||||||
ThreadTimestamp string `json:"thread_ts"`
|
ThreadTimestamp string `json:"thread_ts"`
|
||||||
|
ReplyBroadcast bool `json:"reply_broadcast"`
|
||||||
LinkNames int `json:"link_names"`
|
LinkNames int `json:"link_names"`
|
||||||
Attachments []Attachment `json:"attachments"`
|
Attachments []Attachment `json:"attachments"`
|
||||||
UnfurlLinks bool `json:"unfurl_links"`
|
UnfurlLinks bool `json:"unfurl_links"`
|
||||||
@ -44,22 +45,28 @@ type PostMessageParameters struct {
|
|||||||
IconEmoji string `json:"icon_emoji"`
|
IconEmoji string `json:"icon_emoji"`
|
||||||
Markdown bool `json:"mrkdwn,omitempty"`
|
Markdown bool `json:"mrkdwn,omitempty"`
|
||||||
EscapeText bool `json:"escape_text"`
|
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
|
// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set
|
||||||
func NewPostMessageParameters() PostMessageParameters {
|
func NewPostMessageParameters() PostMessageParameters {
|
||||||
return PostMessageParameters{
|
return PostMessageParameters{
|
||||||
Username: DEFAULT_MESSAGE_USERNAME,
|
Username: DEFAULT_MESSAGE_USERNAME,
|
||||||
AsUser: DEFAULT_MESSAGE_ASUSER,
|
User: DEFAULT_MESSAGE_USERNAME,
|
||||||
Parse: DEFAULT_MESSAGE_PARSE,
|
AsUser: DEFAULT_MESSAGE_ASUSER,
|
||||||
LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
|
Parse: DEFAULT_MESSAGE_PARSE,
|
||||||
Attachments: nil,
|
ThreadTimestamp: DEFAULT_MESSAGE_THREAD_TIMESTAMP,
|
||||||
UnfurlLinks: DEFAULT_MESSAGE_UNFURL_LINKS,
|
LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
|
||||||
UnfurlMedia: DEFAULT_MESSAGE_UNFURL_MEDIA,
|
Attachments: nil,
|
||||||
IconURL: DEFAULT_MESSAGE_ICON_URL,
|
UnfurlLinks: DEFAULT_MESSAGE_UNFURL_LINKS,
|
||||||
IconEmoji: DEFAULT_MESSAGE_ICON_EMOJI,
|
UnfurlMedia: DEFAULT_MESSAGE_UNFURL_MEDIA,
|
||||||
Markdown: DEFAULT_MESSAGE_MARKDOWN,
|
IconURL: DEFAULT_MESSAGE_ICON_URL,
|
||||||
EscapeText: DEFAULT_MESSAGE_ESCAPE_TEXT,
|
IconEmoji: DEFAULT_MESSAGE_ICON_EMOJI,
|
||||||
|
Markdown: DEFAULT_MESSAGE_MARKDOWN,
|
||||||
|
EscapeText: DEFAULT_MESSAGE_ESCAPE_TEXT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,12 +109,43 @@ func (api *Client) PostMessageContext(ctx context.Context, channel, text string,
|
|||||||
return respChannel, respTimestamp, err
|
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
|
// UpdateMessage updates a message in a channel
|
||||||
func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
|
func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
|
||||||
return api.UpdateMessageContext(context.Background(), channel, timestamp, text)
|
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) {
|
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))
|
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.
|
// 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) {
|
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 {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := chatRequest(ctx, channel, values, api.debug)
|
response, err := chatRequest(ctx, api.httpclient, channel, values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
@ -156,9 +194,9 @@ func escapeMessage(message string) string {
|
|||||||
return replacer.Replace(message)
|
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{}
|
response := &chatResponseFull{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := post(ctx, client, path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -171,9 +209,10 @@ func chatRequest(ctx context.Context, path string, values url.Values, debug bool
|
|||||||
type sendMode string
|
type sendMode string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
chatUpdate sendMode = "chat.update"
|
chatUpdate sendMode = "chat.update"
|
||||||
chatPostMessage sendMode = "chat.postMessage"
|
chatPostMessage sendMode = "chat.postMessage"
|
||||||
chatDelete sendMode = "chat.delete"
|
chatDelete sendMode = "chat.delete"
|
||||||
|
chatPostEphemeral sendMode = "chat.postEphemeral"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sendConfig struct {
|
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.
|
// MsgOptionUpdate updates a message based on the timestamp.
|
||||||
func MsgOptionUpdate(timestamp string) MsgOption {
|
func MsgOptionUpdate(timestamp string) MsgOption {
|
||||||
return func(config *sendConfig) error {
|
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.
|
// MsgOptionDisableMediaUnfurl disables media unfurling.
|
||||||
func MsgOptionDisableMediaUnfurl() MsgOption {
|
func MsgOptionDisableMediaUnfurl() MsgOption {
|
||||||
return func(config *sendConfig) error {
|
return func(config *sendConfig) error {
|
||||||
@ -279,6 +335,11 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
|
|||||||
config.values.Set("username", string(params.Username))
|
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.
|
// never generates an error.
|
||||||
MsgOptionAsUser(params.AsUser)(config)
|
MsgOptionAsUser(params.AsUser)(config)
|
||||||
|
|
||||||
@ -314,6 +375,9 @@ func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
|
|||||||
if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP {
|
if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP {
|
||||||
config.values.Set("thread_ts", params.ThreadTimestamp)
|
config.values.Set("thread_ts", params.ThreadTimestamp)
|
||||||
}
|
}
|
||||||
|
if params.ReplyBroadcast != DEFAULT_MESSAGE_REPLY_BROADCAST {
|
||||||
|
config.values.Set("reply_broadcast", "true")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
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
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// Conversation is the foundation for IM and BaseGroupConversation
|
// Conversation is the foundation for IM and BaseGroupConversation
|
||||||
type conversation struct {
|
type conversation struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
@ -9,6 +17,20 @@ type conversation struct {
|
|||||||
Latest *Message `json:"latest,omitempty"`
|
Latest *Message `json:"latest,omitempty"`
|
||||||
UnreadCount int `json:"unread_count,omitempty"`
|
UnreadCount int `json:"unread_count,omitempty"`
|
||||||
UnreadCountDisplay int `json:"unread_count_display,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
|
// GroupConversation is the foundation for Group and Channel
|
||||||
@ -35,3 +57,536 @@ type Purpose struct {
|
|||||||
Creator string `json:"creator"`
|
Creator string `json:"creator"`
|
||||||
LastSet JSONTime `json:"last_set"`
|
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
|
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{}
|
response := &dndResponseFull{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := post(ctx, client, path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// EndDNDContext ends the user's scheduled Do Not Disturb session with a custom context
|
||||||
func (api *Client) EndDNDContext(ctx context.Context) error {
|
func (api *Client) EndDNDContext(ctx context.Context) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &SlackResponse{}
|
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
|
return err
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
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
|
// EndSnoozeContext ends the current user's snooze mode with a custom context
|
||||||
func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) {
|
func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) {
|
||||||
values := url.Values{
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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) {
|
func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDStatus, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if user != nil {
|
if user != nil {
|
||||||
values.Set("user", *user)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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) {
|
func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (map[string]DNDStatus, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"users": {strings.Join(users, ",")},
|
"users": {strings.Join(users, ",")},
|
||||||
}
|
}
|
||||||
response := &dndTeamInfoResponse{}
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
if !response.Ok {
|
||||||
@ -139,10 +142,11 @@ func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) {
|
|||||||
// For more information see the SetSnooze docs
|
// For more information see the SetSnooze docs
|
||||||
func (api *Client) SetSnoozeContext(ctx context.Context, minutes int) (*DNDStatus, error) {
|
func (api *Client) SetSnoozeContext(ctx context.Context, minutes int) (*DNDStatus, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"num_minutes": {strconv.Itoa(minutes)},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// GetEmojiContext retrieves all the emojis with a custom context
|
||||||
func (api *Client) GetEmojiContext(ctx context.Context) (map[string]string, error) {
|
func (api *Client) GetEmojiContext(ctx context.Context) (map[string]string, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
response := &emojiResponseFull{}
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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{}
|
response := &fileResponseFull{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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) {
|
func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count, page int) (*File, []Comment, *Paging, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"file": {fileID},
|
"file": {fileID},
|
||||||
"count": {strconv.Itoa(count)},
|
"count": {strconv.Itoa(count)},
|
||||||
"page": {strconv.Itoa(page)},
|
"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 {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
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
|
// 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) {
|
func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if params.User != DEFAULT_FILES_USER {
|
if params.User != DEFAULT_FILES_USER {
|
||||||
values.Add("user", params.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 {
|
if params.Page != DEFAULT_FILES_PAGE {
|
||||||
values.Add("page", strconv.Itoa(params.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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -221,7 +223,7 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
|
|||||||
}
|
}
|
||||||
response := &fileResponseFull{}
|
response := &fileResponseFull{}
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if params.Filetype != "" {
|
if params.Filetype != "" {
|
||||||
values.Add("filetype", params.Filetype)
|
values.Add("filetype", params.Filetype)
|
||||||
@ -240,11 +242,11 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
|
|||||||
}
|
}
|
||||||
if params.Content != "" {
|
if params.Content != "" {
|
||||||
values.Add("content", 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 != "" {
|
} 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 {
|
} 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -255,23 +257,46 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
|
|||||||
return &response.File, nil
|
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
|
// DeleteFile deletes a file
|
||||||
func (api *Client) DeleteFile(fileID string) error {
|
func (api *Client) DeleteFile(fileID string) error {
|
||||||
return api.DeleteFileContext(context.Background(), fileID)
|
return api.DeleteFileContext(context.Background(), fileID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFileContext deletes a file with a custom context
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"file": {fileID},
|
"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 err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeFilePublicURL disables public/external sharing for a file
|
// 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
|
// RevokeFilePublicURLContext disables public/external sharing for a file with a custom context
|
||||||
func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string) (*File, error) {
|
func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string) (*File, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"file": {fileID},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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) {
|
func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) (*File, []Comment, *Paging, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"file": {fileID},
|
"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 {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
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
|
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{}
|
response := &groupResponseFull{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -45,17 +45,18 @@ func (api *Client) ArchiveGroup(group string) error {
|
|||||||
return api.ArchiveGroupContext(context.Background(), group)
|
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 {
|
func (api *Client) ArchiveGroupContext(ctx context.Context, group string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"channel": {group},
|
||||||
}
|
}
|
||||||
_, err := groupRequest(ctx, "groups.archive", values, api.debug)
|
|
||||||
|
_, err := groupRequest(ctx, api.httpclient, "groups.archive", values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnarchiveGroup unarchives a private group
|
// UnarchiveGroup unarchives a private group
|
||||||
@ -63,13 +64,14 @@ func (api *Client) UnarchiveGroup(group string) error {
|
|||||||
return api.UnarchiveGroupContext(context.Background(), group)
|
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 {
|
func (api *Client) UnarchiveGroupContext(ctx context.Context, group string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"channel": {group},
|
||||||
}
|
}
|
||||||
_, err := groupRequest(ctx, "groups.unarchive", values, api.debug)
|
|
||||||
|
_, err := groupRequest(ctx, api.httpclient, "groups.unarchive", values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -81,13 +83,14 @@ func (api *Client) CreateGroup(group string) (*Group, error) {
|
|||||||
return api.CreateGroupContext(context.Background(), group)
|
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) {
|
func (api *Client) CreateGroupContext(ctx context.Context, group string) (*Group, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"name": {group},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -104,14 +107,15 @@ func (api *Client) CreateChildGroup(group string) (*Group, error) {
|
|||||||
return api.CreateChildGroupContext(context.Background(), group)
|
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
|
// For more information see CreateChildGroup
|
||||||
func (api *Client) CreateChildGroupContext(ctx context.Context, group string) (*Group, error) {
|
func (api *Client) CreateChildGroupContext(ctx context.Context, group string) (*Group, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// CloseGroupContext closes a private group with a custom context
|
||||||
func (api *Client) CloseGroupContext(ctx context.Context, group string) (bool, bool, error) {
|
func (api *Client) CloseGroupContext(ctx context.Context, group string) (bool, bool, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"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 {
|
if err != nil {
|
||||||
return false, false, err
|
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
|
// 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) {
|
func (api *Client) GetGroupHistoryContext(ctx context.Context, group string, params HistoryParameters) (*History, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"channel": {group},
|
||||||
}
|
}
|
||||||
if params.Latest != DEFAULT_HISTORY_LATEST {
|
if params.Latest != DEFAULT_HISTORY_LATEST {
|
||||||
@ -170,7 +175,8 @@ func (api *Client) GetGroupHistoryContext(ctx context.Context, group string, par
|
|||||||
values.Add("unreads", "0")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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) {
|
func (api *Client) InviteUserToGroupContext(ctx context.Context, group, user string) (*Group, bool, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"channel": {group},
|
||||||
"user": {user},
|
"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 {
|
if err != nil {
|
||||||
return nil, false, err
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"channel": {group},
|
||||||
"user": {user},
|
"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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,12 +250,13 @@ func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) {
|
|||||||
// GetGroupsContext retrieves all groups with a custom context
|
// GetGroupsContext retrieves all groups with a custom context
|
||||||
func (api *Client) GetGroupsContext(ctx context.Context, excludeArchived bool) ([]Group, error) {
|
func (api *Client) GetGroupsContext(ctx context.Context, excludeArchived bool) ([]Group, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if excludeArchived {
|
if excludeArchived {
|
||||||
values.Add("exclude_archived", "1")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -261,10 +271,11 @@ func (api *Client) GetGroupInfo(group string) (*Group, error) {
|
|||||||
// GetGroupInfoContext retrieves the given group with a custom context
|
// GetGroupInfoContext retrieves the given group with a custom context
|
||||||
func (api *Client) GetGroupInfoContext(ctx context.Context, group string) (*Group, error) {
|
func (api *Client) GetGroupInfoContext(ctx context.Context, group string) (*Group, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// SetGroupReadMarkContext sets the read mark on a private group with a custom context
|
||||||
// For more details see SetGroupReadMark
|
// 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{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"channel": {group},
|
||||||
"ts": {ts},
|
"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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,10 +315,11 @@ func (api *Client) OpenGroup(group string) (bool, bool, error) {
|
|||||||
// OpenGroupContext opens a private group with a custom context
|
// OpenGroupContext opens a private group with a custom context
|
||||||
func (api *Client) OpenGroupContext(ctx context.Context, group string) (bool, bool, error) {
|
func (api *Client) OpenGroupContext(ctx context.Context, group string) (bool, bool, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"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 {
|
if err != nil {
|
||||||
return false, false, err
|
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
|
// RenameGroupContext renames a group with a custom context
|
||||||
func (api *Client) RenameGroupContext(ctx context.Context, group, name string) (*Channel, error) {
|
func (api *Client) RenameGroupContext(ctx context.Context, group, name string) (*Channel, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"channel": {group},
|
||||||
"name": {name},
|
"name": {name},
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: the created entry in this call returns a string instead of a number
|
// 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.
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// SetGroupPurposeContext sets the group purpose with a custom context
|
||||||
func (api *Client) SetGroupPurposeContext(ctx context.Context, group, purpose string) (string, error) {
|
func (api *Client) SetGroupPurposeContext(ctx context.Context, group, purpose string) (string, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"channel": {group},
|
||||||
"purpose": {purpose},
|
"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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -363,11 +378,12 @@ func (api *Client) SetGroupTopic(group, topic string) (string, error) {
|
|||||||
// SetGroupTopicContext sets the group topic with a custom context
|
// SetGroupTopicContext sets the group topic with a custom context
|
||||||
func (api *Client) SetGroupTopicContext(ctx context.Context, group, topic string) (string, error) {
|
func (api *Client) SetGroupTopicContext(ctx context.Context, group, topic string) (string, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {group},
|
"channel": {group},
|
||||||
"topic": {topic},
|
"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 {
|
if err != nil {
|
||||||
return "", err
|
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"`
|
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{}
|
response := &imResponseFull{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := post(ctx, client, path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// CloseIMChannelContext closes the direct message channel with a custom context
|
||||||
func (api *Client) CloseIMChannelContext(ctx context.Context, channel string) (bool, bool, error) {
|
func (api *Client) CloseIMChannelContext(ctx context.Context, channel string) (bool, bool, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"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 {
|
if err != nil {
|
||||||
return false, false, err
|
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
|
// Returns some status and the channel ID
|
||||||
func (api *Client) OpenIMChannelContext(ctx context.Context, user string) (bool, bool, string, error) {
|
func (api *Client) OpenIMChannelContext(ctx context.Context, user string) (bool, bool, string, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"user": {user},
|
"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 {
|
if err != nil {
|
||||||
return false, false, "", err
|
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
|
// 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) {
|
func (api *Client) MarkIMChannelContext(ctx context.Context, channel, ts string) (err error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
"ts": {ts},
|
"ts": {ts},
|
||||||
}
|
}
|
||||||
_, err = imRequest(ctx, "im.mark", values, api.debug)
|
|
||||||
|
_, err = imRequest(ctx, api.httpclient, "im.mark", values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// GetIMHistoryContext retrieves the direct message channel history with a custom context
|
||||||
func (api *Client) GetIMHistoryContext(ctx context.Context, channel string, params HistoryParameters) (*History, error) {
|
func (api *Client) GetIMHistoryContext(ctx context.Context, channel string, params HistoryParameters) (*History, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
}
|
}
|
||||||
if params.Latest != DEFAULT_HISTORY_LATEST {
|
if params.Latest != DEFAULT_HISTORY_LATEST {
|
||||||
@ -132,7 +135,8 @@ func (api *Client) GetIMHistoryContext(ctx context.Context, channel string, para
|
|||||||
values.Add("unreads", "0")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// GetIMChannelsContext returns the list of direct message channels with a custom context
|
||||||
func (api *Client) GetIMChannelsContext(ctx context.Context) ([]IM, error) {
|
func (api *Client) GetIMChannelsContext(ctx context.Context) ([]IM, error) {
|
||||||
values := url.Values{
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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...))
|
||||||
|
}
|
18
vendor/github.com/nlopes/slack/messages.go
generated
vendored
18
vendor/github.com/nlopes/slack/messages.go
generated
vendored
@ -2,7 +2,8 @@ package slack
|
|||||||
|
|
||||||
// OutgoingMessage is used for the realtime API, and seems incomplete.
|
// OutgoingMessage is used for the realtime API, and seems incomplete.
|
||||||
type OutgoingMessage struct {
|
type OutgoingMessage struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
// channel ID
|
||||||
Channel string `json:"channel,omitempty"`
|
Channel string `json:"channel,omitempty"`
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
@ -28,6 +29,9 @@ type Msg struct {
|
|||||||
PinnedTo []string `json:"pinned_to, omitempty"`
|
PinnedTo []string `json:"pinned_to, omitempty"`
|
||||||
Attachments []Attachment `json:"attachments,omitempty"`
|
Attachments []Attachment `json:"attachments,omitempty"`
|
||||||
Edited *Edited `json:"edited,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
|
// Message Subtypes
|
||||||
SubType string `json:"subtype,omitempty"`
|
SubType string `json:"subtype,omitempty"`
|
||||||
@ -81,6 +85,10 @@ type Msg struct {
|
|||||||
|
|
||||||
// reactions
|
// reactions
|
||||||
Reactions []ItemReaction `json:"reactions,omitempty"`
|
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
|
// Icon is used for bot messages
|
||||||
@ -121,12 +129,12 @@ type Pong struct {
|
|||||||
// NewOutgoingMessage prepares an OutgoingMessage that the user can
|
// NewOutgoingMessage prepares an OutgoingMessage that the user can
|
||||||
// use to send a message. Use this function to properly set the
|
// use to send a message. Use this function to properly set the
|
||||||
// messageID.
|
// messageID.
|
||||||
func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage {
|
func (rtm *RTM) NewOutgoingMessage(text string, channelID string) *OutgoingMessage {
|
||||||
id := rtm.idGen.Next()
|
id := rtm.idGen.Next()
|
||||||
return &OutgoingMessage{
|
return &OutgoingMessage{
|
||||||
ID: id,
|
ID: id,
|
||||||
Type: "message",
|
Type: "message",
|
||||||
Channel: channel,
|
Channel: channelID,
|
||||||
Text: text,
|
Text: text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,11 +142,11 @@ func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage
|
|||||||
// NewTypingMessage prepares an OutgoingMessage that the user can
|
// NewTypingMessage prepares an OutgoingMessage that the user can
|
||||||
// use to send as a typing indicator. Use this function to properly set the
|
// use to send as a typing indicator. Use this function to properly set the
|
||||||
// messageID.
|
// messageID.
|
||||||
func (rtm *RTM) NewTypingMessage(channel string) *OutgoingMessage {
|
func (rtm *RTM) NewTypingMessage(channelID string) *OutgoingMessage {
|
||||||
id := rtm.idGen.Next()
|
id := rtm.idGen.Next()
|
||||||
return &OutgoingMessage{
|
return &OutgoingMessage{
|
||||||
ID: id,
|
ID: id,
|
||||||
Type: "typing",
|
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"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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 {
|
type WebResponse struct {
|
||||||
Ok bool `json:"ok"`
|
Ok bool `json:"ok"`
|
||||||
Error *WebError `json:"error"`
|
Error *WebError `json:"error"`
|
||||||
@ -42,6 +29,14 @@ func (s WebError) Error() string {
|
|||||||
return string(s)
|
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) {
|
func fileUploadReq(ctx context.Context, path, fieldname, filename string, values url.Values, r io.Reader) (*http.Request, error) {
|
||||||
body := &bytes.Buffer{}
|
body := &bytes.Buffer{}
|
||||||
wr := multipart.NewWriter(body)
|
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))
|
logger.Printf("parseResponseBody: %s\n", string(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(response, &intf)
|
return json.Unmarshal(response, &intf)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
fullpath, err := filepath.Abs(fpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -97,23 +87,31 @@ func postLocalWithMultipartResponse(ctx context.Context, path, fpath, fieldname
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
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)
|
req, err := fileUploadReq(ctx, SLACK_API+path, fieldname, name, values, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
resp, err := getHTTPClient().Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
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.
|
// 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)
|
logResponse(resp, debug)
|
||||||
return fmt.Errorf("Slack server error: %s.", resp.Status)
|
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)
|
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())
|
reqBody := strings.NewReader(values.Encode())
|
||||||
req, err := http.NewRequest("POST", endpoint, reqBody)
|
req, err := http.NewRequest("POST", endpoint, reqBody)
|
||||||
if err != nil {
|
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.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
resp, err := getHTTPClient().Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
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.
|
// 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)
|
logResponse(resp, debug)
|
||||||
return fmt.Errorf("Slack server error: %s.", resp.Status)
|
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)
|
return parseResponseBody(resp.Body, &intf, debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
func post(ctx context.Context, path string, values url.Values, intf interface{}, debug bool) error {
|
func post(ctx context.Context, client HTTPRequester, path string, values url.Values, intf interface{}, debug bool) error {
|
||||||
return postForm(ctx, SLACK_API+path, values, intf, debug)
|
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())
|
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 {
|
func logResponse(resp *http.Response, debug bool) error {
|
||||||
@ -167,17 +173,10 @@ func logResponse(resp *http.Response, debug bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHTTPClient() HTTPRequester {
|
func okJsonHandler(rw http.ResponseWriter, r *http.Request) {
|
||||||
if customHTTPClient != nil {
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
return customHTTPClient
|
response, _ := json.Marshal(SlackResponse{
|
||||||
}
|
Ok: true,
|
||||||
|
})
|
||||||
return HTTPClient
|
rw.Write(response)
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
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},
|
"redirect_uri": {redirectURI},
|
||||||
}
|
}
|
||||||
response := &OAuthResponse{}
|
response := &OAuthResponse{}
|
||||||
err = post(ctx, "oauth.access", values, response, debug)
|
err = post(ctx, customHTTPClient, "oauth.access", values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemRef) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if item.Timestamp != "" {
|
if item.Timestamp != "" {
|
||||||
values.Set("timestamp", string(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 != "" {
|
if item.Comment != "" {
|
||||||
values.Set("file_comment", string(item.Comment))
|
values.Set("file_comment", string(item.Comment))
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &SlackResponse{}
|
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
|
return err
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
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 {
|
func (api *Client) RemovePinContext(ctx context.Context, channel string, item ItemRef) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if item.Timestamp != "" {
|
if item.Timestamp != "" {
|
||||||
values.Set("timestamp", string(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 != "" {
|
if item.Comment != "" {
|
||||||
values.Set("file_comment", string(item.Comment))
|
values.Set("file_comment", string(item.Comment))
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &SlackResponse{}
|
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
|
return err
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
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) {
|
func (api *Client) ListPinsContext(ctx context.Context, channel string) ([]Item, *Paging, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &listPinsResponseFull{}
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
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.
|
// 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 {
|
func (api *Client) AddReactionContext(ctx context.Context, name string, item ItemRef) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if name != "" {
|
if name != "" {
|
||||||
values.Set("name", name)
|
values.Set("name", name)
|
||||||
@ -153,8 +153,9 @@ func (api *Client) AddReactionContext(ctx context.Context, name string, item Ite
|
|||||||
if item.Comment != "" {
|
if item.Comment != "" {
|
||||||
values.Set("file_comment", string(item.Comment))
|
values.Set("file_comment", string(item.Comment))
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &SlackResponse{}
|
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
|
return err
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
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.
|
// 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 {
|
func (api *Client) RemoveReactionContext(ctx context.Context, name string, item ItemRef) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if name != "" {
|
if name != "" {
|
||||||
values.Set("name", name)
|
values.Set("name", name)
|
||||||
@ -188,8 +189,9 @@ func (api *Client) RemoveReactionContext(ctx context.Context, name string, item
|
|||||||
if item.Comment != "" {
|
if item.Comment != "" {
|
||||||
values.Set("file_comment", string(item.Comment))
|
values.Set("file_comment", string(item.Comment))
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &SlackResponse{}
|
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
|
return err
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
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
|
// 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) {
|
func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if item.Channel != "" {
|
if item.Channel != "" {
|
||||||
values.Set("channel", string(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 {
|
if params.Full != DEFAULT_REACTIONS_FULL {
|
||||||
values.Set("full", strconv.FormatBool(params.Full))
|
values.Set("full", strconv.FormatBool(params.Full))
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &getReactionsResponseFull{}
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
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.
|
// 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) {
|
func (api *Client) ListReactionsContext(ctx context.Context, params ListReactionsParameters) ([]ReactedItem, *Paging, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if params.User != DEFAULT_REACTIONS_USER {
|
if params.User != DEFAULT_REACTIONS_USER {
|
||||||
values.Add("user", params.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 {
|
if params.Full != DEFAULT_REACTIONS_FULL {
|
||||||
values.Add("full", strconv.FormatBool(params.Full))
|
values.Add("full", strconv.FormatBool(params.Full))
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &listReactionsResponseFull{}
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
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"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
websocketDefaultTimeout = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
// StartRTM calls the "rtm.start" endpoint and returns the provided URL and the full Info block.
|
// 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.
|
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
|
||||||
func (api *Client) StartRTM() (info *Info, websocketURL string, err error) {
|
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.
|
// 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.
|
// 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) {
|
func (api *Client) StartRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) {
|
||||||
response := &infoResponseFull{}
|
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 {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("post: %s", err)
|
return nil, "", fmt.Errorf("post: %s", err)
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
if !response.Ok {
|
||||||
return nil, "", response.Error
|
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)
|
api.Debugln("Using URL:", response.Info.URL)
|
||||||
websocketURL, err = websocketizeURLPort(response.Info.URL)
|
return &response.Info, response.Info.URL, nil
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("parsing response URL: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &response.Info, websocketURL, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block.
|
// 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.
|
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
|
||||||
func (api *Client) ConnectRTM() (info *Info, websocketURL string, err error) {
|
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.
|
// 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.
|
// 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) {
|
func (api *Client) ConnectRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) {
|
||||||
response := &infoResponseFull{}
|
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 {
|
if err != nil {
|
||||||
|
api.Debugf("Failed to connect to RTM: %s", err)
|
||||||
return nil, "", fmt.Errorf("post: %s", err)
|
return nil, "", fmt.Errorf("post: %s", err)
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
if !response.Ok {
|
||||||
return nil, "", response.Error
|
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)
|
api.Debugln("Using URL:", response.Info.URL)
|
||||||
websocketURL, err = websocketizeURLPort(response.Info.URL)
|
return &response.Info, response.Info.URL, nil
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("parsing response URL: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &response.Info, websocketURL, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRTM returns a RTM, which provides a fully managed connection to
|
// NewRTM returns a RTM, which provides a fully managed connection to
|
||||||
@ -90,6 +83,7 @@ func (api *Client) NewRTMWithOptions(options *RTMOptions) *RTM {
|
|||||||
isConnected: false,
|
isConnected: false,
|
||||||
wasIntentional: true,
|
wasIntentional: true,
|
||||||
killChannel: make(chan bool),
|
killChannel: make(chan bool),
|
||||||
|
disconnected: make(chan struct{}),
|
||||||
forcePing: make(chan bool),
|
forcePing: make(chan bool),
|
||||||
rawEvents: make(chan json.RawMessage),
|
rawEvents: make(chan json.RawMessage),
|
||||||
idGen: NewSafeID(1),
|
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) {
|
func (api *Client) _search(ctx context.Context, path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"query": {query},
|
"query": {query},
|
||||||
}
|
}
|
||||||
if params.Sort != DEFAULT_SEARCH_SORT {
|
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 {
|
if params.Page != DEFAULT_SEARCH_PAGE {
|
||||||
values.Add("page", strconv.Itoa(params.Page))
|
values.Add("page", strconv.Itoa(params.Page))
|
||||||
}
|
}
|
||||||
|
|
||||||
response = &searchResponseFull{}
|
response = &searchResponseFull{}
|
||||||
err := post(ctx, path, values, response, api.debug)
|
err := post(ctx, api.httpclient, path, values, response, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
78
vendor/github.com/nlopes/slack/slack.go
generated
vendored
78
vendor/github.com/nlopes/slack/slack.go
generated
vendored
@ -3,18 +3,38 @@ package slack
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"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_API string = "https://slack.com/api/"
|
||||||
var SLACK_WEB_API_FORMAT string = "https://%s.slack.com/api/users.admin.%s?t=%s"
|
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 {
|
type SlackResponse struct {
|
||||||
Ok bool `json:"ok"`
|
Ok bool `json:"ok"`
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
@ -34,22 +54,33 @@ type authTestResponseFull struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
config struct {
|
token string
|
||||||
token string
|
info Info
|
||||||
|
debug bool
|
||||||
|
httpclient HTTPRequester
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option defines an option for a Client
|
||||||
|
type Option func(*Client)
|
||||||
|
|
||||||
|
// OptionHTTPClient - provide a custom http client to the slack client.
|
||||||
|
func OptionHTTPClient(c HTTPRequester) func(*Client) {
|
||||||
|
return func(s *Client) {
|
||||||
|
s.httpclient = c
|
||||||
}
|
}
|
||||||
info Info
|
|
||||||
debug bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogger let's library users supply a logger, so that api debugging
|
// New builds a slack client from the provided token and options.
|
||||||
// can be logged along with the application's debugging info.
|
func New(token string, options ...Option) *Client {
|
||||||
func SetLogger(l *log.Logger) {
|
s := &Client{
|
||||||
logger = l
|
token: token,
|
||||||
}
|
httpclient: customHTTPClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(s)
|
||||||
|
}
|
||||||
|
|
||||||
func New(token string) *Client {
|
|
||||||
s := &Client{}
|
|
||||||
s.config.token = token
|
|
||||||
return s
|
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
|
// 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) {
|
func (api *Client) AuthTestContext(ctx context.Context) (response *AuthTestResponse, error error) {
|
||||||
|
api.Debugf("Challenging auth...")
|
||||||
responseFull := &authTestResponseFull{}
|
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 {
|
if err != nil {
|
||||||
|
api.Debugf("failed to test for auth: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !responseFull.Ok {
|
if !responseFull.Ok {
|
||||||
|
api.Debugf("auth response was not Ok: %s", responseFull.Error)
|
||||||
return nil, errors.New(responseFull.Error)
|
return nil, errors.New(responseFull.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api.Debugf("Auth challenge was successful with response %+v", responseFull.AuthTestResponse)
|
||||||
return &responseFull.AuthTestResponse, nil
|
return &responseFull.AuthTestResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,18 +113,20 @@ func (api *Client) AuthTestContext(ctx context.Context) (response *AuthTestRespo
|
|||||||
func (api *Client) SetDebug(debug bool) {
|
func (api *Client) SetDebug(debug bool) {
|
||||||
api.debug = debug
|
api.debug = debug
|
||||||
if debug && logger == nil {
|
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{}) {
|
func (api *Client) Debugf(format string, v ...interface{}) {
|
||||||
if api.debug {
|
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{}) {
|
func (api *Client) Debugln(v ...interface{}) {
|
||||||
if api.debug {
|
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 {
|
func (api *Client) AddStarContext(ctx context.Context, channel string, item ItemRef) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if item.Timestamp != "" {
|
if item.Timestamp != "" {
|
||||||
values.Set("timestamp", string(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 != "" {
|
if item.Comment != "" {
|
||||||
values.Set("file_comment", string(item.Comment))
|
values.Set("file_comment", string(item.Comment))
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &SlackResponse{}
|
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
|
return err
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
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 {
|
func (api *Client) RemoveStarContext(ctx context.Context, channel string, item ItemRef) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if item.Timestamp != "" {
|
if item.Timestamp != "" {
|
||||||
values.Set("timestamp", string(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 != "" {
|
if item.Comment != "" {
|
||||||
values.Set("file_comment", string(item.Comment))
|
values.Set("file_comment", string(item.Comment))
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &SlackResponse{}
|
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
|
return err
|
||||||
}
|
}
|
||||||
if !response.Ok {
|
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
|
// 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) {
|
func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) ([]Item, *Paging, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if params.User != DEFAULT_STARS_USER {
|
if params.User != DEFAULT_STARS_USER {
|
||||||
values.Add("user", params.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 {
|
if params.Page != DEFAULT_STARS_PAGE {
|
||||||
values.Add("page", strconv.Itoa(params.Page))
|
values.Add("page", strconv.Itoa(params.Page))
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &listResponseFull{}
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
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{}
|
response := &TeamResponse{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := post(ctx, client, path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -81,9 +81,9 @@ func teamRequest(ctx context.Context, path string, values url.Values, debug bool
|
|||||||
return response, nil
|
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{}
|
response := &BillableInfoResponse{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := post(ctx, client, path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -95,9 +95,9 @@ func billableInfoRequest(ctx context.Context, path string, values url.Values, de
|
|||||||
return response.BillableInfo, nil
|
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{}
|
response := &LoginResponse{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := post(ctx, client, path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// GetTeamInfoContext gets the Team Information of the user with a custom context
|
||||||
func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) {
|
func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) {
|
||||||
values := url.Values{
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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) {
|
func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogParameters) ([]Login, *Paging, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if params.Count != DEFAULT_LOGINS_COUNT {
|
if params.Count != DEFAULT_LOGINS_COUNT {
|
||||||
values.Add("count", strconv.Itoa(params.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 {
|
if params.Page != DEFAULT_LOGINS_PAGE {
|
||||||
values.Add("page", strconv.Itoa(params.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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
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) {
|
func (api *Client) GetBillableInfoContext(ctx context.Context, user string) (map[string]BillingActive, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"user": {user},
|
"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.
|
// 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
|
// 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) {
|
func (api *Client) GetBillableInfoForTeamContext(ctx context.Context) (map[string]BillingActive, error) {
|
||||||
values := url.Values{
|
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
|
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{}
|
response := &userGroupResponseFull{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := post(ctx, client, path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// CreateUserGroupContext creates a new user group with a custom context
|
||||||
func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
|
func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"name": {userGroup.Name},
|
"name": {userGroup.Name},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGro
|
|||||||
values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")}
|
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 {
|
if err != nil {
|
||||||
return UserGroup{}, err
|
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
|
// DisableUserGroupContext disables an existing user group with a custom context
|
||||||
func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
|
func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"usergroup": {userGroup},
|
"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 {
|
if err != nil {
|
||||||
return UserGroup{}, err
|
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
|
// EnableUserGroupContext enables an existing user group with a custom context
|
||||||
func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
|
func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"usergroup": {userGroup},
|
"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 {
|
if err != nil {
|
||||||
return UserGroup{}, err
|
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
|
// GetUserGroupsContext returns a list of user groups for the team with a custom context
|
||||||
func (api *Client) GetUserGroupsContext(ctx context.Context) ([]UserGroup, error) {
|
func (api *Client) GetUserGroupsContext(ctx context.Context) ([]UserGroup, error) {
|
||||||
values := url.Values{
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// UpdateUserGroupContext will update an existing user group with a custom context
|
||||||
func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
|
func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"usergroup": {userGroup.ID},
|
"usergroup": {userGroup.ID},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGro
|
|||||||
values["description"] = []string{userGroup.Description}
|
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 {
|
if err != nil {
|
||||||
return UserGroup{}, err
|
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
|
// 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) {
|
func (api *Client) GetUserGroupMembersContext(ctx context.Context, userGroup string) ([]string, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"usergroup": {userGroup},
|
"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 {
|
if err != nil {
|
||||||
return []string{}, err
|
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
|
// 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) {
|
func (api *Client) UpdateUserGroupMembersContext(ctx context.Context, userGroup string, members string) (UserGroup, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"usergroup": {userGroup},
|
"usergroup": {userGroup},
|
||||||
"users": {members},
|
"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 {
|
if err != nil {
|
||||||
return UserGroup{}, err
|
return UserGroup{}, err
|
||||||
}
|
}
|
||||||
|
118
vendor/github.com/nlopes/slack/users.go
generated
vendored
118
vendor/github.com/nlopes/slack/users.go
generated
vendored
@ -15,29 +15,33 @@ const (
|
|||||||
|
|
||||||
// UserProfile contains all the information details of a given user
|
// UserProfile contains all the information details of a given user
|
||||||
type UserProfile struct {
|
type UserProfile struct {
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
RealName string `json:"real_name"`
|
RealName string `json:"real_name"`
|
||||||
RealNameNormalized string `json:"real_name_normalized"`
|
RealNameNormalized string `json:"real_name_normalized"`
|
||||||
Email string `json:"email"`
|
DisplayName string `json:"display_name"`
|
||||||
Skype string `json:"skype"`
|
DisplayNameNormalized string `json:"display_name_normalized"`
|
||||||
Phone string `json:"phone"`
|
Email string `json:"email"`
|
||||||
Image24 string `json:"image_24"`
|
Skype string `json:"skype"`
|
||||||
Image32 string `json:"image_32"`
|
Phone string `json:"phone"`
|
||||||
Image48 string `json:"image_48"`
|
Image24 string `json:"image_24"`
|
||||||
Image72 string `json:"image_72"`
|
Image32 string `json:"image_32"`
|
||||||
Image192 string `json:"image_192"`
|
Image48 string `json:"image_48"`
|
||||||
ImageOriginal string `json:"image_original"`
|
Image72 string `json:"image_72"`
|
||||||
Title string `json:"title"`
|
Image192 string `json:"image_192"`
|
||||||
BotID string `json:"bot_id,omitempty"`
|
ImageOriginal string `json:"image_original"`
|
||||||
ApiAppID string `json:"api_app_id,omitempty"`
|
Title string `json:"title"`
|
||||||
StatusText string `json:"status_text,omitempty"`
|
BotID string `json:"bot_id,omitempty"`
|
||||||
StatusEmoji string `json:"status_emoji,omitempty"`
|
ApiAppID string `json:"api_app_id,omitempty"`
|
||||||
|
StatusText string `json:"status_text,omitempty"`
|
||||||
|
StatusEmoji string `json:"status_emoji,omitempty"`
|
||||||
|
Team string `json:"team"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// User contains all the information of a user
|
// User contains all the information of a user
|
||||||
type User struct {
|
type User struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
TeamID string `json:"team_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Deleted bool `json:"deleted"`
|
Deleted bool `json:"deleted"`
|
||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
@ -52,9 +56,12 @@ type User struct {
|
|||||||
IsPrimaryOwner bool `json:"is_primary_owner"`
|
IsPrimaryOwner bool `json:"is_primary_owner"`
|
||||||
IsRestricted bool `json:"is_restricted"`
|
IsRestricted bool `json:"is_restricted"`
|
||||||
IsUltraRestricted bool `json:"is_ultra_restricted"`
|
IsUltraRestricted bool `json:"is_ultra_restricted"`
|
||||||
|
IsStranger bool `json:"is_stranger"`
|
||||||
|
IsAppUser bool `json:"is_app_user"`
|
||||||
Has2FA bool `json:"has_2fa"`
|
Has2FA bool `json:"has_2fa"`
|
||||||
HasFiles bool `json:"has_files"`
|
HasFiles bool `json:"has_files"`
|
||||||
Presence string `json:"presence"`
|
Presence string `json:"presence"`
|
||||||
|
Locale string `json:"locale"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserPresence contains details about a user online status
|
// 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{}
|
response := &userResponseFull{}
|
||||||
err := post(ctx, path, values, response, debug)
|
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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) {
|
func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*UserPresence, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"user": {user},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// GetUserInfoContext will retrieve the complete user information with a custom context
|
||||||
func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, error) {
|
func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"user": {user},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// GetUsersContext returns the list of users (with their detailed information) with a custom context
|
||||||
func (api *Client) GetUsersContext(ctx context.Context) ([]User, error) {
|
func (api *Client) GetUsersContext(ctx context.Context) ([]User, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"presence": {"1"},
|
"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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return response.Members, nil
|
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
|
// SetUserAsActive marks the currently authenticated user as active
|
||||||
func (api *Client) SetUserAsActive() error {
|
func (api *Client) SetUserAsActive() error {
|
||||||
return api.SetUserAsActiveContext(context.Background())
|
return api.SetUserAsActiveContext(context.Background())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserAsActiveContext marks the currently authenticated user as active with a custom context
|
// 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{
|
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 err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,10 +241,11 @@ func (api *Client) SetUserPresence(presence string) error {
|
|||||||
// SetUserPresenceContext changes the currently authenticated user presence with a custom context
|
// SetUserPresenceContext changes the currently authenticated user presence with a custom context
|
||||||
func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) error {
|
func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) error {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"presence": {presence},
|
"presence": {presence},
|
||||||
}
|
}
|
||||||
_, err := userRequest(ctx, "users.setPresence", values, api.debug)
|
|
||||||
|
_, err := userRequest(ctx, api.httpclient, "users.setPresence", values, api.debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context
|
||||||
func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityResponse, error) {
|
func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityResponse, error) {
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
response := &UserIdentityResponse{}
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -245,15 +276,15 @@ func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityRes
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetUserPhoto changes the currently authenticated user's profile image
|
// SetUserPhoto changes the currently authenticated user's profile image
|
||||||
func (api *Client) SetUserPhoto(ctx context.Context, image string, params UserSetPhotoParams) error {
|
func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error {
|
||||||
return api.SetUserPhoto(context.Background(), image, params)
|
return api.SetUserPhotoContext(context.Background(), image, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
|
// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
|
||||||
func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) error {
|
func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) error {
|
||||||
response := &SlackResponse{}
|
response := &SlackResponse{}
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
}
|
}
|
||||||
if params.CropX != DEFAULT_USER_PHOTO_CROP_X {
|
if params.CropX != DEFAULT_USER_PHOTO_CROP_X {
|
||||||
values.Add("crop_x", string(params.CropX))
|
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 {
|
if params.CropW != DEFAULT_USER_PHOTO_CROP_W {
|
||||||
values.Add("crop_w", string(params.CropW))
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -283,9 +315,10 @@ func (api *Client) DeleteUserPhoto() error {
|
|||||||
func (api *Client) DeleteUserPhotoContext(ctx context.Context) error {
|
func (api *Client) DeleteUserPhotoContext(ctx context.Context) error {
|
||||||
response := &SlackResponse{}
|
response := &SlackResponse{}
|
||||||
values := url.Values{
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -332,13 +365,12 @@ func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
values := url.Values{
|
values := url.Values{
|
||||||
"token": {api.config.token},
|
"token": {api.token},
|
||||||
"profile": {string(profile)},
|
"profile": {string(profile)},
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &userResponseFull{}
|
response := &userResponseFull{}
|
||||||
|
if err = postForm(ctx, api.httpclient, SLACK_API+"users.profile.set", values, response, api.debug); err != nil {
|
||||||
if err = post(ctx, "users.profile.set", values, response, api.debug); err != nil {
|
|
||||||
return err
|
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"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -27,6 +27,7 @@ type RTM struct {
|
|||||||
IncomingEvents chan RTMEvent
|
IncomingEvents chan RTMEvent
|
||||||
outgoingMessages chan OutgoingMessage
|
outgoingMessages chan OutgoingMessage
|
||||||
killChannel chan bool
|
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
|
forcePing chan bool
|
||||||
rawEvents chan json.RawMessage
|
rawEvents chan json.RawMessage
|
||||||
wasIntentional bool
|
wasIntentional bool
|
||||||
@ -59,9 +60,14 @@ type RTMOptions struct {
|
|||||||
|
|
||||||
// Disconnect and wait, blocking until a successful disconnection.
|
// Disconnect and wait, blocking until a successful disconnection.
|
||||||
func (rtm *RTM) Disconnect() error {
|
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 {
|
if !rtm.isConnected {
|
||||||
return errors.New("Invalid call to Disconnect - Slack API is already disconnected")
|
return errors.New("Invalid call to Disconnect - Slack API is already disconnected")
|
||||||
}
|
}
|
||||||
|
|
||||||
rtm.killChannel <- true
|
rtm.killChannel <- true
|
||||||
return nil
|
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)
|
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
|
// OutgoingErrorEvent contains information in case there were errors sending messages
|
||||||
type OutgoingErrorEvent struct {
|
type OutgoingErrorEvent struct {
|
||||||
Message OutgoingMessage
|
Message OutgoingMessage
|
||||||
|
41
vendor/github.com/nlopes/slack/websocket_managed_conn.go
generated
vendored
41
vendor/github.com/nlopes/slack/websocket_managed_conn.go
generated
vendored
@ -4,10 +4,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ManageConnection can be called on a Slack RTM instance returned by the
|
// 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
|
// if err != nil then the connection is sucessful - otherwise it is
|
||||||
// fatal
|
// fatal
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
rtm.Debugf("Failed to connect with RTM on try %d: %s", connectionCount, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rtm.info = info
|
rtm.info = info
|
||||||
@ -44,6 +46,8 @@ func (rtm *RTM) ManageConnection() {
|
|||||||
rtm.conn = conn
|
rtm.conn = conn
|
||||||
rtm.isConnected = true
|
rtm.isConnected = true
|
||||||
|
|
||||||
|
rtm.Debugf("RTM connection succeeded on try %d", connectionCount)
|
||||||
|
|
||||||
keepRunning := make(chan bool)
|
keepRunning := make(chan bool)
|
||||||
// we're now connected (or have failed fatally) so we can set up
|
// we're now connected (or have failed fatally) so we can set up
|
||||||
// listeners
|
// listeners
|
||||||
@ -89,6 +93,7 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke
|
|||||||
}
|
}
|
||||||
// check for fatal errors - currently only invalid_auth
|
// check for fatal errors - currently only invalid_auth
|
||||||
if sErr, ok := err.(*WebError); ok && (sErr.Error() == "invalid_auth" || sErr.Error() == "account_inactive") {
|
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{}}
|
rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}}
|
||||||
return nil, nil, sErr
|
return nil, nil, sErr
|
||||||
}
|
}
|
||||||
@ -99,6 +104,15 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke
|
|||||||
Attempt: boff.attempts,
|
Attempt: boff.attempts,
|
||||||
ErrorObj: err,
|
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
|
// get time we should wait before attempting to connect again
|
||||||
dur := boff.Duration()
|
dur := boff.Duration()
|
||||||
rtm.Debugf("reconnection %d failed: %s", boff.attempts+1, err)
|
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
|
var err error
|
||||||
|
|
||||||
if useRTMStart {
|
if useRTMStart {
|
||||||
|
rtm.Debugf("Starting RTM")
|
||||||
info, url, err = rtm.StartRTM()
|
info, url, err = rtm.StartRTM()
|
||||||
} else {
|
} else {
|
||||||
|
rtm.Debugf("Connecting to RTM")
|
||||||
info, url, err = rtm.ConnectRTM()
|
info, url, err = rtm.ConnectRTM()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
rtm.Debugf("Failed to start or connect to RTM: %s", err)
|
||||||
return nil, nil, 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 {
|
if err != nil {
|
||||||
|
rtm.Debugf("Failed to dial to the websocket: %s", err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return info, conn, 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 {
|
if err := rtm.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := websocket.JSON.Send(rtm.conn, msg); err != nil {
|
if err := rtm.conn.WriteJSON(msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// remove write deadline
|
// remove write deadline
|
||||||
@ -263,7 +285,7 @@ func (rtm *RTM) ping() error {
|
|||||||
// This will block until a frame is available from the websocket.
|
// This will block until a frame is available from the websocket.
|
||||||
func (rtm *RTM) receiveIncomingEvent() {
|
func (rtm *RTM) receiveIncomingEvent() {
|
||||||
event := json.RawMessage{}
|
event := json.RawMessage{}
|
||||||
err := websocket.JSON.Receive(rtm.conn, &event)
|
err := rtm.conn.ReadJSON(&event)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
// EOF's don't seem to signify a failed connection so instead we ignore
|
// 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
|
// them here and detect a failed connection upon attempting to send a
|
||||||
@ -317,10 +339,19 @@ func (rtm *RTM) handleAck(event json.RawMessage) {
|
|||||||
rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
|
rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ack.Ok {
|
if ack.Ok {
|
||||||
rtm.IncomingEvents <- RTMEvent{"ack", ack}
|
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 {
|
} else {
|
||||||
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
|
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{fmt.Errorf("ack decode failure")}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
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.
|
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.
|
- [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.
|
- [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
|
- [vixl44](https://github.com/sebashwa/vixl44) lets you create pixel art inside your terminal using vim movements
|
||||||
|
- [zterm](https://github.com/varunrau/zterm) is a typing game inspired by http://zty.pe/
|
||||||
### API reference
|
|
||||||
[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go)
|
|
||||||
|
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 "os/signal"
|
||||||
import "syscall"
|
import "syscall"
|
||||||
import "runtime"
|
import "runtime"
|
||||||
|
import "time"
|
||||||
|
|
||||||
// public API
|
// public API
|
||||||
|
|
||||||
@ -253,8 +254,8 @@ func CellBuffer() []Cell {
|
|||||||
// NOTE: This API is experimental and may change in future.
|
// NOTE: This API is experimental and may change in future.
|
||||||
func ParseEvent(data []byte) Event {
|
func ParseEvent(data []byte) Event {
|
||||||
event := Event{Type: EventKey}
|
event := Event{Type: EventKey}
|
||||||
ok := extract_event(data, &event)
|
status := extract_event(data, &event, false)
|
||||||
if !ok {
|
if status != event_extracted {
|
||||||
return Event{Type: EventNone, N: event.N}
|
return Event{Type: EventNone, N: event.N}
|
||||||
}
|
}
|
||||||
return event
|
return event
|
||||||
@ -303,34 +304,65 @@ func PollRawEvent(data []byte) Event {
|
|||||||
|
|
||||||
// Wait for an event and return it. This is a blocking function call.
|
// Wait for an event and return it. This is a blocking function call.
|
||||||
func PollEvent() Event {
|
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 event Event
|
||||||
|
var esc_wait_timer *time.Timer
|
||||||
|
var esc_timeout <-chan time.Time
|
||||||
|
|
||||||
// try to extract event from input buffer, return on success
|
// try to extract event from input buffer, return on success
|
||||||
event.Type = EventKey
|
event.Type = EventKey
|
||||||
ok := extract_event(inbuf, &event)
|
status := extract_event(inbuf, &event, true)
|
||||||
if event.N != 0 {
|
if event.N != 0 {
|
||||||
copy(inbuf, inbuf[event.N:])
|
copy(inbuf, inbuf[event.N:])
|
||||||
inbuf = inbuf[:len(inbuf)-event.N]
|
inbuf = inbuf[:len(inbuf)-event.N]
|
||||||
}
|
}
|
||||||
if ok {
|
if status == event_extracted {
|
||||||
return event
|
return event
|
||||||
|
} else if status == esc_wait {
|
||||||
|
esc_wait_timer = time.NewTimer(esc_wait_delay)
|
||||||
|
esc_timeout = esc_wait_timer.C
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case ev := <-input_comm:
|
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 {
|
if ev.err != nil {
|
||||||
return Event{Type: EventError, Err: ev.err}
|
return Event{Type: EventError, Err: ev.err}
|
||||||
}
|
}
|
||||||
|
|
||||||
inbuf = append(inbuf, ev.data...)
|
inbuf = append(inbuf, ev.data...)
|
||||||
input_comm <- ev
|
input_comm <- ev
|
||||||
ok := extract_event(inbuf, &event)
|
status := extract_event(inbuf, &event, true)
|
||||||
if event.N != 0 {
|
if event.N != 0 {
|
||||||
copy(inbuf, inbuf[event.N:])
|
copy(inbuf, inbuf[event.N:])
|
||||||
inbuf = inbuf[:len(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
|
return event
|
||||||
}
|
}
|
||||||
case <-interrupt_comm:
|
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
|
// using bitwise OR ('|'). Although, colors cannot be combined. But you can
|
||||||
// combine attributes and a single color.
|
// 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
|
// For example windows console doesn't support AttrUnderline. And on some
|
||||||
// terminals applying AttrBold to background may result in blinking text. Use
|
// terminals applying AttrBold to background may result in blinking text. Use
|
||||||
// them with caution and test your code on various terminals.
|
// 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
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type extract_event_res int
|
||||||
|
|
||||||
|
const (
|
||||||
|
event_not_extracted extract_event_res = iota
|
||||||
|
event_extracted
|
||||||
|
esc_wait
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// term specific sequences
|
// term specific sequences
|
||||||
keys []string
|
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)
|
return parse_mouse_event(event, bufstr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,17 +448,27 @@ func extract_raw_event(data []byte, event *Event) bool {
|
|||||||
return true
|
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 {
|
if len(inbuf) == 0 {
|
||||||
event.N = 0
|
event.N = 0
|
||||||
return false
|
return event_not_extracted
|
||||||
}
|
}
|
||||||
|
|
||||||
if inbuf[0] == '\033' {
|
if inbuf[0] == '\033' {
|
||||||
// possible escape sequence
|
// possible escape sequence
|
||||||
if n, ok := parse_escape_sequence(event, inbuf); n != 0 {
|
if n, ok := parse_escape_sequence(event, inbuf); n != 0 {
|
||||||
event.N = n
|
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
|
// 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.Key = KeyEsc
|
||||||
event.Mod = 0
|
event.Mod = 0
|
||||||
event.N = 1
|
event.N = 1
|
||||||
return true
|
return event_extracted
|
||||||
case input_mode&InputAlt != 0:
|
case input_mode&InputAlt != 0:
|
||||||
// if we're in alt mode, set Alt modifier to event and redo parsing
|
// if we're in alt mode, set Alt modifier to event and redo parsing
|
||||||
event.Mod = ModAlt
|
event.Mod = ModAlt
|
||||||
ok := extract_event(inbuf[1:], event)
|
status := extract_event(inbuf[1:], event, false)
|
||||||
if ok {
|
if status == event_extracted {
|
||||||
event.N++
|
event.N++
|
||||||
} else {
|
} else {
|
||||||
event.N = 0
|
event.N = 0
|
||||||
}
|
}
|
||||||
return ok
|
return status
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
@ -486,7 +504,7 @@ func extract_event(inbuf []byte, event *Event) bool {
|
|||||||
event.Ch = 0
|
event.Ch = 0
|
||||||
event.Key = Key(inbuf[0])
|
event.Key = Key(inbuf[0])
|
||||||
event.N = 1
|
event.N = 1
|
||||||
return true
|
return event_extracted
|
||||||
}
|
}
|
||||||
|
|
||||||
// the only possible option is utf8 rune
|
// the only possible option is utf8 rune
|
||||||
@ -494,10 +512,10 @@ func extract_event(inbuf []byte, event *Event) bool {
|
|||||||
event.Ch = r
|
event.Ch = r
|
||||||
event.Key = 0
|
event.Key = 0
|
||||||
event.N = n
|
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) {
|
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
|
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 {
|
if (header[1]+header[2])%2 != 0 {
|
||||||
// old quirk to align everything on word boundaries
|
// old quirk to align everything on word boundaries
|
||||||
header[2] += 1
|
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]
|
table_offset = str_offset + 2*header[4]
|
||||||
|
|
||||||
keys = make([]string, 0xFFFF-key_min)
|
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
|
// RankFind is similar to Find, except it will also rank all matches using
|
||||||
// Levenshtein distance.
|
// Levenshtein distance.
|
||||||
func RankFind(source string, targets []string) Ranks {
|
func RankFind(source string, targets []string) ranks {
|
||||||
var r Ranks
|
var r ranks
|
||||||
for _, target := range find(source, targets, noop) {
|
for _, target := range find(source, targets, noop) {
|
||||||
distance := LevenshteinDistance(source, target)
|
distance := LevenshteinDistance(source, target)
|
||||||
r = append(r, Rank{source, target, distance})
|
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.
|
// RankFindFold is a case-insensitive version of RankFind.
|
||||||
func RankFindFold(source string, targets []string) Ranks {
|
func RankFindFold(source string, targets []string) ranks {
|
||||||
var r Ranks
|
var r ranks
|
||||||
for _, target := range find(source, targets, unicode.ToLower) {
|
for _, target := range find(source, targets, unicode.ToLower) {
|
||||||
distance := LevenshteinDistance(source, target)
|
distance := LevenshteinDistance(source, target)
|
||||||
r = append(r, Rank{source, target, distance})
|
r = append(r, Rank{source, target, distance})
|
||||||
@ -152,16 +152,16 @@ type Rank struct {
|
|||||||
Distance int
|
Distance int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ranks []Rank
|
type ranks []Rank
|
||||||
|
|
||||||
func (r Ranks) Len() int {
|
func (r ranks) Len() int {
|
||||||
return len(r)
|
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]
|
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
|
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