commit
f959524a7a
54
Gopkg.lock
generated
54
Gopkg.lock
generated
@ -3,59 +3,93 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:592569a314f98130ac3085243fdbe46f278d3e54c95ce9e0bde9c6b908db82c4"
|
||||
name = "github.com/0xAX/notificator"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "88d57ee9043ba88d6a62e437fa15dda1ca0d2b59"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c2ee2bebf300b3c6d998802bdefe0422a65bcdcdd5c902e1ed518448c56e8f98"
|
||||
name = "github.com/erroneousboat/termui"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "80f245cdfa0488883a3e8602bf3f0c8a3c889a22"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cee8e8ac80df6373e7daa11baf1f98c1b6f7242c49ccae7e1ec34a971dc408d9"
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
|
||||
version = "v1.2.0"
|
||||
pruneopts = "UT"
|
||||
revision = "3ff3320c2a1756a3691521efc290b4701575147c"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f614e627d47e1276989de725dc5e433504a8b5498850711c9d3fcec3bfa7c943"
|
||||
name = "github.com/maruel/panicparse"
|
||||
packages = ["stack"]
|
||||
revision = "ad661195ed0e88491e0f14be6613304e3b1141d6"
|
||||
pruneopts = "UT"
|
||||
revision = "785840568bdc7faa0dfb1cd6c643207f03271f64"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cdb899c199f907ac9fb50495ec71212c95cb5b0e0a8ee0800da0238036091033"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
|
||||
version = "v0.0.2"
|
||||
pruneopts = "UT"
|
||||
revision = "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e68cd472b96cdf7c9f6971ac41bcc1d4d3b23d67c2a31d2399446e295bc88ae9"
|
||||
name = "github.com/mitchellh/go-wordwrap"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ad45545899c7b13c020ea92b2072220eefad42b8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:410e126b7e96640ac0c41bb49bad7dbf2d1c081aa06fd2c75cdb9e65765fae9b"
|
||||
name = "github.com/nlopes/slack"
|
||||
packages = ["."]
|
||||
revision = "8ac1139f0b5b26ddb4a5c05b727e7ce155cc37f4"
|
||||
pruneopts = "UT"
|
||||
revision = "7cfa5619e6becd3db5dfb8e26c06798918e123b2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f335d800550786b6f51ddaedb9d1107a7a72f4a2195e5b039dd7c0e103e119bc"
|
||||
name = "github.com/nsf/termbox-go"
|
||||
packages = ["."]
|
||||
revision = "e2050e41c8847748ec5288741c0b19a8cb26d084"
|
||||
pruneopts = "UT"
|
||||
revision = "b66b20ab708e289ff1eb3e218478302e6aec28ce"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3fd3d634f6815f19ac4b2c5e16d28ec9aa4584d0bba25d1ee6c424d813cca22a"
|
||||
name = "github.com/renstrom/fuzzysearch"
|
||||
packages = ["fuzzy"]
|
||||
revision = "d4ca9dfccd55dc6b076f9880d49c35315922c1f4"
|
||||
version = "v1.0.0"
|
||||
pruneopts = "UT"
|
||||
revision = "b18e754edff4833912ef4dce9eaca885bd3f0de1"
|
||||
version = "v1.0.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "6b883214c48afc975f37de07cda7d2fbd9d235af028191ffc815755619c9cfc6"
|
||||
input-imports = [
|
||||
"github.com/0xAX/notificator",
|
||||
"github.com/erroneousboat/termui",
|
||||
"github.com/mattn/go-runewidth",
|
||||
"github.com/nlopes/slack",
|
||||
"github.com/nsf/termbox-go",
|
||||
"github.com/renstrom/fuzzysearch/fuzzy",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
@ -14,6 +14,7 @@ const (
|
||||
IconChannel = "#"
|
||||
IconGroup = "☰"
|
||||
IconIM = "●"
|
||||
IconMpIM = "●" // TODO
|
||||
IconNotification = "*"
|
||||
|
||||
PresenceAway = "away"
|
||||
@ -22,6 +23,7 @@ const (
|
||||
ChannelTypeChannel = "channel"
|
||||
ChannelTypeGroup = "group"
|
||||
ChannelTypeIM = "im"
|
||||
ChannelTypeMpIM = "mpim"
|
||||
)
|
||||
|
||||
type ChannelItem struct {
|
||||
@ -55,6 +57,8 @@ func (c ChannelItem) ToString() string {
|
||||
icon = IconChannel
|
||||
case ChannelTypeGroup:
|
||||
icon = IconGroup
|
||||
case ChannelTypeMpIM:
|
||||
icon = IconMpIM
|
||||
case ChannelTypeIM:
|
||||
switch c.Presence {
|
||||
case PresenceActive:
|
||||
@ -93,6 +97,7 @@ func (c ChannelItem) GetChannelName() string {
|
||||
|
||||
// Channels is the definition of a Channels component
|
||||
type Channels struct {
|
||||
ChannelItems []ChannelItem
|
||||
List *termui.List
|
||||
SelectedChannel int // index of which channel is selected from the List
|
||||
Offset int // from what offset are channels rendered
|
||||
@ -122,7 +127,7 @@ func CreateChannelsComponent(inputHeight int) *Channels {
|
||||
func (c *Channels) Buffer() termui.Buffer {
|
||||
buf := c.List.Buffer()
|
||||
|
||||
for i, item := range c.List.Items[c.Offset:] {
|
||||
for i, item := range c.ChannelItems[c.Offset:] {
|
||||
|
||||
y := c.List.InnerBounds().Min.Y + i
|
||||
|
||||
@ -134,10 +139,10 @@ func (c *Channels) Buffer() termui.Buffer {
|
||||
var cells []termui.Cell
|
||||
if y == c.CursorPosition {
|
||||
cells = termui.DefaultTxBuilder.Build(
|
||||
item, c.List.ItemBgColor, c.List.ItemFgColor)
|
||||
item.ToString(), c.List.ItemBgColor, c.List.ItemFgColor)
|
||||
} else {
|
||||
cells = termui.DefaultTxBuilder.Build(
|
||||
item, c.List.ItemFgColor, c.List.ItemBgColor)
|
||||
item.ToString(), c.List.ItemFgColor, c.List.ItemBgColor)
|
||||
}
|
||||
|
||||
cells = termui.DTrimTxCls(cells, c.List.InnerWidth())
|
||||
@ -196,8 +201,33 @@ func (c *Channels) SetY(y int) {
|
||||
c.List.SetY(y)
|
||||
}
|
||||
|
||||
func (c *Channels) SetChannels(channels []string) {
|
||||
c.List.Items = channels
|
||||
func (c *Channels) SetChannels(channels []ChannelItem) {
|
||||
c.ChannelItems = channels
|
||||
}
|
||||
|
||||
func (c *Channels) MarkAsRead(channelID int) {
|
||||
c.ChannelItems[channelID].Notification = false
|
||||
}
|
||||
|
||||
func (c *Channels) MarkAsUnread(channelID string) {
|
||||
index := c.FindChannel(channelID)
|
||||
c.ChannelItems[index].Notification = true
|
||||
}
|
||||
|
||||
func (c *Channels) SetPresence(channelID string, presence string) {
|
||||
index := c.FindChannel(channelID)
|
||||
c.ChannelItems[index].Presence = presence
|
||||
}
|
||||
|
||||
func (c *Channels) FindChannel(channelID string) int {
|
||||
var index int
|
||||
for i, channel := range c.ChannelItems {
|
||||
if channel.ID == channelID {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// SetSelectedChannel sets the SelectedChannel given the index
|
||||
@ -205,11 +235,6 @@ func (c *Channels) SetSelectedChannel(index int) {
|
||||
c.SelectedChannel = index
|
||||
}
|
||||
|
||||
// GetSelectedChannel returns the SelectedChannel
|
||||
func (c *Channels) GetSelectedChannel() string {
|
||||
return c.List.Items[c.SelectedChannel]
|
||||
}
|
||||
|
||||
// MoveCursorUp will decrease the SelectedChannel by 1
|
||||
func (c *Channels) MoveCursorUp() {
|
||||
if c.SelectedChannel > 0 {
|
||||
@ -220,7 +245,7 @@ func (c *Channels) MoveCursorUp() {
|
||||
|
||||
// MoveCursorDown will increase the SelectedChannel by 1
|
||||
func (c *Channels) MoveCursorDown() {
|
||||
if c.SelectedChannel < len(c.List.Items)-1 {
|
||||
if c.SelectedChannel < len(c.ChannelItems)-1 {
|
||||
c.SetSelectedChannel(c.SelectedChannel + 1)
|
||||
c.ScrollDown()
|
||||
}
|
||||
@ -235,9 +260,9 @@ func (c *Channels) MoveCursorTop() {
|
||||
|
||||
// MoveCursorBottom will move the cursor to the bottom of the channels
|
||||
func (c *Channels) MoveCursorBottom() {
|
||||
c.SetSelectedChannel(len(c.List.Items) - 1)
|
||||
c.SetSelectedChannel(len(c.ChannelItems) - 1)
|
||||
|
||||
offset := len(c.List.Items) - (c.List.InnerBounds().Max.Y - 1)
|
||||
offset := len(c.ChannelItems) - (c.List.InnerBounds().Max.Y - 1)
|
||||
|
||||
if offset < 0 {
|
||||
c.Offset = 0
|
||||
@ -264,7 +289,7 @@ func (c *Channels) ScrollUp() {
|
||||
func (c *Channels) ScrollDown() {
|
||||
// Is the cursor at the bottom of the channel view?
|
||||
if c.CursorPosition == c.List.InnerBounds().Max.Y-1 {
|
||||
if c.Offset < len(c.List.Items)-1 {
|
||||
if c.Offset < len(c.ChannelItems)-1 {
|
||||
c.Offset++
|
||||
}
|
||||
} else {
|
||||
@ -278,11 +303,16 @@ func (c *Channels) ScrollDown() {
|
||||
func (c *Channels) Search(term string) {
|
||||
c.SearchMatches = make([]int, 0)
|
||||
|
||||
matches := fuzzy.Find(term, c.List.Items)
|
||||
targets := make([]string, 0)
|
||||
for _, c := range c.ChannelItems {
|
||||
targets = append(targets, c.Name)
|
||||
}
|
||||
|
||||
matches := fuzzy.Find(term, targets)
|
||||
|
||||
for _, m := range matches {
|
||||
for i, item := range c.List.Items {
|
||||
if m == item {
|
||||
for i, item := range c.ChannelItems {
|
||||
if m == item.Name {
|
||||
c.SearchMatches = append(c.SearchMatches, i)
|
||||
break
|
||||
}
|
||||
|
@ -3,7 +3,9 @@ package handlers
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/0xAX/notificator"
|
||||
@ -11,6 +13,7 @@ import (
|
||||
"github.com/nlopes/slack"
|
||||
termbox "github.com/nsf/termbox-go"
|
||||
|
||||
"github.com/erroneousboat/slack-term/components"
|
||||
"github.com/erroneousboat/slack-term/config"
|
||||
"github.com/erroneousboat/slack-term/context"
|
||||
"github.com/erroneousboat/slack-term/views"
|
||||
@ -115,7 +118,7 @@ func messageHandler(ctx *context.AppContext) {
|
||||
}
|
||||
|
||||
// Add message to the selected channel
|
||||
if ev.Channel == ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID {
|
||||
if ev.Channel == ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID {
|
||||
|
||||
// Reverse order of messages, mainly done
|
||||
// when attachments are added to message
|
||||
@ -241,7 +244,7 @@ func actionSend(ctx *context.AppContext) {
|
||||
|
||||
// Send message
|
||||
err := ctx.Service.SendMessage(
|
||||
ctx.View.Channels.SelectedChannel,
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
message,
|
||||
)
|
||||
if err != nil {
|
||||
@ -251,8 +254,11 @@ func actionSend(ctx *context.AppContext) {
|
||||
}
|
||||
|
||||
// Clear notification icon if there is any
|
||||
ctx.Service.MarkAsRead(ctx.View.Channels.SelectedChannel)
|
||||
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
||||
channelItem := ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel]
|
||||
if channelItem.Notification {
|
||||
ctx.Service.MarkAsRead(channelItem.ID)
|
||||
ctx.View.Channels.MarkAsRead(ctx.View.Channels.SelectedChannel)
|
||||
}
|
||||
termui.Render(ctx.View.Channels)
|
||||
}
|
||||
}
|
||||
@ -304,7 +310,7 @@ func actionSearchMode(ctx *context.AppContext) {
|
||||
|
||||
func actionGetMessages(ctx *context.AppContext) {
|
||||
msgs := ctx.Service.GetMessages(
|
||||
ctx.Service.Channels[ctx.View.Channels.SelectedChannel],
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.View.Chat.GetMaxItems(),
|
||||
)
|
||||
|
||||
@ -380,7 +386,7 @@ func actionChangeChannel(ctx *context.AppContext) {
|
||||
// Get messages of the SelectedChannel, and get the count of messages
|
||||
// that fit into the Chat component
|
||||
msgs := ctx.Service.GetMessages(
|
||||
ctx.Service.GetSlackChannel(ctx.View.Channels.SelectedChannel),
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.View.Chat.GetMaxItems(),
|
||||
)
|
||||
|
||||
@ -389,12 +395,15 @@ func actionChangeChannel(ctx *context.AppContext) {
|
||||
|
||||
// Set channel name for the Chat pane
|
||||
ctx.View.Chat.SetBorderLabel(
|
||||
ctx.Service.Channels[ctx.View.Channels.SelectedChannel].GetChannelName(),
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].GetChannelName(),
|
||||
)
|
||||
|
||||
// Clear notification icon if there is any
|
||||
ctx.Service.MarkAsRead(ctx.View.Channels.SelectedChannel)
|
||||
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
||||
channelItem := ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel]
|
||||
if channelItem.Notification {
|
||||
ctx.Service.MarkAsRead(channelItem.ID)
|
||||
ctx.View.Channels.MarkAsRead(ctx.View.Channels.SelectedChannel)
|
||||
}
|
||||
|
||||
termui.Render(ctx.View.Channels)
|
||||
termui.Render(ctx.View.Chat)
|
||||
@ -403,8 +412,7 @@ func actionChangeChannel(ctx *context.AppContext) {
|
||||
// actionNewMessage will set the new message indicator for a channel, and
|
||||
// if configured will also display a desktop notification
|
||||
func actionNewMessage(ctx *context.AppContext, ev *slack.MessageEvent) {
|
||||
ctx.Service.MarkAsUnread(ev.Channel)
|
||||
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
||||
ctx.View.Channels.MarkAsUnread(ev.Channel)
|
||||
termui.Render(ctx.View.Channels)
|
||||
|
||||
// Terminal bell
|
||||
@ -412,7 +420,7 @@ func actionNewMessage(ctx *context.AppContext, ev *slack.MessageEvent) {
|
||||
|
||||
// Desktop notification
|
||||
if ctx.Config.Notify == config.NotifyMention {
|
||||
if ctx.Service.CheckNotifyMention(ev) {
|
||||
if isMention(ctx, ev) {
|
||||
createNotifyMessage(ctx, ev)
|
||||
}
|
||||
} else if ctx.Config.Notify == config.NotifyAll {
|
||||
@ -421,8 +429,7 @@ func actionNewMessage(ctx *context.AppContext, ev *slack.MessageEvent) {
|
||||
}
|
||||
|
||||
func actionSetPresence(ctx *context.AppContext, channelID string, presence string) {
|
||||
ctx.Service.SetPresenceChannelEvent(channelID, presence)
|
||||
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
||||
ctx.View.Channels.SetPresence(channelID, presence)
|
||||
termui.Render(ctx.View.Channels)
|
||||
}
|
||||
|
||||
@ -491,20 +498,52 @@ func getKeyString(e termbox.Event) string {
|
||||
return ek
|
||||
}
|
||||
|
||||
// isMention check if the message event either contains a
|
||||
// mention or is posted on an IM channel.
|
||||
func isMention(ctx *context.AppContext, ev *slack.MessageEvent) bool {
|
||||
channel := ctx.View.Channels.ChannelItems[ctx.View.Channels.FindChannel(ev.Channel)]
|
||||
|
||||
if channel.Type == components.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, ctx.Service.CurrentUserID) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func createNotifyMessage(ctx *context.AppContext, ev *slack.MessageEvent) {
|
||||
go func() {
|
||||
if notifyTimer != nil {
|
||||
notifyTimer.Stop()
|
||||
}
|
||||
|
||||
// Only actually notify when time expires
|
||||
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,
|
||||
)
|
||||
var message string
|
||||
channel := ctx.View.Channels.ChannelItems[ctx.View.Channels.FindChannel(ev.Channel)]
|
||||
switch channel.Type {
|
||||
case components.ChannelTypeChannel:
|
||||
message = fmt.Sprintf("Message received on channel: %s", channel.Name)
|
||||
case components.ChannelTypeGroup:
|
||||
message = fmt.Sprintf("Message received in group: %s", channel.Name)
|
||||
case components.ChannelTypeIM:
|
||||
message = fmt.Sprintf("Message received from: %s", channel.Name)
|
||||
default:
|
||||
message = fmt.Sprintf("Message received from: %s", channel.Name)
|
||||
}
|
||||
|
||||
ctx.Notify.Push("slack-term", message, "", notificator.UR_NORMAL)
|
||||
}()
|
||||
}
|
||||
|
478
service/slack.go
478
service/slack.go
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -16,18 +17,11 @@ import (
|
||||
"github.com/erroneousboat/slack-term/config"
|
||||
)
|
||||
|
||||
const (
|
||||
ChannelTypeChannel = "channel"
|
||||
ChannelTypeGroup = "group"
|
||||
ChannelTypeIM = "im"
|
||||
)
|
||||
|
||||
type SlackService struct {
|
||||
Config *config.Config
|
||||
Client *slack.Client
|
||||
RTM *slack.RTM
|
||||
SlackChannels []interface{}
|
||||
Channels []components.ChannelItem
|
||||
Conversations []slack.Channel
|
||||
UserCache map[string]string
|
||||
CurrentUserID string
|
||||
CurrentUsername string
|
||||
@ -75,176 +69,168 @@ func NewSlackService(config *config.Config) (*SlackService, error) {
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
// GetChannels will retrieve all available channels, groups, and im channels.
|
||||
// Because the channels are of different types, we will append them to
|
||||
// an []interface as well as to a []Channel which will give us easy access
|
||||
// to the id and name of the Channel.
|
||||
func (s *SlackService) GetChannels() []string {
|
||||
var chans []components.ChannelItem
|
||||
func (s *SlackService) GetChannels() []components.ChannelItem {
|
||||
slackChans := make([]slack.Channel, 0)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// Channels
|
||||
wg.Add(1)
|
||||
var slackChans []slack.Channel
|
||||
go func() {
|
||||
var err error
|
||||
slackChans, err = s.Client.GetChannels(true)
|
||||
if err != nil {
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// Groups
|
||||
wg.Add(1)
|
||||
var slackGroups []slack.Group
|
||||
go func() {
|
||||
var err error
|
||||
slackGroups, err = s.Client.GetGroups(true)
|
||||
if err != nil {
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// IM
|
||||
wg.Add(1)
|
||||
var slackIM []slack.IM
|
||||
go func() {
|
||||
var err error
|
||||
slackIM, err = s.Client.GetIMChannels()
|
||||
if err != nil {
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Channels
|
||||
for _, chn := range slackChans {
|
||||
if chn.IsMember {
|
||||
s.SlackChannels = append(s.SlackChannels, chn)
|
||||
chans = append(
|
||||
chans, components.ChannelItem{
|
||||
ID: chn.ID,
|
||||
Name: chn.Name,
|
||||
Topic: chn.Topic.Value,
|
||||
Type: components.ChannelTypeChannel,
|
||||
UserID: "",
|
||||
StylePrefix: s.Config.Theme.Channel.Prefix,
|
||||
StyleIcon: s.Config.Theme.Channel.Icon,
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
},
|
||||
)
|
||||
}
|
||||
// Initial request
|
||||
initChans, initCur, err := s.Client.GetConversations(
|
||||
&slack.GetConversationsParameters{
|
||||
ExcludeArchived: "true",
|
||||
Limit: 10,
|
||||
Types: []string{
|
||||
"public_channel",
|
||||
"private_channel",
|
||||
"im",
|
||||
"mpim",
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
|
||||
// Groups
|
||||
for _, grp := range slackGroups {
|
||||
s.SlackChannels = append(s.SlackChannels, grp)
|
||||
chans = append(
|
||||
chans, components.ChannelItem{
|
||||
ID: grp.ID,
|
||||
Name: grp.Name,
|
||||
Topic: grp.Topic.Value,
|
||||
Type: components.ChannelTypeGroup,
|
||||
UserID: "",
|
||||
StylePrefix: s.Config.Theme.Channel.Prefix,
|
||||
StyleIcon: s.Config.Theme.Channel.Icon,
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
slackChans = append(slackChans, initChans...)
|
||||
|
||||
// Paginate over additional channels
|
||||
nextCur := initCur
|
||||
for nextCur != "" {
|
||||
channels, cursor, err := s.Client.GetConversations(
|
||||
&slack.GetConversationsParameters{
|
||||
Cursor: nextCur,
|
||||
ExcludeArchived: "true",
|
||||
Limit: 10,
|
||||
Types: []string{
|
||||
"public_channel",
|
||||
"private_channel",
|
||||
"im",
|
||||
"mpim",
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// IM
|
||||
for _, im := range slackIM {
|
||||
|
||||
// Uncover name, when we can't uncover name for
|
||||
// IM channel this is then probably a deleted
|
||||
// user, because we won't add deleted users
|
||||
// to the UserCache, so we skip it
|
||||
name, ok := s.UserCache[im.User]
|
||||
|
||||
if ok {
|
||||
chans = append(
|
||||
chans,
|
||||
components.ChannelItem{
|
||||
ID: im.ID,
|
||||
Name: name,
|
||||
Topic: "",
|
||||
Type: components.ChannelTypeIM,
|
||||
UserID: im.User,
|
||||
Presence: "",
|
||||
StylePrefix: s.Config.Theme.Channel.Prefix,
|
||||
StyleIcon: s.Config.Theme.Channel.Icon,
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
},
|
||||
)
|
||||
s.SlackChannels = append(s.SlackChannels, im)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
|
||||
slackChans = append(slackChans, channels...)
|
||||
nextCur = cursor
|
||||
}
|
||||
|
||||
s.Channels = chans
|
||||
|
||||
// We set presence of IM channels here because we need to separately
|
||||
// issue an API call for every channel, this will speed up that process
|
||||
s.SetPresenceChannels()
|
||||
|
||||
var channels []string
|
||||
for _, chn := range s.Channels {
|
||||
channels = append(channels, chn.ToString())
|
||||
// We're creating tempChan, because we want to be able to
|
||||
// sort the types of channels into buckets
|
||||
type tempChan struct {
|
||||
channelItem components.ChannelItem
|
||||
slackChannel slack.Channel
|
||||
}
|
||||
|
||||
return channels
|
||||
}
|
||||
// Initialize buckets
|
||||
buckets := make(map[int]map[string]*tempChan)
|
||||
buckets[0] = make(map[string]*tempChan) // Channels
|
||||
buckets[1] = make(map[string]*tempChan) // Group
|
||||
buckets[2] = make(map[string]*tempChan) // MpIM
|
||||
buckets[3] = make(map[string]*tempChan) // IM
|
||||
|
||||
// ChannelsToString will relay the string representation for a channel
|
||||
func (s *SlackService) ChannelsToString() []string {
|
||||
var channels []string
|
||||
for _, chn := range s.Channels {
|
||||
channels = append(channels, chn.ToString())
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
// SetPresence will set presence for all IM channels
|
||||
func (s *SlackService) SetPresenceChannels() {
|
||||
var wg sync.WaitGroup
|
||||
for i, channel := range s.SlackChannels {
|
||||
for _, chn := range slackChans {
|
||||
chanItem := s.createChannelItem(chn)
|
||||
|
||||
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)
|
||||
if chn.IsChannel {
|
||||
if !chn.IsMember {
|
||||
continue
|
||||
}
|
||||
|
||||
chanItem.Type = components.ChannelTypeChannel
|
||||
|
||||
buckets[0][chn.ID] = &tempChan{
|
||||
channelItem: chanItem,
|
||||
slackChannel: chn,
|
||||
}
|
||||
}
|
||||
|
||||
if chn.IsGroup {
|
||||
if !chn.IsMember {
|
||||
continue
|
||||
}
|
||||
|
||||
chanItem.Type = components.ChannelTypeGroup
|
||||
|
||||
buckets[1][chn.ID] = &tempChan{
|
||||
channelItem: chanItem,
|
||||
slackChannel: chn,
|
||||
}
|
||||
}
|
||||
|
||||
if chn.IsMpIM {
|
||||
chanItem.Type = components.ChannelTypeMpIM
|
||||
|
||||
buckets[2][chn.ID] = &tempChan{
|
||||
channelItem: chanItem,
|
||||
slackChannel: chn,
|
||||
}
|
||||
}
|
||||
|
||||
if chn.IsIM {
|
||||
// Check if user is deleted, we do this by checking the user id,
|
||||
// and see if we have the user in the UserCache
|
||||
name, ok := s.UserCache[chn.User]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
chanItem.Name = name
|
||||
chanItem.Type = components.ChannelTypeIM
|
||||
|
||||
buckets[3][chn.User] = &tempChan{
|
||||
channelItem: chanItem,
|
||||
slackChannel: chn,
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(user string, buckets map[int]map[string]*tempChan) {
|
||||
defer wg.Done()
|
||||
|
||||
presence, err := s.GetUserPresence(user)
|
||||
if err != nil {
|
||||
buckets[3][user].channelItem.Presence = "away"
|
||||
return
|
||||
}
|
||||
|
||||
buckets[3][user].channelItem.Presence = presence
|
||||
}(chn.User, buckets)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// SetPresenceChannelEvent will set the presence of a IM channel
|
||||
func (s *SlackService) SetPresenceChannelEvent(userID string, presence string) {
|
||||
// Get the correct Channel from svc.Channels
|
||||
var index int
|
||||
for i, channel := range s.Channels {
|
||||
if userID == channel.UserID {
|
||||
index = i
|
||||
break
|
||||
// Sort the buckets
|
||||
var keys []int
|
||||
for k := range buckets {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Ints(keys)
|
||||
|
||||
var chans []components.ChannelItem
|
||||
for _, k := range keys {
|
||||
|
||||
bucket := buckets[k]
|
||||
|
||||
// Sort channels in every bucket
|
||||
tcArr := make([]tempChan, 0)
|
||||
for _, v := range bucket {
|
||||
tcArr = append(tcArr, *v)
|
||||
}
|
||||
|
||||
sort.Slice(tcArr, func(i, j int) bool {
|
||||
return tcArr[i].channelItem.Name < tcArr[j].channelItem.Name
|
||||
})
|
||||
|
||||
// Add ChannelItem and SlackChannel to the SlackService struct
|
||||
for _, tc := range tcArr {
|
||||
chans = append(chans, tc.channelItem)
|
||||
s.Conversations = append(s.Conversations, tc.slackChannel)
|
||||
}
|
||||
}
|
||||
s.Channels[index].Presence = presence
|
||||
}
|
||||
|
||||
// GetSlackChannel returns the representation of a slack channel
|
||||
func (s *SlackService) GetSlackChannel(selectedChannel int) interface{} {
|
||||
return s.SlackChannels[selectedChannel]
|
||||
return chans
|
||||
}
|
||||
|
||||
// GetUserPresence will get the presence of a specific user
|
||||
@ -257,82 +243,37 @@ func (s *SlackService) GetUserPresence(userID string) (string, error) {
|
||||
return presence.Presence, nil
|
||||
}
|
||||
|
||||
// SetChannelReadMark will set the read mark for a channel, group, and im
|
||||
// channel based on the current time.
|
||||
func (s *SlackService) SetChannelReadMark(channel interface{}) {
|
||||
switch channel := channel.(type) {
|
||||
case slack.Channel:
|
||||
s.Client.SetChannelReadMark(
|
||||
channel.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
case slack.Group:
|
||||
s.Client.SetGroupReadMark(
|
||||
channel.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
case slack.IM:
|
||||
s.Client.MarkIMChannel(
|
||||
channel.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAsRead will set the channel as read
|
||||
func (s *SlackService) MarkAsRead(channelID int) {
|
||||
channel := s.Channels[channelID]
|
||||
func (s *SlackService) MarkAsRead(channelID string) {
|
||||
|
||||
if channel.Notification {
|
||||
s.Channels[channelID].Notification = false
|
||||
// TODO: does this work with other channel types? See old one below,
|
||||
// test this
|
||||
s.Client.SetChannelReadMark(
|
||||
channelID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
|
||||
switch channel.Type {
|
||||
case ChannelTypeChannel:
|
||||
s.Client.SetChannelReadMark(
|
||||
channel.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
case ChannelTypeGroup:
|
||||
s.Client.SetGroupReadMark(
|
||||
channel.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
case ChannelTypeIM:
|
||||
s.Client.MarkIMChannel(
|
||||
channel.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FindChannel will loop over s.Channels to find the index where the
|
||||
// channelID equals the ID
|
||||
func (s *SlackService) FindChannel(channelID string) int {
|
||||
var index int
|
||||
for i, channel := range s.Channels {
|
||||
if channel.ID == channelID {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
// MarkAsUnread will set the channel as unread
|
||||
func (s *SlackService) MarkAsUnread(channelID string) {
|
||||
index := s.FindChannel(channelID)
|
||||
s.Channels[index].Notification = true
|
||||
}
|
||||
|
||||
// GetChannelName will return the name for a specific channelID
|
||||
func (s *SlackService) GetChannelName(channelID string) string {
|
||||
index := s.FindChannel(channelID)
|
||||
return s.Channels[index].Name
|
||||
// switch channel.Type {
|
||||
// case ChannelTypeChannel:
|
||||
// s.Client.SetChannelReadMark(
|
||||
// channel.ID, fmt.Sprintf("%f",
|
||||
// float64(time.Now().Unix())),
|
||||
// )
|
||||
// case ChannelTypeGroup:
|
||||
// s.Client.SetGroupReadMark(
|
||||
// channel.ID, fmt.Sprintf("%f",
|
||||
// float64(time.Now().Unix())),
|
||||
// )
|
||||
// case ChannelTypeIM:
|
||||
// s.Client.MarkIMChannel(
|
||||
// channel.ID, fmt.Sprintf("%f",
|
||||
// float64(time.Now().Unix())),
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
// SendMessage will send a message to a particular channel
|
||||
func (s *SlackService) SendMessage(channelID int, message string) error {
|
||||
func (s *SlackService) SendMessage(channelID string, message string) error {
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#PostMessageParameters
|
||||
postParams := slack.PostMessageParameters{
|
||||
@ -342,7 +283,7 @@ func (s *SlackService) SendMessage(channelID int, message string) error {
|
||||
}
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#Client.PostMessage
|
||||
_, _, err := s.Client.PostMessage(s.Channels[channelID].ID, message, postParams)
|
||||
_, _, err := s.Client.PostMessage(channelID, message, postParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -352,33 +293,18 @@ func (s *SlackService) SendMessage(channelID int, message string) error {
|
||||
|
||||
// GetMessages will get messages for a channel, group or im channel delimited
|
||||
// by a count.
|
||||
func (s *SlackService) GetMessages(channel interface{}, count int) []components.Message {
|
||||
// https://api.slack.com/methods/channels.history
|
||||
historyParams := slack.HistoryParameters{
|
||||
Count: count,
|
||||
func (s *SlackService) GetMessages(channelID string, count int) []components.Message {
|
||||
// TODO: check other parameters
|
||||
// https://godoc.org/github.com/nlopes/slack#GetConversationHistoryParameters
|
||||
historyParams := slack.GetConversationHistoryParameters{
|
||||
ChannelID: channelID,
|
||||
Limit: count,
|
||||
Inclusive: false,
|
||||
Unreads: false,
|
||||
}
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#History
|
||||
history := new(slack.History)
|
||||
var err error
|
||||
switch chnType := channel.(type) {
|
||||
case slack.Channel:
|
||||
history, err = s.Client.GetChannelHistory(chnType.ID, historyParams)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
case slack.Group:
|
||||
history, err = s.Client.GetGroupHistory(chnType.ID, historyParams)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
case slack.IM:
|
||||
history, err = s.Client.GetIMHistory(chnType.ID, historyParams)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
history, err := s.Client.GetConversationHistory(&historyParams)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
|
||||
// Construct the messages
|
||||
@ -540,44 +466,6 @@ func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent
|
||||
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:
|
||||
// - emoji's
|
||||
// - mentions
|
||||
@ -701,3 +589,15 @@ func (s *SlackService) CreateMessageFromAttachments(atts []slack.Attachment) []c
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
func (s *SlackService) createChannelItem(chn slack.Channel) components.ChannelItem {
|
||||
return components.ChannelItem{
|
||||
ID: chn.ID,
|
||||
Name: chn.Name,
|
||||
Topic: chn.Topic.Value,
|
||||
UserID: chn.User,
|
||||
StylePrefix: s.Config.Theme.Channel.Prefix,
|
||||
StyleIcon: s.Config.Theme.Channel.Icon,
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
}
|
||||
}
|
||||
|
2
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
2
vendor/github.com/gorilla/websocket/.gitignore
generated
vendored
@ -22,4 +22,4 @@ _testmain.go
|
||||
*.exe
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
*.iml
|
||||
|
10
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
10
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
@ -4,10 +4,12 @@ sudo: false
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.4
|
||||
- go: 1.5
|
||||
- go: 1.6
|
||||
- go: 1.7
|
||||
- go: 1.8
|
||||
- go: 1.5.x
|
||||
- go: 1.6.x
|
||||
- go: 1.7.x
|
||||
- go: 1.8.x
|
||||
- go: 1.9.x
|
||||
- go: 1.10.x
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
1
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
1
vendor/github.com/gorilla/websocket/AUTHORS
generated
vendored
@ -4,5 +4,6 @@
|
||||
# Please keep the list sorted.
|
||||
|
||||
Gary Burd <gary@beagledreams.com>
|
||||
Google LLC (https://opensource.google.com/)
|
||||
Joachim Bauch <mail@joachim-bauch.de>
|
||||
|
||||
|
2
vendor/github.com/gorilla/websocket/README.md
generated
vendored
2
vendor/github.com/gorilla/websocket/README.md
generated
vendored
@ -51,7 +51,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn
|
||||
<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:
|
||||
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
|
||||
|
154
vendor/github.com/gorilla/websocket/client.go
generated
vendored
154
vendor/github.com/gorilla/websocket/client.go
generated
vendored
@ -5,10 +5,8 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -88,50 +86,6 @@ type Dialer struct {
|
||||
|
||||
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
|
||||
@ -150,11 +104,15 @@ func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||
return hostPort, hostNoPort
|
||||
}
|
||||
|
||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
||||
// DefaultDialer is a dialer with all fields set to the default values.
|
||||
var DefaultDialer = &Dialer{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
HandshakeTimeout: 45 * time.Second,
|
||||
}
|
||||
|
||||
// nilDialer is dialer to use when receiver is nil.
|
||||
var nilDialer Dialer = *DefaultDialer
|
||||
|
||||
// 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
|
||||
@ -167,9 +125,7 @@ var DefaultDialer = &Dialer{
|
||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
|
||||
if d == nil {
|
||||
d = &Dialer{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
d = &nilDialer
|
||||
}
|
||||
|
||||
challengeKey, err := generateChallengeKey()
|
||||
@ -177,7 +133,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
u, err := parseURL(urlStr)
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -237,31 +193,15 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||
k == "Sec-Websocket-Extensions" ||
|
||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||
case k == "Sec-Websocket-Protocol":
|
||||
req.Header["Sec-WebSocket-Protocol"] = vs
|
||||
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
|
||||
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
@ -269,13 +209,47 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||
deadline = time.Now().Add(d.HandshakeTimeout)
|
||||
}
|
||||
|
||||
// Get network dial function.
|
||||
netDial := d.NetDial
|
||||
if netDial == nil {
|
||||
netDialer := &net.Dialer{Deadline: deadline}
|
||||
netDial = netDialer.Dial
|
||||
}
|
||||
|
||||
netConn, err := netDial("tcp", targetHostPort)
|
||||
// If needed, wrap the dial function to set the connection deadline.
|
||||
if !deadline.Equal(time.Time{}) {
|
||||
forwardDial := netDial
|
||||
netDial = func(network, addr string) (net.Conn, error) {
|
||||
c, err := forwardDial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.SetDeadline(deadline)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
|
||||
// If needed, wrap the dial function to connect through a proxy.
|
||||
if d.Proxy != nil {
|
||||
proxyURL, err := d.Proxy(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if proxyURL != nil {
|
||||
dialer, err := proxy_FromURL(proxyURL, netDialerFunc(netDial))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
netDial = dialer.Dial
|
||||
}
|
||||
}
|
||||
|
||||
hostPort, hostNoPort := hostPortNoPort(u)
|
||||
netConn, err := netDial("tcp", hostPort)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -286,42 +260,6 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
||||
}
|
||||
}()
|
||||
|
||||
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 == "" {
|
||||
|
70
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
70
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
@ -76,7 +76,7 @@ const (
|
||||
// is UTF-8 encoded text.
|
||||
PingMessage = 9
|
||||
|
||||
// PongMessage denotes a ping control message. The optional message payload
|
||||
// PongMessage denotes a pong control message. The optional message payload
|
||||
// is UTF-8 encoded text.
|
||||
PongMessage = 10
|
||||
)
|
||||
@ -100,9 +100,8 @@ func (e *netError) Error() string { return e.msg }
|
||||
func (e *netError) Temporary() bool { return e.temporary }
|
||||
func (e *netError) Timeout() bool { return e.timeout }
|
||||
|
||||
// CloseError represents close frame.
|
||||
// CloseError represents a close message.
|
||||
type CloseError struct {
|
||||
|
||||
// Code is defined in RFC 6455, section 11.7.
|
||||
Code int
|
||||
|
||||
@ -343,7 +342,8 @@ func (c *Conn) Subprotocol() string {
|
||||
return c.subprotocol
|
||||
}
|
||||
|
||||
// Close closes the underlying network connection without sending or waiting for a close frame.
|
||||
// Close closes the underlying network connection without sending or waiting
|
||||
// for a close message.
|
||||
func (c *Conn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
@ -370,7 +370,7 @@ func (c *Conn) writeFatal(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
|
||||
func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error {
|
||||
<-c.mu
|
||||
defer func() { c.mu <- true }()
|
||||
|
||||
@ -382,15 +382,14 @@ func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error {
|
||||
}
|
||||
|
||||
c.conn.SetWriteDeadline(deadline)
|
||||
for _, buf := range bufs {
|
||||
if len(buf) > 0 {
|
||||
_, err := c.conn.Write(buf)
|
||||
if err != nil {
|
||||
return c.writeFatal(err)
|
||||
}
|
||||
}
|
||||
if len(buf1) == 0 {
|
||||
_, err = c.conn.Write(buf0)
|
||||
} else {
|
||||
err = c.writeBufs(buf0, buf1)
|
||||
}
|
||||
if err != nil {
|
||||
return c.writeFatal(err)
|
||||
}
|
||||
|
||||
if frameType == CloseMessage {
|
||||
c.writeFatal(ErrCloseSent)
|
||||
}
|
||||
@ -484,6 +483,9 @@ func (c *Conn) prepWrite(messageType int) error {
|
||||
//
|
||||
// There can be at most one open writer on a connection. NextWriter closes the
|
||||
// previous writer if the application has not already done so.
|
||||
//
|
||||
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
|
||||
// PongMessage) are supported.
|
||||
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
||||
if err := c.prepWrite(messageType); err != nil {
|
||||
return nil, err
|
||||
@ -764,7 +766,6 @@ func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||
// Read methods
|
||||
|
||||
func (c *Conn) advanceFrame() (int, error) {
|
||||
|
||||
// 1. Skip remainder of previous frame.
|
||||
|
||||
if c.readRemaining > 0 {
|
||||
@ -1033,7 +1034,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
}
|
||||
|
||||
// SetReadLimit sets the maximum size for a message read from the peer. If a
|
||||
// message exceeds the limit, the connection sends a close frame to the peer
|
||||
// message exceeds the limit, the connection sends a close message to the peer
|
||||
// and returns ErrReadLimit to the application.
|
||||
func (c *Conn) SetReadLimit(limit int64) {
|
||||
c.readLimit = limit
|
||||
@ -1046,24 +1047,22 @@ func (c *Conn) CloseHandler() func(code int, text string) error {
|
||||
|
||||
// SetCloseHandler sets the handler for close messages received from the peer.
|
||||
// The code argument to h is the received close code or CloseNoStatusReceived
|
||||
// if the close message is empty. The default close handler sends a close frame
|
||||
// back to the peer.
|
||||
// if the close message is empty. The default close handler sends a close
|
||||
// message back to the peer.
|
||||
//
|
||||
// The application must read the connection to process close messages as
|
||||
// described in the section on Control Frames above.
|
||||
// The handler function is called from the NextReader, ReadMessage and message
|
||||
// reader Read methods. The application must read the connection to process
|
||||
// close messages as described in the section on Control Messages above.
|
||||
//
|
||||
// The connection read methods return a CloseError when a close frame is
|
||||
// The connection read methods return a CloseError when a close message is
|
||||
// received. Most applications should handle close messages as part of their
|
||||
// normal error handling. Applications should only set a close handler when the
|
||||
// application must perform some action before sending a close frame back to
|
||||
// application must perform some action before sending a close message back to
|
||||
// the peer.
|
||||
func (c *Conn) SetCloseHandler(h func(code int, text string) error) {
|
||||
if h == nil {
|
||||
h = func(code int, text string) error {
|
||||
message := []byte{}
|
||||
if code != CloseNoStatusReceived {
|
||||
message = FormatCloseMessage(code, "")
|
||||
}
|
||||
message := FormatCloseMessage(code, "")
|
||||
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
|
||||
return nil
|
||||
}
|
||||
@ -1077,11 +1076,12 @@ func (c *Conn) PingHandler() func(appData string) error {
|
||||
}
|
||||
|
||||
// SetPingHandler sets the handler for ping messages received from the peer.
|
||||
// The appData argument to h is the PING frame application data. The default
|
||||
// The appData argument to h is the PING message application data. The default
|
||||
// ping handler sends a pong to the peer.
|
||||
//
|
||||
// The application must read the connection to process ping messages as
|
||||
// described in the section on Control Frames above.
|
||||
// The handler function is called from the NextReader, ReadMessage and message
|
||||
// reader Read methods. The application must read the connection to process
|
||||
// ping messages as described in the section on Control Messages above.
|
||||
func (c *Conn) SetPingHandler(h func(appData string) error) {
|
||||
if h == nil {
|
||||
h = func(message string) error {
|
||||
@ -1103,11 +1103,12 @@ func (c *Conn) PongHandler() func(appData string) error {
|
||||
}
|
||||
|
||||
// SetPongHandler sets the handler for pong messages received from the peer.
|
||||
// The appData argument to h is the PONG frame application data. The default
|
||||
// The appData argument to h is the PONG message application data. The default
|
||||
// pong handler does nothing.
|
||||
//
|
||||
// The application must read the connection to process ping messages as
|
||||
// described in the section on Control Frames above.
|
||||
// The handler function is called from the NextReader, ReadMessage and message
|
||||
// reader Read methods. The application must read the connection to process
|
||||
// pong messages as described in the section on Control Messages above.
|
||||
func (c *Conn) SetPongHandler(h func(appData string) error) {
|
||||
if h == nil {
|
||||
h = func(string) error { return nil }
|
||||
@ -1141,7 +1142,14 @@ func (c *Conn) SetCompressionLevel(level int) error {
|
||||
}
|
||||
|
||||
// FormatCloseMessage formats closeCode and text as a WebSocket close message.
|
||||
// An empty message is returned for code CloseNoStatusReceived.
|
||||
func FormatCloseMessage(closeCode int, text string) []byte {
|
||||
if closeCode == CloseNoStatusReceived {
|
||||
// Return empty message because it's illegal to send
|
||||
// CloseNoStatusReceived. Return non-nil value in case application
|
||||
// checks for nil.
|
||||
return []byte{}
|
||||
}
|
||||
buf := make([]byte, 2+len(text))
|
||||
binary.BigEndian.PutUint16(buf, uint16(closeCode))
|
||||
copy(buf[2:], text)
|
||||
|
15
vendor/github.com/gorilla/websocket/conn_write.go
generated
vendored
Normal file
15
vendor/github.com/gorilla/websocket/conn_write.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 go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import "net"
|
||||
|
||||
func (c *Conn) writeBufs(bufs ...[]byte) error {
|
||||
b := net.Buffers(bufs)
|
||||
_, err := b.WriteTo(c.conn)
|
||||
return err
|
||||
}
|
18
vendor/github.com/gorilla/websocket/conn_write_legacy.go
generated
vendored
Normal file
18
vendor/github.com/gorilla/websocket/conn_write_legacy.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.8
|
||||
|
||||
package websocket
|
||||
|
||||
func (c *Conn) writeBufs(bufs ...[]byte) error {
|
||||
for _, buf := range bufs {
|
||||
if len(buf) > 0 {
|
||||
if _, err := c.conn.Write(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
56
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
56
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
@ -6,9 +6,8 @@
|
||||
//
|
||||
// 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:
|
||||
// The Conn type represents a WebSocket connection. A server application calls
|
||||
// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn:
|
||||
//
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// ReadBufferSize: 1024,
|
||||
@ -31,10 +30,12 @@
|
||||
// for {
|
||||
// messageType, p, err := conn.ReadMessage()
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// return
|
||||
// }
|
||||
// if err = conn.WriteMessage(messageType, p); err != nil {
|
||||
// return err
|
||||
// if err := conn.WriteMessage(messageType, p); err != nil {
|
||||
// log.Println(err)
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
//
|
||||
@ -85,20 +86,26 @@
|
||||
// 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 close messages by calling the handler function
|
||||
// set with the SetCloseHandler method and by returning a *CloseError from the
|
||||
// NextReader, ReadMessage or the message Read method. The default close
|
||||
// handler sends a close message to the peer.
|
||||
//
|
||||
// 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.
|
||||
// Connections handle received ping messages by calling the handler function
|
||||
// set with the SetPingHandler method. The default ping handler sends a pong
|
||||
// message to the peer.
|
||||
//
|
||||
// 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.
|
||||
// Connections handle received pong messages by calling the handler function
|
||||
// set with the SetPongHandler method. The default pong handler does nothing.
|
||||
// If an application sends ping messages, then the application should set a
|
||||
// pong handler to receive the corresponding pong.
|
||||
//
|
||||
// The application must read the connection to process ping, pong and close
|
||||
// The control message handler functions are called from the NextReader,
|
||||
// ReadMessage and message reader Read methods. The default close and ping
|
||||
// handlers can block these methods for a short time when the handler writes to
|
||||
// the connection.
|
||||
//
|
||||
// The application must read the connection to process close, ping and pong
|
||||
// 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:
|
||||
@ -137,19 +144,12 @@
|
||||
// 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.
|
||||
// the handshake if the Origin request header is present and the Origin host is
|
||||
// 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.
|
||||
// The deprecated package-level Upgrade function does not perform origin
|
||||
// checking. The application is responsible for checking the Origin header
|
||||
// before calling the Upgrade function.
|
||||
//
|
||||
// Compression EXPERIMENTAL
|
||||
//
|
||||
|
11
vendor/github.com/gorilla/websocket/json.go
generated
vendored
11
vendor/github.com/gorilla/websocket/json.go
generated
vendored
@ -9,12 +9,14 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// WriteJSON is deprecated, use c.WriteJSON instead.
|
||||
// WriteJSON writes the JSON encoding of v as a message.
|
||||
//
|
||||
// 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.
|
||||
// WriteJSON writes the JSON encoding of v as a message.
|
||||
//
|
||||
// See the documentation for encoding/json Marshal for details about the
|
||||
// conversion of Go values to JSON.
|
||||
@ -31,7 +33,10 @@ func (c *Conn) WriteJSON(v interface{}) error {
|
||||
return err2
|
||||
}
|
||||
|
||||
// ReadJSON is deprecated, use c.ReadJSON instead.
|
||||
// ReadJSON reads the next JSON-encoded message from the connection and stores
|
||||
// it in the value pointed to by v.
|
||||
//
|
||||
// Deprecated: Use c.ReadJSON instead.
|
||||
func ReadJSON(c *Conn, v interface{}) error {
|
||||
return c.ReadJSON(v)
|
||||
}
|
||||
|
1
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
1
vendor/github.com/gorilla/websocket/mask.go
generated
vendored
@ -11,7 +11,6 @@ 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 {
|
||||
|
77
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
Normal file
77
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
// 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 (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type netDialerFunc func(network, addr string) (net.Conn, error)
|
||||
|
||||
func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
|
||||
return fn(network, addr)
|
||||
}
|
||||
|
||||
func init() {
|
||||
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
||||
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type httpProxyDialer struct {
|
||||
proxyURL *url.URL
|
||||
fowardDial func(network, addr string) (net.Conn, error)
|
||||
}
|
||||
|
||||
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
|
||||
hostPort, _ := hostPortNoPort(hpd.proxyURL)
|
||||
conn, err := hpd.fowardDial(network, hostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connectHeader := make(http.Header)
|
||||
if user := hpd.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: addr},
|
||||
Host: addr,
|
||||
Header: connectHeader,
|
||||
}
|
||||
|
||||
if err := connectReq.Write(conn); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Read response. It's OK to use and discard buffered reader here becaue
|
||||
// the remote server does not speak until spoken to.
|
||||
br := bufio.NewReader(conn)
|
||||
resp, err := http.ReadResponse(br, connectReq)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
conn.Close()
|
||||
f := strings.SplitN(resp.Status, " ", 2)
|
||||
return nil, errors.New(f[1])
|
||||
}
|
||||
return conn, nil
|
||||
}
|
55
vendor/github.com/gorilla/websocket/server.go
generated
vendored
55
vendor/github.com/gorilla/websocket/server.go
generated
vendored
@ -34,9 +34,11 @@ type Upgrader struct {
|
||||
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
|
||||
// preference. If this field is not nil, then the Upgrade method negotiates a
|
||||
// subprotocol by selecting the first match in this list with a protocol
|
||||
// requested by the client.
|
||||
// requested by the client. If there's no match, then no protocol is
|
||||
// negotiated (the Sec-Websocket-Protocol header is not included in the
|
||||
// handshake response).
|
||||
Subprotocols []string
|
||||
|
||||
// Error specifies the function for generating HTTP error responses. If Error
|
||||
@ -44,8 +46,12 @@ type Upgrader struct {
|
||||
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 is nil, then a safe default is used: return false if the
|
||||
// Origin request header is present and the origin host is not equal to
|
||||
// request Host header.
|
||||
//
|
||||
// A CheckOrigin function should carefully validate the request origin to
|
||||
// prevent cross-site request forgery.
|
||||
CheckOrigin func(r *http.Request) bool
|
||||
|
||||
// EnableCompression specify if the server should attempt to negotiate per
|
||||
@ -76,7 +82,7 @@ func checkSameOrigin(r *http.Request) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return u.Host == r.Host
|
||||
return equalASCIIFold(u.Host, r.Host)
|
||||
}
|
||||
|
||||
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string {
|
||||
@ -99,42 +105,44 @@ func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header
|
||||
//
|
||||
// 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).
|
||||
// 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")
|
||||
}
|
||||
const badHandshake = "websocket: the client is not using the websocket protocol: "
|
||||
|
||||
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")
|
||||
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'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")
|
||||
return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header")
|
||||
}
|
||||
|
||||
if r.Method != "GET" {
|
||||
return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok {
|
||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported")
|
||||
}
|
||||
|
||||
checkOrigin := u.CheckOrigin
|
||||
if checkOrigin == nil {
|
||||
checkOrigin = checkSameOrigin
|
||||
}
|
||||
if !checkOrigin(r) {
|
||||
return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed")
|
||||
return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin")
|
||||
}
|
||||
|
||||
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||
if challengeKey == "" {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank")
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank")
|
||||
}
|
||||
|
||||
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||
@ -184,12 +192,12 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
p = append(p, computeAcceptKey(challengeKey)...)
|
||||
p = append(p, "\r\n"...)
|
||||
if c.subprotocol != "" {
|
||||
p = append(p, "Sec-Websocket-Protocol: "...)
|
||||
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"...)
|
||||
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" {
|
||||
@ -230,13 +238,14 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
|
||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
|
||||
//
|
||||
// This function is deprecated, use websocket.Upgrader instead.
|
||||
// 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:
|
||||
// Upgrade does not perform origin checking. The application is responsible for
|
||||
// checking the Origin header before calling Upgrade. An example implementation
|
||||
// of the same origin policy check is:
|
||||
//
|
||||
// if req.Header.Get("Origin") != "http://"+req.Host {
|
||||
// http.Error(w, "Origin not allowed", 403)
|
||||
// http.Error(w, "Origin not allowed", http.StatusForbidden)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
|
33
vendor/github.com/gorilla/websocket/util.go
generated
vendored
33
vendor/github.com/gorilla/websocket/util.go
generated
vendored
@ -11,6 +11,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
||||
@ -111,14 +112,14 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||
case escape:
|
||||
escape = false
|
||||
p[j] = b
|
||||
j += 1
|
||||
j++
|
||||
case b == '\\':
|
||||
escape = true
|
||||
case b == '"':
|
||||
return string(p[:j]), s[i+1:]
|
||||
default:
|
||||
p[j] = b
|
||||
j += 1
|
||||
j++
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
@ -127,8 +128,31 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// equalASCIIFold returns true if s is equal to t with ASCII case folding.
|
||||
func equalASCIIFold(s, t string) bool {
|
||||
for s != "" && t != "" {
|
||||
sr, size := utf8.DecodeRuneInString(s)
|
||||
s = s[size:]
|
||||
tr, size := utf8.DecodeRuneInString(t)
|
||||
t = t[size:]
|
||||
if sr == tr {
|
||||
continue
|
||||
}
|
||||
if 'A' <= sr && sr <= 'Z' {
|
||||
sr = sr + 'a' - 'A'
|
||||
}
|
||||
if 'A' <= tr && tr <= 'Z' {
|
||||
tr = tr + 'a' - 'A'
|
||||
}
|
||||
if sr != tr {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return s == t
|
||||
}
|
||||
|
||||
// tokenListContainsValue returns true if the 1#token header with the given
|
||||
// name contains token.
|
||||
// name contains a token equal to value with ASCII case folding.
|
||||
func tokenListContainsValue(header http.Header, name string, value string) bool {
|
||||
headers:
|
||||
for _, s := range header[name] {
|
||||
@ -142,7 +166,7 @@ headers:
|
||||
if s != "" && s[0] != ',' {
|
||||
continue headers
|
||||
}
|
||||
if strings.EqualFold(t, value) {
|
||||
if equalASCIIFold(t, value) {
|
||||
return true
|
||||
}
|
||||
if s == "" {
|
||||
@ -156,7 +180,6 @@ headers:
|
||||
|
||||
// parseExtensiosn parses WebSocket extensions from a header.
|
||||
func parseExtensions(header http.Header) []map[string]string {
|
||||
|
||||
// From RFC 6455:
|
||||
//
|
||||
// Sec-WebSocket-Extensions = extension-list
|
||||
|
473
vendor/github.com/gorilla/websocket/x_net_proxy.go
generated
vendored
Normal file
473
vendor/github.com/gorilla/websocket/x_net_proxy.go
generated
vendored
Normal file
@ -0,0 +1,473 @@
|
||||
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
|
||||
//go:generate bundle -o x_net_proxy.go golang.org/x/net/proxy
|
||||
|
||||
// Package proxy provides support for a variety of protocols to proxy network
|
||||
// data.
|
||||
//
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type proxy_direct struct{}
|
||||
|
||||
// Direct is a direct proxy: one that makes network connections directly.
|
||||
var proxy_Direct = proxy_direct{}
|
||||
|
||||
func (proxy_direct) Dial(network, addr string) (net.Conn, error) {
|
||||
return net.Dial(network, addr)
|
||||
}
|
||||
|
||||
// A PerHost directs connections to a default Dialer unless the host name
|
||||
// requested matches one of a number of exceptions.
|
||||
type proxy_PerHost struct {
|
||||
def, bypass proxy_Dialer
|
||||
|
||||
bypassNetworks []*net.IPNet
|
||||
bypassIPs []net.IP
|
||||
bypassZones []string
|
||||
bypassHosts []string
|
||||
}
|
||||
|
||||
// NewPerHost returns a PerHost Dialer that directs connections to either
|
||||
// defaultDialer or bypass, depending on whether the connection matches one of
|
||||
// the configured rules.
|
||||
func proxy_NewPerHost(defaultDialer, bypass proxy_Dialer) *proxy_PerHost {
|
||||
return &proxy_PerHost{
|
||||
def: defaultDialer,
|
||||
bypass: bypass,
|
||||
}
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the given network through either
|
||||
// defaultDialer or bypass.
|
||||
func (p *proxy_PerHost) Dial(network, addr string) (c net.Conn, err error) {
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.dialerForRequest(host).Dial(network, addr)
|
||||
}
|
||||
|
||||
func (p *proxy_PerHost) dialerForRequest(host string) proxy_Dialer {
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
for _, net := range p.bypassNetworks {
|
||||
if net.Contains(ip) {
|
||||
return p.bypass
|
||||
}
|
||||
}
|
||||
for _, bypassIP := range p.bypassIPs {
|
||||
if bypassIP.Equal(ip) {
|
||||
return p.bypass
|
||||
}
|
||||
}
|
||||
return p.def
|
||||
}
|
||||
|
||||
for _, zone := range p.bypassZones {
|
||||
if strings.HasSuffix(host, zone) {
|
||||
return p.bypass
|
||||
}
|
||||
if host == zone[1:] {
|
||||
// For a zone ".example.com", we match "example.com"
|
||||
// too.
|
||||
return p.bypass
|
||||
}
|
||||
}
|
||||
for _, bypassHost := range p.bypassHosts {
|
||||
if bypassHost == host {
|
||||
return p.bypass
|
||||
}
|
||||
}
|
||||
return p.def
|
||||
}
|
||||
|
||||
// AddFromString parses a string that contains comma-separated values
|
||||
// specifying hosts that should use the bypass proxy. Each value is either an
|
||||
// IP address, a CIDR range, a zone (*.example.com) or a host name
|
||||
// (localhost). A best effort is made to parse the string and errors are
|
||||
// ignored.
|
||||
func (p *proxy_PerHost) AddFromString(s string) {
|
||||
hosts := strings.Split(s, ",")
|
||||
for _, host := range hosts {
|
||||
host = strings.TrimSpace(host)
|
||||
if len(host) == 0 {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(host, "/") {
|
||||
// We assume that it's a CIDR address like 127.0.0.0/8
|
||||
if _, net, err := net.ParseCIDR(host); err == nil {
|
||||
p.AddNetwork(net)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
p.AddIP(ip)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(host, "*.") {
|
||||
p.AddZone(host[1:])
|
||||
continue
|
||||
}
|
||||
p.AddHost(host)
|
||||
}
|
||||
}
|
||||
|
||||
// AddIP specifies an IP address that will use the bypass proxy. Note that
|
||||
// this will only take effect if a literal IP address is dialed. A connection
|
||||
// to a named host will never match an IP.
|
||||
func (p *proxy_PerHost) AddIP(ip net.IP) {
|
||||
p.bypassIPs = append(p.bypassIPs, ip)
|
||||
}
|
||||
|
||||
// AddNetwork specifies an IP range that will use the bypass proxy. Note that
|
||||
// this will only take effect if a literal IP address is dialed. A connection
|
||||
// to a named host will never match.
|
||||
func (p *proxy_PerHost) AddNetwork(net *net.IPNet) {
|
||||
p.bypassNetworks = append(p.bypassNetworks, net)
|
||||
}
|
||||
|
||||
// AddZone specifies a DNS suffix that will use the bypass proxy. A zone of
|
||||
// "example.com" matches "example.com" and all of its subdomains.
|
||||
func (p *proxy_PerHost) AddZone(zone string) {
|
||||
if strings.HasSuffix(zone, ".") {
|
||||
zone = zone[:len(zone)-1]
|
||||
}
|
||||
if !strings.HasPrefix(zone, ".") {
|
||||
zone = "." + zone
|
||||
}
|
||||
p.bypassZones = append(p.bypassZones, zone)
|
||||
}
|
||||
|
||||
// AddHost specifies a host name that will use the bypass proxy.
|
||||
func (p *proxy_PerHost) AddHost(host string) {
|
||||
if strings.HasSuffix(host, ".") {
|
||||
host = host[:len(host)-1]
|
||||
}
|
||||
p.bypassHosts = append(p.bypassHosts, host)
|
||||
}
|
||||
|
||||
// A Dialer is a means to establish a connection.
|
||||
type proxy_Dialer interface {
|
||||
// Dial connects to the given address via the proxy.
|
||||
Dial(network, addr string) (c net.Conn, err error)
|
||||
}
|
||||
|
||||
// Auth contains authentication parameters that specific Dialers may require.
|
||||
type proxy_Auth struct {
|
||||
User, Password string
|
||||
}
|
||||
|
||||
// FromEnvironment returns the dialer specified by the proxy related variables in
|
||||
// the environment.
|
||||
func proxy_FromEnvironment() proxy_Dialer {
|
||||
allProxy := proxy_allProxyEnv.Get()
|
||||
if len(allProxy) == 0 {
|
||||
return proxy_Direct
|
||||
}
|
||||
|
||||
proxyURL, err := url.Parse(allProxy)
|
||||
if err != nil {
|
||||
return proxy_Direct
|
||||
}
|
||||
proxy, err := proxy_FromURL(proxyURL, proxy_Direct)
|
||||
if err != nil {
|
||||
return proxy_Direct
|
||||
}
|
||||
|
||||
noProxy := proxy_noProxyEnv.Get()
|
||||
if len(noProxy) == 0 {
|
||||
return proxy
|
||||
}
|
||||
|
||||
perHost := proxy_NewPerHost(proxy, proxy_Direct)
|
||||
perHost.AddFromString(noProxy)
|
||||
return perHost
|
||||
}
|
||||
|
||||
// proxySchemes is a map from URL schemes to a function that creates a Dialer
|
||||
// from a URL with such a scheme.
|
||||
var proxy_proxySchemes map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error)
|
||||
|
||||
// RegisterDialerType takes a URL scheme and a function to generate Dialers from
|
||||
// a URL with that scheme and a forwarding Dialer. Registered schemes are used
|
||||
// by FromURL.
|
||||
func proxy_RegisterDialerType(scheme string, f func(*url.URL, proxy_Dialer) (proxy_Dialer, error)) {
|
||||
if proxy_proxySchemes == nil {
|
||||
proxy_proxySchemes = make(map[string]func(*url.URL, proxy_Dialer) (proxy_Dialer, error))
|
||||
}
|
||||
proxy_proxySchemes[scheme] = f
|
||||
}
|
||||
|
||||
// FromURL returns a Dialer given a URL specification and an underlying
|
||||
// Dialer for it to make network requests.
|
||||
func proxy_FromURL(u *url.URL, forward proxy_Dialer) (proxy_Dialer, error) {
|
||||
var auth *proxy_Auth
|
||||
if u.User != nil {
|
||||
auth = new(proxy_Auth)
|
||||
auth.User = u.User.Username()
|
||||
if p, ok := u.User.Password(); ok {
|
||||
auth.Password = p
|
||||
}
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "socks5":
|
||||
return proxy_SOCKS5("tcp", u.Host, auth, forward)
|
||||
}
|
||||
|
||||
// If the scheme doesn't match any of the built-in schemes, see if it
|
||||
// was registered by another package.
|
||||
if proxy_proxySchemes != nil {
|
||||
if f, ok := proxy_proxySchemes[u.Scheme]; ok {
|
||||
return f(u, forward)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("proxy: unknown scheme: " + u.Scheme)
|
||||
}
|
||||
|
||||
var (
|
||||
proxy_allProxyEnv = &proxy_envOnce{
|
||||
names: []string{"ALL_PROXY", "all_proxy"},
|
||||
}
|
||||
proxy_noProxyEnv = &proxy_envOnce{
|
||||
names: []string{"NO_PROXY", "no_proxy"},
|
||||
}
|
||||
)
|
||||
|
||||
// envOnce looks up an environment variable (optionally by multiple
|
||||
// names) once. It mitigates expensive lookups on some platforms
|
||||
// (e.g. Windows).
|
||||
// (Borrowed from net/http/transport.go)
|
||||
type proxy_envOnce struct {
|
||||
names []string
|
||||
once sync.Once
|
||||
val string
|
||||
}
|
||||
|
||||
func (e *proxy_envOnce) Get() string {
|
||||
e.once.Do(e.init)
|
||||
return e.val
|
||||
}
|
||||
|
||||
func (e *proxy_envOnce) init() {
|
||||
for _, n := range e.names {
|
||||
e.val = os.Getenv(n)
|
||||
if e.val != "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
|
||||
// with an optional username and password. See RFC 1928 and RFC 1929.
|
||||
func proxy_SOCKS5(network, addr string, auth *proxy_Auth, forward proxy_Dialer) (proxy_Dialer, error) {
|
||||
s := &proxy_socks5{
|
||||
network: network,
|
||||
addr: addr,
|
||||
forward: forward,
|
||||
}
|
||||
if auth != nil {
|
||||
s.user = auth.User
|
||||
s.password = auth.Password
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type proxy_socks5 struct {
|
||||
user, password string
|
||||
network, addr string
|
||||
forward proxy_Dialer
|
||||
}
|
||||
|
||||
const proxy_socks5Version = 5
|
||||
|
||||
const (
|
||||
proxy_socks5AuthNone = 0
|
||||
proxy_socks5AuthPassword = 2
|
||||
)
|
||||
|
||||
const proxy_socks5Connect = 1
|
||||
|
||||
const (
|
||||
proxy_socks5IP4 = 1
|
||||
proxy_socks5Domain = 3
|
||||
proxy_socks5IP6 = 4
|
||||
)
|
||||
|
||||
var proxy_socks5Errors = []string{
|
||||
"",
|
||||
"general failure",
|
||||
"connection forbidden",
|
||||
"network unreachable",
|
||||
"host unreachable",
|
||||
"connection refused",
|
||||
"TTL expired",
|
||||
"command not supported",
|
||||
"address type not supported",
|
||||
}
|
||||
|
||||
// Dial connects to the address addr on the given network via the SOCKS5 proxy.
|
||||
func (s *proxy_socks5) Dial(network, addr string) (net.Conn, error) {
|
||||
switch network {
|
||||
case "tcp", "tcp6", "tcp4":
|
||||
default:
|
||||
return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network)
|
||||
}
|
||||
|
||||
conn, err := s.forward.Dial(s.network, s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.connect(conn, addr); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// connect takes an existing connection to a socks5 proxy server,
|
||||
// and commands the server to extend that connection to target,
|
||||
// which must be a canonical address with a host and port.
|
||||
func (s *proxy_socks5) connect(conn net.Conn, target string) error {
|
||||
host, portStr, err := net.SplitHostPort(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to parse port number: " + portStr)
|
||||
}
|
||||
if port < 1 || port > 0xffff {
|
||||
return errors.New("proxy: port number out of range: " + portStr)
|
||||
}
|
||||
|
||||
// the size here is just an estimate
|
||||
buf := make([]byte, 0, 6+len(host))
|
||||
|
||||
buf = append(buf, proxy_socks5Version)
|
||||
if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 {
|
||||
buf = append(buf, 2 /* num auth methods */, proxy_socks5AuthNone, proxy_socks5AuthPassword)
|
||||
} else {
|
||||
buf = append(buf, 1 /* num auth methods */, proxy_socks5AuthNone)
|
||||
}
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
if buf[0] != 5 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0])))
|
||||
}
|
||||
if buf[1] == 0xff {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
|
||||
}
|
||||
|
||||
// See RFC 1929
|
||||
if buf[1] == proxy_socks5AuthPassword {
|
||||
buf = buf[:0]
|
||||
buf = append(buf, 1 /* password protocol version */)
|
||||
buf = append(buf, uint8(len(s.user)))
|
||||
buf = append(buf, s.user...)
|
||||
buf = append(buf, uint8(len(s.password)))
|
||||
buf = append(buf, s.password...)
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if buf[1] != 0 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password")
|
||||
}
|
||||
}
|
||||
|
||||
buf = buf[:0]
|
||||
buf = append(buf, proxy_socks5Version, proxy_socks5Connect, 0 /* reserved */)
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
buf = append(buf, proxy_socks5IP4)
|
||||
ip = ip4
|
||||
} else {
|
||||
buf = append(buf, proxy_socks5IP6)
|
||||
}
|
||||
buf = append(buf, ip...)
|
||||
} else {
|
||||
if len(host) > 255 {
|
||||
return errors.New("proxy: destination host name too long: " + host)
|
||||
}
|
||||
buf = append(buf, proxy_socks5Domain)
|
||||
buf = append(buf, byte(len(host)))
|
||||
buf = append(buf, host...)
|
||||
}
|
||||
buf = append(buf, byte(port>>8), byte(port))
|
||||
|
||||
if _, err := conn.Write(buf); err != nil {
|
||||
return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(conn, buf[:4]); err != nil {
|
||||
return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
failure := "unknown error"
|
||||
if int(buf[1]) < len(proxy_socks5Errors) {
|
||||
failure = proxy_socks5Errors[buf[1]]
|
||||
}
|
||||
|
||||
if len(failure) > 0 {
|
||||
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure)
|
||||
}
|
||||
|
||||
bytesToDiscard := 0
|
||||
switch buf[3] {
|
||||
case proxy_socks5IP4:
|
||||
bytesToDiscard = net.IPv4len
|
||||
case proxy_socks5IP6:
|
||||
bytesToDiscard = net.IPv6len
|
||||
case proxy_socks5Domain:
|
||||
_, err := io.ReadFull(conn, buf[:1])
|
||||
if err != nil {
|
||||
return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
bytesToDiscard = int(buf[0])
|
||||
default:
|
||||
return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr)
|
||||
}
|
||||
|
||||
if cap(buf) < bytesToDiscard {
|
||||
buf = make([]byte, bytesToDiscard)
|
||||
} else {
|
||||
buf = buf[:bytesToDiscard]
|
||||
}
|
||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
||||
return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
// Also need to discard the port number
|
||||
if _, err := io.ReadFull(conn, buf[:2]); err != nil {
|
||||
return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
109
vendor/github.com/maruel/panicparse/stack/bucket.go
generated
vendored
Normal file
109
vendor/github.com/maruel/panicparse/stack/bucket.go
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright 2015 Marc-Antoine Ruel. All rights reserved.
|
||||
// Use of this source code is governed under the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package stack
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Similarity is the level at which two call lines arguments must match to be
|
||||
// considered similar enough to coalesce them.
|
||||
type Similarity int
|
||||
|
||||
const (
|
||||
// ExactFlags requires same bits (e.g. Locked).
|
||||
ExactFlags Similarity = iota
|
||||
// ExactLines requests the exact same arguments on the call line.
|
||||
ExactLines
|
||||
// AnyPointer considers different pointers a similar call line.
|
||||
AnyPointer
|
||||
// AnyValue accepts any value as similar call line.
|
||||
AnyValue
|
||||
)
|
||||
|
||||
// Bucketize returns the number of similar goroutines.
|
||||
func Bucketize(goroutines []Goroutine, similar Similarity) map[*Signature][]Goroutine {
|
||||
out := map[*Signature][]Goroutine{}
|
||||
// O(n²). Fix eventually.
|
||||
for _, routine := range goroutines {
|
||||
found := false
|
||||
for key := range out {
|
||||
// When a match is found, this effectively drops the other goroutine ID.
|
||||
if key.Similar(&routine.Signature, similar) {
|
||||
found = true
|
||||
if !key.Equal(&routine.Signature) {
|
||||
// Almost but not quite equal. There's different pointers passed
|
||||
// around but the same values. Zap out the different values.
|
||||
newKey := key.Merge(&routine.Signature)
|
||||
out[newKey] = append(out[key], routine)
|
||||
delete(out, key)
|
||||
} else {
|
||||
out[key] = append(out[key], routine)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
key := &Signature{}
|
||||
*key = routine.Signature
|
||||
out[key] = []Goroutine{routine}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Bucket is a stack trace signature and the list of goroutines that fits this
|
||||
// signature.
|
||||
type Bucket struct {
|
||||
Signature
|
||||
Routines []Goroutine
|
||||
}
|
||||
|
||||
// First returns true if it contains the first goroutine, e.g. the ones that
|
||||
// likely generated the panic() call, if any.
|
||||
func (b *Bucket) First() bool {
|
||||
for _, r := range b.Routines {
|
||||
if r.First {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Less does reverse sort.
|
||||
func (b *Bucket) Less(r *Bucket) bool {
|
||||
if b.First() {
|
||||
return true
|
||||
}
|
||||
if r.First() {
|
||||
return false
|
||||
}
|
||||
return b.Signature.Less(&r.Signature)
|
||||
}
|
||||
|
||||
// Buckets is a list of Bucket sorted by repeation count.
|
||||
type Buckets []Bucket
|
||||
|
||||
func (b Buckets) Len() int {
|
||||
return len(b)
|
||||
}
|
||||
|
||||
func (b Buckets) Less(i, j int) bool {
|
||||
return b[i].Less(&b[j])
|
||||
}
|
||||
|
||||
func (b Buckets) Swap(i, j int) {
|
||||
b[j], b[i] = b[i], b[j]
|
||||
}
|
||||
|
||||
// SortBuckets creates a list of Bucket from each goroutine stack trace count.
|
||||
func SortBuckets(buckets map[*Signature][]Goroutine) Buckets {
|
||||
out := make(Buckets, 0, len(buckets))
|
||||
for signature, count := range buckets {
|
||||
out = append(out, Bucket{*signature, count})
|
||||
}
|
||||
sort.Sort(out)
|
||||
return out
|
||||
}
|
41
vendor/github.com/maruel/panicparse/stack/source.go
generated
vendored
41
vendor/github.com/maruel/panicparse/stack/source.go
generated
vendored
@ -49,11 +49,11 @@ func (c *cache) augmentGoroutine(goroutine *Goroutine) {
|
||||
// For each call site, look at the next call and populate it. Then we can
|
||||
// walk back and reformat things.
|
||||
for i := range goroutine.Stack.Calls {
|
||||
c.load(goroutine.Stack.Calls[i].SourcePath)
|
||||
c.load(goroutine.Stack.Calls[i].LocalSourcePath())
|
||||
}
|
||||
|
||||
// Once all loaded, we can look at the next call when available.
|
||||
for i := 1; i < len(goroutine.Stack.Calls); i++ {
|
||||
for i := 0; i < len(goroutine.Stack.Calls)-1; i++ {
|
||||
// Get the AST from the previous call and process the call line with it.
|
||||
if f := c.getFuncAST(&goroutine.Stack.Calls[i]); f != nil {
|
||||
processCall(&goroutine.Stack.Calls[i], f)
|
||||
@ -101,7 +101,7 @@ func (c *cache) load(fileName string) {
|
||||
}
|
||||
|
||||
func (c *cache) getFuncAST(call *Call) *ast.FuncDecl {
|
||||
if p := c.parsed[call.SourcePath]; p != nil {
|
||||
if p := c.parsed[call.LocalSourcePath()]; p != nil {
|
||||
return p.getFuncAST(call.Func.Name(), call.Line)
|
||||
}
|
||||
return nil
|
||||
@ -115,6 +115,15 @@ type parsedFile struct {
|
||||
// getFuncAST gets the callee site function AST representation for the code
|
||||
// inside the function f at line l.
|
||||
func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) {
|
||||
if len(p.lineToByteOffset) <= l {
|
||||
// The line number in the stack trace line does not exist in the file. That
|
||||
// can only mean that the sources on disk do not match the sources used to
|
||||
// build the binary.
|
||||
// TODO(maruel): This should be surfaced, so that source parsing is
|
||||
// completely ignored.
|
||||
return
|
||||
}
|
||||
|
||||
// Walk the AST to find the lineToByteOffset that fits the line number.
|
||||
var lastFunc *ast.FuncDecl
|
||||
var found ast.Node
|
||||
@ -155,20 +164,18 @@ func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) {
|
||||
}
|
||||
|
||||
func name(n ast.Node) string {
|
||||
if _, ok := n.(*ast.InterfaceType); ok {
|
||||
switch t := n.(type) {
|
||||
case *ast.InterfaceType:
|
||||
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.
|
||||
@ -189,6 +196,10 @@ func fieldToType(f *ast.Field) (string, bool) {
|
||||
return arg.Sel.Name, false
|
||||
case *ast.StarExpr:
|
||||
return "*" + name(arg.X), false
|
||||
case *ast.MapType:
|
||||
return fmt.Sprintf("map[%s]%s", name(arg.Key), name(arg.Value)), false
|
||||
case *ast.ChanType:
|
||||
return fmt.Sprintf("chan %s", name(arg.Value)), false
|
||||
default:
|
||||
// TODO(maruel): Implement anything missing.
|
||||
return "<unknown>", false
|
||||
|
318
vendor/github.com/maruel/panicparse/stack/stack.go
generated
vendored
318
vendor/github.com/maruel/panicparse/stack/stack.go
generated
vendored
@ -5,7 +5,7 @@
|
||||
// Package stack analyzes stack dump of Go processes and simplifies it.
|
||||
//
|
||||
// It is mostly useful on servers will large number of identical goroutines,
|
||||
// making the crash dump harder to read than strictly necesary.
|
||||
// making the crash dump harder to read than strictly necessary.
|
||||
package stack
|
||||
|
||||
import (
|
||||
@ -14,9 +14,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
@ -35,7 +37,7 @@ var (
|
||||
// - found next stack barrier at 0x123; expected
|
||||
// - runtime: unexpected return pc for FUNC_NAME called from 0x123
|
||||
|
||||
reRoutineHeader = regexp.MustCompile("^goroutine (\\d+) \\[([^\\]]+)\\]\\:\n$")
|
||||
reRoutineHeader = regexp.MustCompile("^goroutine (\\d+) \\[([^\\]]+)\\]\\:\r?\n$")
|
||||
reMinutes = regexp.MustCompile("^(\\d+) minutes$")
|
||||
reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable")
|
||||
// See gentraceback() in src/runtime/traceback.go for more information.
|
||||
@ -54,34 +56,30 @@ var (
|
||||
// when a signal is not correctly handled. It is printed with m.throwing>0.
|
||||
// These are discarded.
|
||||
// - For cgo, the source file may be "??".
|
||||
reFile = regexp.MustCompile("^(?:\t| +)(\\?\\?|\\<autogenerated\\>|.+\\.(?:c|go|s))\\:(\\d+)(?:| \\+0x[0-9a-f]+)(?:| fp=0x[0-9a-f]+ sp=0x[0-9a-f]+)\n$")
|
||||
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$")
|
||||
// Sadly, it doesn't note the goroutine number so we could cascade them per
|
||||
// parenthood.
|
||||
reCreated = regexp.MustCompile("^created by (.+)\n$")
|
||||
reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\n$")
|
||||
reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\n$")
|
||||
// Include frequent GOROOT value on Windows, distro provided and user
|
||||
// installed path. This simplifies the user's life when processing a trace
|
||||
// generated on another VM.
|
||||
// TODO(maruel): Guess the path automatically via traces containing the
|
||||
// 'runtime' package, which is very frequent. This would be "less bad" than
|
||||
// throwing up random values at the parser.
|
||||
goroots = []string{runtime.GOROOT(), "c:/go", "/usr/lib/go", "/usr/local/go"}
|
||||
)
|
||||
reCreated = regexp.MustCompile("^created by (.+)\r?\n$")
|
||||
reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\r?\n$")
|
||||
reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\r?\n$")
|
||||
|
||||
// Similarity is the level at which two call lines arguments must match to be
|
||||
// considered similar enough to coalesce them.
|
||||
type Similarity int
|
||||
// TODO(maruel): This is a global state, affected by ParseDump(). This will
|
||||
// be refactored in v2.
|
||||
|
||||
const (
|
||||
// ExactFlags requires same bits (e.g. Locked).
|
||||
ExactFlags Similarity = iota
|
||||
// ExactLines requests the exact same arguments on the call line.
|
||||
ExactLines
|
||||
// AnyPointer considers different pointers a similar call line.
|
||||
AnyPointer
|
||||
// AnyValue accepts any value as similar call line.
|
||||
AnyValue
|
||||
// goroot is the GOROOT as detected in the traceback, not the on the host.
|
||||
//
|
||||
// It can be empty if no root was determined, for example the traceback
|
||||
// contains only non-stdlib source references.
|
||||
goroot string
|
||||
// gopaths is the GOPATH as detected in the traceback, with the value being
|
||||
// the corresponding path mapped to the host.
|
||||
//
|
||||
// It can be empty if only stdlib code is in the traceback or if no local
|
||||
// sources were matched up. In the general case there is only one.
|
||||
gopaths map[string]string
|
||||
// Corresponding local values on the host.
|
||||
localgoroot = runtime.GOROOT()
|
||||
localgopaths = getGOPATHs()
|
||||
)
|
||||
|
||||
// Function is a function call.
|
||||
@ -250,7 +248,7 @@ func (a *Args) Merge(r *Args) Args {
|
||||
|
||||
// Call is an item in the stack trace.
|
||||
type Call struct {
|
||||
SourcePath string // Full path name of the source file
|
||||
SourcePath string // Full path name of the source file as seen in the trace
|
||||
Line int // Line number
|
||||
Func Function // Fully qualified function name (encoded).
|
||||
Args Args // Call arguments
|
||||
@ -287,7 +285,23 @@ func (c *Call) SourceLine() string {
|
||||
return fmt.Sprintf("%s:%d", c.SourceName(), c.Line)
|
||||
}
|
||||
|
||||
// LocalSourcePath is the full path name of the source file as seen in the host.
|
||||
func (c *Call) LocalSourcePath() string {
|
||||
// TODO(maruel): Call needs members goroot and gopaths.
|
||||
if strings.HasPrefix(c.SourcePath, goroot) {
|
||||
return filepath.Join(localgoroot, c.SourcePath[len(goroot):])
|
||||
}
|
||||
for prefix, dest := range gopaths {
|
||||
if strings.HasPrefix(c.SourcePath, prefix) {
|
||||
return filepath.Join(dest, c.SourcePath[len(prefix):])
|
||||
}
|
||||
}
|
||||
return c.SourcePath
|
||||
}
|
||||
|
||||
// FullSourceLine returns "/path/to/source.go:line".
|
||||
//
|
||||
// This file path is mutated to look like the local path.
|
||||
func (c *Call) FullSourceLine() string {
|
||||
return fmt.Sprintf("%s:%d", c.SourcePath, c.Line)
|
||||
}
|
||||
@ -302,13 +316,8 @@ const testMainSource = "_test" + string(os.PathSeparator) + "_testmain.go"
|
||||
// IsStdlib returns true if it is a Go standard library function. This includes
|
||||
// the 'go test' generated main executable.
|
||||
func (c *Call) IsStdlib() bool {
|
||||
for _, goroot := range goroots {
|
||||
if strings.HasPrefix(c.SourcePath, goroot) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// Consider _test/_testmain.go as stdlib since it's injected by "go test".
|
||||
return c.PkgSource() == testMainSource
|
||||
return (goroot != "" && strings.HasPrefix(c.SourcePath, goroot)) || c.PkgSource() == testMainSource
|
||||
}
|
||||
|
||||
// IsPkgMain returns true if it is in the main package.
|
||||
@ -525,91 +534,6 @@ type Goroutine struct {
|
||||
First bool // First is the goroutine first printed, normally the one that crashed.
|
||||
}
|
||||
|
||||
// Bucketize returns the number of similar goroutines.
|
||||
func Bucketize(goroutines []Goroutine, similar Similarity) map[*Signature][]Goroutine {
|
||||
out := map[*Signature][]Goroutine{}
|
||||
// O(n²). Fix eventually.
|
||||
for _, routine := range goroutines {
|
||||
found := false
|
||||
for key := range out {
|
||||
// When a match is found, this effectively drops the other goroutine ID.
|
||||
if key.Similar(&routine.Signature, similar) {
|
||||
found = true
|
||||
if !key.Equal(&routine.Signature) {
|
||||
// Almost but not quite equal. There's different pointers passed
|
||||
// around but the same values. Zap out the different values.
|
||||
newKey := key.Merge(&routine.Signature)
|
||||
out[newKey] = append(out[key], routine)
|
||||
delete(out, key)
|
||||
} else {
|
||||
out[key] = append(out[key], routine)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
key := &Signature{}
|
||||
*key = routine.Signature
|
||||
out[key] = []Goroutine{routine}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Bucket is a stack trace signature and the list of goroutines that fits this
|
||||
// signature.
|
||||
type Bucket struct {
|
||||
Signature
|
||||
Routines []Goroutine
|
||||
}
|
||||
|
||||
// First returns true if it contains the first goroutine, e.g. the ones that
|
||||
// likely generated the panic() call, if any.
|
||||
func (b *Bucket) First() bool {
|
||||
for _, r := range b.Routines {
|
||||
if r.First {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Less does reverse sort.
|
||||
func (b *Bucket) Less(r *Bucket) bool {
|
||||
if b.First() {
|
||||
return true
|
||||
}
|
||||
if r.First() {
|
||||
return false
|
||||
}
|
||||
return b.Signature.Less(&r.Signature)
|
||||
}
|
||||
|
||||
// Buckets is a list of Bucket sorted by repeation count.
|
||||
type Buckets []Bucket
|
||||
|
||||
func (b Buckets) Len() int {
|
||||
return len(b)
|
||||
}
|
||||
|
||||
func (b Buckets) Less(i, j int) bool {
|
||||
return b[i].Less(&b[j])
|
||||
}
|
||||
|
||||
func (b Buckets) Swap(i, j int) {
|
||||
b[j], b[i] = b[i], b[j]
|
||||
}
|
||||
|
||||
// SortBuckets creates a list of Bucket from each goroutine stack trace count.
|
||||
func SortBuckets(buckets map[*Signature][]Goroutine) Buckets {
|
||||
out := make(Buckets, 0, len(buckets))
|
||||
for signature, count := range buckets {
|
||||
out = append(out, Bucket{*signature, count})
|
||||
}
|
||||
sort.Sort(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// scanLines is similar to bufio.ScanLines except that it:
|
||||
// - doesn't drop '\n'
|
||||
// - doesn't strip '\r'
|
||||
@ -656,7 +580,7 @@ func ParseDump(r io.Reader, out io.Writer) ([]Goroutine, error) {
|
||||
firstLine := false
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line == "\n" {
|
||||
if line == "\n" || line == "\r\n" {
|
||||
if goroutine != nil {
|
||||
goroutine = nil
|
||||
continue
|
||||
@ -763,13 +687,30 @@ func ParseDump(r io.Reader, out io.Writer) ([]Goroutine, error) {
|
||||
goroutine = nil
|
||||
}
|
||||
nameArguments(goroutines)
|
||||
// Mutate global state.
|
||||
// TODO(maruel): Make this part of the context instead of a global.
|
||||
if goroot == "" {
|
||||
findRoots(goroutines)
|
||||
}
|
||||
return goroutines, scanner.Err()
|
||||
}
|
||||
|
||||
// NoRebase disables GOROOT and GOPATH guessing in ParseDump().
|
||||
//
|
||||
// BUG: This function will be removed in v2, as ParseDump() will accept a flag
|
||||
// explicitly.
|
||||
func NoRebase() {
|
||||
goroot = runtime.GOROOT()
|
||||
gopaths = map[string]string{}
|
||||
for _, p := range getGOPATHs() {
|
||||
gopaths[p] = p
|
||||
}
|
||||
}
|
||||
|
||||
// Private stuff.
|
||||
|
||||
func nameArguments(goroutines []Goroutine) {
|
||||
// Set a name for any pointer occuring more than once.
|
||||
// Set a name for any pointer occurring more than once.
|
||||
type object struct {
|
||||
args []*Arg
|
||||
inPrimary bool
|
||||
@ -791,7 +732,7 @@ func nameArguments(goroutines []Goroutine) {
|
||||
}
|
||||
// CreatedBy.Args is never set.
|
||||
}
|
||||
order := uint64Slice{}
|
||||
order := make(uint64Slice, 0, len(objects)/2)
|
||||
for k, obj := range objects {
|
||||
if len(obj.args) > 1 && obj.inPrimary {
|
||||
order = append(order, k)
|
||||
@ -807,7 +748,7 @@ func nameArguments(goroutines []Goroutine) {
|
||||
}
|
||||
|
||||
// Now do the rest. This is done so the output is deterministic.
|
||||
order = uint64Slice{}
|
||||
order = make(uint64Slice, 0, len(objects))
|
||||
for k := range objects {
|
||||
order = append(order, k)
|
||||
}
|
||||
@ -825,6 +766,139 @@ func nameArguments(goroutines []Goroutine) {
|
||||
}
|
||||
}
|
||||
|
||||
// hasPathPrefix returns true if any of s is the prefix of p.
|
||||
func hasPathPrefix(p string, s map[string]string) bool {
|
||||
for prefix := range s {
|
||||
if strings.HasPrefix(p, prefix+"/") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getFiles returns all the source files deduped and ordered.
|
||||
func getFiles(goroutines []Goroutine) []string {
|
||||
files := map[string]struct{}{}
|
||||
for _, g := range goroutines {
|
||||
for _, c := range g.Stack.Calls {
|
||||
files[c.SourcePath] = struct{}{}
|
||||
}
|
||||
}
|
||||
out := make([]string, 0, len(files))
|
||||
for f := range files {
|
||||
out = append(out, f)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// splitPath splits a path into its components.
|
||||
//
|
||||
// The first item has its initial path separator kept.
|
||||
func splitPath(p string) []string {
|
||||
if p == "" {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
s := ""
|
||||
for _, c := range p {
|
||||
if c != '/' || (len(out) == 0 && strings.Count(s, "/") == len(s)) {
|
||||
s += string(c)
|
||||
} else if s != "" {
|
||||
out = append(out, s)
|
||||
s = ""
|
||||
}
|
||||
}
|
||||
if s != "" {
|
||||
out = append(out, s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// isFile returns true if the path is a valid file.
|
||||
func isFile(p string) bool {
|
||||
// TODO(maruel): Is it faster to open the file or to stat it? Worth a perf
|
||||
// test on Windows.
|
||||
i, err := os.Stat(p)
|
||||
return err == nil && !i.IsDir()
|
||||
}
|
||||
|
||||
// isRootIn returns a root if the file split in parts is rooted in root.
|
||||
func rootedIn(root string, parts []string) string {
|
||||
//log.Printf("rootIn(%s, %v)", root, parts)
|
||||
for i := 1; i < len(parts); i++ {
|
||||
suffix := filepath.Join(parts[i:]...)
|
||||
if isFile(filepath.Join(root, suffix)) {
|
||||
return filepath.Join(parts[:i]...)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// findRoots sets global variables goroot and gopath.
|
||||
//
|
||||
// TODO(maruel): In v2, it will be a property of the new struct that will
|
||||
// contain the goroutines.
|
||||
func findRoots(goroutines []Goroutine) {
|
||||
gopaths = map[string]string{}
|
||||
for _, f := range getFiles(goroutines) {
|
||||
// TODO(maruel): Could a stack dump have mixed cases? I think it's
|
||||
// possible, need to confirm and handle.
|
||||
//log.Printf(" Analyzing %s", f)
|
||||
if goroot != "" && strings.HasPrefix(f, goroot+"/") {
|
||||
continue
|
||||
}
|
||||
if gopaths != nil && hasPathPrefix(f, gopaths) {
|
||||
continue
|
||||
}
|
||||
parts := splitPath(f)
|
||||
if goroot == "" {
|
||||
if r := rootedIn(localgoroot, parts); r != "" {
|
||||
goroot = r
|
||||
log.Printf("Found GOROOT=%s", goroot)
|
||||
continue
|
||||
}
|
||||
}
|
||||
found := false
|
||||
for _, l := range localgopaths {
|
||||
if r := rootedIn(l, parts); r != "" {
|
||||
log.Printf("Found GOPATH=%s", r)
|
||||
gopaths[r] = l
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// If the source is not found, just too bad.
|
||||
//log.Printf("Failed to find locally: %s / %s", f, goroot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getGOPATHs() []string {
|
||||
var out []string
|
||||
for _, v := range filepath.SplitList(os.Getenv("GOPATH")) {
|
||||
// Disallow non-absolute paths?
|
||||
if v != "" {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
if len(out) == 0 {
|
||||
homeDir := ""
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
homeDir = os.Getenv("HOME")
|
||||
if homeDir == "" {
|
||||
panic(fmt.Sprintf("Could not get current user or $HOME: %s\n", err.Error()))
|
||||
}
|
||||
} else {
|
||||
homeDir = u.HomeDir
|
||||
}
|
||||
out = []string{homeDir + "go"}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type uint64Slice []uint64
|
||||
|
||||
func (a uint64Slice) Len() int { return len(a) }
|
||||
|
14
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
14
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
@ -1,13 +1,24 @@
|
||||
package runewidth
|
||||
|
||||
import "os"
|
||||
|
||||
var (
|
||||
// EastAsianWidth will be set true if the current locale is CJK
|
||||
EastAsianWidth = IsEastAsian()
|
||||
EastAsianWidth bool
|
||||
|
||||
// DefaultCondition is a condition in current locale
|
||||
DefaultCondition = &Condition{EastAsianWidth}
|
||||
)
|
||||
|
||||
func init() {
|
||||
env := os.Getenv("RUNEWIDTH_EASTASIAN")
|
||||
if env == "" {
|
||||
EastAsianWidth = IsEastAsian()
|
||||
} else {
|
||||
EastAsianWidth = env == "1"
|
||||
}
|
||||
}
|
||||
|
||||
type interval struct {
|
||||
first rune
|
||||
last rune
|
||||
@ -55,6 +66,7 @@ var private = table{
|
||||
var nonprint = table{
|
||||
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
|
||||
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
|
||||
{0x2028, 0x2029},
|
||||
{0x202A, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
|
||||
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
|
||||
}
|
||||
|
7
vendor/github.com/nlopes/slack/CHANGELOG.md
generated
vendored
7
vendor/github.com/nlopes/slack/CHANGELOG.md
generated
vendored
@ -1,3 +1,10 @@
|
||||
### v0.3.0 - July 30, 2018
|
||||
full differences can be viewed using `git log --oneline --decorate --color v0.2.0..v0.3.0`
|
||||
- slack events initial support added. (still considered experimental and undergoing changes, stability not promised)
|
||||
- vendored depedencies using dep, ensure using up to date tooling before filing issues.
|
||||
- RTM has improved its ability to identify dead connections and reconnect automatically (worth calling out in case it has unintended side effects).
|
||||
- bug fixes (various timestamp handling, error handling, RTM locking, etc).
|
||||
|
||||
### v0.2.0 - Feb 10, 2018
|
||||
|
||||
Release adds a bunch of functionality and improvements, mainly to give people a recent version to vendor against.
|
||||
|
2
vendor/github.com/nlopes/slack/bots.go
generated
vendored
2
vendor/github.com/nlopes/slack/bots.go
generated
vendored
@ -21,7 +21,7 @@ type botResponseFull struct {
|
||||
|
||||
func botRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*botResponseFull, error) {
|
||||
response := &botResponseFull{}
|
||||
err := post(ctx, client, path, values, response, debug)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
17
vendor/github.com/nlopes/slack/chat.go
generated
vendored
17
vendor/github.com/nlopes/slack/chat.go
generated
vendored
@ -164,7 +164,7 @@ func (api *Client) SendMessageContext(ctx context.Context, channelID string, opt
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
if err = post(ctx, api.httpclient, string(config.mode), config.values, &response, api.debug); err != nil {
|
||||
if err = postSlackMethod(ctx, api.httpclient, string(config.mode), config.values, &response, api.debug); err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
@ -207,6 +207,7 @@ const (
|
||||
chatPostMessage sendMode = "chat.postMessage"
|
||||
chatDelete sendMode = "chat.delete"
|
||||
chatPostEphemeral sendMode = "chat.postEphemeral"
|
||||
chatMeMessage sendMode = "chat.meMessage"
|
||||
)
|
||||
|
||||
type sendConfig struct {
|
||||
@ -247,6 +248,14 @@ func MsgOptionPostEphemeral2(userID string) MsgOption {
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionMeMessage posts a "me message" type from the calling user
|
||||
func MsgOptionMeMessage() MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.mode = chatMeMessage
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionUpdate updates a message based on the timestamp.
|
||||
func MsgOptionUpdate(timestamp string) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
@ -373,7 +382,11 @@ func MsgOptionCompose(options ...MsgOption) MsgOption {
|
||||
func MsgOptionParse(b bool) MsgOption {
|
||||
return func(c *sendConfig) error {
|
||||
var v string
|
||||
if b { v = "1" } else { v = "0" }
|
||||
if b {
|
||||
v = "1"
|
||||
} else {
|
||||
v = "0"
|
||||
}
|
||||
c.values.Set("parse", v)
|
||||
return nil
|
||||
}
|
||||
|
34
vendor/github.com/nlopes/slack/conversation.go
generated
vendored
34
vendor/github.com/nlopes/slack/conversation.go
generated
vendored
@ -29,6 +29,8 @@ type conversation struct {
|
||||
NameNormalized string `json:"name_normalized"`
|
||||
NumMembers int `json:"num_members"`
|
||||
Priority float64 `json:"priority"`
|
||||
User string `json:"user"`
|
||||
|
||||
// TODO support pending_shared
|
||||
// TODO support previous_names
|
||||
}
|
||||
@ -90,7 +92,7 @@ func (api *Client) GetUsersInConversationContext(ctx context.Context, params *Ge
|
||||
ResponseMetaData responseMetaData `json:"response_metadata"`
|
||||
SlackResponse
|
||||
}{}
|
||||
err := post(ctx, api.httpclient, "conversations.members", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.members", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -112,7 +114,7 @@ func (api *Client) ArchiveConversationContext(ctx context.Context, channelID str
|
||||
"channel": {channelID},
|
||||
}
|
||||
response := SlackResponse{}
|
||||
err := post(ctx, api.httpclient, "conversations.archive", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.archive", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -132,7 +134,7 @@ func (api *Client) UnArchiveConversationContext(ctx context.Context, channelID s
|
||||
"channel": {channelID},
|
||||
}
|
||||
response := SlackResponse{}
|
||||
err := post(ctx, api.httpclient, "conversations.unarchive", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.unarchive", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -156,7 +158,7 @@ func (api *Client) SetTopicOfConversationContext(ctx context.Context, channelID,
|
||||
SlackResponse
|
||||
Channel *Channel `json:"channel"`
|
||||
}{}
|
||||
err := post(ctx, api.httpclient, "conversations.setTopic", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.setTopic", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -180,7 +182,7 @@ func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelI
|
||||
SlackResponse
|
||||
Channel *Channel `json:"channel"`
|
||||
}{}
|
||||
err := post(ctx, api.httpclient, "conversations.setPurpose", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.setPurpose", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -204,7 +206,7 @@ func (api *Client) RenameConversationContext(ctx context.Context, channelID, cha
|
||||
SlackResponse
|
||||
Channel *Channel `json:"channel"`
|
||||
}{}
|
||||
err := post(ctx, api.httpclient, "conversations.rename", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.rename", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -228,7 +230,7 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel
|
||||
SlackResponse
|
||||
Channel *Channel `json:"channel"`
|
||||
}{}
|
||||
err := post(ctx, api.httpclient, "conversations.invite", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.invite", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -249,7 +251,7 @@ func (api *Client) KickUserFromConversationContext(ctx context.Context, channelI
|
||||
"user": {user},
|
||||
}
|
||||
response := SlackResponse{}
|
||||
err := post(ctx, api.httpclient, "conversations.kick", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.kick", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -274,7 +276,7 @@ func (api *Client) CloseConversationContext(ctx context.Context, channelID strin
|
||||
AlreadyClosed bool `json:"already_closed"`
|
||||
}{}
|
||||
|
||||
err = post(ctx, api.httpclient, "conversations.close", values, &response, api.debug)
|
||||
err = postSlackMethod(ctx, api.httpclient, "conversations.close", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
@ -337,6 +339,10 @@ func (api *Client) LeaveConversationContext(ctx context.Context, channelID strin
|
||||
}
|
||||
|
||||
response, err := channelRequest(ctx, api.httpclient, "conversations.leave", values, api.debug)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return response.NotInChannel, err
|
||||
}
|
||||
|
||||
@ -388,7 +394,7 @@ func (api *Client) GetConversationRepliesContext(ctx context.Context, params *Ge
|
||||
Messages []Message `json:"messages"`
|
||||
}{}
|
||||
|
||||
err = post(ctx, api.httpclient, "conversations.replies", values, &response, api.debug)
|
||||
err = postSlackMethod(ctx, api.httpclient, "conversations.replies", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, false, "", err
|
||||
}
|
||||
@ -428,7 +434,7 @@ func (api *Client) GetConversationsContext(ctx context.Context, params *GetConve
|
||||
ResponseMetaData responseMetaData `json:"response_metadata"`
|
||||
SlackResponse
|
||||
}{}
|
||||
err = post(ctx, api.httpclient, "conversations.list", values, &response, api.debug)
|
||||
err = postSlackMethod(ctx, api.httpclient, "conversations.list", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -465,7 +471,7 @@ func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConv
|
||||
AlreadyOpen bool `json:"already_open"`
|
||||
SlackResponse
|
||||
}{}
|
||||
err := post(ctx, api.httpclient, "conversations.open", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.open", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, false, false, err
|
||||
}
|
||||
@ -489,7 +495,7 @@ func (api *Client) JoinConversationContext(ctx context.Context, channelID string
|
||||
} `json:"response_metadata"`
|
||||
SlackResponse
|
||||
}{}
|
||||
err := post(ctx, api.httpclient, "conversations.join", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.join", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
@ -551,7 +557,7 @@ func (api *Client) GetConversationHistoryContext(ctx context.Context, params *Ge
|
||||
|
||||
response := GetConversationHistoryResponse{}
|
||||
|
||||
err := post(ctx, api.httpclient, "conversations.history", values, &response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.history", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
108
vendor/github.com/nlopes/slack/dialog.go
generated
vendored
108
vendor/github.com/nlopes/slack/dialog.go
generated
vendored
@ -2,80 +2,80 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type DialogTrigger struct {
|
||||
TriggerId string `json:"trigger_id"` //Required. Must respond within 3 seconds.
|
||||
Dialog Dialog `json:"dialog"` //Required.
|
||||
TriggerId string `json:"trigger_id"` //Required. Must respond within 3 seconds.
|
||||
Dialog Dialog `json:"dialog"` //Required.
|
||||
}
|
||||
|
||||
type Dialog struct {
|
||||
CallbackId string `json:"callback_id"` //Required.
|
||||
Title string `json:"title"` //Required.
|
||||
SubmitLabel string `json:"submit_label,omitempty"` //Optional. Default value is 'Submit'
|
||||
NotifyOnCancel bool `json:"notify_on_cancel,omitempty"` //Optional. Default value is false
|
||||
Elements []DialogElement `json:"elements"` //Required.
|
||||
CallbackId string `json:"callback_id"` //Required.
|
||||
Title string `json:"title"` //Required.
|
||||
SubmitLabel string `json:"submit_label,omitempty"` //Optional. Default value is 'Submit'
|
||||
NotifyOnCancel bool `json:"notify_on_cancel,omitempty"` //Optional. Default value is false
|
||||
Elements []DialogElement `json:"elements"` //Required.
|
||||
}
|
||||
|
||||
type DialogElement interface {}
|
||||
type DialogElement interface{}
|
||||
|
||||
type DialogTextElement struct {
|
||||
Label string `json:"label"` //Required.
|
||||
Name string `json:"name"` //Required.
|
||||
Type string `json:"type"` //Required. Allowed values: "text", "textarea", "select".
|
||||
Placeholder string `json:"placeholder,omitempty"` //Optional.
|
||||
Optional bool `json:"optional,omitempty"` //Optional. Default value is false
|
||||
Value string `json:"value,omitempty"` //Optional.
|
||||
MaxLength int `json:"max_length,omitempty"` //Optional.
|
||||
MinLength int `json:"min_length,omitempty"` //Optional,. Default value is 0
|
||||
Hint string `json:"hint,omitempty"` //Optional.
|
||||
Subtype string `json:"subtype,omitempty"` //Optional. Allowed values: "email", "number", "tel", "url".
|
||||
Label string `json:"label"` //Required.
|
||||
Name string `json:"name"` //Required.
|
||||
Type string `json:"type"` //Required. Allowed values: "text", "textarea", "select".
|
||||
Placeholder string `json:"placeholder,omitempty"` //Optional.
|
||||
Optional bool `json:"optional,omitempty"` //Optional. Default value is false
|
||||
Value string `json:"value,omitempty"` //Optional.
|
||||
MaxLength int `json:"max_length,omitempty"` //Optional.
|
||||
MinLength int `json:"min_length,omitempty"` //Optional,. Default value is 0
|
||||
Hint string `json:"hint,omitempty"` //Optional.
|
||||
Subtype string `json:"subtype,omitempty"` //Optional. Allowed values: "email", "number", "tel", "url".
|
||||
}
|
||||
|
||||
type DialogSelectElement struct {
|
||||
Label string `json:"label"` //Required.
|
||||
Name string `json:"name"` //Required.
|
||||
Type string `json:"type"` //Required. Allowed values: "text", "textarea", "select".
|
||||
Placeholder string `json:"placeholder,omitempty"` //Optional.
|
||||
Optional bool `json:"optional,omitempty"` //Optional. Default value is false
|
||||
Value string `json:"value,omitempty"` //Optional.
|
||||
DataSource string `json:"data_source,omitempty"` //Optional. Allowed values: "users", "channels", "conversations", "external".
|
||||
SelectedOptions string `json:"selected_options,omitempty"` //Optional. Default value for "external" only
|
||||
Options []DialogElementOption `json:"options,omitempty"` //One of options or option_groups is required.
|
||||
OptionGroups []DialogElementOption `json:"option_groups,omitempty"` //Provide up to 100 options.
|
||||
Label string `json:"label"` //Required.
|
||||
Name string `json:"name"` //Required.
|
||||
Type string `json:"type"` //Required. Allowed values: "text", "textarea", "select".
|
||||
Placeholder string `json:"placeholder,omitempty"` //Optional.
|
||||
Optional bool `json:"optional,omitempty"` //Optional. Default value is false
|
||||
Value string `json:"value,omitempty"` //Optional.
|
||||
DataSource string `json:"data_source,omitempty"` //Optional. Allowed values: "users", "channels", "conversations", "external".
|
||||
SelectedOptions string `json:"selected_options,omitempty"` //Optional. Default value for "external" only
|
||||
Options []DialogElementOption `json:"options,omitempty"` //One of options or option_groups is required.
|
||||
OptionGroups []DialogElementOption `json:"option_groups,omitempty"` //Provide up to 100 options.
|
||||
}
|
||||
|
||||
type DialogElementOption struct {
|
||||
Label string `json:"label"` //Required.
|
||||
Value string `json:"value"` //Required.
|
||||
Label string `json:"label"` //Required.
|
||||
Value string `json:"value"` //Required.
|
||||
}
|
||||
|
||||
// DialogCallback is sent from Slack when a user submits a form from within a dialog
|
||||
type DialogCallback struct {
|
||||
Type string `json:"type"`
|
||||
CallbackID string `json:"callback_id"`
|
||||
Team Team `json:"team"`
|
||||
Channel Channel `json:"channel"`
|
||||
User User `json:"user"`
|
||||
ActionTs string `json:"action_ts"`
|
||||
Token string `json:"token"`
|
||||
ResponseURL string `json:"response_url"`
|
||||
Submission map[string]string `json:"submission"`
|
||||
Type string `json:"type"`
|
||||
CallbackID string `json:"callback_id"`
|
||||
Team Team `json:"team"`
|
||||
Channel Channel `json:"channel"`
|
||||
User User `json:"user"`
|
||||
ActionTs string `json:"action_ts"`
|
||||
Token string `json:"token"`
|
||||
ResponseURL string `json:"response_url"`
|
||||
Submission map[string]string `json:"submission"`
|
||||
}
|
||||
|
||||
// DialogSuggestionCallback is sent from Slack when a user types in a select field with an external data source
|
||||
type DialogSuggestionCallback struct {
|
||||
Type string `json:"type"`
|
||||
Token string `json:"token"`
|
||||
ActionTs string `json:"action_ts"`
|
||||
Team Team `json:"team"`
|
||||
User User `json:"user"`
|
||||
Channel Channel `json:"channel"`
|
||||
ElementName string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
CallbackID string `json:"callback_id"`
|
||||
Type string `json:"type"`
|
||||
Token string `json:"token"`
|
||||
ActionTs string `json:"action_ts"`
|
||||
Team Team `json:"team"`
|
||||
User User `json:"user"`
|
||||
Channel Channel `json:"channel"`
|
||||
ElementName string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
CallbackID string `json:"callback_id"`
|
||||
}
|
||||
|
||||
// OpenDialog opens a dialog window where the triggerId originated from
|
||||
@ -90,18 +90,18 @@ func (api *Client) OpenDialogContext(ctx context.Context, triggerId string, dial
|
||||
}
|
||||
|
||||
resp := DialogTrigger{
|
||||
TriggerId: triggerId,
|
||||
Dialog: dialog,
|
||||
TriggerId: triggerId,
|
||||
Dialog: dialog,
|
||||
}
|
||||
jsonResp, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
response := &SlackResponse{}
|
||||
endpoint := SLACK_API+"dialog.open"
|
||||
if err := postJson(ctx, api.httpclient, endpoint, api.token, jsonResp, response, api.debug); err != nil {
|
||||
endpoint := SLACK_API + "dialog.open"
|
||||
if err := postJSON(ctx, api.httpclient, endpoint, api.token, jsonResp, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Err()
|
||||
}
|
||||
}
|
||||
|
6
vendor/github.com/nlopes/slack/dnd.go
generated
vendored
6
vendor/github.com/nlopes/slack/dnd.go
generated
vendored
@ -38,7 +38,7 @@ type dndTeamInfoResponse struct {
|
||||
|
||||
func dndRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*dndResponseFull, error) {
|
||||
response := &dndResponseFull{}
|
||||
err := post(ctx, client, path, values, response, debug)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -61,7 +61,7 @@ func (api *Client) EndDNDContext(ctx context.Context) error {
|
||||
|
||||
response := &SlackResponse{}
|
||||
|
||||
if err := post(ctx, api.httpclient, "dnd.endDnd", values, response, api.debug); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "dnd.endDnd", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (m
|
||||
}
|
||||
response := &dndTeamInfoResponse{}
|
||||
|
||||
if err := post(ctx, api.httpclient, "dnd.teamInfo", values, response, api.debug); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "dnd.teamInfo", values, response, api.debug); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !response.Ok {
|
||||
|
2
vendor/github.com/nlopes/slack/emoji.go
generated
vendored
2
vendor/github.com/nlopes/slack/emoji.go
generated
vendored
@ -23,7 +23,7 @@ func (api *Client) GetEmojiContext(ctx context.Context) (map[string]string, erro
|
||||
}
|
||||
response := &emojiResponseFull{}
|
||||
|
||||
err := post(ctx, api.httpclient, "emoji.list", values, response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "emoji.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
2
vendor/github.com/nlopes/slack/im.go
generated
vendored
2
vendor/github.com/nlopes/slack/im.go
generated
vendored
@ -31,7 +31,7 @@ type IM struct {
|
||||
|
||||
func imRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*imResponseFull, error) {
|
||||
response := &imResponseFull{}
|
||||
err := post(ctx, client, path, values, response, debug)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
16
vendor/github.com/nlopes/slack/messages.go
generated
vendored
16
vendor/github.com/nlopes/slack/messages.go
generated
vendored
@ -69,7 +69,7 @@ type Msg struct {
|
||||
ParentUserId string `json:"parent_user_id,omitempty"`
|
||||
|
||||
// file_share, file_comment, file_mention
|
||||
File *File `json:"file,omitempty"`
|
||||
Files []File `json:"files,omitempty"`
|
||||
|
||||
// file_share
|
||||
Upload bool `json:"upload,omitempty"`
|
||||
@ -89,8 +89,8 @@ type Msg struct {
|
||||
|
||||
// slash commands and interactive messages
|
||||
ResponseType string `json:"response_type,omitempty"`
|
||||
ReplaceOriginal bool `json:"replace_original,omitempty"`
|
||||
DeleteOriginal bool `json:"delete_original,omitempty"`
|
||||
ReplaceOriginal bool `json:"replace_original"`
|
||||
DeleteOriginal bool `json:"delete_original"`
|
||||
}
|
||||
|
||||
// Icon is used for bot messages
|
||||
@ -118,14 +118,16 @@ type Event struct {
|
||||
|
||||
// Ping contains information about a Ping Event
|
||||
type Ping struct {
|
||||
ID int `json:"id"`
|
||||
Type string `json:"type"`
|
||||
ID int `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// Pong contains information about a Pong Event
|
||||
type Pong struct {
|
||||
Type string `json:"type"`
|
||||
ReplyTo int `json:"reply_to"`
|
||||
Type string `json:"type"`
|
||||
ReplyTo int `json:"reply_to"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}
|
||||
|
||||
// NewOutgoingMessage prepares an OutgoingMessage that the user can
|
||||
|
17
vendor/github.com/nlopes/slack/misc.go
generated
vendored
17
vendor/github.com/nlopes/slack/misc.go
generated
vendored
@ -171,7 +171,8 @@ func doPost(ctx context.Context, client HTTPRequester, req *http.Request, intf i
|
||||
return parseResponseBody(resp.Body, intf, debug)
|
||||
}
|
||||
|
||||
func postJson(ctx context.Context, client HTTPRequester, endpoint, token string, json []byte, intf interface{}, debug bool) error {
|
||||
// post JSON.
|
||||
func postJSON(ctx context.Context, client HTTPRequester, endpoint, token string, json []byte, intf interface{}, debug bool) error {
|
||||
reqBody := bytes.NewBuffer(json)
|
||||
req, err := http.NewRequest("POST", endpoint, reqBody)
|
||||
if err != nil {
|
||||
@ -182,6 +183,7 @@ func postJson(ctx context.Context, client HTTPRequester, endpoint, token string,
|
||||
return doPost(ctx, client, req, intf, debug)
|
||||
}
|
||||
|
||||
// post a url encoded form.
|
||||
func postForm(ctx context.Context, client HTTPRequester, endpoint string, values url.Values, intf interface{}, debug bool) error {
|
||||
reqBody := strings.NewReader(values.Encode())
|
||||
req, err := http.NewRequest("POST", endpoint, reqBody)
|
||||
@ -192,7 +194,8 @@ func postForm(ctx context.Context, client HTTPRequester, endpoint string, values
|
||||
return doPost(ctx, client, req, intf, debug)
|
||||
}
|
||||
|
||||
func post(ctx context.Context, client HTTPRequester, path string, values url.Values, intf interface{}, debug bool) error {
|
||||
// post to a slack web method.
|
||||
func postSlackMethod(ctx context.Context, client HTTPRequester, path string, values url.Values, intf interface{}, debug bool) error {
|
||||
return postForm(ctx, client, SLACK_API+path, values, intf, debug)
|
||||
}
|
||||
|
||||
@ -214,7 +217,7 @@ func logResponse(resp *http.Response, debug bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func okJsonHandler(rw http.ResponseWriter, r *http.Request) {
|
||||
func okJSONHandler(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
response, _ := json.Marshal(SlackResponse{
|
||||
Ok: true,
|
||||
@ -227,3 +230,11 @@ type errorString string
|
||||
func (t errorString) Error() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
// timerReset safely reset a timer, see time.Timer.Reset for details.
|
||||
func timerReset(t *time.Timer, d time.Duration) {
|
||||
if !t.Stop() {
|
||||
<-t.C
|
||||
}
|
||||
t.Reset(d)
|
||||
}
|
||||
|
2
vendor/github.com/nlopes/slack/oauth.go
generated
vendored
2
vendor/github.com/nlopes/slack/oauth.go
generated
vendored
@ -55,7 +55,7 @@ func GetOAuthResponseContext(ctx context.Context, clientID, clientSecret, code,
|
||||
"redirect_uri": {redirectURI},
|
||||
}
|
||||
response := &OAuthResponse{}
|
||||
err = post(ctx, customHTTPClient, "oauth.access", values, response, debug)
|
||||
err = postSlackMethod(ctx, customHTTPClient, "oauth.access", values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
6
vendor/github.com/nlopes/slack/pins.go
generated
vendored
6
vendor/github.com/nlopes/slack/pins.go
generated
vendored
@ -34,7 +34,7 @@ func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemR
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := post(ctx, api.httpclient, "pins.add", values, response, api.debug); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "pins.add", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ func (api *Client) RemovePinContext(ctx context.Context, channel string, item It
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := post(ctx, api.httpclient, "pins.remove", values, response, api.debug); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "pins.remove", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ func (api *Client) ListPinsContext(ctx context.Context, channel string) ([]Item,
|
||||
}
|
||||
|
||||
response := &listPinsResponseFull{}
|
||||
err := post(ctx, api.httpclient, "pins.list", values, response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "pins.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
8
vendor/github.com/nlopes/slack/reactions.go
generated
vendored
8
vendor/github.com/nlopes/slack/reactions.go
generated
vendored
@ -155,7 +155,7 @@ func (api *Client) AddReactionContext(ctx context.Context, name string, item Ite
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := post(ctx, api.httpclient, "reactions.add", values, response, api.debug); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "reactions.add", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -189,7 +189,7 @@ func (api *Client) RemoveReactionContext(ctx context.Context, name string, item
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := post(ctx, api.httpclient, "reactions.remove", values, response, api.debug); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "reactions.remove", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -223,7 +223,7 @@ func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params
|
||||
}
|
||||
|
||||
response := &getReactionsResponseFull{}
|
||||
if err := post(ctx, api.httpclient, "reactions.get", values, response, api.debug); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "reactions.get", values, response, api.debug); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !response.Ok {
|
||||
@ -256,7 +256,7 @@ func (api *Client) ListReactionsContext(ctx context.Context, params ListReaction
|
||||
}
|
||||
|
||||
response := &listReactionsResponseFull{}
|
||||
err := post(ctx, api.httpclient, "reactions.list", values, response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "reactions.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
26
vendor/github.com/nlopes/slack/rtm.go
generated
vendored
26
vendor/github.com/nlopes/slack/rtm.go
generated
vendored
@ -12,6 +12,15 @@ import (
|
||||
|
||||
const (
|
||||
websocketDefaultTimeout = 10 * time.Second
|
||||
defaultPingInterval = 30 * time.Second
|
||||
)
|
||||
|
||||
const (
|
||||
rtmEventTypeAck = ""
|
||||
rtmEventTypeHello = "hello"
|
||||
rtmEventTypeGoodbye = "goodbye"
|
||||
rtmEventTypePong = "pong"
|
||||
rtmEventTypeDesktopNotification = "desktop_notification"
|
||||
)
|
||||
|
||||
// StartRTM calls the "rtm.start" endpoint and returns the provided URL and the full Info block.
|
||||
@ -29,7 +38,7 @@ func (api *Client) StartRTM() (info *Info, websocketURL string, err error) {
|
||||
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
|
||||
func (api *Client) StartRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) {
|
||||
response := &infoResponseFull{}
|
||||
err = post(ctx, api.httpclient, "rtm.start", url.Values{"token": {api.token}}, response, api.debug)
|
||||
err = postSlackMethod(ctx, api.httpclient, "rtm.start", url.Values{"token": {api.token}}, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -54,7 +63,7 @@ func (api *Client) ConnectRTM() (info *Info, websocketURL string, err error) {
|
||||
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
|
||||
func (api *Client) ConnectRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) {
|
||||
response := &infoResponseFull{}
|
||||
err = post(ctx, api.httpclient, "rtm.connect", url.Values{"token": {api.token}}, response, api.debug)
|
||||
err = postSlackMethod(ctx, api.httpclient, "rtm.connect", url.Values{"token": {api.token}}, response, api.debug)
|
||||
if err != nil {
|
||||
api.Debugf("Failed to connect to RTM: %s", err)
|
||||
return nil, "", err
|
||||
@ -83,6 +92,14 @@ func RTMOptionDialer(d *websocket.Dialer) RTMOption {
|
||||
}
|
||||
}
|
||||
|
||||
// RTMOptionPingInterval determines how often to deliver a ping message to slack.
|
||||
func RTMOptionPingInterval(d time.Duration) RTMOption {
|
||||
return func(rtm *RTM) {
|
||||
rtm.pingInterval = d
|
||||
rtm.resetDeadman()
|
||||
}
|
||||
}
|
||||
|
||||
// NewRTM returns a RTM, which provides a fully managed connection to
|
||||
// Slack's websocket-based Real-Time Messaging protocol.
|
||||
func (api *Client) NewRTM(options ...RTMOption) *RTM {
|
||||
@ -90,11 +107,12 @@ func (api *Client) NewRTM(options ...RTMOption) *RTM {
|
||||
Client: *api,
|
||||
IncomingEvents: make(chan RTMEvent, 50),
|
||||
outgoingMessages: make(chan OutgoingMessage, 20),
|
||||
pings: make(map[int]time.Time),
|
||||
pingInterval: defaultPingInterval,
|
||||
pingDeadman: time.NewTimer(deadmanDuration(defaultPingInterval)),
|
||||
isConnected: false,
|
||||
wasIntentional: true,
|
||||
killChannel: make(chan bool),
|
||||
disconnected: make(chan struct{}),
|
||||
disconnected: make(chan struct{}, 1),
|
||||
forcePing: make(chan bool),
|
||||
rawEvents: make(chan json.RawMessage),
|
||||
idGen: NewSafeID(1),
|
||||
|
2
vendor/github.com/nlopes/slack/search.go
generated
vendored
2
vendor/github.com/nlopes/slack/search.go
generated
vendored
@ -104,7 +104,7 @@ func (api *Client) _search(ctx context.Context, path, query string, params Searc
|
||||
}
|
||||
|
||||
response = &searchResponseFull{}
|
||||
err := post(ctx, api.httpclient, path, values, response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, path, values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
2
vendor/github.com/nlopes/slack/slack.go
generated
vendored
2
vendor/github.com/nlopes/slack/slack.go
generated
vendored
@ -101,7 +101,7 @@ func (api *Client) AuthTest() (response *AuthTestResponse, error error) {
|
||||
func (api *Client) AuthTestContext(ctx context.Context) (response *AuthTestResponse, error error) {
|
||||
api.Debugf("Challenging auth...")
|
||||
responseFull := &authTestResponseFull{}
|
||||
err := post(ctx, api.httpclient, "auth.test", url.Values{"token": {api.token}}, responseFull, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "auth.test", url.Values{"token": {api.token}}, responseFull, api.debug)
|
||||
if err != nil {
|
||||
api.Debugf("failed to test for auth: %s", err)
|
||||
return nil, err
|
||||
|
6
vendor/github.com/nlopes/slack/stars.go
generated
vendored
6
vendor/github.com/nlopes/slack/stars.go
generated
vendored
@ -58,7 +58,7 @@ func (api *Client) AddStarContext(ctx context.Context, channel string, item Item
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := post(ctx, api.httpclient, "stars.add", values, response, api.debug); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "stars.add", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@ func (api *Client) RemoveStarContext(ctx context.Context, channel string, item I
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := post(ctx, api.httpclient, "stars.remove", values, response, api.debug); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "stars.remove", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters)
|
||||
}
|
||||
|
||||
response := &listResponseFull{}
|
||||
err := post(ctx, api.httpclient, "stars.list", values, response, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "stars.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
6
vendor/github.com/nlopes/slack/team.go
generated
vendored
6
vendor/github.com/nlopes/slack/team.go
generated
vendored
@ -69,7 +69,7 @@ func NewAccessLogParameters() AccessLogParameters {
|
||||
|
||||
func teamRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*TeamResponse, error) {
|
||||
response := &TeamResponse{}
|
||||
err := post(ctx, client, path, values, response, debug)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -83,7 +83,7 @@ func teamRequest(ctx context.Context, client HTTPRequester, path string, values
|
||||
|
||||
func billableInfoRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (map[string]BillingActive, error) {
|
||||
response := &BillableInfoResponse{}
|
||||
err := post(ctx, client, path, values, response, debug)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -97,7 +97,7 @@ func billableInfoRequest(ctx context.Context, client HTTPRequester, path string,
|
||||
|
||||
func accessLogsRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*LoginResponse, error) {
|
||||
response := &LoginResponse{}
|
||||
err := post(ctx, client, path, values, response, debug)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
2
vendor/github.com/nlopes/slack/usergroups.go
generated
vendored
2
vendor/github.com/nlopes/slack/usergroups.go
generated
vendored
@ -42,7 +42,7 @@ type userGroupResponseFull struct {
|
||||
|
||||
func userGroupRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*userGroupResponseFull, error) {
|
||||
response := &userGroupResponseFull{}
|
||||
err := post(ctx, client, path, values, response, debug)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
8
vendor/github.com/nlopes/slack/users.go
generated
vendored
8
vendor/github.com/nlopes/slack/users.go
generated
vendored
@ -168,9 +168,9 @@ type TeamIdentity struct {
|
||||
}
|
||||
|
||||
type userResponseFull struct {
|
||||
Members []User `json:"members,omitempty"`
|
||||
User `json:"user,omitempty"`
|
||||
UserPresence
|
||||
Members []User `json:"members,omitempty"`
|
||||
User `json:"user,omitempty"`
|
||||
UserPresence
|
||||
SlackResponse
|
||||
Metadata ResponseMetadata `json:"response_metadata"`
|
||||
}
|
||||
@ -547,7 +547,7 @@ func (api *Client) GetUserProfileContext(ctx context.Context, userID string, inc
|
||||
}
|
||||
resp := &getUserProfileResponse{}
|
||||
|
||||
err := post(ctx, api.httpclient, "users.profile.get", values, &resp, api.debug)
|
||||
err := postSlackMethod(ctx, api.httpclient, "users.profile.get", values, &resp, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
33
vendor/github.com/nlopes/slack/webhooks.go
generated
vendored
Normal file
33
vendor/github.com/nlopes/slack/webhooks.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type WebhookMessage struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
Attachments []Attachment `json:"attachments,omitempty"`
|
||||
}
|
||||
|
||||
func PostWebhook(url string, msg *WebhookMessage) error {
|
||||
raw, err := json.Marshal(msg)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal failed")
|
||||
}
|
||||
|
||||
response, err := http.Post(url, "application/json", bytes.NewReader(raw));
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to post webhook")
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return statusCodeError{Code: response.StatusCode, Status: response.Status}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
30
vendor/github.com/nlopes/slack/websocket.go
generated
vendored
30
vendor/github.com/nlopes/slack/websocket.go
generated
vendored
@ -20,8 +20,9 @@ const (
|
||||
//
|
||||
// Create this element with Client's NewRTM() or NewRTMWithOptions(*RTMOptions)
|
||||
type RTM struct {
|
||||
idGen IDGenerator
|
||||
pings map[int]time.Time
|
||||
idGen IDGenerator
|
||||
pingInterval time.Duration
|
||||
pingDeadman *time.Timer
|
||||
|
||||
// Connection life-cycle
|
||||
conn *websocket.Conn
|
||||
@ -71,9 +72,14 @@ func (rtm *RTM) Disconnect() error {
|
||||
// avoid RTM disconnect race conditions
|
||||
rtm.mu.Lock()
|
||||
defer rtm.mu.Unlock()
|
||||
// this channel is always closed on disconnect. lets the ManagedConnection() function
|
||||
// properly clean up.
|
||||
close(rtm.disconnected)
|
||||
|
||||
// always push into the disconnected channel when invoked,
|
||||
// this lets the ManagedConnection() function properly clean up.
|
||||
// if the buffer is full then just continue on.
|
||||
select {
|
||||
case rtm.disconnected <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
if !rtm.isConnected {
|
||||
return errors.New("Invalid call to Disconnect - Slack API is already disconnected")
|
||||
@ -83,12 +89,6 @@ func (rtm *RTM) Disconnect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reconnect only makes sense if you've successfully disconnectd with Disconnect().
|
||||
func (rtm *RTM) Reconnect() error {
|
||||
logger.Println("RTM::Reconnect not implemented!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInfo returns the info structure received when calling
|
||||
// "startrtm", holding all channels, groups and other metadata needed
|
||||
// to implement a full chat client. It will be non-nil after a call to
|
||||
@ -108,3 +108,11 @@ func (rtm *RTM) SendMessage(msg *OutgoingMessage) {
|
||||
|
||||
rtm.outgoingMessages <- *msg
|
||||
}
|
||||
|
||||
func (rtm *RTM) resetDeadman() {
|
||||
timerReset(rtm.pingDeadman, deadmanDuration(rtm.pingInterval))
|
||||
}
|
||||
|
||||
func deadmanDuration(d time.Duration) time.Duration {
|
||||
return d * 4
|
||||
}
|
||||
|
87
vendor/github.com/nlopes/slack/websocket_managed_conn.go
generated
vendored
87
vendor/github.com/nlopes/slack/websocket_managed_conn.go
generated
vendored
@ -26,36 +26,33 @@ import (
|
||||
// The defined error events are located in websocket_internals.go.
|
||||
func (rtm *RTM) ManageConnection() {
|
||||
var (
|
||||
err error
|
||||
connectionCount int
|
||||
info *Info
|
||||
conn *websocket.Conn
|
||||
err error
|
||||
info *Info
|
||||
conn *websocket.Conn
|
||||
)
|
||||
|
||||
for {
|
||||
// BEGIN SENSITIVE CODE, make sure lock is unlocked in this section.
|
||||
rtm.mu.Lock()
|
||||
connectionCount++
|
||||
for connectionCount := 0; ; connectionCount++ {
|
||||
// start trying to connect
|
||||
// the returned err is already passed onto the IncomingEvents channel
|
||||
if info, conn, err = rtm.connect(connectionCount, rtm.useRTMStart); err != nil {
|
||||
// when the connection is unsuccessful its fatal, and we need to bail out.
|
||||
rtm.Debugf("Failed to connect with RTM on try %d: %s", connectionCount, err)
|
||||
rtm.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// lock to prevent data races with Disconnect particularly around isConnected
|
||||
// and conn.
|
||||
rtm.mu.Lock()
|
||||
rtm.conn = conn
|
||||
rtm.isConnected = true
|
||||
rtm.info = info
|
||||
rtm.mu.Unlock()
|
||||
|
||||
rtm.IncomingEvents <- RTMEvent{"connected", &ConnectedEvent{
|
||||
ConnectionCount: connectionCount,
|
||||
Info: info,
|
||||
}}
|
||||
|
||||
rtm.conn = conn
|
||||
rtm.isConnected = true
|
||||
rtm.mu.Unlock()
|
||||
// END SENSITIVE CODE
|
||||
|
||||
rtm.Debugf("RTM connection succeeded on try %d", connectionCount)
|
||||
|
||||
keepRunning := make(chan bool)
|
||||
@ -64,7 +61,7 @@ func (rtm *RTM) ManageConnection() {
|
||||
go rtm.handleIncomingEvents(keepRunning)
|
||||
|
||||
// this should be a blocking call until the connection has ended
|
||||
rtm.handleEvents(keepRunning, 30*time.Second)
|
||||
rtm.handleEvents(keepRunning)
|
||||
|
||||
// after being disconnected we need to check if it was intentional
|
||||
// if not then we should try to reconnect
|
||||
@ -198,8 +195,8 @@ func (rtm *RTM) killConnection(keepRunning chan bool, intentional bool) error {
|
||||
// interval. This also sends outgoing messages that are received from the RTM's
|
||||
// outgoingMessages channel. This also handles incoming raw events from the RTM
|
||||
// rawEvents channel.
|
||||
func (rtm *RTM) handleEvents(keepRunning chan bool, interval time.Duration) {
|
||||
ticker := time.NewTicker(interval)
|
||||
func (rtm *RTM) handleEvents(keepRunning chan bool) {
|
||||
ticker := time.NewTicker(rtm.pingInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
@ -207,7 +204,12 @@ func (rtm *RTM) handleEvents(keepRunning chan bool, interval time.Duration) {
|
||||
case intentional := <-rtm.killChannel:
|
||||
_ = rtm.killConnection(keepRunning, intentional)
|
||||
return
|
||||
// send pings on ticker interval
|
||||
|
||||
// detect when the connection is dead.
|
||||
case <-rtm.pingDeadman.C:
|
||||
rtm.Debugln("deadman switch trigger disconnecting")
|
||||
_ = rtm.killConnection(keepRunning, false)
|
||||
// send pings on ticker interval
|
||||
case <-ticker.C:
|
||||
err := rtm.ping()
|
||||
if err != nil {
|
||||
@ -225,7 +227,11 @@ func (rtm *RTM) handleEvents(keepRunning chan bool, interval time.Duration) {
|
||||
rtm.sendOutgoingMessage(msg)
|
||||
// listen for incoming messages that need to be parsed
|
||||
case rawEvent := <-rtm.rawEvents:
|
||||
rtm.handleRawEvent(rawEvent)
|
||||
switch rtm.handleRawEvent(rawEvent) {
|
||||
case rtmEventTypeGoodbye:
|
||||
_ = rtm.killConnection(keepRunning, false)
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -295,9 +301,7 @@ func (rtm *RTM) sendOutgoingMessage(msg OutgoingMessage) {
|
||||
func (rtm *RTM) ping() error {
|
||||
id := rtm.idGen.Next()
|
||||
rtm.Debugln("Sending PING ", id)
|
||||
rtm.pings[id] = time.Now()
|
||||
|
||||
msg := &Ping{ID: id, Type: "ping"}
|
||||
msg := &Ping{ID: id, Type: "ping", Timestamp: time.Now().Unix()}
|
||||
|
||||
if err := rtm.sendWithDeadline(msg); err != nil {
|
||||
rtm.Debugf("RTM Error sending 'PING %d': %s", id, err.Error())
|
||||
@ -339,25 +343,31 @@ func (rtm *RTM) receiveIncomingEvent() error {
|
||||
|
||||
// handleRawEvent takes a raw JSON message received from the slack websocket
|
||||
// and handles the encoded event.
|
||||
func (rtm *RTM) handleRawEvent(rawEvent json.RawMessage) {
|
||||
// returns the event type of the message.
|
||||
func (rtm *RTM) handleRawEvent(rawEvent json.RawMessage) string {
|
||||
event := &Event{}
|
||||
err := json.Unmarshal(rawEvent, event)
|
||||
if err != nil {
|
||||
rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
|
||||
return
|
||||
return ""
|
||||
}
|
||||
|
||||
switch event.Type {
|
||||
case "":
|
||||
case rtmEventTypeAck:
|
||||
rtm.handleAck(rawEvent)
|
||||
case "hello":
|
||||
case rtmEventTypeHello:
|
||||
rtm.IncomingEvents <- RTMEvent{"hello", &HelloEvent{}}
|
||||
case "pong":
|
||||
case rtmEventTypePong:
|
||||
rtm.handlePong(rawEvent)
|
||||
case "desktop_notification":
|
||||
case rtmEventTypeGoodbye:
|
||||
// just return the event type up for goodbye, will be handled by caller.
|
||||
case rtmEventTypeDesktopNotification:
|
||||
rtm.Debugln("Received desktop notification, ignoring")
|
||||
default:
|
||||
rtm.handleEvent(event.Type, rawEvent)
|
||||
}
|
||||
|
||||
return event.Type
|
||||
}
|
||||
|
||||
// handleAck handles an incoming 'ACK' message.
|
||||
@ -388,19 +398,20 @@ func (rtm *RTM) handleAck(event json.RawMessage) {
|
||||
// a previously sent 'PING' message. This is then used to compute the
|
||||
// connection's latency.
|
||||
func (rtm *RTM) handlePong(event json.RawMessage) {
|
||||
pong := &Pong{}
|
||||
if err := json.Unmarshal(event, pong); err != nil {
|
||||
rtm.Debugln("RTM Error unmarshalling 'pong' event:", err)
|
||||
var (
|
||||
p Pong
|
||||
)
|
||||
|
||||
rtm.resetDeadman()
|
||||
|
||||
if err := json.Unmarshal(event, &p); err != nil {
|
||||
logger.Println("RTM Error unmarshalling 'pong' event:", err)
|
||||
rtm.Debugln(" -> Erroneous 'ping' event:", string(event))
|
||||
return
|
||||
}
|
||||
if pingTime, exists := rtm.pings[pong.ReplyTo]; exists {
|
||||
latency := time.Since(pingTime)
|
||||
rtm.IncomingEvents <- RTMEvent{"latency_report", &LatencyReport{Value: latency}}
|
||||
delete(rtm.pings, pong.ReplyTo)
|
||||
} else {
|
||||
rtm.Debugln("RTM Error - unmatched 'pong' event:", string(event))
|
||||
}
|
||||
|
||||
latency := time.Since(time.Unix(p.Timestamp, 0))
|
||||
rtm.IncomingEvents <- RTMEvent{"latency_report", &LatencyReport{Value: latency}}
|
||||
}
|
||||
|
||||
// handleEvent is the "default" response to an event that does not have a
|
||||
|
8
vendor/github.com/nsf/termbox-go/README.md
generated
vendored
8
vendor/github.com/nsf/termbox-go/README.md
generated
vendored
@ -11,7 +11,6 @@ For examples of what can be done take a look at demos in the _demos directory. Y
|
||||
|
||||
There are also some interesting projects using termbox-go:
|
||||
- [godit](https://github.com/nsf/godit) is an emacsish lightweight text editor written using termbox.
|
||||
- [gomatrix](https://github.com/GeertJohan/gomatrix) connects to The Matrix and displays its data streams in your terminal.
|
||||
- [gotetris](https://github.com/jjinux/gotetris) is an implementation of Tetris.
|
||||
- [sokoban-go](https://github.com/rn2dy/sokoban-go) is an implementation of sokoban game.
|
||||
- [hecate](https://github.com/evanmiller/hecate) is a hex editor designed by Satan.
|
||||
@ -36,3 +35,10 @@ There are also some interesting projects using termbox-go:
|
||||
- [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
|
||||
- [zterm](https://github.com/varunrau/zterm) is a typing game inspired by http://zty.pe/
|
||||
- [gotypist](https://github.com/pb-/gotypist) is a fun touch-typing tutor following Steve Yegge's method.
|
||||
- [cointop](https://github.com/miguelmota/cointop) is an interactive terminal based UI application for tracking cryptocurrencies.
|
||||
- [pexpo](https://github.com/nnao45/pexpo) is a terminal sending ping tool written in Go.
|
||||
- [jid](https://github.com/simeji/jid) is an interactive JSON drill down tool using filtering queries like jq.
|
||||
|
||||
### API reference
|
||||
[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go)
|
||||
|
32
vendor/github.com/nsf/termbox-go/termbox_windows.go
generated
vendored
32
vendor/github.com/nsf/termbox-go/termbox_windows.go
generated
vendored
@ -1,5 +1,6 @@
|
||||
package termbox
|
||||
|
||||
import "math"
|
||||
import "syscall"
|
||||
import "unsafe"
|
||||
import "unicode/utf16"
|
||||
@ -57,6 +58,10 @@ type (
|
||||
control_key_state dword
|
||||
event_flags dword
|
||||
}
|
||||
console_font_info struct {
|
||||
font uint32
|
||||
font_size coord
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
@ -94,6 +99,7 @@ var (
|
||||
proc_create_event = kernel32.NewProc("CreateEventW")
|
||||
proc_wait_for_multiple_objects = kernel32.NewProc("WaitForMultipleObjects")
|
||||
proc_set_event = kernel32.NewProc("SetEvent")
|
||||
proc_get_current_console_font = kernel32.NewProc("GetCurrentConsoleFont")
|
||||
get_system_metrics = moduser32.NewProc("GetSystemMetrics")
|
||||
)
|
||||
|
||||
@ -339,6 +345,19 @@ func set_event(ev syscall.Handle) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func get_current_console_font(h syscall.Handle, info *console_font_info) (err error) {
|
||||
r0, _, e1 := syscall.Syscall(proc_get_current_console_font.Addr(),
|
||||
3, uintptr(h), 0, uintptr(unsafe.Pointer(info)))
|
||||
if int(r0) == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type diff_msg struct {
|
||||
pos short
|
||||
lines short
|
||||
@ -383,6 +402,7 @@ var (
|
||||
tmp_coord0 = coord{0, 0}
|
||||
tmp_coord = coord{0, 0}
|
||||
tmp_rect = small_rect{0, 0, 0, 0}
|
||||
tmp_finfo console_font_info
|
||||
)
|
||||
|
||||
func get_cursor_position(out syscall.Handle) coord {
|
||||
@ -411,9 +431,14 @@ func get_win_min_size(out syscall.Handle) coord {
|
||||
}
|
||||
}
|
||||
|
||||
err1 := get_current_console_font(out, &tmp_finfo)
|
||||
if err1 != nil {
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
return coord{
|
||||
x: short(x),
|
||||
y: short(y),
|
||||
x: short(math.Ceil(float64(x) / float64(tmp_finfo.font_size.x))),
|
||||
y: short(math.Ceil(float64(y) / float64(tmp_finfo.font_size.y))),
|
||||
}
|
||||
}
|
||||
|
||||
@ -442,8 +467,9 @@ func get_win_size(out syscall.Handle) coord {
|
||||
}
|
||||
|
||||
func update_size_maybe() {
|
||||
size := get_term_size(out)
|
||||
size := get_win_size(out)
|
||||
if size.x != term_size.x || size.y != term_size.y {
|
||||
set_console_screen_buffer_size(out, size)
|
||||
term_size = size
|
||||
back_buffer.resize(int(size.x), int(size.y))
|
||||
front_buffer.resize(int(size.x), int(size.y))
|
||||
|
6
vendor/github.com/nsf/termbox-go/terminfo.go
generated
vendored
6
vendor/github.com/nsf/termbox-go/terminfo.go
generated
vendored
@ -69,6 +69,12 @@ func load_terminfo() ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// next, /lib/terminfo
|
||||
data, err = ti_try_path("/lib/terminfo")
|
||||
if err == nil {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// fall back to /usr/share/terminfo
|
||||
return ti_try_path("/usr/share/terminfo")
|
||||
}
|
||||
|
24
vendor/github.com/pkg/errors/.gitignore
generated
vendored
Normal file
24
vendor/github.com/pkg/errors/.gitignore
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# 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
|
11
vendor/github.com/pkg/errors/.travis.yml
generated
vendored
Normal file
11
vendor/github.com/pkg/errors/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- 1.7.1
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
23
vendor/github.com/pkg/errors/LICENSE
generated
vendored
Normal file
23
vendor/github.com/pkg/errors/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||
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.
|
52
vendor/github.com/pkg/errors/README.md
generated
vendored
Normal file
52
vendor/github.com/pkg/errors/README.md
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors)
|
||||
|
||||
Package errors provides simple error handling primitives.
|
||||
|
||||
`go get github.com/pkg/errors`
|
||||
|
||||
The traditional error handling idiom in Go is roughly akin to
|
||||
```go
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||
|
||||
## Adding context to an error
|
||||
|
||||
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||
```go
|
||||
_, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read failed")
|
||||
}
|
||||
```
|
||||
## Retrieving the cause of an error
|
||||
|
||||
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||
```go
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
```
|
||||
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||
```go
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *MyError:
|
||||
// handle specifically
|
||||
default:
|
||||
// unknown error
|
||||
}
|
||||
```
|
||||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
|
||||
## Licence
|
||||
|
||||
BSD-2-Clause
|
32
vendor/github.com/pkg/errors/appveyor.yml
generated
vendored
Normal file
32
vendor/github.com/pkg/errors/appveyor.yml
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||
shallow_clone: true # for startup speed
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# http://www.appveyor.com/docs/installed-software
|
||||
install:
|
||||
# some helpful output for debugging builds
|
||||
- go version
|
||||
- go env
|
||||
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||
# but MSYS2 at C:\msys64 has mingw64
|
||||
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||
- gcc --version
|
||||
- g++ --version
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- set PATH=C:\gopath\bin;%PATH%
|
||||
- go test -v ./...
|
||||
|
||||
#artifacts:
|
||||
# - path: '%GOPATH%\bin\*.exe'
|
||||
deploy: off
|
269
vendor/github.com/pkg/errors/errors.go
generated
vendored
Normal file
269
vendor/github.com/pkg/errors/errors.go
generated
vendored
Normal file
@ -0,0 +1,269 @@
|
||||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// and the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||
// functions destructure errors.Wrap into its component operations of annotating
|
||||
// an error with a stack trace and an a message, respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error which does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// causer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface.
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// Where errors.StackTrace is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// stackTracer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is call, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
178
vendor/github.com/pkg/errors/stack.go
generated
vendored
Normal file
178
vendor/github.com/pkg/errors/stack.go
generated
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s path of source file relative to the compile time GOPATH
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
}
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
||||
|
||||
func trimGOPATH(name, file string) string {
|
||||
// Here we want to get the source file path relative to the compile time
|
||||
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||
// the import path, which does not include the GOPATH. Thus we can trim
|
||||
// segments from the beginning of the file path until the number of path
|
||||
// separators remaining is one more than the number of path separators in
|
||||
// the function name. For example, given:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// file /home/user/src/pkg/sub/file.go
|
||||
// fn.Name() pkg/sub.Type.Method
|
||||
//
|
||||
// We want to produce:
|
||||
//
|
||||
// pkg/sub/file.go
|
||||
//
|
||||
// From this we can easily see that fn.Name() has one less path separator
|
||||
// than our desired output. We count separators from the end of the file
|
||||
// path until it finds two more than in the function name and then move
|
||||
// one character forward to preserve the initial path segment without a
|
||||
// leading separator.
|
||||
const sep = "/"
|
||||
goal := strings.Count(name, sep) + 2
|
||||
i := len(file)
|
||||
for n := 0; n < goal; n++ {
|
||||
i = strings.LastIndex(file[:i], sep)
|
||||
if i == -1 {
|
||||
// not enough separators found, set i so that the slice expression
|
||||
// below leaves file unmodified
|
||||
i = -len(sep)
|
||||
break
|
||||
}
|
||||
}
|
||||
// get back to 0 or trim the leading separator
|
||||
file = file[i+len(sep):]
|
||||
return file
|
||||
}
|
2
vendor/github.com/renstrom/fuzzysearch/LICENSE
generated
vendored
2
vendor/github.com/renstrom/fuzzysearch/LICENSE
generated
vendored
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Peter Renström
|
||||
Copyright (c) 2018 Peter Lithammer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
42
vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go
generated
vendored
42
vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go
generated
vendored
@ -112,31 +112,34 @@ Outer:
|
||||
}
|
||||
|
||||
// Count up remaining char
|
||||
for len(target) > 0 {
|
||||
target = target[utf8.RuneLen(rune(target[0])):]
|
||||
runeDiff++
|
||||
}
|
||||
runeDiff += utf8.RuneCountInString(target)
|
||||
|
||||
return runeDiff
|
||||
}
|
||||
|
||||
// RankFind is similar to Find, except it will also rank all matches using
|
||||
// Levenshtein distance.
|
||||
func RankFind(source string, targets []string) ranks {
|
||||
var r ranks
|
||||
for _, target := range find(source, targets, noop) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance})
|
||||
func RankFind(source string, targets []string) Ranks {
|
||||
var r Ranks
|
||||
|
||||
for index, target := range targets {
|
||||
if match(source, target, noop) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance, index})
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// RankFindFold is a case-insensitive version of RankFind.
|
||||
func RankFindFold(source string, targets []string) ranks {
|
||||
var r ranks
|
||||
for _, target := range find(source, targets, unicode.ToLower) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance})
|
||||
func RankFindFold(source string, targets []string) Ranks {
|
||||
var r Ranks
|
||||
|
||||
for index, target := range targets {
|
||||
if match(source, target, unicode.ToLower) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance, index})
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
@ -150,18 +153,21 @@ type Rank struct {
|
||||
|
||||
// Distance is the Levenshtein distance between Source and Target.
|
||||
Distance int
|
||||
|
||||
// Location of Target in original list
|
||||
OriginalIndex int
|
||||
}
|
||||
|
||||
type ranks []Rank
|
||||
type Ranks []Rank
|
||||
|
||||
func (r ranks) Len() int {
|
||||
func (r Ranks) Len() int {
|
||||
return len(r)
|
||||
}
|
||||
|
||||
func (r ranks) Swap(i, j int) {
|
||||
func (r Ranks) Swap(i, j int) {
|
||||
r[i], r[j] = r[j], r[i]
|
||||
}
|
||||
|
||||
func (r ranks) Less(i, j int) bool {
|
||||
func (r Ranks) Less(i, j int) bool {
|
||||
return r[i].Distance < r[j].Distance
|
||||
}
|
||||
|
@ -33,12 +33,14 @@ func CreateView(config *config.Config, svc *service.SlackService) *View {
|
||||
|
||||
// Chat: fill the component
|
||||
msgs := svc.GetMessages(
|
||||
svc.GetSlackChannel(channels.SelectedChannel),
|
||||
channels.ChannelItems[channels.SelectedChannel].ID,
|
||||
chat.GetMaxItems(),
|
||||
)
|
||||
|
||||
chat.SetMessages(msgs)
|
||||
chat.SetBorderLabel(svc.Channels[channels.SelectedChannel].GetChannelName())
|
||||
chat.SetBorderLabel(
|
||||
channels.ChannelItems[channels.SelectedChannel].GetChannelName(),
|
||||
)
|
||||
|
||||
// Debug: create the component
|
||||
debug := components.CreateDebugComponent(input.Par.Height)
|
||||
|
Loading…
Reference in New Issue
Block a user