Compare commits
1 Commits
master
...
presence-o
Author | SHA1 | Date | |
---|---|---|---|
|
825e88b407 |
6
.github/issue_template.md
vendored
6
.github/issue_template.md
vendored
@ -1,6 +0,0 @@
|
||||
Please read [CONTRIBUTING.md](https://github.com/erroneousboat/slack-term/blob/master/CONTRIBUTING.md)
|
||||
|
||||
---
|
||||
|
||||
Version:
|
||||
Installation method:
|
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@ -1 +0,0 @@
|
||||
Please read [CONTRIBUTING.md](https://github.com/erroneousboat/slack-term/blob/master/CONTRIBUTING.md)
|
31
Dockerfile
31
Dockerfile
@ -1,31 +0,0 @@
|
||||
FROM golang:alpine as builder
|
||||
|
||||
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
||||
ENV GOPATH /go
|
||||
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates
|
||||
|
||||
COPY . /go/src/github.com/erroneousboat/slack-term
|
||||
|
||||
RUN set -x \
|
||||
&& apk add --no-cache --virtual .build-deps \
|
||||
git \
|
||||
gcc \
|
||||
libc-dev \
|
||||
libgcc \
|
||||
make \
|
||||
&& cd /go/src/github.com/erroneousboat/slack-term \
|
||||
&& make build \
|
||||
&& mv ./bin/slack-term /usr/bin/slack-term \
|
||||
&& apk del .build-deps \
|
||||
&& rm -rf /go
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
ENV USER root
|
||||
|
||||
COPY --from=builder /usr/bin/slack-term /usr/bin/slack-term
|
||||
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs
|
||||
|
||||
ENTRYPOINT stty cols 25 && slack-term -config config
|
95
Gopkg.lock
generated
Normal file
95
Gopkg.lock
generated
Normal file
@ -0,0 +1,95 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[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 = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "3ff3320c2a1756a3691521efc290b4701575147c"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f614e627d47e1276989de725dc5e433504a8b5498850711c9d3fcec3bfa7c943"
|
||||
name = "github.com/maruel/panicparse"
|
||||
packages = ["stack"]
|
||||
pruneopts = "UT"
|
||||
revision = "785840568bdc7faa0dfb1cd6c643207f03271f64"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cdb899c199f907ac9fb50495ec71212c95cb5b0e0a8ee0800da0238036091033"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
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 = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7cfa5619e6becd3db5dfb8e26c06798918e123b2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f335d800550786b6f51ddaedb9d1107a7a72f4a2195e5b039dd7c0e103e119bc"
|
||||
name = "github.com/nsf/termbox-go"
|
||||
packages = ["."]
|
||||
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"]
|
||||
pruneopts = "UT"
|
||||
revision = "b18e754edff4833912ef4dce9eaca885bd3f0de1"
|
||||
version = "v1.0.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
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
|
50
Gopkg.toml
Normal file
50
Gopkg.toml
Normal file
@ -0,0 +1,50 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/erroneousboat/termui"
|
||||
revision = "80f245cdfa0488883a3e8602bf3f0c8a3c889a22"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
version = "0.0.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/nlopes/slack"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/nsf/termbox-go"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/renstrom/fuzzysearch"
|
||||
version = "1.0.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
15
Makefile
15
Makefile
@ -22,10 +22,6 @@ dev: build
|
||||
# We're setting the OS to linux (in case someone builds the binary on Mac or
|
||||
# Windows)
|
||||
#
|
||||
# `-mod=vendor`
|
||||
# This ensures that the build process will use the modules in the vendor
|
||||
# folder.
|
||||
#
|
||||
# `-a`
|
||||
# Force rebuilding of package, all import will be rebuilt with cgo disabled,
|
||||
# which means all the imports will be rebuilt with cgo disabled.
|
||||
@ -43,17 +39,17 @@ dev: build
|
||||
# Location of the source files
|
||||
build:
|
||||
@ echo "+ $@"
|
||||
@ CGO_ENABLED=0 go build -mod=vendor -a -installsuffix cgo -o ./bin/slack-term .
|
||||
@ CGO_ENABLED=0 go build -a -installsuffix cgo -o ./bin/slack-term .
|
||||
|
||||
# Cross-compile
|
||||
# http://dave.cheney.net/2015/08/22/cross-compilation-with-go-1-5
|
||||
build-linux:
|
||||
@ echo "+ $@"
|
||||
@ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -mod=vendor -a -installsuffix cgo -o ./bin/slack-term-linux-amd64 .
|
||||
@ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -installsuffix cgo -o ./bin/slack-term-linux-amd64 .
|
||||
|
||||
build-mac:
|
||||
@ echo "+ $@"
|
||||
@ GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -mod=vendor -a -installsuffix cgo -o ./bin/slack-term-darwin-amd64 .
|
||||
@ GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -a -installsuffix cgo -o ./bin/slack-term-darwin-amd64 .
|
||||
|
||||
run: build
|
||||
@ echo "+ $@"
|
||||
@ -63,11 +59,6 @@ install:
|
||||
@ echo "+ $@"
|
||||
@ go install .
|
||||
|
||||
modules:
|
||||
@ echo "+ $@"
|
||||
@ go mod tidy
|
||||
@ go mod vendor
|
||||
|
||||
build-all: build build-linux build-mac
|
||||
|
||||
.PHONY: default test build build-linux build-mac run install
|
||||
|
32
README.md
32
README.md
@ -29,29 +29,15 @@ $ cd $GOPATH/src/github.com/erroneousboat/slack-term
|
||||
$ go install .
|
||||
```
|
||||
|
||||
#### Via docker
|
||||
|
||||
You can also run it with docker, make sure you have a valid config file
|
||||
on your host system.
|
||||
|
||||
```bash
|
||||
docker run -it -v [config-file]:/config erroneousboat/slack-term
|
||||
```
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
1. Get a slack token, click [here](https://github.com/erroneousboat/slack-term/wiki#running-slack-term-without-legacy-tokens)
|
||||
1. Get a slack token, click [here](https://api.slack.com/docs/oauth-test-tokens)
|
||||
|
||||
2. Running `slack-term` for the first time, will create a default config file at
|
||||
`~/.config/slack-term/config`.
|
||||
|
||||
```bash
|
||||
$ slack-term
|
||||
```
|
||||
|
||||
3. Update the config file and update your `slack_token` For more configuration
|
||||
options of the `config` file, see the [wiki](https://github.com/erroneousboat/slack-term/wiki).
|
||||
2. Create a `.slack-term` file, and place it in your home directory. Below is
|
||||
an example of such a file. You are only required to specify a
|
||||
`slack_token`. For more configuration options of the `.slack-term` file,
|
||||
see the [wiki](https://github.com/erroneousboat/slack-term/wiki).
|
||||
|
||||
```javascript
|
||||
{
|
||||
@ -63,7 +49,7 @@ Usage
|
||||
-----
|
||||
|
||||
When everything is setup correctly you can run `slack-term` with the following
|
||||
command:
|
||||
command:
|
||||
|
||||
```bash
|
||||
$ slack-term
|
||||
@ -73,7 +59,7 @@ Default Key Mapping
|
||||
-------------------
|
||||
|
||||
Below are the default key-mappings for `slack-term`, you can change them
|
||||
in your `config` file.
|
||||
in your `.slack-term` file.
|
||||
|
||||
| mode | key | action |
|
||||
|---------|-----------|----------------------------|
|
||||
@ -83,9 +69,6 @@ in your `config` file.
|
||||
| command | `j` | move channel cursor down |
|
||||
| command | `g` | move channel cursor top |
|
||||
| command | `G` | move channel cursor bottom |
|
||||
| command | `K` | thread up |
|
||||
| command | `J` | thread down |
|
||||
| command | `G` | move channel cursor bottom |
|
||||
| command | `pg-up` | scroll chat pane up |
|
||||
| command | `ctrl-b` | scroll chat pane up |
|
||||
| command | `ctrl-u` | scroll chat pane up |
|
||||
@ -94,7 +77,6 @@ in your `config` file.
|
||||
| command | `ctrl-d` | scroll chat pane down |
|
||||
| command | `n` | next search match |
|
||||
| command | `N` | previous search match |
|
||||
| command | `,` | jump to next notification |
|
||||
| command | `q` | quit |
|
||||
| command | `f1` | help |
|
||||
| insert | `left` | move input cursor left |
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"html"
|
||||
|
||||
"github.com/erroneousboat/termui"
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
"github.com/renstrom/fuzzysearch/fuzzy"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -108,13 +108,13 @@ type Channels struct {
|
||||
}
|
||||
|
||||
// CreateChannels is the constructor for the Channels component
|
||||
func CreateChannelsComponent(height int) *Channels {
|
||||
func CreateChannelsComponent(inputHeight int) *Channels {
|
||||
channels := &Channels{
|
||||
List: termui.NewList(),
|
||||
}
|
||||
|
||||
channels.List.BorderLabel = "Channels"
|
||||
channels.List.Height = height
|
||||
channels.List.Height = termui.TermHeight() - inputHeight
|
||||
|
||||
channels.SelectedChannel = 0
|
||||
channels.Offset = 0
|
||||
@ -148,16 +148,17 @@ func (c *Channels) Buffer() termui.Buffer {
|
||||
// Append ellipsis when overflows
|
||||
cells = termui.DTrimTxCls(cells, c.List.InnerWidth())
|
||||
|
||||
x := c.List.InnerBounds().Min.X
|
||||
x := 0
|
||||
for _, cell := range cells {
|
||||
buf.Set(x, y, cell)
|
||||
x += cell.Width()
|
||||
width := cell.Width()
|
||||
buf.Set(c.List.InnerBounds().Min.X+x, y, cell)
|
||||
x += width
|
||||
}
|
||||
|
||||
// When not at the end of the pane fill it up empty characters
|
||||
for x < c.List.InnerBounds().Max.X {
|
||||
for x < c.List.InnerBounds().Max.X-1 {
|
||||
if y == c.CursorPosition {
|
||||
buf.Set(x, y,
|
||||
buf.Set(x+1, y,
|
||||
termui.Cell{
|
||||
Ch: ' ',
|
||||
Fg: c.List.ItemBgColor,
|
||||
@ -166,7 +167,7 @@ func (c *Channels) Buffer() termui.Buffer {
|
||||
)
|
||||
} else {
|
||||
buf.Set(
|
||||
x, y,
|
||||
x+1, y,
|
||||
termui.Cell{
|
||||
Ch: ' ',
|
||||
Fg: c.List.ItemFgColor,
|
||||
@ -235,11 +236,6 @@ func (c *Channels) SetSelectedChannel(index int) {
|
||||
c.SelectedChannel = index
|
||||
}
|
||||
|
||||
// Get SelectedChannel returns the ChannelItem that is currently selected
|
||||
func (c *Channels) GetSelectedChannel() ChannelItem {
|
||||
return c.ChannelItems[c.SelectedChannel]
|
||||
}
|
||||
|
||||
// MoveCursorUp will decrease the SelectedChannel by 1
|
||||
func (c *Channels) MoveCursorUp() {
|
||||
if c.SelectedChannel > 0 {
|
||||
@ -325,14 +321,17 @@ func (c *Channels) Search(term string) {
|
||||
}
|
||||
|
||||
if len(c.SearchMatches) > 0 {
|
||||
c.GotoPositionSearch(0)
|
||||
c.GotoPosition(0)
|
||||
c.SearchPosition = 0
|
||||
}
|
||||
}
|
||||
|
||||
// GotoPosition is used by to automatically scroll to a specific
|
||||
// location in the channels component
|
||||
func (c *Channels) GotoPosition(newPos int) {
|
||||
// GotoPosition is used by the search functionality to automatically
|
||||
// scroll to a specific location in the channels component
|
||||
func (c *Channels) GotoPosition(position int) {
|
||||
|
||||
// The new position
|
||||
newPos := c.SearchMatches[position]
|
||||
|
||||
// Is the new position in range of the current view?
|
||||
minRange := c.Offset
|
||||
@ -359,18 +358,11 @@ func (c *Channels) GotoPosition(newPos int) {
|
||||
c.CursorPosition = (newPos - c.Offset) + 1
|
||||
}
|
||||
|
||||
// GotoPosition is used by the search functionality to automatically
|
||||
// scroll to a specific location in the channels component
|
||||
func (c *Channels) GotoPositionSearch(position int) {
|
||||
newPos := c.SearchMatches[position]
|
||||
c.GotoPosition(newPos)
|
||||
}
|
||||
|
||||
// SearchNext allows us to cycle through the c.SearchMatches
|
||||
func (c *Channels) SearchNext() {
|
||||
newPosition := c.SearchPosition + 1
|
||||
if newPosition <= len(c.SearchMatches)-1 {
|
||||
c.GotoPositionSearch(newPosition)
|
||||
c.GotoPosition(newPosition)
|
||||
c.SearchPosition = newPosition
|
||||
}
|
||||
}
|
||||
@ -379,17 +371,7 @@ func (c *Channels) SearchNext() {
|
||||
func (c *Channels) SearchPrev() {
|
||||
newPosition := c.SearchPosition - 1
|
||||
if newPosition >= 0 {
|
||||
c.GotoPositionSearch(newPosition)
|
||||
c.GotoPosition(newPosition)
|
||||
c.SearchPosition = newPosition
|
||||
}
|
||||
}
|
||||
|
||||
// Jump to the first channel with a notification
|
||||
func (c *Channels) Jump() {
|
||||
for i, channel := range c.ChannelItems {
|
||||
if channel.Notification {
|
||||
c.GotoPosition(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,19 +12,58 @@ import (
|
||||
"github.com/erroneousboat/slack-term/config"
|
||||
)
|
||||
|
||||
var (
|
||||
COLORS = []string{
|
||||
"fg-black",
|
||||
"fg-red",
|
||||
"fg-green",
|
||||
"fg-yellow",
|
||||
"fg-blue",
|
||||
"fg-magenta",
|
||||
"fg-cyan",
|
||||
"fg-white",
|
||||
}
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Time time.Time
|
||||
Name string
|
||||
Content string
|
||||
|
||||
StyleTime string
|
||||
StyleName string
|
||||
StyleText string
|
||||
|
||||
FormatTime string
|
||||
}
|
||||
|
||||
func (m Message) colorizeName(styleName string) string {
|
||||
if strings.Contains(styleName, "colorize") {
|
||||
var sum int
|
||||
for _, c := range m.Name {
|
||||
sum = sum + int(c)
|
||||
}
|
||||
|
||||
i := sum % len(COLORS)
|
||||
|
||||
return strings.Replace(m.StyleName, "colorize", COLORS[i], -1)
|
||||
}
|
||||
|
||||
return styleName
|
||||
}
|
||||
|
||||
// Chat is the definition of a Chat component
|
||||
type Chat struct {
|
||||
List *termui.List
|
||||
Messages map[string]Message
|
||||
Messages []Message
|
||||
Offset int
|
||||
}
|
||||
|
||||
// CreateChatComponent is the constructor for the Chat struct
|
||||
// CreateChat is the constructor for the Chat struct
|
||||
func CreateChatComponent(inputHeight int) *Chat {
|
||||
chat := &Chat{
|
||||
List: termui.NewList(),
|
||||
Messages: make(map[string]Message),
|
||||
Offset: 0,
|
||||
List: termui.NewList(),
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
chat.List.Height = termui.TermHeight() - inputHeight
|
||||
@ -35,8 +74,59 @@ func CreateChatComponent(inputHeight int) *Chat {
|
||||
|
||||
// Buffer implements interface termui.Bufferer
|
||||
func (c *Chat) Buffer() termui.Buffer {
|
||||
// Convert Messages into termui.Cell
|
||||
cells := c.MessagesToCells(c.Messages)
|
||||
// Build cells. We're building parts of the message individually, or else
|
||||
// DefaultTxBuilder will interpret potential markdown usage in a message
|
||||
// as well.
|
||||
cells := make([]termui.Cell, 0)
|
||||
for i, msg := range c.Messages {
|
||||
|
||||
// When msg.Time and msg.Name are empty (in the case of attachments)
|
||||
// don't add the time and name parts.
|
||||
if (msg.Time != time.Time{} && msg.Name != "") {
|
||||
// Time
|
||||
cells = append(cells, termui.DefaultTxBuilder.Build(
|
||||
fmt.Sprintf(
|
||||
"[[%s]](%s) ",
|
||||
msg.Time.Format(msg.FormatTime),
|
||||
msg.StyleTime,
|
||||
),
|
||||
termui.ColorDefault, termui.ColorDefault)...,
|
||||
)
|
||||
|
||||
// Name
|
||||
cells = append(cells, termui.DefaultTxBuilder.Build(
|
||||
fmt.Sprintf("[<%s>](%s) ",
|
||||
msg.Name,
|
||||
msg.colorizeName(msg.StyleName),
|
||||
),
|
||||
termui.ColorDefault, termui.ColorDefault)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Hack, in order to get the correct fg and bg attributes. This is
|
||||
// because the readAttr function in termui is unexported.
|
||||
txCells := termui.DefaultTxBuilder.Build(
|
||||
fmt.Sprintf("[.](%s)", msg.StyleText),
|
||||
termui.ColorDefault, termui.ColorDefault,
|
||||
)
|
||||
|
||||
// Text
|
||||
for _, r := range msg.Content {
|
||||
cells = append(
|
||||
cells,
|
||||
termui.Cell{
|
||||
Ch: r,
|
||||
Fg: txCells[0].Fg,
|
||||
Bg: txCells[0].Bg,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Add a newline after every message
|
||||
if i < len(c.Messages)-1 {
|
||||
cells = append(cells, termui.Cell{Ch: '\n'})
|
||||
}
|
||||
}
|
||||
|
||||
// We will create an array of Line structs, this allows us
|
||||
// to more easily render the items in a list. We will range
|
||||
@ -172,43 +262,17 @@ func (c *Chat) SetMessages(messages []Message) {
|
||||
// Reset offset first, when scrolling in view and changing channels we
|
||||
// want the offset to be 0 when loading new messages
|
||||
c.Offset = 0
|
||||
for _, msg := range messages {
|
||||
c.Messages[msg.ID] = msg
|
||||
}
|
||||
c.Messages = messages
|
||||
}
|
||||
|
||||
// AddMessage adds a single message to Messages
|
||||
func (c *Chat) AddMessage(message Message) {
|
||||
c.Messages[message.ID] = message
|
||||
}
|
||||
|
||||
// AddReply adds a single reply to a parent thread, it also sets
|
||||
// the thread separator
|
||||
func (c *Chat) AddReply(parentID string, message Message) {
|
||||
// It is possible that a message is received but the parent is not
|
||||
// present in the chat view
|
||||
if _, ok := c.Messages[parentID]; ok {
|
||||
message.Thread = " "
|
||||
c.Messages[parentID].Messages[message.ID] = message
|
||||
} else {
|
||||
c.AddMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
// IsNewThread check whether a message that is going to be added as
|
||||
// a child to a parent message, is the first one or not
|
||||
func (c *Chat) IsNewThread(parentID string) bool {
|
||||
if parent, ok := c.Messages[parentID]; ok {
|
||||
if len(parent.Messages) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
c.Messages = append(c.Messages, message)
|
||||
}
|
||||
|
||||
// ClearMessages clear the c.Messages
|
||||
func (c *Chat) ClearMessages() {
|
||||
c.Messages = make(map[string]Message)
|
||||
c.Messages = make([]Message, 0)
|
||||
}
|
||||
|
||||
// ScrollUp will render the chat messages based on the Offset of the Chat
|
||||
@ -248,101 +312,23 @@ func (c *Chat) SetBorderLabel(channelName string) {
|
||||
c.List.BorderLabel = channelName
|
||||
}
|
||||
|
||||
// MessagesToCells is a wrapper around MessageToCells to use for a slice of
|
||||
// of type Message
|
||||
func (c *Chat) MessagesToCells(msgs map[string]Message) []termui.Cell {
|
||||
cells := make([]termui.Cell, 0)
|
||||
sortedMessages := SortMessages(msgs)
|
||||
|
||||
for i, msg := range sortedMessages {
|
||||
cells = append(cells, c.MessageToCells(msg)...)
|
||||
|
||||
if len(msg.Messages) > 0 {
|
||||
cells = append(cells, termui.Cell{Ch: '\n'})
|
||||
cells = append(cells, c.MessagesToCells(msg.Messages)...)
|
||||
}
|
||||
|
||||
// Add a newline after every message
|
||||
if i < len(sortedMessages)-1 {
|
||||
cells = append(cells, termui.Cell{Ch: '\n'})
|
||||
}
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
|
||||
// MessageToCells will convert a Message struct to termui.Cell
|
||||
//
|
||||
// We're building parts of the message individually, or else DefaultTxBuilder
|
||||
// will interpret potential markdown usage in a message as well.
|
||||
func (c *Chat) MessageToCells(msg Message) []termui.Cell {
|
||||
cells := make([]termui.Cell, 0)
|
||||
|
||||
// When msg.Time and msg.Name are empty (in the case of attachments)
|
||||
// don't add the time and name parts.
|
||||
if (msg.Time != time.Time{} && msg.Name != "") {
|
||||
// Time
|
||||
cells = append(cells, termui.DefaultTxBuilder.Build(
|
||||
msg.GetTime(),
|
||||
termui.ColorDefault, termui.ColorDefault)...,
|
||||
)
|
||||
|
||||
// Thread
|
||||
cells = append(cells, termui.DefaultTxBuilder.Build(
|
||||
msg.GetThread(),
|
||||
termui.ColorDefault, termui.ColorDefault)...,
|
||||
)
|
||||
|
||||
// Name
|
||||
cells = append(cells, termui.DefaultTxBuilder.Build(
|
||||
msg.GetName(),
|
||||
termui.ColorDefault, termui.ColorDefault)...,
|
||||
)
|
||||
}
|
||||
|
||||
// Hack, in order to get the correct fg and bg attributes. This is
|
||||
// because the readAttr function in termui is unexported.
|
||||
txCells := termui.DefaultTxBuilder.Build(
|
||||
msg.GetContent(),
|
||||
termui.ColorDefault, termui.ColorDefault,
|
||||
)
|
||||
|
||||
// Text
|
||||
for _, r := range msg.Content {
|
||||
cells = append(
|
||||
cells,
|
||||
termui.Cell{
|
||||
Ch: r,
|
||||
Fg: txCells[0].Fg,
|
||||
Bg: txCells[0].Bg,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return cells
|
||||
}
|
||||
|
||||
// Help shows the usage and key bindings in the chat pane
|
||||
func (c *Chat) Help(usage string, cfg *config.Config) {
|
||||
msgUsage := Message{
|
||||
ID: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||
Content: usage,
|
||||
help := []Message{
|
||||
Message{
|
||||
Content: usage,
|
||||
},
|
||||
}
|
||||
|
||||
c.Messages[msgUsage.ID] = msgUsage
|
||||
|
||||
for mode, mapping := range cfg.KeyMap {
|
||||
msgMode := Message{
|
||||
ID: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||
Content: fmt.Sprintf("%s", strings.ToUpper(mode)),
|
||||
}
|
||||
c.Messages[msgMode.ID] = msgMode
|
||||
help = append(
|
||||
help,
|
||||
Message{
|
||||
Content: fmt.Sprintf("%s", strings.ToUpper(mode)),
|
||||
},
|
||||
)
|
||||
|
||||
msgNewline := Message{
|
||||
ID: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||
Content: "",
|
||||
}
|
||||
c.Messages[msgNewline.ID] = msgNewline
|
||||
help = append(help, Message{Content: ""})
|
||||
|
||||
var keys []string
|
||||
for k := range mapping {
|
||||
@ -351,14 +337,16 @@ func (c *Chat) Help(usage string, cfg *config.Config) {
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
msgKey := Message{
|
||||
ID: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||
Content: fmt.Sprintf(" %-12s%-15s", k, mapping[k]),
|
||||
}
|
||||
c.Messages[msgKey.ID] = msgKey
|
||||
help = append(
|
||||
help,
|
||||
Message{
|
||||
Content: fmt.Sprintf(" %-12s%-15s", k, mapping[k]),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
msgNewline.ID = fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
c.Messages[msgNewline.ID] = msgNewline
|
||||
help = append(help, Message{Content: ""})
|
||||
}
|
||||
|
||||
c.Messages = help
|
||||
}
|
||||
|
@ -1,10 +1,6 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/erroneousboat/termui"
|
||||
)
|
||||
import "github.com/erroneousboat/termui"
|
||||
|
||||
// Debug can be used to relay debugging information in the Debug component,
|
||||
// see event.go on how to use it
|
||||
@ -20,7 +16,6 @@ func CreateDebugComponent(inputHeight int) *Debug {
|
||||
|
||||
debug.List.BorderLabel = "Debug"
|
||||
debug.List.Height = termui.TermHeight() - inputHeight
|
||||
debug.List.Overflow = "wrap"
|
||||
|
||||
return debug
|
||||
}
|
||||
@ -61,15 +56,3 @@ func (d *Debug) Println(text string) {
|
||||
|
||||
termui.Render(d)
|
||||
}
|
||||
|
||||
func (d *Debug) Sprintf(format string, a ...interface{}) {
|
||||
text := fmt.Sprintf(format, a...)
|
||||
d.List.Items = append(d.List.Items, text)
|
||||
|
||||
// When at the end remove first item
|
||||
if len(d.List.Items) > d.List.InnerBounds().Max.Y-1 {
|
||||
d.List.Items = d.List.Items[1:]
|
||||
}
|
||||
|
||||
termui.Render(d)
|
||||
}
|
||||
|
@ -85,18 +85,7 @@ func (i *Input) Insert(key rune) {
|
||||
// Backspace will remove a character in front of the CursorPositionText
|
||||
func (i *Input) Backspace() {
|
||||
if i.CursorPositionText > 0 {
|
||||
|
||||
// We want the cursor to stay in the same spot when the text
|
||||
// overflow, revealing the test on the left side when using
|
||||
// backspace. When all the text has been revealed will move
|
||||
// the cursor to the left.
|
||||
if i.Offset > 0 {
|
||||
i.Offset--
|
||||
i.CursorPositionText--
|
||||
} else {
|
||||
i.MoveCursorLeft()
|
||||
}
|
||||
|
||||
i.MoveCursorLeft()
|
||||
i.Text = append(i.Text[0:i.CursorPositionText], i.Text[i.CursorPositionText+1:]...)
|
||||
i.Par.Text = string(i.Text[i.Offset:])
|
||||
}
|
||||
|
@ -1,95 +0,0 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
COLORS = []string{
|
||||
"fg-black",
|
||||
"fg-red",
|
||||
"fg-green",
|
||||
"fg-yellow",
|
||||
"fg-blue",
|
||||
"fg-magenta",
|
||||
"fg-cyan",
|
||||
"fg-white",
|
||||
}
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
ID string
|
||||
Messages map[string]Message
|
||||
|
||||
Time time.Time
|
||||
Thread string
|
||||
Name string
|
||||
Content string
|
||||
|
||||
StyleTime string
|
||||
StyleThread string
|
||||
StyleName string
|
||||
StyleText string
|
||||
|
||||
FormatTime string
|
||||
}
|
||||
|
||||
func (m Message) GetTime() string {
|
||||
return fmt.Sprintf(
|
||||
"[[%s]](%s) ",
|
||||
m.Time.Format(m.FormatTime),
|
||||
m.StyleTime,
|
||||
)
|
||||
}
|
||||
|
||||
func (m Message) GetThread() string {
|
||||
return fmt.Sprintf("[%s](%s)",
|
||||
m.Thread,
|
||||
m.StyleThread,
|
||||
)
|
||||
}
|
||||
|
||||
func (m Message) GetName() string {
|
||||
return fmt.Sprintf("[<%s>](%s) ",
|
||||
m.Name,
|
||||
m.colorizeName(m.StyleName),
|
||||
)
|
||||
}
|
||||
|
||||
func (m Message) GetContent() string {
|
||||
return fmt.Sprintf("[.](%s)", m.StyleText)
|
||||
}
|
||||
|
||||
func (m Message) colorizeName(styleName string) string {
|
||||
if strings.Contains(styleName, "colorize") {
|
||||
var sum int
|
||||
for _, c := range m.Name {
|
||||
sum = sum + int(c)
|
||||
}
|
||||
|
||||
i := sum % len(COLORS)
|
||||
|
||||
return strings.Replace(m.StyleName, "colorize", COLORS[i], -1)
|
||||
}
|
||||
|
||||
return styleName
|
||||
}
|
||||
|
||||
func SortMessages(msgs map[string]Message) []Message {
|
||||
keys := make([]string, 0)
|
||||
for k := range msgs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
sortedMessages := make([]Message, 0)
|
||||
for _, k := range keys {
|
||||
sortedMessages = append(sortedMessages, msgs[k])
|
||||
}
|
||||
|
||||
return sortedMessages
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"github.com/erroneousboat/termui"
|
||||
)
|
||||
|
||||
type Threads struct {
|
||||
*Channels
|
||||
}
|
||||
|
||||
func CreateThreadsComponent(height int) *Threads {
|
||||
threads := &Threads{
|
||||
Channels: &Channels{
|
||||
List: termui.NewList(),
|
||||
},
|
||||
}
|
||||
|
||||
threads.List.BorderLabel = "Threads"
|
||||
threads.List.Height = height
|
||||
|
||||
threads.SelectedChannel = 0
|
||||
threads.Offset = 0
|
||||
threads.CursorPosition = threads.List.InnerBounds().Min.Y
|
||||
|
||||
return threads
|
||||
}
|
@ -4,11 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
fp "path/filepath"
|
||||
|
||||
"github.com/OpenPeeDeeP/xdg"
|
||||
"github.com/erroneousboat/termui"
|
||||
)
|
||||
|
||||
@ -24,7 +21,6 @@ type Config struct {
|
||||
Emoji bool `json:"emoji"`
|
||||
SidebarWidth int `json:"sidebar_width"`
|
||||
MainWidth int `json:"-"`
|
||||
ThreadsWidth int `json:"threads_width"`
|
||||
KeyMap map[string]keyMapping `json:"key_map"`
|
||||
Theme Theme `json:"theme"`
|
||||
}
|
||||
@ -35,18 +31,13 @@ type keyMapping map[string]string
|
||||
func NewConfig(filepath string) (*Config, error) {
|
||||
cfg := getDefaultConfig()
|
||||
|
||||
// Open config file, and when none is found or present create
|
||||
// a default empty one, at the default filepath location
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
file, err = CreateConfigFile(filepath)
|
||||
if err != nil {
|
||||
return &cfg, fmt.Errorf("couldn't open the slack-term config file: (%v)", err)
|
||||
}
|
||||
return &cfg, fmt.Errorf("couldn't find the slack-term config file: %v", err)
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(file).Decode(&cfg); err != nil {
|
||||
return &cfg, fmt.Errorf("the slack-term config file isn't valid json: (%v)", err)
|
||||
return &cfg, fmt.Errorf("the slack-term config file isn't valid json: %v", err)
|
||||
}
|
||||
|
||||
if cfg.SidebarWidth < 1 || cfg.SidebarWidth > 11 {
|
||||
@ -74,32 +65,10 @@ func NewConfig(filepath string) (*Config, error) {
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func CreateConfigFile(filepath string) (*os.File, error) {
|
||||
filepath = fmt.Sprintf("%s/slack-term/%s", xdg.ConfigHome(), "config")
|
||||
|
||||
if _, err := os.Stat(filepath); os.IsNotExist(err) {
|
||||
os.MkdirAll(fp.Dir(filepath), os.ModePerm)
|
||||
}
|
||||
|
||||
payload := "{\"slack_token\": \"\"}"
|
||||
err := ioutil.WriteFile(filepath, []byte(payload), 0755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func getDefaultConfig() Config {
|
||||
return Config{
|
||||
SidebarWidth: 1,
|
||||
MainWidth: 11,
|
||||
ThreadsWidth: 1,
|
||||
Notify: "",
|
||||
Emoji: false,
|
||||
KeyMap: map[string]keyMapping{
|
||||
@ -110,8 +79,6 @@ func getDefaultConfig() Config {
|
||||
"j": "channel-down",
|
||||
"g": "channel-top",
|
||||
"G": "channel-bottom",
|
||||
"K": "thread-up",
|
||||
"J": "thread-down",
|
||||
"<previous>": "chat-up",
|
||||
"C-b": "chat-up",
|
||||
"C-u": "chat-up",
|
||||
@ -120,7 +87,6 @@ func getDefaultConfig() Config {
|
||||
"C-d": "chat-down",
|
||||
"n": "channel-search-next",
|
||||
"N": "channel-search-prev",
|
||||
"'": "channel-jump",
|
||||
"q": "quit",
|
||||
"<f1>": "help",
|
||||
},
|
||||
@ -162,7 +128,6 @@ func getDefaultConfig() Config {
|
||||
Message: Message{
|
||||
Time: "",
|
||||
TimeFormat: "15:04",
|
||||
Thread: "fg-bold",
|
||||
Name: "",
|
||||
Text: "",
|
||||
},
|
||||
|
@ -18,7 +18,6 @@ type View struct {
|
||||
type Message struct {
|
||||
Time string `json:"time"`
|
||||
Name string `json:"name"`
|
||||
Thread string `json:"thread"`
|
||||
Text string `json:"text"`
|
||||
TimeFormat string `json:"time_format"`
|
||||
}
|
||||
|
@ -19,9 +19,6 @@ const (
|
||||
CommandMode = "command"
|
||||
InsertMode = "insert"
|
||||
SearchMode = "search"
|
||||
|
||||
ChatFocus = iota
|
||||
ThreadFocus
|
||||
)
|
||||
|
||||
type AppContext struct {
|
||||
@ -34,7 +31,6 @@ type AppContext struct {
|
||||
Config *config.Config
|
||||
Debug bool
|
||||
Mode string
|
||||
Focus int
|
||||
Notify *notificator.Notificator
|
||||
}
|
||||
|
||||
@ -89,58 +85,32 @@ func CreateAppContext(flgConfig string, flgToken string, flgDebug bool, version
|
||||
return nil, err
|
||||
}
|
||||
|
||||
columns := []*termui.Row{
|
||||
termui.NewCol(config.SidebarWidth, 0, view.Channels),
|
||||
}
|
||||
|
||||
threads := false
|
||||
if len(view.Threads.ChannelItems) > 0 {
|
||||
threads = true
|
||||
}
|
||||
|
||||
// Setup the interface
|
||||
if threads && flgDebug {
|
||||
columns = append(
|
||||
columns,
|
||||
[]*termui.Row{
|
||||
termui.NewCol(config.MainWidth-config.ThreadsWidth-3, 0, view.Chat),
|
||||
termui.NewCol(config.ThreadsWidth, 0, view.Threads),
|
||||
termui.NewCol(3, 0, view.Debug),
|
||||
}...,
|
||||
)
|
||||
} else if threads {
|
||||
columns = append(
|
||||
columns,
|
||||
[]*termui.Row{
|
||||
termui.NewCol(config.MainWidth-config.ThreadsWidth, 0, view.Chat),
|
||||
termui.NewCol(config.ThreadsWidth, 0, view.Threads),
|
||||
}...,
|
||||
)
|
||||
} else if flgDebug {
|
||||
columns = append(
|
||||
columns,
|
||||
[]*termui.Row{
|
||||
if flgDebug {
|
||||
termui.Body.AddRows(
|
||||
termui.NewRow(
|
||||
termui.NewCol(config.SidebarWidth, 0, view.Channels),
|
||||
termui.NewCol(config.MainWidth-5, 0, view.Chat),
|
||||
termui.NewCol(config.MainWidth-6, 0, view.Debug),
|
||||
}...,
|
||||
),
|
||||
termui.NewRow(
|
||||
termui.NewCol(config.SidebarWidth, 0, view.Mode),
|
||||
termui.NewCol(config.MainWidth, 0, view.Input),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
columns = append(
|
||||
columns,
|
||||
[]*termui.Row{
|
||||
termui.Body.AddRows(
|
||||
termui.NewRow(
|
||||
termui.NewCol(config.SidebarWidth, 0, view.Channels),
|
||||
termui.NewCol(config.MainWidth, 0, view.Chat),
|
||||
}...,
|
||||
),
|
||||
termui.NewRow(
|
||||
termui.NewCol(config.SidebarWidth, 0, view.Mode),
|
||||
termui.NewCol(config.MainWidth, 0, view.Input),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
termui.Body.AddRows(
|
||||
termui.NewRow(columns...),
|
||||
termui.NewRow(
|
||||
termui.NewCol(config.SidebarWidth, 0, view.Mode),
|
||||
termui.NewCol(config.MainWidth, 0, view.Input),
|
||||
),
|
||||
)
|
||||
|
||||
termui.Body.Align()
|
||||
termui.Render(termui.Body)
|
||||
|
||||
@ -154,7 +124,6 @@ func CreateAppContext(flgConfig string, flgToken string, flgDebug bool, version
|
||||
Config: config,
|
||||
Debug: flgDebug,
|
||||
Mode: CommandMode,
|
||||
Focus: ChatFocus,
|
||||
Notify: notify,
|
||||
}, nil
|
||||
}
|
||||
|
21
go.mod
21
go.mod
@ -1,21 +0,0 @@
|
||||
module github.com/erroneousboat/slack-term
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20171022182052-88d57ee9043b
|
||||
github.com/OpenPeeDeeP/xdg v0.2.0
|
||||
github.com/erroneousboat/termui v0.0.0-20170923115141-80f245cdfa04
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.0
|
||||
github.com/maruel/panicparse v1.1.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.7
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||
github.com/nsf/termbox-go v0.0.0-20191229070316-58d4fcbce2a7
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/slack-go/slack v0.6.3
|
||||
github.com/stretchr/testify v1.4.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4 // indirect
|
||||
)
|
53
go.sum
53
go.sum
@ -1,53 +0,0 @@
|
||||
github.com/0xAX/notificator v0.0.0-20171022182052-88d57ee9043b h1:Sn+u6zpXFyfm2X7ruh+z6SJiUVyFg8YElh6HIOhrRCA=
|
||||
github.com/0xAX/notificator v0.0.0-20171022182052-88d57ee9043b/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
|
||||
github.com/OpenPeeDeeP/xdg v0.2.0 h1:xr89rnllbkRkM7SV9Y++FJ8TGkbdkhhBQm5kOkGT7AE=
|
||||
github.com/OpenPeeDeeP/xdg v0.2.0/go.mod h1:tMoSueLQlMf0TCldjrJLNIjAc5qAOIcHt5REi88/Ygo=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/erroneousboat/termui v0.0.0-20170923115141-80f245cdfa04 h1:DaFwoQC0Neeb2y2CVFxDPrS1BeyWAkKc4VVBDTZ0N98=
|
||||
github.com/erroneousboat/termui v0.0.0-20170923115141-80f245cdfa04/go.mod h1:UPpsbgDrqmUayOFOkCGD7+xrBIml/1dA0dsqTRnZqac=
|
||||
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
||||
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lithammer/fuzzysearch v1.1.0 h1:go9v8tLCrNTTlH42OAaq4eHFe81TDHEnlrMEb6R4f+A=
|
||||
github.com/lithammer/fuzzysearch v1.1.0/go.mod h1:Bqx4wo8lTOFcJr3ckpY6HA9lEIOO0H5HrkJ5CsN56HQ=
|
||||
github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5IkRc=
|
||||
github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA=
|
||||
github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
|
||||
github.com/nsf/termbox-go v0.0.0-20191229070316-58d4fcbce2a7 h1:OkWEy7aQeQTbgdrcGi9bifx+Y6bMM7ae7y42hDFaBvA=
|
||||
github.com/nsf/termbox-go v0.0.0-20191229070316-58d4fcbce2a7/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/slack-go/slack v0.6.3 h1:qU037g8gQ71EuH6S9zYKnvYrEUj0fLFH4HFekFqBoRU=
|
||||
github.com/slack-go/slack v0.6.3/go.mod h1:HE4RwNe7YpOg/F0vqo5PwXH3Hki31TplTvKRW9dGGaw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
@ -11,8 +11,8 @@ import (
|
||||
|
||||
"github.com/0xAX/notificator"
|
||||
"github.com/erroneousboat/termui"
|
||||
"github.com/nlopes/slack"
|
||||
termbox "github.com/nsf/termbox-go"
|
||||
"github.com/slack-go/slack"
|
||||
|
||||
"github.com/erroneousboat/slack-term/components"
|
||||
"github.com/erroneousboat/slack-term/config"
|
||||
@ -44,16 +44,12 @@ var actionMap = map[string]func(*context.AppContext){
|
||||
"channel-bottom": actionMoveCursorBottomChannels,
|
||||
"channel-search-next": actionSearchNextChannels,
|
||||
"channel-search-prev": actionSearchPrevChannels,
|
||||
"channel-jump": actionJumpChannels,
|
||||
"thread-up": actionMoveCursorUpThreads,
|
||||
"thread-down": actionMoveCursorDownThreads,
|
||||
"chat-up": actionScrollUpChat,
|
||||
"chat-down": actionScrollDownChat,
|
||||
"help": actionHelp,
|
||||
}
|
||||
|
||||
// Initialize will start a combination of event handlers and 'background tasks'
|
||||
func Initialize(ctx *context.AppContext) {
|
||||
func RegisterEventHandlers(ctx *context.AppContext) {
|
||||
|
||||
// Keyboard events
|
||||
eventHandler(ctx)
|
||||
@ -119,12 +115,12 @@ func messageHandler(ctx *context.AppContext) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case rtmEvent := <-ctx.Service.RTM.IncomingEvents:
|
||||
switch ev := rtmEvent.Data.(type) {
|
||||
case msg := <-ctx.Service.RTM.IncomingEvents:
|
||||
switch ev := msg.Data.(type) {
|
||||
case *slack.MessageEvent:
|
||||
|
||||
// Construct message
|
||||
msg, err := ctx.Service.CreateMessageFromMessageEvent(ev, ev.Channel)
|
||||
msg, err := ctx.Service.CreateMessageFromMessageEvent(ev)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -132,33 +128,15 @@ func messageHandler(ctx *context.AppContext) {
|
||||
// Add message to the selected channel
|
||||
if ev.Channel == ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID {
|
||||
|
||||
// Get the thread timestamp of the event, we need to
|
||||
// check the previous message as well, because edited
|
||||
// message don't have the thread timestamp
|
||||
var threadTimestamp string
|
||||
if ev.ThreadTimestamp != "" {
|
||||
threadTimestamp = ev.ThreadTimestamp
|
||||
} else if ev.PreviousMessage != nil && ev.PreviousMessage.ThreadTimestamp != "" {
|
||||
threadTimestamp = ev.PreviousMessage.ThreadTimestamp
|
||||
} else {
|
||||
threadTimestamp = ""
|
||||
// Reverse order of messages, mainly done
|
||||
// when attachments are added to message
|
||||
for i := len(msg) - 1; i >= 0; i-- {
|
||||
ctx.View.Chat.AddMessage(
|
||||
msg[i],
|
||||
)
|
||||
}
|
||||
|
||||
// When timestamp isn't set this is a thread reply,
|
||||
// handle as such
|
||||
if threadTimestamp != "" {
|
||||
ctx.View.Chat.AddReply(threadTimestamp, msg)
|
||||
} else if threadTimestamp == "" && ctx.Focus == context.ChatFocus {
|
||||
ctx.View.Chat.AddMessage(msg)
|
||||
}
|
||||
|
||||
// we (mis)use actionChangeChannel, to rerender, the
|
||||
// view when a new thread has been started
|
||||
if ctx.View.Chat.IsNewThread(threadTimestamp) {
|
||||
actionChangeChannel(ctx)
|
||||
} else {
|
||||
termui.Render(ctx.View.Chat)
|
||||
}
|
||||
termui.Render(ctx.View.Chat)
|
||||
|
||||
// TODO: set Chat.Offset to 0, to automatically scroll
|
||||
// down?
|
||||
@ -225,64 +203,6 @@ func actionResizeEvent(ctx *context.AppContext, ev termbox.Event) {
|
||||
termui.Render(termui.Body)
|
||||
}
|
||||
|
||||
func actionRedrawGrid(ctx *context.AppContext, threads bool, debug bool) {
|
||||
termui.Clear()
|
||||
termui.Body = termui.NewGrid()
|
||||
termui.Body.X = 0
|
||||
termui.Body.Y = 0
|
||||
termui.Body.BgColor = termui.ThemeAttr("bg")
|
||||
termui.Body.Width = termui.TermWidth()
|
||||
|
||||
columns := []*termui.Row{
|
||||
termui.NewCol(ctx.Config.SidebarWidth, 0, ctx.View.Channels),
|
||||
}
|
||||
|
||||
if threads && debug {
|
||||
columns = append(
|
||||
columns,
|
||||
[]*termui.Row{
|
||||
termui.NewCol(ctx.Config.MainWidth-ctx.Config.ThreadsWidth-3, 0, ctx.View.Chat),
|
||||
termui.NewCol(ctx.Config.ThreadsWidth, 0, ctx.View.Threads),
|
||||
termui.NewCol(3, 0, ctx.View.Debug),
|
||||
}...,
|
||||
)
|
||||
} else if threads {
|
||||
columns = append(
|
||||
columns,
|
||||
[]*termui.Row{
|
||||
termui.NewCol(ctx.Config.MainWidth-ctx.Config.ThreadsWidth, 0, ctx.View.Chat),
|
||||
termui.NewCol(ctx.Config.ThreadsWidth, 0, ctx.View.Threads),
|
||||
}...,
|
||||
)
|
||||
} else if debug {
|
||||
columns = append(
|
||||
columns,
|
||||
[]*termui.Row{
|
||||
termui.NewCol(ctx.Config.MainWidth-5, 0, ctx.View.Chat),
|
||||
termui.NewCol(ctx.Config.MainWidth-6, 0, ctx.View.Debug),
|
||||
}...,
|
||||
)
|
||||
} else {
|
||||
columns = append(
|
||||
columns,
|
||||
[]*termui.Row{
|
||||
termui.NewCol(ctx.Config.MainWidth, 0, ctx.View.Chat),
|
||||
}...,
|
||||
)
|
||||
}
|
||||
|
||||
termui.Body.AddRows(
|
||||
termui.NewRow(columns...),
|
||||
termui.NewRow(
|
||||
termui.NewCol(ctx.Config.SidebarWidth, 0, ctx.View.Mode),
|
||||
termui.NewCol(ctx.Config.MainWidth, 0, ctx.View.Input),
|
||||
),
|
||||
)
|
||||
|
||||
termui.Body.Align()
|
||||
termui.Render(termui.Body)
|
||||
}
|
||||
|
||||
func actionInput(view *views.View, key rune) {
|
||||
view.Input.Insert(key)
|
||||
termui.Render(view.Input)
|
||||
@ -328,10 +248,10 @@ func actionSend(ctx *context.AppContext) {
|
||||
// quick succession of actionSend
|
||||
message := ctx.View.Input.GetText()
|
||||
ctx.View.Input.Clear()
|
||||
termui.Render(ctx.View.Input)
|
||||
ctx.View.Refresh()
|
||||
|
||||
// Send slash command
|
||||
isCmd, err := ctx.Service.SendCommand(
|
||||
// Send message
|
||||
err := ctx.Service.SendMessage(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
message,
|
||||
)
|
||||
@ -341,39 +261,10 @@ func actionSend(ctx *context.AppContext) {
|
||||
)
|
||||
}
|
||||
|
||||
// Send message
|
||||
if !isCmd {
|
||||
if ctx.Focus == context.ChatFocus {
|
||||
err := ctx.Service.SendMessage(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
message,
|
||||
)
|
||||
if err != nil {
|
||||
ctx.View.Debug.Println(
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ctx.Focus == context.ThreadFocus {
|
||||
err := ctx.Service.SendReply(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.View.Threads.ChannelItems[ctx.View.Threads.SelectedChannel].ID,
|
||||
message,
|
||||
)
|
||||
if err != nil {
|
||||
ctx.View.Debug.Println(
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear notification icon if there is any
|
||||
channelItem := ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel]
|
||||
if channelItem.Notification {
|
||||
ctx.Service.MarkAsRead(channelItem)
|
||||
ctx.Service.MarkAsRead(channelItem.ID)
|
||||
ctx.View.Channels.MarkAsRead(ctx.View.Channels.SelectedChannel)
|
||||
}
|
||||
termui.Render(ctx.View.Channels)
|
||||
@ -426,7 +317,7 @@ func actionSearchMode(ctx *context.AppContext) {
|
||||
}
|
||||
|
||||
func actionGetMessages(ctx *context.AppContext) {
|
||||
msgs, _, err := ctx.Service.GetMessages(
|
||||
msgs, err := ctx.Service.GetMessages(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.View.Chat.GetMaxItems(),
|
||||
)
|
||||
@ -501,18 +392,13 @@ func actionSearchPrevChannels(ctx *context.AppContext) {
|
||||
actionChangeChannel(ctx)
|
||||
}
|
||||
|
||||
func actionJumpChannels(ctx *context.AppContext) {
|
||||
ctx.View.Channels.Jump()
|
||||
actionChangeChannel(ctx)
|
||||
}
|
||||
|
||||
func actionChangeChannel(ctx *context.AppContext) {
|
||||
// Clear messages from Chat pane
|
||||
ctx.View.Chat.ClearMessages()
|
||||
|
||||
// Get messages of the SelectedChannel, and get the count of messages
|
||||
// that fit into the Chat component
|
||||
msgs, threads, err := ctx.Service.GetMessages(
|
||||
msgs, err := ctx.Service.GetMessages(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.View.Chat.GetMaxItems(),
|
||||
)
|
||||
@ -525,23 +411,6 @@ func actionChangeChannel(ctx *context.AppContext) {
|
||||
// Set messages for the channel
|
||||
ctx.View.Chat.SetMessages(msgs)
|
||||
|
||||
// Set the threads identifiers in the threads pane
|
||||
var haveThreads bool
|
||||
if len(threads) > 0 {
|
||||
haveThreads = true
|
||||
|
||||
// Make the first thread the current Channel
|
||||
ctx.View.Threads.SetChannels(
|
||||
append(
|
||||
[]components.ChannelItem{ctx.View.Channels.GetSelectedChannel()},
|
||||
threads...,
|
||||
),
|
||||
)
|
||||
|
||||
// Reset position of SelectedChannel
|
||||
ctx.View.Threads.MoveCursorTop()
|
||||
}
|
||||
|
||||
// Set channel name for the Chat pane
|
||||
ctx.View.Chat.SetBorderLabel(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].GetChannelName(),
|
||||
@ -550,105 +419,14 @@ func actionChangeChannel(ctx *context.AppContext) {
|
||||
// Clear notification icon if there is any
|
||||
channelItem := ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel]
|
||||
if channelItem.Notification {
|
||||
ctx.Service.MarkAsRead(channelItem)
|
||||
ctx.Service.MarkAsRead(channelItem.ID)
|
||||
ctx.View.Channels.MarkAsRead(ctx.View.Channels.SelectedChannel)
|
||||
}
|
||||
|
||||
// Redraw grid, necessary when threads and/or debug is set. We will redraw
|
||||
// the grid when there are threads, or we just came from a thread and went
|
||||
// to a channel without threads. Hence the clearing of ChannelItems of
|
||||
// Threads.
|
||||
if haveThreads {
|
||||
actionRedrawGrid(ctx, haveThreads, ctx.Debug)
|
||||
} else if !haveThreads && len(ctx.View.Threads.ChannelItems) > 0 {
|
||||
ctx.View.Threads.SetChannels([]components.ChannelItem{})
|
||||
actionRedrawGrid(ctx, haveThreads, ctx.Debug)
|
||||
} else {
|
||||
termui.Render(ctx.View.Threads)
|
||||
termui.Render(ctx.View.Channels)
|
||||
termui.Render(ctx.View.Chat)
|
||||
}
|
||||
|
||||
// Set focus, necessary to know when replying to thread or chat
|
||||
ctx.Focus = context.ChatFocus
|
||||
}
|
||||
|
||||
func actionChangeThread(ctx *context.AppContext) {
|
||||
// Clear messages from Chat pane
|
||||
ctx.View.Chat.ClearMessages()
|
||||
|
||||
// The first channel in the Thread list is current Channel. Set context
|
||||
// Focus and messages accordingly.
|
||||
var err error
|
||||
msgs := []components.Message{}
|
||||
if ctx.View.Threads.SelectedChannel == 0 {
|
||||
ctx.Focus = context.ChatFocus
|
||||
|
||||
msgs, _, err = ctx.Service.GetMessages(
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.View.Chat.GetMaxItems(),
|
||||
)
|
||||
if err != nil {
|
||||
termbox.Close()
|
||||
log.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
} else {
|
||||
ctx.Focus = context.ThreadFocus
|
||||
|
||||
msgs, err = ctx.Service.GetMessageByID(
|
||||
ctx.View.Threads.ChannelItems[ctx.View.Threads.SelectedChannel].ID,
|
||||
ctx.View.Channels.ChannelItems[ctx.View.Channels.SelectedChannel].ID,
|
||||
)
|
||||
if err != nil {
|
||||
termbox.Close()
|
||||
log.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// Set messages for the channel
|
||||
ctx.View.Chat.SetMessages(msgs)
|
||||
|
||||
termui.Render(ctx.View.Channels)
|
||||
termui.Render(ctx.View.Threads)
|
||||
termui.Render(ctx.View.Chat)
|
||||
}
|
||||
|
||||
func actionMoveCursorUpThreads(ctx *context.AppContext) {
|
||||
go func() {
|
||||
if scrollTimer != nil {
|
||||
scrollTimer.Stop()
|
||||
}
|
||||
|
||||
ctx.View.Threads.MoveCursorUp()
|
||||
termui.Render(ctx.View.Threads)
|
||||
|
||||
scrollTimer = time.NewTimer(time.Second / 4)
|
||||
<-scrollTimer.C
|
||||
|
||||
// Only actually change channel when the timer expires
|
||||
actionChangeThread(ctx)
|
||||
}()
|
||||
}
|
||||
|
||||
func actionMoveCursorDownThreads(ctx *context.AppContext) {
|
||||
go func() {
|
||||
if scrollTimer != nil {
|
||||
scrollTimer.Stop()
|
||||
}
|
||||
|
||||
ctx.View.Threads.MoveCursorDown()
|
||||
termui.Render(ctx.View.Threads)
|
||||
|
||||
scrollTimer = time.NewTimer(time.Second / 4)
|
||||
<-scrollTimer.C
|
||||
|
||||
// Only actually change thread when the timer expires
|
||||
actionChangeThread(ctx)
|
||||
}()
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@ -702,7 +480,6 @@ func actionScrollDownChat(ctx *context.AppContext) {
|
||||
}
|
||||
|
||||
func actionHelp(ctx *context.AppContext) {
|
||||
ctx.View.Chat.ClearMessages()
|
||||
ctx.View.Chat.Help(ctx.Usage, ctx.Config)
|
||||
termui.Render(ctx.View.Chat)
|
||||
}
|
||||
|
19
main.go
19
main.go
@ -5,8 +5,9 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
|
||||
"github.com/OpenPeeDeeP/xdg"
|
||||
"github.com/erroneousboat/termui"
|
||||
termbox "github.com/nsf/termbox-go"
|
||||
|
||||
@ -15,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
VERSION = "master"
|
||||
VERSION = "v0.4.1"
|
||||
USAGE = `NAME:
|
||||
slack-term - slack client for your terminal
|
||||
|
||||
@ -44,15 +45,17 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
// Find the default config file
|
||||
configFile := xdg.New("slack-term", "").QueryConfig("config")
|
||||
// Get home dir for config file default
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Parse flags
|
||||
flag.StringVar(
|
||||
&flgConfig,
|
||||
"config",
|
||||
configFile,
|
||||
path.Join(usr.HomeDir, ".slack-term"),
|
||||
"location of config file",
|
||||
)
|
||||
|
||||
@ -105,8 +108,8 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Initialize handlers
|
||||
handlers.Initialize(ctx)
|
||||
// Register handlers
|
||||
handlers.RegisterEventHandlers(ctx)
|
||||
|
||||
termui.Loop()
|
||||
}
|
||||
|
601
service/slack.go
601
service/slack.go
@ -3,9 +3,6 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"log"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
@ -13,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/nlopes/slack"
|
||||
|
||||
"github.com/erroneousboat/slack-term/components"
|
||||
"github.com/erroneousboat/slack-term/config"
|
||||
@ -25,7 +22,6 @@ type SlackService struct {
|
||||
RTM *slack.RTM
|
||||
Conversations []slack.Channel
|
||||
UserCache map[string]string
|
||||
ThreadCache map[string]string
|
||||
CurrentUserID string
|
||||
CurrentUsername string
|
||||
}
|
||||
@ -34,10 +30,9 @@ type SlackService struct {
|
||||
// the RTM and a Client
|
||||
func NewSlackService(config *config.Config) (*SlackService, error) {
|
||||
svc := &SlackService{
|
||||
Config: config,
|
||||
Client: slack.New(config.SlackToken),
|
||||
UserCache: make(map[string]string),
|
||||
ThreadCache: make(map[string]string),
|
||||
Config: config,
|
||||
Client: slack.New(config.SlackToken),
|
||||
UserCache: make(map[string]string),
|
||||
}
|
||||
|
||||
// Get user associated with token, mainly
|
||||
@ -63,13 +58,12 @@ func NewSlackService(config *config.Config) (*SlackService, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get name of current user, and set presence to active
|
||||
// Get name of current user
|
||||
currentUser, err := svc.Client.GetUserInfo(svc.CurrentUserID)
|
||||
if err != nil {
|
||||
svc.CurrentUsername = "slack-term"
|
||||
}
|
||||
svc.CurrentUsername = currentUser.Name
|
||||
svc.SetUserAsActive()
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
@ -145,10 +139,6 @@ func (s *SlackService) GetChannels() ([]components.ChannelItem, error) {
|
||||
|
||||
chanItem.Type = components.ChannelTypeChannel
|
||||
|
||||
if chn.UnreadCount > 0 {
|
||||
chanItem.Notification = true
|
||||
}
|
||||
|
||||
buckets[0][chn.ID] = &tempChan{
|
||||
channelItem: chanItem,
|
||||
slackChannel: chn,
|
||||
@ -168,10 +158,6 @@ func (s *SlackService) GetChannels() ([]components.ChannelItem, error) {
|
||||
|
||||
chanItem.Type = components.ChannelTypeMpIM
|
||||
|
||||
if chn.UnreadCount > 0 {
|
||||
chanItem.Notification = true
|
||||
}
|
||||
|
||||
buckets[2][chn.ID] = &tempChan{
|
||||
channelItem: chanItem,
|
||||
slackChannel: chn,
|
||||
@ -180,10 +166,6 @@ func (s *SlackService) GetChannels() ([]components.ChannelItem, error) {
|
||||
|
||||
chanItem.Type = components.ChannelTypeGroup
|
||||
|
||||
if chn.UnreadCount > 0 {
|
||||
chanItem.Notification = true
|
||||
}
|
||||
|
||||
buckets[1][chn.ID] = &tempChan{
|
||||
channelItem: chanItem,
|
||||
slackChannel: chn,
|
||||
@ -205,10 +187,6 @@ func (s *SlackService) GetChannels() ([]components.ChannelItem, error) {
|
||||
chanItem.Type = components.ChannelTypeIM
|
||||
chanItem.Presence = "away"
|
||||
|
||||
if chn.UnreadCount > 0 {
|
||||
chanItem.Notification = true
|
||||
}
|
||||
|
||||
buckets[3][chn.User] = &tempChan{
|
||||
channelItem: chanItem,
|
||||
slackChannel: chn,
|
||||
@ -260,51 +238,26 @@ func (s *SlackService) GetUserPresence(userID string) (string, error) {
|
||||
return presence.Presence, nil
|
||||
}
|
||||
|
||||
// Set current user presence to active
|
||||
func (s *SlackService) SetUserAsActive() {
|
||||
s.Client.SetUserPresence("auto")
|
||||
}
|
||||
|
||||
// MarkAsRead will set the channel as read
|
||||
func (s *SlackService) MarkAsRead(channelItem components.ChannelItem) {
|
||||
switch channelItem.Type {
|
||||
case components.ChannelTypeChannel:
|
||||
s.Client.SetChannelReadMark(
|
||||
channelItem.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
case components.ChannelTypeGroup:
|
||||
s.Client.SetGroupReadMark(
|
||||
channelItem.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
case components.ChannelTypeMpIM:
|
||||
s.Client.MarkIMChannel(
|
||||
channelItem.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
case components.ChannelTypeIM:
|
||||
s.Client.MarkIMChannel(
|
||||
channelItem.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
}
|
||||
func (s *SlackService) MarkAsRead(channelID string) {
|
||||
s.Client.SetChannelReadMark(
|
||||
channelID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
}
|
||||
|
||||
// SendMessage will send a message to a particular channel
|
||||
func (s *SlackService) SendMessage(channelID string, message string) error {
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#PostMessageParameters
|
||||
postParams := slack.MsgOptionPostMessageParameters(slack.PostMessageParameters{
|
||||
postParams := slack.PostMessageParameters{
|
||||
AsUser: true,
|
||||
Username: s.CurrentUsername,
|
||||
LinkNames: 1,
|
||||
})
|
||||
|
||||
text := slack.MsgOptionText(message, true)
|
||||
}
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#Client.PostMessage
|
||||
_, _, err := s.Client.PostMessage(channelID, text, postParams)
|
||||
_, _, err := s.Client.PostMessage(channelID, message, postParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -312,103 +265,9 @@ func (s *SlackService) SendMessage(channelID string, message string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendReply will send a message to a particular thread, specifying the
|
||||
// ThreadTimestamp will make it reply to that specific thread. (see:
|
||||
// https://api.slack.com/docs/message-threading, 'Posting replies')
|
||||
func (s *SlackService) SendReply(channelID string, threadID string, message string) error {
|
||||
// https://godoc.org/github.com/nlopes/slack#PostMessageParameters
|
||||
postParams := slack.MsgOptionPostMessageParameters(slack.PostMessageParameters{
|
||||
AsUser: true,
|
||||
Username: s.CurrentUsername,
|
||||
LinkNames: 1,
|
||||
ThreadTimestamp: threadID,
|
||||
})
|
||||
|
||||
text := slack.MsgOptionText(message, true)
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#Client.PostMessage
|
||||
_, _, err := s.Client.PostMessage(channelID, text, postParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendCommand will send a specific command to slack. First we check
|
||||
// wether we are dealing with a command, and if it is one of the supported
|
||||
// ones.
|
||||
//
|
||||
// NOTE: slack slash commands that are sent to the slack api are undocumented,
|
||||
// and as such we need to update the message option that direct it to the
|
||||
// correct api endpoint.
|
||||
//
|
||||
// https://github.com/ErikKalkoken/slackApiDoc/blob/master/chat.command.md
|
||||
func (s *SlackService) SendCommand(channelID string, message string) (bool, error) {
|
||||
// First check if it begins with slash and a command
|
||||
r, err := regexp.Compile(`^/\w+`)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
match := r.MatchString(message)
|
||||
if !match {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Execute the the command when supported
|
||||
switch r.FindString(message) {
|
||||
case "/thread":
|
||||
r := regexp.MustCompile(`(?P<cmd>^/\w+) (?P<id>\w+) (?P<msg>.*)`)
|
||||
subMatch := r.FindStringSubmatch(message)
|
||||
|
||||
if len(subMatch) < 4 {
|
||||
return false, errors.New("'/thread' command malformed")
|
||||
}
|
||||
|
||||
threadID := s.ThreadCache[subMatch[2]]
|
||||
msg := subMatch[3]
|
||||
|
||||
err := s.SendReply(channelID, threadID, msg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
default:
|
||||
r := regexp.MustCompile(`(?P<cmd>^/\w+) (?P<text>.*)`)
|
||||
subMatch := r.FindStringSubmatch(message)
|
||||
|
||||
if len(subMatch) < 3 {
|
||||
return false, errors.New("slash command malformed")
|
||||
}
|
||||
|
||||
cmd := subMatch[1]
|
||||
text := subMatch[2]
|
||||
|
||||
msgOption := slack.UnsafeMsgOptionEndpoint(
|
||||
fmt.Sprintf("%s%s", slack.APIURL, "chat.command"),
|
||||
func(urlValues url.Values) {
|
||||
urlValues.Add("command", cmd)
|
||||
urlValues.Add("text", text)
|
||||
},
|
||||
)
|
||||
|
||||
_, _, err := s.Client.PostMessage(channelID, msgOption)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// GetMessages will get messages for a channel, group or im channel delimited
|
||||
// by a count. It will return the messages, the thread identifiers
|
||||
// (as ChannelItem), and and error.
|
||||
func (s *SlackService) GetMessages(channelID string, count int) ([]components.Message, []components.ChannelItem, error) {
|
||||
// by a count.
|
||||
func (s *SlackService) GetMessages(channelID string, count int) ([]components.Message, error) {
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#GetConversationHistoryParameters
|
||||
historyParams := slack.GetConversationHistoryParameters{
|
||||
@ -419,27 +278,14 @@ func (s *SlackService) GetMessages(channelID string, count int) ([]components.Me
|
||||
|
||||
history, err := s.Client.GetConversationHistory(&historyParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Construct the messages
|
||||
var messages []components.Message
|
||||
var threads []components.ChannelItem
|
||||
for _, message := range history.Messages {
|
||||
msg := s.CreateMessage(message, channelID)
|
||||
messages = append(messages, msg)
|
||||
|
||||
// FIXME: create boolean isThread
|
||||
if msg.Thread != "" {
|
||||
threads = append(threads, components.ChannelItem{
|
||||
ID: msg.ID,
|
||||
Name: msg.Thread,
|
||||
Type: components.ChannelTypeGroup,
|
||||
StylePrefix: s.Config.Theme.Channel.Prefix,
|
||||
StyleIcon: s.Config.Theme.Channel.Icon,
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
})
|
||||
}
|
||||
msg := s.CreateMessage(message)
|
||||
messages = append(messages, msg...)
|
||||
}
|
||||
|
||||
// Reverse the order of the messages, we want the newest in
|
||||
@ -449,45 +295,18 @@ func (s *SlackService) GetMessages(channelID string, count int) ([]components.Me
|
||||
messagesReversed = append(messagesReversed, messages[i])
|
||||
}
|
||||
|
||||
return messagesReversed, threads, nil
|
||||
}
|
||||
|
||||
// CreateMessageByID will construct an array of components.Message with only
|
||||
// 1 message, using the message ID (Timestamp).
|
||||
//
|
||||
// For the choice of history parameters see:
|
||||
// https://api.slack.com/messaging/retrieving
|
||||
func (s *SlackService) GetMessageByID(messageID string, channelID string) ([]components.Message, error) {
|
||||
|
||||
var msgs []components.Message
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#GetConversationHistoryParameters
|
||||
historyParams := slack.GetConversationHistoryParameters{
|
||||
ChannelID: channelID,
|
||||
Limit: 1,
|
||||
Inclusive: true,
|
||||
Latest: messageID,
|
||||
}
|
||||
|
||||
history, err := s.Client.GetConversationHistory(&historyParams)
|
||||
if err != nil {
|
||||
return msgs, err
|
||||
}
|
||||
|
||||
// We break because we're only asking for 1 message
|
||||
for _, message := range history.Messages {
|
||||
msgs = append(msgs, s.CreateMessage(message, channelID))
|
||||
break
|
||||
}
|
||||
|
||||
return msgs, nil
|
||||
return messagesReversed, nil
|
||||
}
|
||||
|
||||
// CreateMessage will create a string formatted message that can be rendered
|
||||
// in the Chat pane.
|
||||
//
|
||||
// [23:59] <erroneousboat> Hello world!
|
||||
func (s *SlackService) CreateMessage(message slack.Message, channelID string) components.Message {
|
||||
//
|
||||
// This returns an array of string because we will try to uncover attachments
|
||||
// associated with messages.
|
||||
func (s *SlackService) CreateMessage(message slack.Message) []components.Message {
|
||||
var msgs []components.Message
|
||||
var name string
|
||||
|
||||
// Get username from cache
|
||||
@ -496,21 +315,12 @@ func (s *SlackService) CreateMessage(message slack.Message, channelID string) co
|
||||
// Name not in cache
|
||||
if !ok {
|
||||
if message.BotID != "" {
|
||||
// Name not found, perhaps a bot, use Username
|
||||
name, ok = s.UserCache[message.BotID]
|
||||
if !ok {
|
||||
if message.Username != "" {
|
||||
name = message.Username
|
||||
s.UserCache[message.BotID] = message.Username
|
||||
} else {
|
||||
bot, err := s.Client.GetBotInfo(message.BotID)
|
||||
if err != nil {
|
||||
name = "unkown"
|
||||
s.UserCache[message.BotID] = name
|
||||
} else {
|
||||
name = bot.Name
|
||||
s.UserCache[message.BotID] = bot.Name
|
||||
}
|
||||
}
|
||||
// Not found in cache, add it
|
||||
name = message.Username
|
||||
s.UserCache[message.BotID] = message.Username
|
||||
}
|
||||
} else {
|
||||
// Not a bot, not in cache, get user info
|
||||
@ -529,6 +339,11 @@ func (s *SlackService) CreateMessage(message slack.Message, channelID string) co
|
||||
name = "unknown"
|
||||
}
|
||||
|
||||
// When there are attachments append them
|
||||
if len(message.Attachments) > 0 {
|
||||
msgs = append(msgs, s.CreateMessageFromAttachments(message.Attachments)...)
|
||||
}
|
||||
|
||||
// Parse time
|
||||
floatTime, err := strconv.ParseFloat(message.Timestamp, 64)
|
||||
if err != nil {
|
||||
@ -538,240 +353,96 @@ func (s *SlackService) CreateMessage(message slack.Message, channelID string) co
|
||||
|
||||
// Format message
|
||||
msg := components.Message{
|
||||
ID: message.Timestamp,
|
||||
Messages: make(map[string]components.Message),
|
||||
Time: time.Unix(intTime, 0),
|
||||
Name: name,
|
||||
Content: parseMessage(s, message.Text),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleThread: s.Config.Theme.Message.Thread,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
Time: time.Unix(intTime, 0),
|
||||
Name: name,
|
||||
Content: parseMessage(s, message.Text),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
}
|
||||
|
||||
// When there are attachments, add them to Messages
|
||||
//
|
||||
// NOTE: attachments don't have an id or a timestamp that we can
|
||||
// use as a key value for the Messages field, so we use the index
|
||||
// of the returned array.
|
||||
if len(message.Attachments) > 0 {
|
||||
atts := s.CreateMessageFromAttachments(message.Attachments)
|
||||
|
||||
for i, a := range atts {
|
||||
msg.Messages[strconv.Itoa(i)] = a
|
||||
}
|
||||
}
|
||||
|
||||
// When there are files, add them to Messages
|
||||
if len(message.Files) > 0 {
|
||||
files := s.CreateMessageFromFiles(message.Files)
|
||||
for _, file := range files {
|
||||
msg.Messages[file.ID] = file
|
||||
}
|
||||
}
|
||||
|
||||
// When the message timestamp and thread timestamp are the same, we
|
||||
// have a parent message. This means it contains a thread with replies.
|
||||
//
|
||||
// Additionally, we set the thread timestamp in the s.ThreadCache with
|
||||
// the base62 representation of the timestamp. We do this because
|
||||
// we if we want to reply to a thread, we need to reference this
|
||||
// timestamp. Which is too long to type, we shorten it and remember the
|
||||
// reference in the cache.
|
||||
if message.ThreadTimestamp != "" && message.ThreadTimestamp == message.Timestamp {
|
||||
|
||||
// Set the thread identifier for thread cache
|
||||
f, _ := strconv.ParseFloat(message.ThreadTimestamp, 64)
|
||||
threadID := hashID(int(f))
|
||||
s.ThreadCache[threadID] = message.ThreadTimestamp
|
||||
|
||||
// Set thread prefix for message
|
||||
msg.Thread = fmt.Sprintf("%s ", threadID)
|
||||
|
||||
// Create the message replies from the thread
|
||||
replies := s.CreateMessageFromReplies(message.ThreadTimestamp, channelID)
|
||||
for _, reply := range replies {
|
||||
msg.Messages[reply.ID] = reply
|
||||
}
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
// CreateMessageFromReplies will create components.Message struct from
|
||||
// the conversation replies from slack.
|
||||
//
|
||||
// Useful documentation:
|
||||
//
|
||||
// https://api.slack.com/docs/message-threading
|
||||
// https://api.slack.com/methods/conversations.replies
|
||||
// https://godoc.org/github.com/nlopes/slack#Client.GetConversationReplies
|
||||
// https://godoc.org/github.com/nlopes/slack#GetConversationRepliesParameters
|
||||
func (s *SlackService) CreateMessageFromReplies(messageID string, channelID string) []components.Message {
|
||||
msgs := make([]slack.Message, 0)
|
||||
|
||||
initReplies, _, initCur, err := s.Client.GetConversationReplies(
|
||||
&slack.GetConversationRepliesParameters{
|
||||
ChannelID: channelID,
|
||||
Timestamp: messageID,
|
||||
Limit: 200,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
|
||||
msgs = append(msgs, initReplies...)
|
||||
|
||||
nextCur := initCur
|
||||
for nextCur != "" {
|
||||
conversationReplies, _, cursor, err := s.Client.GetConversationReplies(&slack.GetConversationRepliesParameters{
|
||||
ChannelID: channelID,
|
||||
Timestamp: messageID,
|
||||
Cursor: nextCur,
|
||||
Limit: 200,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err) // FIXME
|
||||
}
|
||||
|
||||
msgs = append(msgs, conversationReplies...)
|
||||
nextCur = cursor
|
||||
}
|
||||
|
||||
var replies []components.Message
|
||||
for _, reply := range msgs {
|
||||
// Because the conversations api returns an entire thread (a
|
||||
// message plus all the messages in reply), we need to check if
|
||||
// one of the replies isn't the parent that we started with.
|
||||
//
|
||||
// Keep in mind that the api returns the replies with the latest
|
||||
// as the first element.
|
||||
if reply.ThreadTimestamp != "" && reply.ThreadTimestamp == reply.Timestamp {
|
||||
continue
|
||||
}
|
||||
|
||||
msg := s.CreateMessage(reply, channelID)
|
||||
|
||||
// Set the thread separator
|
||||
msg.Thread = " "
|
||||
|
||||
replies = append(replies, msg)
|
||||
}
|
||||
|
||||
return replies
|
||||
}
|
||||
|
||||
// CreateMessageFromAttachments will construct an array of strings from the
|
||||
// Field values of Attachments of a Message.
|
||||
func (s *SlackService) CreateMessageFromAttachments(atts []slack.Attachment) []components.Message {
|
||||
var msgs []components.Message
|
||||
for _, att := range atts {
|
||||
for _, field := range att.Fields {
|
||||
msgs = append(msgs, components.Message{
|
||||
Content: fmt.Sprintf(
|
||||
"%s %s",
|
||||
field.Title,
|
||||
field.Value,
|
||||
),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleThread: s.Config.Theme.Message.Thread,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if att.Pretext != "" {
|
||||
msgs = append(
|
||||
msgs,
|
||||
components.Message{
|
||||
Content: fmt.Sprintf("%s", att.Pretext),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleThread: s.Config.Theme.Message.Thread,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if att.Text != "" {
|
||||
msgs = append(
|
||||
msgs,
|
||||
components.Message{
|
||||
Content: fmt.Sprintf("%s", att.Text),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleThread: s.Config.Theme.Message.Thread,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if att.Title != "" {
|
||||
msgs = append(
|
||||
msgs,
|
||||
components.Message{
|
||||
Content: fmt.Sprintf("%s", att.Title),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleThread: s.Config.Theme.Message.Thread,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
msgs = append(msgs, msg)
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
// CreateMessageFromFiles will create components.Message struct from
|
||||
// conversation attached files
|
||||
func (s *SlackService) CreateMessageFromFiles(files []slack.File) []components.Message {
|
||||
func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent) ([]components.Message, error) {
|
||||
|
||||
var msgs []components.Message
|
||||
|
||||
for _, file := range files {
|
||||
msgs = append(msgs, components.Message{
|
||||
Content: fmt.Sprintf(
|
||||
"%s %s", file.Title, file.URLPrivate,
|
||||
),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleThread: s.Config.Theme.Message.Thread,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent, channelID string) (components.Message, error) {
|
||||
msg := slack.Message{Msg: message.Msg}
|
||||
var name string
|
||||
|
||||
switch message.SubType {
|
||||
case "message_changed":
|
||||
// Append (edited) when an edited message is received
|
||||
msg = slack.Message{Msg: *message.SubMessage}
|
||||
msg.Text = fmt.Sprintf("%s (edited)", msg.Text)
|
||||
message = &slack.MessageEvent{Msg: *message.SubMessage}
|
||||
message.Text = fmt.Sprintf("%s (edited)", message.Text)
|
||||
case "message_replied":
|
||||
return components.Message{}, errors.New("ignoring reply events")
|
||||
// Ignore reply events
|
||||
return nil, errors.New("ignoring reply events")
|
||||
}
|
||||
|
||||
return s.CreateMessage(msg, channelID), nil
|
||||
// Get username from cache
|
||||
name, ok := s.UserCache[message.User]
|
||||
|
||||
// Name not in cache
|
||||
if !ok {
|
||||
if message.BotID != "" {
|
||||
// Name not found, perhaps a bot, use Username
|
||||
name, ok = s.UserCache[message.BotID]
|
||||
if !ok {
|
||||
// Not found in cache, add it
|
||||
name = message.Username
|
||||
s.UserCache[message.BotID] = message.Username
|
||||
}
|
||||
} else {
|
||||
// Not a bot, not in cache, get user info
|
||||
user, err := s.Client.GetUserInfo(message.User)
|
||||
if err != nil {
|
||||
name = "unknown"
|
||||
s.UserCache[message.User] = name
|
||||
} else {
|
||||
name = user.Name
|
||||
s.UserCache[message.User] = user.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name = "unknown"
|
||||
}
|
||||
|
||||
// When there are attachments append them
|
||||
if len(message.Attachments) > 0 {
|
||||
msgs = append(msgs, s.CreateMessageFromAttachments(message.Attachments)...)
|
||||
}
|
||||
|
||||
// Parse time
|
||||
floatTime, err := strconv.ParseFloat(message.Timestamp, 64)
|
||||
if err != nil {
|
||||
floatTime = 0.0
|
||||
}
|
||||
intTime := int64(floatTime)
|
||||
|
||||
// Format message
|
||||
msg := components.Message{
|
||||
Time: time.Unix(intTime, 0),
|
||||
Name: name,
|
||||
Content: parseMessage(s, message.Text),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
}
|
||||
|
||||
msgs = append(msgs, msg)
|
||||
|
||||
return msgs, nil
|
||||
}
|
||||
|
||||
// parseMessage will parse a message string and find and replace:
|
||||
// - emoji's
|
||||
// - mentions
|
||||
// - html unescape
|
||||
func parseMessage(s *SlackService, msg string) string {
|
||||
if s.Config.Emoji {
|
||||
msg = parseEmoji(msg)
|
||||
@ -779,8 +450,6 @@ func parseMessage(s *SlackService, msg string) string {
|
||||
|
||||
msg = parseMentions(s, msg)
|
||||
|
||||
msg = html.UnescapeString(msg)
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
@ -845,6 +514,56 @@ func parseEmoji(msg string) string {
|
||||
)
|
||||
}
|
||||
|
||||
// CreateMessageFromAttachments will construct a array of string of the Field
|
||||
// values of Attachments from a Message.
|
||||
func (s *SlackService) CreateMessageFromAttachments(atts []slack.Attachment) []components.Message {
|
||||
var msgs []components.Message
|
||||
for _, att := range atts {
|
||||
for i := len(att.Fields) - 1; i >= 0; i-- {
|
||||
msgs = append(msgs, components.Message{
|
||||
Content: fmt.Sprintf(
|
||||
"%s %s",
|
||||
att.Fields[i].Title,
|
||||
att.Fields[i].Value,
|
||||
),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if att.Text != "" {
|
||||
msgs = append(
|
||||
msgs,
|
||||
components.Message{
|
||||
Content: fmt.Sprintf("%s", att.Text),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if att.Title != "" {
|
||||
msgs = append(
|
||||
msgs,
|
||||
components.Message{
|
||||
Content: fmt.Sprintf("%s", att.Title),
|
||||
StyleTime: s.Config.Theme.Message.Time,
|
||||
StyleName: s.Config.Theme.Message.Name,
|
||||
StyleText: s.Config.Theme.Message.Text,
|
||||
FormatTime: s.Config.Theme.Message.TimeFormat,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
func (s *SlackService) createChannelItem(chn slack.Channel) components.ChannelItem {
|
||||
return components.ChannelItem{
|
||||
ID: chn.ID,
|
||||
@ -856,15 +575,3 @@ func (s *SlackService) createChannelItem(chn slack.Channel) components.ChannelIt
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
}
|
||||
}
|
||||
|
||||
func hashID(input int) string {
|
||||
const base62Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
|
||||
|
||||
hash := ""
|
||||
for input > 0 {
|
||||
hash = string(base62Alphabet[input%62]) + hash
|
||||
input = int(input / 62)
|
||||
}
|
||||
|
||||
return hash
|
||||
}
|
||||
|
28
snapcraft.yaml
Normal file
28
snapcraft.yaml
Normal file
@ -0,0 +1,28 @@
|
||||
name: slack-term
|
||||
version: git
|
||||
summary: Slack client for your terminal
|
||||
description: |
|
||||
A Slack client for your terminal.
|
||||
* Get a slack token from https://api.slack.com/docs/oauth-test-tokens
|
||||
* Create $HOME/snap/slack-term/current/slack-term.json
|
||||
* Contents detailed at https://github.com/erroneousboat/slack-term
|
||||
* slack-term --config $HOME/snap/slack-term/current/slack-term.json
|
||||
|
||||
grade: stable
|
||||
confinement: strict
|
||||
|
||||
apps:
|
||||
slack-term:
|
||||
command: slack-term
|
||||
plugs:
|
||||
- network
|
||||
- home
|
||||
|
||||
parts:
|
||||
go:
|
||||
source-tag: go1.7.5
|
||||
slack-term:
|
||||
after: [go]
|
||||
source: .
|
||||
plugin: go
|
||||
go-importpath: github.com/erroneousboat/slack-term
|
3
vendor/github.com/OpenPeeDeeP/xdg/.gitignore
generated
vendored
3
vendor/github.com/OpenPeeDeeP/xdg/.gitignore
generated
vendored
@ -1,3 +0,0 @@
|
||||
*.test
|
||||
*.out
|
||||
.DS_STORE
|
12
vendor/github.com/OpenPeeDeeP/xdg/.travis.yml
generated
vendored
12
vendor/github.com/OpenPeeDeeP/xdg/.travis.yml
generated
vendored
@ -1,12 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.11.x
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
script:
|
||||
- go test -v -race -covermode=atomic -coverprofile=coverage.txt
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
29
vendor/github.com/OpenPeeDeeP/xdg/LICENSE
generated
vendored
29
vendor/github.com/OpenPeeDeeP/xdg/LICENSE
generated
vendored
@ -1,29 +0,0 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017, OpenPeeDeeP
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
25
vendor/github.com/OpenPeeDeeP/xdg/README.md
generated
vendored
25
vendor/github.com/OpenPeeDeeP/xdg/README.md
generated
vendored
@ -1,25 +0,0 @@
|
||||
# XDG [![Build status](https://ci.appveyor.com/api/projects/status/9eoupq9jgsu2p0jv?svg=true)](https://ci.appveyor.com/project/dixonwille/xdg) [![Build Status](https://travis-ci.org/OpenPeeDeeP/xdg.svg?branch=master)](https://travis-ci.org/OpenPeeDeeP/xdg) [![Go Report Card](https://goreportcard.com/badge/github.com/OpenPeeDeeP/xdg)](https://goreportcard.com/report/github.com/OpenPeeDeeP/xdg) [![GoDoc](https://godoc.org/github.com/OpenPeeDeeP/xdg?status.svg)](https://godoc.org/github.com/OpenPeeDeeP/xdg) [![codecov](https://codecov.io/gh/OpenPeeDeeP/xdg/branch/master/graph/badge.svg)](https://codecov.io/gh/OpenPeeDeeP/xdg)
|
||||
|
||||
A cross platform package that tries to follow [XDG Standard](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) when possible. Since XDG is linux specific, I am only able to follow standards to the T on linux. But for the other operating systems I am finding similar best practice locations for the files.
|
||||
|
||||
## Locations Per OS
|
||||
|
||||
The following table shows what is used if the envrionment variable is not set. If the variable is set then this package uses that. Linux follows the default standards. Mac does when it comes to the home directory but for system wide it uses the standard `/Library/Application Support`. As for Windows, the variable defaults are just other environment variables set up by the operation system.
|
||||
|
||||
> When creating `XDG` application the `Vendor` and `Application` names are appeneded to the end of the path to keep projects unique.
|
||||
|
||||
| | Linux | Mac | Windows |
|
||||
| ---: | :---: | :---: | :---: |
|
||||
| `XDG_DATA_DIRS` | [`/usr/local/share`, `/usr/share`] | [`/Library/Application Support`] | `%PROGRAMDATA%` |
|
||||
| `XDG_DATA_HOME` | `~/.local/share` | `~/Library/Application Support` | `%APPDATA%` |
|
||||
| `XDG_CONFIG_DIRS` | [`/etc/xdg`] | [`/Library/Application Support`] | `%PROGRAMDATA%` |
|
||||
| `XDG_CONFIG_HOME` | `~/.config` | `~/Library/Application Support` | `%APPDATA%` |
|
||||
| `XDG_CACHE_HOME` | `~/.cache` | `~/Library/Cache` | `%LOCALAPPDATA%` |
|
||||
|
||||
## Notes
|
||||
|
||||
- This package does not merge files if they exist across different directories.
|
||||
- The `Query` methods search through the system variables, `DIRS`, first (when using environment variables first in the variable has presidence). It then checks home variables, `HOME`.
|
||||
- This package will not create any directories for you. In the standard, it states the following:
|
||||
|
||||
> If, when attempting to write a file, the destination directory is non-existant an attempt should be made to create it with permission `0700`. If the destination directory exists already the permissions should not be changed. The application should be prepared to handle the case where the file could not be written, either because the directory was non-existant and could not be created, or for any other reason. In such case it may chose to present an error message to the user.
|
16
vendor/github.com/OpenPeeDeeP/xdg/appveyor.yml
generated
vendored
16
vendor/github.com/OpenPeeDeeP/xdg/appveyor.yml
generated
vendored
@ -1,16 +0,0 @@
|
||||
version: 0.0.1_{build}
|
||||
build: off
|
||||
platform: x64
|
||||
clone_folder: c:\gopath\src\github.com\OpenPeeDeeP\xdg
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
stack: go 1.11
|
||||
install:
|
||||
- go get -t -v ./...
|
||||
- cinst codecov
|
||||
before_test:
|
||||
- go vet ./...
|
||||
test_script:
|
||||
- go test -v -race -covermode=atomic -coverprofile=coverage.txt
|
||||
on_success:
|
||||
- codecov -f coverage.txt
|
8
vendor/github.com/OpenPeeDeeP/xdg/go.mod
generated
vendored
8
vendor/github.com/OpenPeeDeeP/xdg/go.mod
generated
vendored
@ -1,8 +0,0 @@
|
||||
module github.com/OpenPeeDeeP/xdg
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.1.1 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
)
|
8
vendor/github.com/OpenPeeDeeP/xdg/go.sum
generated
vendored
8
vendor/github.com/OpenPeeDeeP/xdg/go.sum
generated
vendored
@ -1,8 +0,0 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
163
vendor/github.com/OpenPeeDeeP/xdg/xdg.go
generated
vendored
163
vendor/github.com/OpenPeeDeeP/xdg/xdg.go
generated
vendored
@ -1,163 +0,0 @@
|
||||
// Copyright (c) 2017, OpenPeeDeeP. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package xdg impelements the XDG standard for application file locations.
|
||||
package xdg
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var defaulter xdgDefaulter = new(osDefaulter)
|
||||
|
||||
type xdgDefaulter interface {
|
||||
defaultDataHome() string
|
||||
defaultDataDirs() []string
|
||||
defaultConfigHome() string
|
||||
defaultConfigDirs() []string
|
||||
defaultCacheHome() string
|
||||
}
|
||||
|
||||
type osDefaulter struct {
|
||||
}
|
||||
|
||||
//This method is used in the testing suit
|
||||
// nolint: deadcode
|
||||
func setDefaulter(def xdgDefaulter) {
|
||||
defaulter = def
|
||||
}
|
||||
|
||||
// XDG is information about the currently running application
|
||||
type XDG struct {
|
||||
Vendor string
|
||||
Application string
|
||||
}
|
||||
|
||||
// New returns an instance of XDG that is used to grab files for application use
|
||||
func New(vendor, application string) *XDG {
|
||||
return &XDG{
|
||||
Vendor: vendor,
|
||||
Application: application,
|
||||
}
|
||||
}
|
||||
|
||||
// DataHome returns the location that should be used for user specific data files for this specific application
|
||||
func (x *XDG) DataHome() string {
|
||||
return filepath.Join(DataHome(), x.Vendor, x.Application)
|
||||
}
|
||||
|
||||
// DataDirs returns a list of locations that should be used for system wide data files for this specific application
|
||||
func (x *XDG) DataDirs() []string {
|
||||
dataDirs := DataDirs()
|
||||
for i, dir := range dataDirs {
|
||||
dataDirs[i] = filepath.Join(dir, x.Vendor, x.Application)
|
||||
}
|
||||
return dataDirs
|
||||
}
|
||||
|
||||
// ConfigHome returns the location that should be used for user specific config files for this specific application
|
||||
func (x *XDG) ConfigHome() string {
|
||||
return filepath.Join(ConfigHome(), x.Vendor, x.Application)
|
||||
}
|
||||
|
||||
// ConfigDirs returns a list of locations that should be used for system wide config files for this specific application
|
||||
func (x *XDG) ConfigDirs() []string {
|
||||
configDirs := ConfigDirs()
|
||||
for i, dir := range configDirs {
|
||||
configDirs[i] = filepath.Join(dir, x.Vendor, x.Application)
|
||||
}
|
||||
return configDirs
|
||||
}
|
||||
|
||||
// CacheHome returns the location that should be used for application cache files for this specific application
|
||||
func (x *XDG) CacheHome() string {
|
||||
return filepath.Join(CacheHome(), x.Vendor, x.Application)
|
||||
}
|
||||
|
||||
// QueryData looks for the given filename in XDG paths for data files.
|
||||
// Returns an empty string if one was not found.
|
||||
func (x *XDG) QueryData(filename string) string {
|
||||
dirs := x.DataDirs()
|
||||
dirs = append([]string{x.DataHome()}, dirs...)
|
||||
return returnExist(filename, dirs)
|
||||
}
|
||||
|
||||
// QueryConfig looks for the given filename in XDG paths for config files.
|
||||
// Returns an empty string if one was not found.
|
||||
func (x *XDG) QueryConfig(filename string) string {
|
||||
dirs := x.ConfigDirs()
|
||||
dirs = append([]string{x.ConfigHome()}, dirs...)
|
||||
return returnExist(filename, dirs)
|
||||
}
|
||||
|
||||
// QueryCache looks for the given filename in XDG paths for cache files.
|
||||
// Returns an empty string if one was not found.
|
||||
func (x *XDG) QueryCache(filename string) string {
|
||||
return returnExist(filename, []string{x.CacheHome()})
|
||||
}
|
||||
|
||||
func returnExist(filename string, dirs []string) string {
|
||||
for _, dir := range dirs {
|
||||
_, err := os.Stat(filepath.Join(dir, filename))
|
||||
if (err != nil && os.IsExist(err)) || err == nil {
|
||||
return filepath.Join(dir, filename)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DataHome returns the location that should be used for user specific data files
|
||||
func DataHome() string {
|
||||
dataHome := os.Getenv("XDG_DATA_HOME")
|
||||
if dataHome == "" {
|
||||
dataHome = defaulter.defaultDataHome()
|
||||
}
|
||||
return dataHome
|
||||
}
|
||||
|
||||
// DataDirs returns a list of locations that should be used for system wide data files
|
||||
func DataDirs() []string {
|
||||
var dataDirs []string
|
||||
dataDirsStr := os.Getenv("XDG_DATA_DIRS")
|
||||
if dataDirsStr != "" {
|
||||
dataDirs = strings.Split(dataDirsStr, string(os.PathListSeparator))
|
||||
}
|
||||
if len(dataDirs) == 0 {
|
||||
dataDirs = defaulter.defaultDataDirs()
|
||||
}
|
||||
return dataDirs
|
||||
}
|
||||
|
||||
// ConfigHome returns the location that should be used for user specific config files
|
||||
func ConfigHome() string {
|
||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if configHome == "" {
|
||||
configHome = defaulter.defaultConfigHome()
|
||||
}
|
||||
return configHome
|
||||
}
|
||||
|
||||
// ConfigDirs returns a list of locations that should be used for system wide config files
|
||||
func ConfigDirs() []string {
|
||||
var configDirs []string
|
||||
configDirsStr := os.Getenv("XDG_CONFIG_DIRS")
|
||||
if configDirsStr != "" {
|
||||
configDirs = strings.Split(configDirsStr, string(os.PathListSeparator))
|
||||
}
|
||||
if len(configDirs) == 0 {
|
||||
configDirs = defaulter.defaultConfigDirs()
|
||||
}
|
||||
return configDirs
|
||||
}
|
||||
|
||||
// CacheHome returns the location that should be used for application cache files
|
||||
func CacheHome() string {
|
||||
cacheHome := os.Getenv("XDG_CACHE_HOME")
|
||||
if cacheHome == "" {
|
||||
cacheHome = defaulter.defaultCacheHome()
|
||||
}
|
||||
return cacheHome
|
||||
}
|
30
vendor/github.com/OpenPeeDeeP/xdg/xdg_darwin.go
generated
vendored
30
vendor/github.com/OpenPeeDeeP/xdg/xdg_darwin.go
generated
vendored
@ -1,30 +0,0 @@
|
||||
// Copyright (c) 2017, OpenPeeDeeP. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xdg
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func (o *osDefaulter) defaultDataHome() string {
|
||||
return filepath.Join(os.Getenv("HOME"), "Library", "Application Support")
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultDataDirs() []string {
|
||||
return []string{filepath.Join("/Library", "Application Support")}
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultConfigHome() string {
|
||||
return filepath.Join(os.Getenv("HOME"), "Library", "Application Support")
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultConfigDirs() []string {
|
||||
return []string{filepath.Join("/Library", "Application Support")}
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultCacheHome() string {
|
||||
return filepath.Join(os.Getenv("HOME"), "Library", "Caches")
|
||||
}
|
30
vendor/github.com/OpenPeeDeeP/xdg/xdg_linux.go
generated
vendored
30
vendor/github.com/OpenPeeDeeP/xdg/xdg_linux.go
generated
vendored
@ -1,30 +0,0 @@
|
||||
// Copyright (c) 2017, OpenPeeDeeP. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xdg
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func (o *osDefaulter) defaultDataHome() string {
|
||||
return filepath.Join(os.Getenv("HOME"), ".local", "share")
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultDataDirs() []string {
|
||||
return []string{"/usr/local/share/", "/usr/share/"}
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultConfigHome() string {
|
||||
return filepath.Join(os.Getenv("HOME"), ".config")
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultConfigDirs() []string {
|
||||
return []string{"/etc/xdg"}
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultCacheHome() string {
|
||||
return filepath.Join(os.Getenv("HOME"), ".cache")
|
||||
}
|
27
vendor/github.com/OpenPeeDeeP/xdg/xdg_windows.go
generated
vendored
27
vendor/github.com/OpenPeeDeeP/xdg/xdg_windows.go
generated
vendored
@ -1,27 +0,0 @@
|
||||
// Copyright (c) 2017, OpenPeeDeeP. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package xdg
|
||||
|
||||
import "os"
|
||||
|
||||
func (o *osDefaulter) defaultDataHome() string {
|
||||
return os.Getenv("APPDATA")
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultDataDirs() []string {
|
||||
return []string{os.Getenv("PROGRAMDATA")}
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultConfigHome() string {
|
||||
return os.Getenv("APPDATA")
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultConfigDirs() []string {
|
||||
return []string{os.Getenv("PROGRAMDATA")}
|
||||
}
|
||||
|
||||
func (o *osDefaulter) defaultCacheHome() string {
|
||||
return os.Getenv("LOCALAPPDATA")
|
||||
}
|
21
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
Normal file
21
vendor/github.com/gorilla/websocket/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.4
|
||||
- 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
|
||||
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go vet $(go list ./... | grep -v /vendor/)
|
||||
- go test -v -race ./...
|
12
vendor/github.com/gorilla/websocket/README.md
generated
vendored
12
vendor/github.com/gorilla/websocket/README.md
generated
vendored
@ -1,14 +1,14 @@
|
||||
# Gorilla WebSocket
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
|
||||
[![CircleCI](https://circleci.com/gh/gorilla/websocket.svg?style=svg)](https://circleci.com/gh/gorilla/websocket)
|
||||
|
||||
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
||||
|
||||
[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket)
|
||||
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
|
||||
|
||||
### Documentation
|
||||
|
||||
* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc)
|
||||
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
|
||||
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
|
||||
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
|
||||
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
|
||||
@ -27,7 +27,7 @@ package API is stable.
|
||||
### Protocol Compliance
|
||||
|
||||
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
|
||||
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
|
||||
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn
|
||||
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
|
||||
|
||||
### Gorilla WebSocket compared with other packages
|
||||
@ -40,7 +40,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn
|
||||
</tr>
|
||||
<tr>
|
||||
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
|
||||
<tr><td>Passes <a href="https://github.com/crossbario/autobahn-testsuite">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
|
||||
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
|
||||
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
|
||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
|
||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
|
||||
|
103
vendor/github.com/gorilla/websocket/client.go
generated
vendored
103
vendor/github.com/gorilla/websocket/client.go
generated
vendored
@ -6,14 +6,12 @@ package websocket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
@ -53,10 +51,6 @@ type Dialer struct {
|
||||
// NetDial is nil, net.Dial is used.
|
||||
NetDial func(network, addr string) (net.Conn, error)
|
||||
|
||||
// NetDialContext specifies the dial function for creating TCP connections. If
|
||||
// NetDialContext is nil, net.DialContext is used.
|
||||
NetDialContext func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// Proxy specifies a function to return a proxy for a given
|
||||
// Request. If the function returns a non-nil error, the
|
||||
// request is aborted with the provided error.
|
||||
@ -70,22 +64,11 @@ type Dialer struct {
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||
// size is zero, then a useful default size is used. The I/O buffer sizes
|
||||
// do not limit the size of the messages that can be sent or received.
|
||||
ReadBufferSize, WriteBufferSize int
|
||||
|
||||
// WriteBufferPool is a pool of buffers for write operations. If the value
|
||||
// is not set, then write buffers are allocated to the connection for the
|
||||
// lifetime of the connection.
|
||||
//
|
||||
// A pool is most useful when the application has a modest volume of writes
|
||||
// across a large number of connections.
|
||||
//
|
||||
// Applications should use a single pool for each unique value of
|
||||
// WriteBufferSize.
|
||||
WriteBufferPool BufferPool
|
||||
|
||||
// Subprotocols specifies the client's requested subprotocols.
|
||||
Subprotocols []string
|
||||
|
||||
@ -101,11 +84,6 @@ type Dialer struct {
|
||||
Jar http.CookieJar
|
||||
}
|
||||
|
||||
// Dial creates a new client connection by calling DialContext with a background context.
|
||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
return d.DialContext(context.Background(), urlStr, requestHeader)
|
||||
}
|
||||
|
||||
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||
|
||||
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
||||
@ -133,20 +111,19 @@ var DefaultDialer = &Dialer{
|
||||
}
|
||||
|
||||
// nilDialer is dialer to use when receiver is nil.
|
||||
var nilDialer = *DefaultDialer
|
||||
var nilDialer Dialer = *DefaultDialer
|
||||
|
||||
// DialContext creates a new client connection. Use requestHeader to specify the
|
||||
// Dial creates a new client connection. Use requestHeader to specify the
|
||||
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
||||
// Use the response.Header to get the selected subprotocol
|
||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||
//
|
||||
// The context will be used in the request and in the Dialer.
|
||||
//
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||
// etcetera. The response body may not contain the entire response and does not
|
||||
// need to be closed by the application.
|
||||
func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||
|
||||
if d == nil {
|
||||
d = &nilDialer
|
||||
}
|
||||
@ -184,7 +161,6 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
Header: make(http.Header),
|
||||
Host: u.Host,
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
// Set the cookies present in the cookie jar of the dialer
|
||||
if d.Jar != nil {
|
||||
@ -228,30 +204,20 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
req.Header["Sec-WebSocket-Extensions"] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"}
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if d.HandshakeTimeout != 0 {
|
||||
var cancel func()
|
||||
ctx, cancel = context.WithTimeout(ctx, d.HandshakeTimeout)
|
||||
defer cancel()
|
||||
deadline = time.Now().Add(d.HandshakeTimeout)
|
||||
}
|
||||
|
||||
// Get network dial function.
|
||||
var netDial func(network, add string) (net.Conn, error)
|
||||
|
||||
if d.NetDialContext != nil {
|
||||
netDial = func(network, addr string) (net.Conn, error) {
|
||||
return d.NetDialContext(ctx, network, addr)
|
||||
}
|
||||
} else if d.NetDial != nil {
|
||||
netDial = d.NetDial
|
||||
} else {
|
||||
netDialer := &net.Dialer{}
|
||||
netDial = func(network, addr string) (net.Conn, error) {
|
||||
return netDialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
netDial := d.NetDial
|
||||
if netDial == nil {
|
||||
netDialer := &net.Dialer{Deadline: deadline}
|
||||
netDial = netDialer.Dial
|
||||
}
|
||||
|
||||
// If needed, wrap the dial function to set the connection deadline.
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
if !deadline.Equal(time.Time{}) {
|
||||
forwardDial := netDial
|
||||
netDial = func(network, addr string) (net.Conn, error) {
|
||||
c, err := forwardDial(network, addr)
|
||||
@ -283,17 +249,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
}
|
||||
|
||||
hostPort, hostNoPort := hostPortNoPort(u)
|
||||
trace := httptrace.ContextClientTrace(ctx)
|
||||
if trace != nil && trace.GetConn != nil {
|
||||
trace.GetConn(hostPort)
|
||||
}
|
||||
|
||||
netConn, err := netDial("tcp", hostPort)
|
||||
if trace != nil && trace.GotConn != nil {
|
||||
trace.GotConn(httptrace.GotConnInfo{
|
||||
Conn: netConn,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -311,31 +267,22 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
}
|
||||
tlsConn := tls.Client(netConn, cfg)
|
||||
netConn = tlsConn
|
||||
|
||||
var err error
|
||||
if trace != nil {
|
||||
err = doHandshakeWithTrace(trace, tlsConn, cfg)
|
||||
} else {
|
||||
err = doHandshake(tlsConn, cfg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !cfg.InsecureSkipVerify {
|
||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize, d.WriteBufferPool, nil, nil)
|
||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
|
||||
|
||||
if err := req.Write(netConn); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if trace != nil && trace.GotFirstResponseByte != nil {
|
||||
if peek, err := conn.br.Peek(1); err == nil && len(peek) == 1 {
|
||||
trace.GotFirstResponseByte()
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(conn.br, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -381,15 +328,3 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h
|
||||
netConn = nil // to avoid close in defer.
|
||||
return conn, resp, nil
|
||||
}
|
||||
|
||||
func doHandshake(tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !cfg.InsecureSkipVerify {
|
||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
218
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
218
vendor/github.com/gorilla/websocket/conn.go
generated
vendored
@ -223,20 +223,6 @@ func isValidReceivedCloseCode(code int) bool {
|
||||
return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999)
|
||||
}
|
||||
|
||||
// BufferPool represents a pool of buffers. The *sync.Pool type satisfies this
|
||||
// interface. The type of the value stored in a pool is not specified.
|
||||
type BufferPool interface {
|
||||
// Get gets a value from the pool or returns nil if the pool is empty.
|
||||
Get() interface{}
|
||||
// Put adds a value to the pool.
|
||||
Put(interface{})
|
||||
}
|
||||
|
||||
// writePoolData is the type added to the write buffer pool. This wrapper is
|
||||
// used to prevent applications from peeking at and depending on the values
|
||||
// added to the pool.
|
||||
type writePoolData struct{ buf []byte }
|
||||
|
||||
// The Conn type represents a WebSocket connection.
|
||||
type Conn struct {
|
||||
conn net.Conn
|
||||
@ -244,10 +230,8 @@ type Conn struct {
|
||||
subprotocol string
|
||||
|
||||
// Write fields
|
||||
mu chan struct{} // used as mutex to protect write to conn
|
||||
writeBuf []byte // frame is constructed in this buffer.
|
||||
writePool BufferPool
|
||||
writeBufSize int
|
||||
mu chan bool // used as mutex to protect write to conn
|
||||
writeBuf []byte // frame is constructed in this buffer.
|
||||
writeDeadline time.Time
|
||||
writer io.WriteCloser // the current writer returned to the application
|
||||
isWriting bool // for best-effort concurrent write detection
|
||||
@ -260,12 +244,10 @@ type Conn struct {
|
||||
newCompressionWriter func(io.WriteCloser, int) io.WriteCloser
|
||||
|
||||
// Read fields
|
||||
reader io.ReadCloser // the current reader returned to the application
|
||||
readErr error
|
||||
br *bufio.Reader
|
||||
// bytes remaining in current frame.
|
||||
// set setReadRemaining to safely update this value and prevent overflow
|
||||
readRemaining int64
|
||||
reader io.ReadCloser // the current reader returned to the application
|
||||
readErr error
|
||||
br *bufio.Reader
|
||||
readRemaining int64 // bytes remaining in current frame.
|
||||
readFinal bool // true the current message has more frames.
|
||||
readLength int64 // Message size.
|
||||
readLimit int64 // Maximum message size.
|
||||
@ -281,29 +263,64 @@ type Conn struct {
|
||||
newDecompressionReader func(io.Reader) io.ReadCloser
|
||||
}
|
||||
|
||||
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, writeBufferPool BufferPool, br *bufio.Reader, writeBuf []byte) *Conn {
|
||||
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn {
|
||||
return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil)
|
||||
}
|
||||
|
||||
type writeHook struct {
|
||||
p []byte
|
||||
}
|
||||
|
||||
func (wh *writeHook) Write(p []byte) (int, error) {
|
||||
wh.p = p
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn {
|
||||
mu := make(chan bool, 1)
|
||||
mu <- true
|
||||
|
||||
var br *bufio.Reader
|
||||
if readBufferSize == 0 && brw != nil && brw.Reader != nil {
|
||||
// Reuse the supplied bufio.Reader if the buffer has a useful size.
|
||||
// This code assumes that peek on a reader returns
|
||||
// bufio.Reader.buf[:0].
|
||||
brw.Reader.Reset(conn)
|
||||
if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 {
|
||||
br = brw.Reader
|
||||
}
|
||||
}
|
||||
if br == nil {
|
||||
if readBufferSize == 0 {
|
||||
readBufferSize = defaultReadBufferSize
|
||||
} else if readBufferSize < maxControlFramePayloadSize {
|
||||
// must be large enough for control frame
|
||||
}
|
||||
if readBufferSize < maxControlFramePayloadSize {
|
||||
readBufferSize = maxControlFramePayloadSize
|
||||
}
|
||||
br = bufio.NewReaderSize(conn, readBufferSize)
|
||||
}
|
||||
|
||||
if writeBufferSize <= 0 {
|
||||
writeBufferSize = defaultWriteBufferSize
|
||||
}
|
||||
writeBufferSize += maxFrameHeaderSize
|
||||
|
||||
if writeBuf == nil && writeBufferPool == nil {
|
||||
writeBuf = make([]byte, writeBufferSize)
|
||||
var writeBuf []byte
|
||||
if writeBufferSize == 0 && brw != nil && brw.Writer != nil {
|
||||
// Use the bufio.Writer's buffer if the buffer has a useful size. This
|
||||
// code assumes that bufio.Writer.buf[:1] is passed to the
|
||||
// bufio.Writer's underlying writer.
|
||||
var wh writeHook
|
||||
brw.Writer.Reset(&wh)
|
||||
brw.Writer.WriteByte(0)
|
||||
brw.Flush()
|
||||
if cap(wh.p) >= maxFrameHeaderSize+256 {
|
||||
writeBuf = wh.p[:cap(wh.p)]
|
||||
}
|
||||
}
|
||||
|
||||
if writeBuf == nil {
|
||||
if writeBufferSize == 0 {
|
||||
writeBufferSize = defaultWriteBufferSize
|
||||
}
|
||||
writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize)
|
||||
}
|
||||
|
||||
mu := make(chan struct{}, 1)
|
||||
mu <- struct{}{}
|
||||
c := &Conn{
|
||||
isServer: isServer,
|
||||
br: br,
|
||||
@ -311,8 +328,6 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int,
|
||||
mu: mu,
|
||||
readFinal: true,
|
||||
writeBuf: writeBuf,
|
||||
writePool: writeBufferPool,
|
||||
writeBufSize: writeBufferSize,
|
||||
enableWriteCompression: true,
|
||||
compressionLevel: defaultCompressionLevel,
|
||||
}
|
||||
@ -322,17 +337,6 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int,
|
||||
return c
|
||||
}
|
||||
|
||||
// setReadRemaining tracks the number of bytes remaining on the connection. If n
|
||||
// overflows, an ErrReadLimit is returned.
|
||||
func (c *Conn) setReadRemaining(n int64) error {
|
||||
if n < 0 {
|
||||
return ErrReadLimit
|
||||
}
|
||||
|
||||
c.readRemaining = n
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subprotocol returns the negotiated protocol for the connection.
|
||||
func (c *Conn) Subprotocol() string {
|
||||
return c.subprotocol
|
||||
@ -366,18 +370,9 @@ func (c *Conn) writeFatal(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) read(n int) ([]byte, error) {
|
||||
p, err := c.br.Peek(n)
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
c.br.Discard(len(p))
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error {
|
||||
<-c.mu
|
||||
defer func() { c.mu <- struct{}{} }()
|
||||
defer func() { c.mu <- true }()
|
||||
|
||||
c.writeErrMu.Lock()
|
||||
err := c.writeErr
|
||||
@ -429,7 +424,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
||||
maskBytes(key, 0, buf[6:])
|
||||
}
|
||||
|
||||
d := 1000 * time.Hour
|
||||
d := time.Hour * 1000
|
||||
if !deadline.IsZero() {
|
||||
d = deadline.Sub(time.Now())
|
||||
if d < 0 {
|
||||
@ -444,7 +439,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
||||
case <-timer.C:
|
||||
return errWriteTimeout
|
||||
}
|
||||
defer func() { c.mu <- struct{}{} }()
|
||||
defer func() { c.mu <- true }()
|
||||
|
||||
c.writeErrMu.Lock()
|
||||
err := c.writeErr
|
||||
@ -464,8 +459,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
||||
return err
|
||||
}
|
||||
|
||||
// beginMessage prepares a connection and message writer for a new message.
|
||||
func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
|
||||
func (c *Conn) prepWrite(messageType int) error {
|
||||
// Close previous writer if not already closed by the application. It's
|
||||
// probably better to return an error in this situation, but we cannot
|
||||
// change this without breaking existing applications.
|
||||
@ -481,23 +475,7 @@ func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
|
||||
c.writeErrMu.Lock()
|
||||
err := c.writeErr
|
||||
c.writeErrMu.Unlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mw.c = c
|
||||
mw.frameType = messageType
|
||||
mw.pos = maxFrameHeaderSize
|
||||
|
||||
if c.writeBuf == nil {
|
||||
wpd, ok := c.writePool.Get().(writePoolData)
|
||||
if ok {
|
||||
c.writeBuf = wpd.buf
|
||||
} else {
|
||||
c.writeBuf = make([]byte, c.writeBufSize)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// NextWriter returns a writer for the next message to send. The writer's Close
|
||||
@ -509,11 +487,16 @@ func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
|
||||
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
|
||||
// PongMessage) are supported.
|
||||
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
||||
var mw messageWriter
|
||||
if err := c.beginMessage(&mw, messageType); err != nil {
|
||||
if err := c.prepWrite(messageType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.writer = &mw
|
||||
|
||||
mw := &messageWriter{
|
||||
c: c,
|
||||
frameType: messageType,
|
||||
pos: maxFrameHeaderSize,
|
||||
}
|
||||
c.writer = mw
|
||||
if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
|
||||
w := c.newCompressionWriter(c.writer, c.compressionLevel)
|
||||
mw.compress = true
|
||||
@ -530,16 +513,10 @@ type messageWriter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *messageWriter) endMessage(err error) error {
|
||||
func (w *messageWriter) fatal(err error) error {
|
||||
if w.err != nil {
|
||||
return err
|
||||
}
|
||||
c := w.c
|
||||
w.err = err
|
||||
c.writer = nil
|
||||
if c.writePool != nil {
|
||||
c.writePool.Put(writePoolData{buf: c.writeBuf})
|
||||
c.writeBuf = nil
|
||||
w.err = err
|
||||
w.c.writer = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -553,7 +530,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
// Check for invalid control frames.
|
||||
if isControl(w.frameType) &&
|
||||
(!final || length > maxControlFramePayloadSize) {
|
||||
return w.endMessage(errInvalidControlFrame)
|
||||
return w.fatal(errInvalidControlFrame)
|
||||
}
|
||||
|
||||
b0 := byte(w.frameType)
|
||||
@ -598,7 +575,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
|
||||
maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])
|
||||
if len(extra) > 0 {
|
||||
return w.endMessage(c.writeFatal(errors.New("websocket: internal error, extra used in client mode")))
|
||||
return c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -619,11 +596,11 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
c.isWriting = false
|
||||
|
||||
if err != nil {
|
||||
return w.endMessage(err)
|
||||
return w.fatal(err)
|
||||
}
|
||||
|
||||
if final {
|
||||
w.endMessage(errWriteClosed)
|
||||
c.writer = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -721,7 +698,11 @@ func (w *messageWriter) Close() error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
return w.flushFrame(true, nil)
|
||||
if err := w.flushFrame(true, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
w.err = errWriteClosed
|
||||
return nil
|
||||
}
|
||||
|
||||
// WritePreparedMessage writes prepared message into connection.
|
||||
@ -753,10 +734,10 @@ func (c *Conn) WriteMessage(messageType int, data []byte) error {
|
||||
if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
|
||||
// Fast path with no allocations and single frame.
|
||||
|
||||
var mw messageWriter
|
||||
if err := c.beginMessage(&mw, messageType); err != nil {
|
||||
if err := c.prepWrite(messageType); err != nil {
|
||||
return err
|
||||
}
|
||||
mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
|
||||
n := copy(c.writeBuf[mw.pos:], data)
|
||||
mw.pos += n
|
||||
data = data[n:]
|
||||
@ -803,7 +784,7 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
final := p[0]&finalBit != 0
|
||||
frameType := int(p[0] & 0xf)
|
||||
mask := p[1]&maskBit != 0
|
||||
c.setReadRemaining(int64(p[1] & 0x7f))
|
||||
c.readRemaining = int64(p[1] & 0x7f)
|
||||
|
||||
c.readDecompress = false
|
||||
if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 {
|
||||
@ -837,17 +818,7 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType))
|
||||
}
|
||||
|
||||
// 3. Read and parse frame length as per
|
||||
// https://tools.ietf.org/html/rfc6455#section-5.2
|
||||
//
|
||||
// The length of the "Payload data", in bytes: if 0-125, that is the payload
|
||||
// length.
|
||||
// - If 126, the following 2 bytes interpreted as a 16-bit unsigned
|
||||
// integer are the payload length.
|
||||
// - If 127, the following 8 bytes interpreted as
|
||||
// a 64-bit unsigned integer (the most significant bit MUST be 0) are the
|
||||
// payload length. Multibyte length quantities are expressed in network byte
|
||||
// order.
|
||||
// 3. Read and parse frame length.
|
||||
|
||||
switch c.readRemaining {
|
||||
case 126:
|
||||
@ -855,19 +826,13 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
if err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
|
||||
if err := c.setReadRemaining(int64(binary.BigEndian.Uint16(p))); err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
c.readRemaining = int64(binary.BigEndian.Uint16(p))
|
||||
case 127:
|
||||
p, err := c.read(8)
|
||||
if err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
|
||||
if err := c.setReadRemaining(int64(binary.BigEndian.Uint64(p))); err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
c.readRemaining = int64(binary.BigEndian.Uint64(p))
|
||||
}
|
||||
|
||||
// 4. Handle frame masking.
|
||||
@ -890,12 +855,6 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage {
|
||||
|
||||
c.readLength += c.readRemaining
|
||||
// Don't allow readLength to overflow in the presence of a large readRemaining
|
||||
// counter.
|
||||
if c.readLength < 0 {
|
||||
return noFrame, ErrReadLimit
|
||||
}
|
||||
|
||||
if c.readLimit > 0 && c.readLength > c.readLimit {
|
||||
c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait))
|
||||
return noFrame, ErrReadLimit
|
||||
@ -909,7 +868,7 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
var payload []byte
|
||||
if c.readRemaining > 0 {
|
||||
payload, err = c.read(int(c.readRemaining))
|
||||
c.setReadRemaining(0)
|
||||
c.readRemaining = 0
|
||||
if err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
@ -982,7 +941,6 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
|
||||
c.readErr = hideTempErr(err)
|
||||
break
|
||||
}
|
||||
|
||||
if frameType == TextMessage || frameType == BinaryMessage {
|
||||
c.messageReader = &messageReader{c}
|
||||
c.reader = c.messageReader
|
||||
@ -1023,9 +981,7 @@ func (r *messageReader) Read(b []byte) (int, error) {
|
||||
if c.isServer {
|
||||
c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
|
||||
}
|
||||
rem := c.readRemaining
|
||||
rem -= int64(n)
|
||||
c.setReadRemaining(rem)
|
||||
c.readRemaining -= int64(n)
|
||||
if c.readRemaining > 0 && c.readErr == io.EOF {
|
||||
c.readErr = errUnexpectedEOF
|
||||
}
|
||||
@ -1077,7 +1033,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetReadLimit sets the maximum size in bytes for a message read from the peer. If a
|
||||
// SetReadLimit sets the maximum size for a message read from the peer. If a
|
||||
// message exceeds the limit, the connection sends a close message to the peer
|
||||
// and returns ErrReadLimit to the application.
|
||||
func (c *Conn) SetReadLimit(limit int64) {
|
||||
|
18
vendor/github.com/gorilla/websocket/conn_read.go
generated
vendored
Normal file
18
vendor/github.com/gorilla/websocket/conn_read.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.5
|
||||
|
||||
package websocket
|
||||
|
||||
import "io"
|
||||
|
||||
func (c *Conn) read(n int) ([]byte, error) {
|
||||
p, err := c.br.Peek(n)
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
c.br.Discard(len(p))
|
||||
return p, err
|
||||
}
|
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
Normal file
21
vendor/github.com/gorilla/websocket/conn_read_legacy.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package websocket
|
||||
|
||||
import "io"
|
||||
|
||||
func (c *Conn) read(n int) ([]byte, error) {
|
||||
p, err := c.br.Peek(n)
|
||||
if err == io.EOF {
|
||||
err = errUnexpectedEOF
|
||||
}
|
||||
if len(p) > 0 {
|
||||
// advance over the bytes just read
|
||||
io.ReadFull(c.br, p)
|
||||
}
|
||||
return p, err
|
||||
}
|
47
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
47
vendor/github.com/gorilla/websocket/doc.go
generated
vendored
@ -151,53 +151,6 @@
|
||||
// checking. The application is responsible for checking the Origin header
|
||||
// before calling the Upgrade function.
|
||||
//
|
||||
// Buffers
|
||||
//
|
||||
// Connections buffer network input and output to reduce the number
|
||||
// of system calls when reading or writing messages.
|
||||
//
|
||||
// Write buffers are also used for constructing WebSocket frames. See RFC 6455,
|
||||
// Section 5 for a discussion of message framing. A WebSocket frame header is
|
||||
// written to the network each time a write buffer is flushed to the network.
|
||||
// Decreasing the size of the write buffer can increase the amount of framing
|
||||
// overhead on the connection.
|
||||
//
|
||||
// The buffer sizes in bytes are specified by the ReadBufferSize and
|
||||
// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default
|
||||
// size of 4096 when a buffer size field is set to zero. The Upgrader reuses
|
||||
// buffers created by the HTTP server when a buffer size field is set to zero.
|
||||
// The HTTP server buffers have a size of 4096 at the time of this writing.
|
||||
//
|
||||
// The buffer sizes do not limit the size of a message that can be read or
|
||||
// written by a connection.
|
||||
//
|
||||
// Buffers are held for the lifetime of the connection by default. If the
|
||||
// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the
|
||||
// write buffer only when writing a message.
|
||||
//
|
||||
// Applications should tune the buffer sizes to balance memory use and
|
||||
// performance. Increasing the buffer size uses more memory, but can reduce the
|
||||
// number of system calls to read or write the network. In the case of writing,
|
||||
// increasing the buffer size can reduce the number of frame headers written to
|
||||
// the network.
|
||||
//
|
||||
// Some guidelines for setting buffer parameters are:
|
||||
//
|
||||
// Limit the buffer sizes to the maximum expected message size. Buffers larger
|
||||
// than the largest message do not provide any benefit.
|
||||
//
|
||||
// Depending on the distribution of message sizes, setting the buffer size to
|
||||
// a value less than the maximum expected message size can greatly reduce memory
|
||||
// use with a small impact on performance. Here's an example: If 99% of the
|
||||
// messages are smaller than 256 bytes and the maximum message size is 512
|
||||
// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls
|
||||
// than a buffer size of 512 bytes. The memory savings is 50%.
|
||||
//
|
||||
// A write buffer pool is useful when the application has a modest number
|
||||
// writes over a large number of connections. when buffers are pooled, a larger
|
||||
// buffer size has a reduced impact on total memory use and has the benefit of
|
||||
// reducing system calls and frame overhead.
|
||||
//
|
||||
// Compression EXPERIMENTAL
|
||||
//
|
||||
// Per message compression extensions (RFC 7692) are experimentally supported
|
||||
|
3
vendor/github.com/gorilla/websocket/go.mod
generated
vendored
3
vendor/github.com/gorilla/websocket/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module github.com/gorilla/websocket
|
||||
|
||||
go 1.12
|
0
vendor/github.com/gorilla/websocket/go.sum
generated
vendored
0
vendor/github.com/gorilla/websocket/go.sum
generated
vendored
42
vendor/github.com/gorilla/websocket/join.go
generated
vendored
42
vendor/github.com/gorilla/websocket/join.go
generated
vendored
@ -1,42 +0,0 @@
|
||||
// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JoinMessages concatenates received messages to create a single io.Reader.
|
||||
// The string term is appended to each message. The returned reader does not
|
||||
// support concurrent calls to the Read method.
|
||||
func JoinMessages(c *Conn, term string) io.Reader {
|
||||
return &joinReader{c: c, term: term}
|
||||
}
|
||||
|
||||
type joinReader struct {
|
||||
c *Conn
|
||||
term string
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (r *joinReader) Read(p []byte) (int, error) {
|
||||
if r.r == nil {
|
||||
var err error
|
||||
_, r.r, err = r.c.NextReader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if r.term != "" {
|
||||
r.r = io.MultiReader(r.r, strings.NewReader(r.term))
|
||||
}
|
||||
}
|
||||
n, err := r.r.Read(p)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
r.r = nil
|
||||
}
|
||||
return n, err
|
||||
}
|
5
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
5
vendor/github.com/gorilla/websocket/prepared.go
generated
vendored
@ -19,6 +19,7 @@ import (
|
||||
type PreparedMessage struct {
|
||||
messageType int
|
||||
data []byte
|
||||
err error
|
||||
mu sync.Mutex
|
||||
frames map[prepareKey]*preparedFrame
|
||||
}
|
||||
@ -73,8 +74,8 @@ func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) {
|
||||
// Prepare a frame using a 'fake' connection.
|
||||
// TODO: Refactor code in conn.go to allow more direct construction of
|
||||
// the frame.
|
||||
mu := make(chan struct{}, 1)
|
||||
mu <- struct{}{}
|
||||
mu := make(chan bool, 1)
|
||||
mu <- true
|
||||
var nc prepareConn
|
||||
c := &Conn{
|
||||
conn: &nc,
|
||||
|
8
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
8
vendor/github.com/gorilla/websocket/proxy.go
generated
vendored
@ -22,18 +22,18 @@ func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
|
||||
|
||||
func init() {
|
||||
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
||||
return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil
|
||||
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type httpProxyDialer struct {
|
||||
proxyURL *url.URL
|
||||
forwardDial func(network, addr string) (net.Conn, error)
|
||||
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.forwardDial(network, hostPort)
|
||||
conn, err := hpd.fowardDial(network, hostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
85
vendor/github.com/gorilla/websocket/server.go
generated
vendored
85
vendor/github.com/gorilla/websocket/server.go
generated
vendored
@ -7,7 +7,7 @@ package websocket
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -27,23 +27,12 @@ type Upgrader struct {
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||
// size is zero, then buffers allocated by the HTTP server are used. The
|
||||
// I/O buffer sizes do not limit the size of the messages that can be sent
|
||||
// or received.
|
||||
ReadBufferSize, WriteBufferSize int
|
||||
|
||||
// WriteBufferPool is a pool of buffers for write operations. If the value
|
||||
// is not set, then write buffers are allocated to the connection for the
|
||||
// lifetime of the connection.
|
||||
//
|
||||
// A pool is most useful when the application has a modest volume of writes
|
||||
// across a large number of connections.
|
||||
//
|
||||
// Applications should use a single pool for each unique value of
|
||||
// WriteBufferSize.
|
||||
WriteBufferPool BufferPool
|
||||
|
||||
// Subprotocols specifies the server's supported protocols in order of
|
||||
// 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
|
||||
@ -153,7 +142,7 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
|
||||
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||
if challengeKey == "" {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank")
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank")
|
||||
}
|
||||
|
||||
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||
@ -170,12 +159,17 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
netConn net.Conn
|
||||
err error
|
||||
)
|
||||
|
||||
h, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker")
|
||||
}
|
||||
var brw *bufio.ReadWriter
|
||||
netConn, brw, err := h.Hijack()
|
||||
netConn, brw, err = h.Hijack()
|
||||
if err != nil {
|
||||
return u.returnError(w, r, http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
@ -185,21 +179,7 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
return nil, errors.New("websocket: client sent data before handshake is complete")
|
||||
}
|
||||
|
||||
var br *bufio.Reader
|
||||
if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 {
|
||||
// Reuse hijacked buffered reader as connection reader.
|
||||
br = brw.Reader
|
||||
}
|
||||
|
||||
buf := bufioWriterBuffer(netConn, brw.Writer)
|
||||
|
||||
var writeBuf []byte
|
||||
if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 {
|
||||
// Reuse hijacked write buffer as connection buffer.
|
||||
writeBuf = buf
|
||||
}
|
||||
|
||||
c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf)
|
||||
c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw)
|
||||
c.subprotocol = subprotocol
|
||||
|
||||
if compress {
|
||||
@ -207,13 +187,7 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
c.newDecompressionReader = decompressNoContextTakeover
|
||||
}
|
||||
|
||||
// Use larger of hijacked buffer and connection write buffer for header.
|
||||
p := buf
|
||||
if len(c.writeBuf) > len(p) {
|
||||
p = c.writeBuf
|
||||
}
|
||||
p = p[:0]
|
||||
|
||||
p := c.writeBuf[:0]
|
||||
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...)
|
||||
p = append(p, computeAcceptKey(challengeKey)...)
|
||||
p = append(p, "\r\n"...)
|
||||
@ -324,40 +298,3 @@ func IsWebSocketUpgrade(r *http.Request) bool {
|
||||
return tokenListContainsValue(r.Header, "Connection", "upgrade") &&
|
||||
tokenListContainsValue(r.Header, "Upgrade", "websocket")
|
||||
}
|
||||
|
||||
// bufioReaderSize size returns the size of a bufio.Reader.
|
||||
func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int {
|
||||
// This code assumes that peek on a reset reader returns
|
||||
// bufio.Reader.buf[:0].
|
||||
// TODO: Use bufio.Reader.Size() after Go 1.10
|
||||
br.Reset(originalReader)
|
||||
if p, err := br.Peek(0); err == nil {
|
||||
return cap(p)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// writeHook is an io.Writer that records the last slice passed to it vio
|
||||
// io.Writer.Write.
|
||||
type writeHook struct {
|
||||
p []byte
|
||||
}
|
||||
|
||||
func (wh *writeHook) Write(p []byte) (int, error) {
|
||||
wh.p = p
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// bufioWriterBuffer grabs the buffer from a bufio.Writer.
|
||||
func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte {
|
||||
// This code assumes that bufio.Writer.buf[:1] is passed to the
|
||||
// bufio.Writer's underlying writer.
|
||||
var wh writeHook
|
||||
bw.Reset(&wh)
|
||||
bw.WriteByte(0)
|
||||
bw.Flush()
|
||||
|
||||
bw.Reset(originalWriter)
|
||||
|
||||
return wh.p[:cap(wh.p)]
|
||||
}
|
||||
|
19
vendor/github.com/gorilla/websocket/trace.go
generated
vendored
19
vendor/github.com/gorilla/websocket/trace.go
generated
vendored
@ -1,19 +0,0 @@
|
||||
// +build go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http/httptrace"
|
||||
)
|
||||
|
||||
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||
if trace.TLSHandshakeStart != nil {
|
||||
trace.TLSHandshakeStart()
|
||||
}
|
||||
err := doHandshake(tlsConn, cfg)
|
||||
if trace.TLSHandshakeDone != nil {
|
||||
trace.TLSHandshakeDone(tlsConn.ConnectionState(), err)
|
||||
}
|
||||
return err
|
||||
}
|
12
vendor/github.com/gorilla/websocket/trace_17.go
generated
vendored
12
vendor/github.com/gorilla/websocket/trace_17.go
generated
vendored
@ -1,12 +0,0 @@
|
||||
// +build !go1.8
|
||||
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http/httptrace"
|
||||
)
|
||||
|
||||
func doHandshakeWithTrace(trace *httptrace.ClientTrace, tlsConn *tls.Conn, cfg *tls.Config) error {
|
||||
return doHandshake(tlsConn, cfg)
|
||||
}
|
134
vendor/github.com/gorilla/websocket/util.go
generated
vendored
134
vendor/github.com/gorilla/websocket/util.go
generated
vendored
@ -31,113 +31,68 @@ func generateChallengeKey() (string, error) {
|
||||
return base64.StdEncoding.EncodeToString(p), nil
|
||||
}
|
||||
|
||||
// Token octets per RFC 2616.
|
||||
var isTokenOctet = [256]bool{
|
||||
'!': true,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'W': true,
|
||||
'V': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'|': true,
|
||||
'~': true,
|
||||
// Octet types from RFC 2616.
|
||||
var octetTypes [256]byte
|
||||
|
||||
const (
|
||||
isTokenOctet = 1 << iota
|
||||
isSpaceOctet
|
||||
)
|
||||
|
||||
func init() {
|
||||
// From RFC 2616
|
||||
//
|
||||
// OCTET = <any 8-bit sequence of data>
|
||||
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||
// CR = <US-ASCII CR, carriage return (13)>
|
||||
// LF = <US-ASCII LF, linefeed (10)>
|
||||
// SP = <US-ASCII SP, space (32)>
|
||||
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||
// <"> = <US-ASCII double-quote mark (34)>
|
||||
// CRLF = CR LF
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
// TEXT = <any OCTET except CTLs, but including LWS>
|
||||
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
||||
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
||||
// token = 1*<any CHAR except CTLs or separators>
|
||||
// qdtext = <any TEXT except <">>
|
||||
|
||||
for c := 0; c < 256; c++ {
|
||||
var t byte
|
||||
isCtl := c <= 31 || c == 127
|
||||
isChar := 0 <= c && c <= 127
|
||||
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
||||
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
||||
t |= isSpaceOctet
|
||||
}
|
||||
if isChar && !isCtl && !isSeparator {
|
||||
t |= isTokenOctet
|
||||
}
|
||||
octetTypes[c] = t
|
||||
}
|
||||
}
|
||||
|
||||
// skipSpace returns a slice of the string s with all leading RFC 2616 linear
|
||||
// whitespace removed.
|
||||
func skipSpace(s string) (rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if b := s[i]; b != ' ' && b != '\t' {
|
||||
if octetTypes[s[i]]&isSpaceOctet == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[i:]
|
||||
}
|
||||
|
||||
// nextToken returns the leading RFC 2616 token of s and the string following
|
||||
// the token.
|
||||
func nextToken(s string) (token, rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if !isTokenOctet[s[i]] {
|
||||
if octetTypes[s[i]]&isTokenOctet == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:]
|
||||
}
|
||||
|
||||
// nextTokenOrQuoted returns the leading token or quoted string per RFC 2616
|
||||
// and the string following the token or quoted string.
|
||||
func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||
if !strings.HasPrefix(s, "\"") {
|
||||
return nextToken(s)
|
||||
@ -173,8 +128,7 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// equalASCIIFold returns true if s is equal to t with ASCII case folding as
|
||||
// defined in RFC 4790.
|
||||
// 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)
|
||||
@ -224,7 +178,7 @@ headers:
|
||||
return false
|
||||
}
|
||||
|
||||
// parseExtensions parses WebSocket extensions from a header.
|
||||
// parseExtensiosn parses WebSocket extensions from a header.
|
||||
func parseExtensions(header http.Header) []map[string]string {
|
||||
// From RFC 6455:
|
||||
//
|
||||
|
253
vendor/github.com/lithammer/fuzzysearch/fuzzy/fuzzy.go
generated
vendored
253
vendor/github.com/lithammer/fuzzysearch/fuzzy/fuzzy.go
generated
vendored
@ -1,253 +0,0 @@
|
||||
// Fuzzy searching allows for flexibly matching a string with partial input,
|
||||
// useful for filtering data very quickly based on lightweight user input.
|
||||
package fuzzy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/runes"
|
||||
"golang.org/x/text/transform"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
var foldTransformer = unicodeFoldTransformer{}
|
||||
var noopTransformer = transform.Nop
|
||||
var normalizeTransformer = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
||||
var normalizeFoldTransformer = transform.Chain(normalizeTransformer, foldTransformer)
|
||||
|
||||
// Match returns true if source matches target using a fuzzy-searching
|
||||
// algorithm. Note that it doesn't implement Levenshtein distance (see
|
||||
// RankMatch instead), but rather a simplified version where there's no
|
||||
// approximation. The method will return true only if each character in the
|
||||
// source can be found in the target and occurs after the preceding matches.
|
||||
func Match(source, target string) bool {
|
||||
return match(source, target, noopTransformer)
|
||||
}
|
||||
|
||||
// MatchFold is a case-insensitive version of Match.
|
||||
func MatchFold(source, target string) bool {
|
||||
return match(source, target, foldTransformer)
|
||||
}
|
||||
|
||||
// MatchNormalized is a unicode-normalized version of Match.
|
||||
func MatchNormalized(source, target string) bool {
|
||||
return match(source, target, normalizeTransformer)
|
||||
}
|
||||
|
||||
// MatchNormalizedFold is a unicode-normalized and case-insensitive version of Match.
|
||||
func MatchNormalizedFold(source, target string) bool {
|
||||
return match(source, target, normalizeFoldTransformer)
|
||||
}
|
||||
|
||||
func match(source, target string, transformer transform.Transformer) bool {
|
||||
source = stringTransform(source, transformer)
|
||||
target = stringTransform(target, transformer)
|
||||
|
||||
lenDiff := len(target) - len(source)
|
||||
|
||||
if lenDiff < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if lenDiff == 0 && source == target {
|
||||
return true
|
||||
}
|
||||
|
||||
Outer:
|
||||
for _, r1 := range source {
|
||||
for i, r2 := range target {
|
||||
if r1 == r2 {
|
||||
target = target[i+utf8.RuneLen(r2):]
|
||||
continue Outer
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Find will return a list of strings in targets that fuzzy matches source.
|
||||
func Find(source string, targets []string) []string {
|
||||
return find(source, targets, noopTransformer)
|
||||
}
|
||||
|
||||
// FindFold is a case-insensitive version of Find.
|
||||
func FindFold(source string, targets []string) []string {
|
||||
return find(source, targets, foldTransformer)
|
||||
}
|
||||
|
||||
// FindNormalized is a unicode-normalized version of Find.
|
||||
func FindNormalized(source string, targets []string) []string {
|
||||
return find(source, targets, normalizeTransformer)
|
||||
}
|
||||
|
||||
// FindNormalizedFold is a unicode-normalized and case-insensitive version of Find.
|
||||
func FindNormalizedFold(source string, targets []string) []string {
|
||||
return find(source, targets, normalizeFoldTransformer)
|
||||
}
|
||||
|
||||
func find(source string, targets []string, transformer transform.Transformer) []string {
|
||||
var matches []string
|
||||
|
||||
for _, target := range targets {
|
||||
if match(source, target, transformer) {
|
||||
matches = append(matches, target)
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
// RankMatch is similar to Match except it will measure the Levenshtein
|
||||
// distance between the source and the target and return its result. If there
|
||||
// was no match, it will return -1.
|
||||
// Given the requirements of match, RankMatch only needs to perform a subset of
|
||||
// the Levenshtein calculation, only deletions need be considered, required
|
||||
// additions and substitutions would fail the match test.
|
||||
func RankMatch(source, target string) int {
|
||||
return rank(source, target, noopTransformer)
|
||||
}
|
||||
|
||||
// RankMatchFold is a case-insensitive version of RankMatch.
|
||||
func RankMatchFold(source, target string) int {
|
||||
return rank(source, target, foldTransformer)
|
||||
}
|
||||
|
||||
// RankMatchNormalized is a unicode-normalized version of RankMatch.
|
||||
func RankMatchNormalized(source, target string) int {
|
||||
return rank(source, target, normalizeTransformer)
|
||||
}
|
||||
|
||||
// RankMatchNormalizedFold is a unicode-normalized and case-insensitive version of RankMatch.
|
||||
func RankMatchNormalizedFold(source, target string) int {
|
||||
return rank(source, target, normalizeFoldTransformer)
|
||||
}
|
||||
|
||||
func rank(source, target string, transformer transform.Transformer) int {
|
||||
lenDiff := len(target) - len(source)
|
||||
|
||||
if lenDiff < 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
source = stringTransform(source, transformer)
|
||||
target = stringTransform(target, transformer)
|
||||
|
||||
if lenDiff == 0 && source == target {
|
||||
return 0
|
||||
}
|
||||
|
||||
runeDiff := 0
|
||||
|
||||
Outer:
|
||||
for _, r1 := range source {
|
||||
for i, r2 := range target {
|
||||
if r1 == r2 {
|
||||
target = target[i+utf8.RuneLen(r2):]
|
||||
continue Outer
|
||||
} else {
|
||||
runeDiff++
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Count up remaining char
|
||||
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 {
|
||||
return rankFind(source, targets, noopTransformer)
|
||||
}
|
||||
|
||||
// RankFindFold is a case-insensitive version of RankFind.
|
||||
func RankFindFold(source string, targets []string) Ranks {
|
||||
return rankFind(source, targets, foldTransformer)
|
||||
}
|
||||
|
||||
// RankFindNormalized is a unicode-normalizedversion of RankFind.
|
||||
func RankFindNormalized(source string, targets []string) Ranks {
|
||||
return rankFind(source, targets, normalizeTransformer)
|
||||
}
|
||||
|
||||
// RankFindNormalizedFold is a unicode-normalized and case-insensitive version of RankFind.
|
||||
func RankFindNormalizedFold(source string, targets []string) Ranks {
|
||||
return rankFind(source, targets, normalizeFoldTransformer)
|
||||
}
|
||||
|
||||
func rankFind(source string, targets []string, transformer transform.Transformer) Ranks {
|
||||
var r Ranks
|
||||
|
||||
for index, target := range targets {
|
||||
if match(source, target, transformer) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance, index})
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type Rank struct {
|
||||
// Source is used as the source for matching.
|
||||
Source string
|
||||
|
||||
// Target is the word matched against.
|
||||
Target string
|
||||
|
||||
// Distance is the Levenshtein distance between Source and Target.
|
||||
Distance int
|
||||
|
||||
// Location of Target in original list
|
||||
OriginalIndex int
|
||||
}
|
||||
|
||||
type Ranks []Rank
|
||||
|
||||
func (r Ranks) Len() int {
|
||||
return len(r)
|
||||
}
|
||||
|
||||
func (r Ranks) Swap(i, j int) {
|
||||
r[i], r[j] = r[j], r[i]
|
||||
}
|
||||
|
||||
func (r Ranks) Less(i, j int) bool {
|
||||
return r[i].Distance < r[j].Distance
|
||||
}
|
||||
|
||||
func stringTransform(s string, t transform.Transformer) (transformed string) {
|
||||
var err error
|
||||
transformed, _, err = transform.String(t, s)
|
||||
if err != nil {
|
||||
transformed = s
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type unicodeFoldTransformer struct{}
|
||||
|
||||
func (unicodeFoldTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||
runes := bytes.Runes(src)
|
||||
var lowerRunes []rune
|
||||
for _, r := range runes {
|
||||
lowerRunes = append(lowerRunes, unicode.ToLower(r))
|
||||
}
|
||||
|
||||
srcBytes := []byte(string(lowerRunes))
|
||||
n := copy(dst, srcBytes)
|
||||
if n < len(srcBytes) {
|
||||
err = transform.ErrShortDst
|
||||
}
|
||||
|
||||
return n, n, err
|
||||
}
|
||||
|
||||
func (unicodeFoldTransformer) Reset() {}
|
3
vendor/github.com/mattn/go-runewidth/go.mod
generated
vendored
3
vendor/github.com/mattn/go-runewidth/go.mod
generated
vendored
@ -1,3 +0,0 @@
|
||||
module github.com/mattn/go-runewidth
|
||||
|
||||
go 1.9
|
1085
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
1085
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
File diff suppressed because it is too large
Load Diff
8
vendor/github.com/mattn/go-runewidth/runewidth_appengine.go
generated
vendored
8
vendor/github.com/mattn/go-runewidth/runewidth_appengine.go
generated
vendored
@ -1,8 +0,0 @@
|
||||
// +build appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
return false
|
||||
}
|
1
vendor/github.com/mattn/go-runewidth/runewidth_js.go
generated
vendored
1
vendor/github.com/mattn/go-runewidth/runewidth_js.go
generated
vendored
@ -1,5 +1,4 @@
|
||||
// +build js
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
|
4
vendor/github.com/mattn/go-runewidth/runewidth_posix.go
generated
vendored
4
vendor/github.com/mattn/go-runewidth/runewidth_posix.go
generated
vendored
@ -1,6 +1,4 @@
|
||||
// +build !windows
|
||||
// +build !js
|
||||
// +build !appengine
|
||||
// +build !windows,!js
|
||||
|
||||
package runewidth
|
||||
|
||||
|
427
vendor/github.com/mattn/go-runewidth/runewidth_table.go
generated
vendored
427
vendor/github.com/mattn/go-runewidth/runewidth_table.go
generated
vendored
@ -1,427 +0,0 @@
|
||||
package runewidth
|
||||
|
||||
var combining = table{
|
||||
{0x0300, 0x036F}, {0x0483, 0x0489}, {0x07EB, 0x07F3},
|
||||
{0x0C00, 0x0C00}, {0x0C04, 0x0C04}, {0x0D00, 0x0D01},
|
||||
{0x135D, 0x135F}, {0x1A7F, 0x1A7F}, {0x1AB0, 0x1ABE},
|
||||
{0x1B6B, 0x1B73}, {0x1DC0, 0x1DF9}, {0x1DFB, 0x1DFF},
|
||||
{0x20D0, 0x20F0}, {0x2CEF, 0x2CF1}, {0x2DE0, 0x2DFF},
|
||||
{0x3099, 0x309A}, {0xA66F, 0xA672}, {0xA674, 0xA67D},
|
||||
{0xA69E, 0xA69F}, {0xA6F0, 0xA6F1}, {0xA8E0, 0xA8F1},
|
||||
{0xFE20, 0xFE2F}, {0x101FD, 0x101FD}, {0x10376, 0x1037A},
|
||||
{0x10F46, 0x10F50}, {0x11300, 0x11301}, {0x1133B, 0x1133C},
|
||||
{0x11366, 0x1136C}, {0x11370, 0x11374}, {0x16AF0, 0x16AF4},
|
||||
{0x1D165, 0x1D169}, {0x1D16D, 0x1D172}, {0x1D17B, 0x1D182},
|
||||
{0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD}, {0x1D242, 0x1D244},
|
||||
{0x1E000, 0x1E006}, {0x1E008, 0x1E018}, {0x1E01B, 0x1E021},
|
||||
{0x1E023, 0x1E024}, {0x1E026, 0x1E02A}, {0x1E8D0, 0x1E8D6},
|
||||
}
|
||||
|
||||
var doublewidth = table{
|
||||
{0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A},
|
||||
{0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3},
|
||||
{0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2648, 0x2653},
|
||||
{0x267F, 0x267F}, {0x2693, 0x2693}, {0x26A1, 0x26A1},
|
||||
{0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5},
|
||||
{0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA},
|
||||
{0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA},
|
||||
{0x26FD, 0x26FD}, {0x2705, 0x2705}, {0x270A, 0x270B},
|
||||
{0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E},
|
||||
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
|
||||
{0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C},
|
||||
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x2E99},
|
||||
{0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB},
|
||||
{0x3000, 0x303E}, {0x3041, 0x3096}, {0x3099, 0x30FF},
|
||||
{0x3105, 0x312F}, {0x3131, 0x318E}, {0x3190, 0x31BA},
|
||||
{0x31C0, 0x31E3}, {0x31F0, 0x321E}, {0x3220, 0x3247},
|
||||
{0x3250, 0x4DBF}, {0x4E00, 0xA48C}, {0xA490, 0xA4C6},
|
||||
{0xA960, 0xA97C}, {0xAC00, 0xD7A3}, {0xF900, 0xFAFF},
|
||||
{0xFE10, 0xFE19}, {0xFE30, 0xFE52}, {0xFE54, 0xFE66},
|
||||
{0xFE68, 0xFE6B}, {0xFF01, 0xFF60}, {0xFFE0, 0xFFE6},
|
||||
{0x16FE0, 0x16FE3}, {0x17000, 0x187F7}, {0x18800, 0x18AF2},
|
||||
{0x1B000, 0x1B11E}, {0x1B150, 0x1B152}, {0x1B164, 0x1B167},
|
||||
{0x1B170, 0x1B2FB}, {0x1F004, 0x1F004}, {0x1F0CF, 0x1F0CF},
|
||||
{0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A}, {0x1F200, 0x1F202},
|
||||
{0x1F210, 0x1F23B}, {0x1F240, 0x1F248}, {0x1F250, 0x1F251},
|
||||
{0x1F260, 0x1F265}, {0x1F300, 0x1F320}, {0x1F32D, 0x1F335},
|
||||
{0x1F337, 0x1F37C}, {0x1F37E, 0x1F393}, {0x1F3A0, 0x1F3CA},
|
||||
{0x1F3CF, 0x1F3D3}, {0x1F3E0, 0x1F3F0}, {0x1F3F4, 0x1F3F4},
|
||||
{0x1F3F8, 0x1F43E}, {0x1F440, 0x1F440}, {0x1F442, 0x1F4FC},
|
||||
{0x1F4FF, 0x1F53D}, {0x1F54B, 0x1F54E}, {0x1F550, 0x1F567},
|
||||
{0x1F57A, 0x1F57A}, {0x1F595, 0x1F596}, {0x1F5A4, 0x1F5A4},
|
||||
{0x1F5FB, 0x1F64F}, {0x1F680, 0x1F6C5}, {0x1F6CC, 0x1F6CC},
|
||||
{0x1F6D0, 0x1F6D2}, {0x1F6D5, 0x1F6D5}, {0x1F6EB, 0x1F6EC},
|
||||
{0x1F6F4, 0x1F6FA}, {0x1F7E0, 0x1F7EB}, {0x1F90D, 0x1F971},
|
||||
{0x1F973, 0x1F976}, {0x1F97A, 0x1F9A2}, {0x1F9A5, 0x1F9AA},
|
||||
{0x1F9AE, 0x1F9CA}, {0x1F9CD, 0x1F9FF}, {0x1FA70, 0x1FA73},
|
||||
{0x1FA78, 0x1FA7A}, {0x1FA80, 0x1FA82}, {0x1FA90, 0x1FA95},
|
||||
{0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
|
||||
}
|
||||
|
||||
var ambiguous = table{
|
||||
{0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8},
|
||||
{0x00AA, 0x00AA}, {0x00AD, 0x00AE}, {0x00B0, 0x00B4},
|
||||
{0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6},
|
||||
{0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1},
|
||||
{0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED},
|
||||
{0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA},
|
||||
{0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101},
|
||||
{0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B},
|
||||
{0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133},
|
||||
{0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144},
|
||||
{0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153},
|
||||
{0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE},
|
||||
{0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4},
|
||||
{0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA},
|
||||
{0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261},
|
||||
{0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB},
|
||||
{0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB},
|
||||
{0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0300, 0x036F},
|
||||
{0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1},
|
||||
{0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F},
|
||||
{0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016},
|
||||
{0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022},
|
||||
{0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033},
|
||||
{0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E},
|
||||
{0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084},
|
||||
{0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105},
|
||||
{0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116},
|
||||
{0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B},
|
||||
{0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B},
|
||||
{0x2170, 0x2179}, {0x2189, 0x2189}, {0x2190, 0x2199},
|
||||
{0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4},
|
||||
{0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203},
|
||||
{0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F},
|
||||
{0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A},
|
||||
{0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225},
|
||||
{0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237},
|
||||
{0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C},
|
||||
{0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267},
|
||||
{0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283},
|
||||
{0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299},
|
||||
{0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312},
|
||||
{0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573},
|
||||
{0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1},
|
||||
{0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7},
|
||||
{0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8},
|
||||
{0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5},
|
||||
{0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609},
|
||||
{0x260E, 0x260F}, {0x261C, 0x261C}, {0x261E, 0x261E},
|
||||
{0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661},
|
||||
{0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D},
|
||||
{0x266F, 0x266F}, {0x269E, 0x269F}, {0x26BF, 0x26BF},
|
||||
{0x26C6, 0x26CD}, {0x26CF, 0x26D3}, {0x26D5, 0x26E1},
|
||||
{0x26E3, 0x26E3}, {0x26E8, 0x26E9}, {0x26EB, 0x26F1},
|
||||
{0x26F4, 0x26F4}, {0x26F6, 0x26F9}, {0x26FB, 0x26FC},
|
||||
{0x26FE, 0x26FF}, {0x273D, 0x273D}, {0x2776, 0x277F},
|
||||
{0x2B56, 0x2B59}, {0x3248, 0x324F}, {0xE000, 0xF8FF},
|
||||
{0xFE00, 0xFE0F}, {0xFFFD, 0xFFFD}, {0x1F100, 0x1F10A},
|
||||
{0x1F110, 0x1F12D}, {0x1F130, 0x1F169}, {0x1F170, 0x1F18D},
|
||||
{0x1F18F, 0x1F190}, {0x1F19B, 0x1F1AC}, {0xE0100, 0xE01EF},
|
||||
{0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD},
|
||||
}
|
||||
var notassigned = table{
|
||||
{0x27E6, 0x27ED}, {0x2985, 0x2986},
|
||||
}
|
||||
|
||||
var neutral = table{
|
||||
{0x0000, 0x001F}, {0x007F, 0x00A0}, {0x00A9, 0x00A9},
|
||||
{0x00AB, 0x00AB}, {0x00B5, 0x00B5}, {0x00BB, 0x00BB},
|
||||
{0x00C0, 0x00C5}, {0x00C7, 0x00CF}, {0x00D1, 0x00D6},
|
||||
{0x00D9, 0x00DD}, {0x00E2, 0x00E5}, {0x00E7, 0x00E7},
|
||||
{0x00EB, 0x00EB}, {0x00EE, 0x00EF}, {0x00F1, 0x00F1},
|
||||
{0x00F4, 0x00F6}, {0x00FB, 0x00FB}, {0x00FD, 0x00FD},
|
||||
{0x00FF, 0x0100}, {0x0102, 0x0110}, {0x0112, 0x0112},
|
||||
{0x0114, 0x011A}, {0x011C, 0x0125}, {0x0128, 0x012A},
|
||||
{0x012C, 0x0130}, {0x0134, 0x0137}, {0x0139, 0x013E},
|
||||
{0x0143, 0x0143}, {0x0145, 0x0147}, {0x014C, 0x014C},
|
||||
{0x014E, 0x0151}, {0x0154, 0x0165}, {0x0168, 0x016A},
|
||||
{0x016C, 0x01CD}, {0x01CF, 0x01CF}, {0x01D1, 0x01D1},
|
||||
{0x01D3, 0x01D3}, {0x01D5, 0x01D5}, {0x01D7, 0x01D7},
|
||||
{0x01D9, 0x01D9}, {0x01DB, 0x01DB}, {0x01DD, 0x0250},
|
||||
{0x0252, 0x0260}, {0x0262, 0x02C3}, {0x02C5, 0x02C6},
|
||||
{0x02C8, 0x02C8}, {0x02CC, 0x02CC}, {0x02CE, 0x02CF},
|
||||
{0x02D1, 0x02D7}, {0x02DC, 0x02DC}, {0x02DE, 0x02DE},
|
||||
{0x02E0, 0x02FF}, {0x0370, 0x0377}, {0x037A, 0x037F},
|
||||
{0x0384, 0x038A}, {0x038C, 0x038C}, {0x038E, 0x0390},
|
||||
{0x03AA, 0x03B0}, {0x03C2, 0x03C2}, {0x03CA, 0x0400},
|
||||
{0x0402, 0x040F}, {0x0450, 0x0450}, {0x0452, 0x052F},
|
||||
{0x0531, 0x0556}, {0x0559, 0x058A}, {0x058D, 0x058F},
|
||||
{0x0591, 0x05C7}, {0x05D0, 0x05EA}, {0x05EF, 0x05F4},
|
||||
{0x0600, 0x061C}, {0x061E, 0x070D}, {0x070F, 0x074A},
|
||||
{0x074D, 0x07B1}, {0x07C0, 0x07FA}, {0x07FD, 0x082D},
|
||||
{0x0830, 0x083E}, {0x0840, 0x085B}, {0x085E, 0x085E},
|
||||
{0x0860, 0x086A}, {0x08A0, 0x08B4}, {0x08B6, 0x08BD},
|
||||
{0x08D3, 0x0983}, {0x0985, 0x098C}, {0x098F, 0x0990},
|
||||
{0x0993, 0x09A8}, {0x09AA, 0x09B0}, {0x09B2, 0x09B2},
|
||||
{0x09B6, 0x09B9}, {0x09BC, 0x09C4}, {0x09C7, 0x09C8},
|
||||
{0x09CB, 0x09CE}, {0x09D7, 0x09D7}, {0x09DC, 0x09DD},
|
||||
{0x09DF, 0x09E3}, {0x09E6, 0x09FE}, {0x0A01, 0x0A03},
|
||||
{0x0A05, 0x0A0A}, {0x0A0F, 0x0A10}, {0x0A13, 0x0A28},
|
||||
{0x0A2A, 0x0A30}, {0x0A32, 0x0A33}, {0x0A35, 0x0A36},
|
||||
{0x0A38, 0x0A39}, {0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42},
|
||||
{0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A51, 0x0A51},
|
||||
{0x0A59, 0x0A5C}, {0x0A5E, 0x0A5E}, {0x0A66, 0x0A76},
|
||||
{0x0A81, 0x0A83}, {0x0A85, 0x0A8D}, {0x0A8F, 0x0A91},
|
||||
{0x0A93, 0x0AA8}, {0x0AAA, 0x0AB0}, {0x0AB2, 0x0AB3},
|
||||
{0x0AB5, 0x0AB9}, {0x0ABC, 0x0AC5}, {0x0AC7, 0x0AC9},
|
||||
{0x0ACB, 0x0ACD}, {0x0AD0, 0x0AD0}, {0x0AE0, 0x0AE3},
|
||||
{0x0AE6, 0x0AF1}, {0x0AF9, 0x0AFF}, {0x0B01, 0x0B03},
|
||||
{0x0B05, 0x0B0C}, {0x0B0F, 0x0B10}, {0x0B13, 0x0B28},
|
||||
{0x0B2A, 0x0B30}, {0x0B32, 0x0B33}, {0x0B35, 0x0B39},
|
||||
{0x0B3C, 0x0B44}, {0x0B47, 0x0B48}, {0x0B4B, 0x0B4D},
|
||||
{0x0B56, 0x0B57}, {0x0B5C, 0x0B5D}, {0x0B5F, 0x0B63},
|
||||
{0x0B66, 0x0B77}, {0x0B82, 0x0B83}, {0x0B85, 0x0B8A},
|
||||
{0x0B8E, 0x0B90}, {0x0B92, 0x0B95}, {0x0B99, 0x0B9A},
|
||||
{0x0B9C, 0x0B9C}, {0x0B9E, 0x0B9F}, {0x0BA3, 0x0BA4},
|
||||
{0x0BA8, 0x0BAA}, {0x0BAE, 0x0BB9}, {0x0BBE, 0x0BC2},
|
||||
{0x0BC6, 0x0BC8}, {0x0BCA, 0x0BCD}, {0x0BD0, 0x0BD0},
|
||||
{0x0BD7, 0x0BD7}, {0x0BE6, 0x0BFA}, {0x0C00, 0x0C0C},
|
||||
{0x0C0E, 0x0C10}, {0x0C12, 0x0C28}, {0x0C2A, 0x0C39},
|
||||
{0x0C3D, 0x0C44}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D},
|
||||
{0x0C55, 0x0C56}, {0x0C58, 0x0C5A}, {0x0C60, 0x0C63},
|
||||
{0x0C66, 0x0C6F}, {0x0C77, 0x0C8C}, {0x0C8E, 0x0C90},
|
||||
{0x0C92, 0x0CA8}, {0x0CAA, 0x0CB3}, {0x0CB5, 0x0CB9},
|
||||
{0x0CBC, 0x0CC4}, {0x0CC6, 0x0CC8}, {0x0CCA, 0x0CCD},
|
||||
{0x0CD5, 0x0CD6}, {0x0CDE, 0x0CDE}, {0x0CE0, 0x0CE3},
|
||||
{0x0CE6, 0x0CEF}, {0x0CF1, 0x0CF2}, {0x0D00, 0x0D03},
|
||||
{0x0D05, 0x0D0C}, {0x0D0E, 0x0D10}, {0x0D12, 0x0D44},
|
||||
{0x0D46, 0x0D48}, {0x0D4A, 0x0D4F}, {0x0D54, 0x0D63},
|
||||
{0x0D66, 0x0D7F}, {0x0D82, 0x0D83}, {0x0D85, 0x0D96},
|
||||
{0x0D9A, 0x0DB1}, {0x0DB3, 0x0DBB}, {0x0DBD, 0x0DBD},
|
||||
{0x0DC0, 0x0DC6}, {0x0DCA, 0x0DCA}, {0x0DCF, 0x0DD4},
|
||||
{0x0DD6, 0x0DD6}, {0x0DD8, 0x0DDF}, {0x0DE6, 0x0DEF},
|
||||
{0x0DF2, 0x0DF4}, {0x0E01, 0x0E3A}, {0x0E3F, 0x0E5B},
|
||||
{0x0E81, 0x0E82}, {0x0E84, 0x0E84}, {0x0E86, 0x0E8A},
|
||||
{0x0E8C, 0x0EA3}, {0x0EA5, 0x0EA5}, {0x0EA7, 0x0EBD},
|
||||
{0x0EC0, 0x0EC4}, {0x0EC6, 0x0EC6}, {0x0EC8, 0x0ECD},
|
||||
{0x0ED0, 0x0ED9}, {0x0EDC, 0x0EDF}, {0x0F00, 0x0F47},
|
||||
{0x0F49, 0x0F6C}, {0x0F71, 0x0F97}, {0x0F99, 0x0FBC},
|
||||
{0x0FBE, 0x0FCC}, {0x0FCE, 0x0FDA}, {0x1000, 0x10C5},
|
||||
{0x10C7, 0x10C7}, {0x10CD, 0x10CD}, {0x10D0, 0x10FF},
|
||||
{0x1160, 0x1248}, {0x124A, 0x124D}, {0x1250, 0x1256},
|
||||
{0x1258, 0x1258}, {0x125A, 0x125D}, {0x1260, 0x1288},
|
||||
{0x128A, 0x128D}, {0x1290, 0x12B0}, {0x12B2, 0x12B5},
|
||||
{0x12B8, 0x12BE}, {0x12C0, 0x12C0}, {0x12C2, 0x12C5},
|
||||
{0x12C8, 0x12D6}, {0x12D8, 0x1310}, {0x1312, 0x1315},
|
||||
{0x1318, 0x135A}, {0x135D, 0x137C}, {0x1380, 0x1399},
|
||||
{0x13A0, 0x13F5}, {0x13F8, 0x13FD}, {0x1400, 0x169C},
|
||||
{0x16A0, 0x16F8}, {0x1700, 0x170C}, {0x170E, 0x1714},
|
||||
{0x1720, 0x1736}, {0x1740, 0x1753}, {0x1760, 0x176C},
|
||||
{0x176E, 0x1770}, {0x1772, 0x1773}, {0x1780, 0x17DD},
|
||||
{0x17E0, 0x17E9}, {0x17F0, 0x17F9}, {0x1800, 0x180E},
|
||||
{0x1810, 0x1819}, {0x1820, 0x1878}, {0x1880, 0x18AA},
|
||||
{0x18B0, 0x18F5}, {0x1900, 0x191E}, {0x1920, 0x192B},
|
||||
{0x1930, 0x193B}, {0x1940, 0x1940}, {0x1944, 0x196D},
|
||||
{0x1970, 0x1974}, {0x1980, 0x19AB}, {0x19B0, 0x19C9},
|
||||
{0x19D0, 0x19DA}, {0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E},
|
||||
{0x1A60, 0x1A7C}, {0x1A7F, 0x1A89}, {0x1A90, 0x1A99},
|
||||
{0x1AA0, 0x1AAD}, {0x1AB0, 0x1ABE}, {0x1B00, 0x1B4B},
|
||||
{0x1B50, 0x1B7C}, {0x1B80, 0x1BF3}, {0x1BFC, 0x1C37},
|
||||
{0x1C3B, 0x1C49}, {0x1C4D, 0x1C88}, {0x1C90, 0x1CBA},
|
||||
{0x1CBD, 0x1CC7}, {0x1CD0, 0x1CFA}, {0x1D00, 0x1DF9},
|
||||
{0x1DFB, 0x1F15}, {0x1F18, 0x1F1D}, {0x1F20, 0x1F45},
|
||||
{0x1F48, 0x1F4D}, {0x1F50, 0x1F57}, {0x1F59, 0x1F59},
|
||||
{0x1F5B, 0x1F5B}, {0x1F5D, 0x1F5D}, {0x1F5F, 0x1F7D},
|
||||
{0x1F80, 0x1FB4}, {0x1FB6, 0x1FC4}, {0x1FC6, 0x1FD3},
|
||||
{0x1FD6, 0x1FDB}, {0x1FDD, 0x1FEF}, {0x1FF2, 0x1FF4},
|
||||
{0x1FF6, 0x1FFE}, {0x2000, 0x200F}, {0x2011, 0x2012},
|
||||
{0x2017, 0x2017}, {0x201A, 0x201B}, {0x201E, 0x201F},
|
||||
{0x2023, 0x2023}, {0x2028, 0x202F}, {0x2031, 0x2031},
|
||||
{0x2034, 0x2034}, {0x2036, 0x203A}, {0x203C, 0x203D},
|
||||
{0x203F, 0x2064}, {0x2066, 0x2071}, {0x2075, 0x207E},
|
||||
{0x2080, 0x2080}, {0x2085, 0x208E}, {0x2090, 0x209C},
|
||||
{0x20A0, 0x20A8}, {0x20AA, 0x20AB}, {0x20AD, 0x20BF},
|
||||
{0x20D0, 0x20F0}, {0x2100, 0x2102}, {0x2104, 0x2104},
|
||||
{0x2106, 0x2108}, {0x210A, 0x2112}, {0x2114, 0x2115},
|
||||
{0x2117, 0x2120}, {0x2123, 0x2125}, {0x2127, 0x212A},
|
||||
{0x212C, 0x2152}, {0x2155, 0x215A}, {0x215F, 0x215F},
|
||||
{0x216C, 0x216F}, {0x217A, 0x2188}, {0x218A, 0x218B},
|
||||
{0x219A, 0x21B7}, {0x21BA, 0x21D1}, {0x21D3, 0x21D3},
|
||||
{0x21D5, 0x21E6}, {0x21E8, 0x21FF}, {0x2201, 0x2201},
|
||||
{0x2204, 0x2206}, {0x2209, 0x220A}, {0x220C, 0x220E},
|
||||
{0x2210, 0x2210}, {0x2212, 0x2214}, {0x2216, 0x2219},
|
||||
{0x221B, 0x221C}, {0x2221, 0x2222}, {0x2224, 0x2224},
|
||||
{0x2226, 0x2226}, {0x222D, 0x222D}, {0x222F, 0x2233},
|
||||
{0x2238, 0x223B}, {0x223E, 0x2247}, {0x2249, 0x224B},
|
||||
{0x224D, 0x2251}, {0x2253, 0x225F}, {0x2262, 0x2263},
|
||||
{0x2268, 0x2269}, {0x226C, 0x226D}, {0x2270, 0x2281},
|
||||
{0x2284, 0x2285}, {0x2288, 0x2294}, {0x2296, 0x2298},
|
||||
{0x229A, 0x22A4}, {0x22A6, 0x22BE}, {0x22C0, 0x2311},
|
||||
{0x2313, 0x2319}, {0x231C, 0x2328}, {0x232B, 0x23E8},
|
||||
{0x23ED, 0x23EF}, {0x23F1, 0x23F2}, {0x23F4, 0x2426},
|
||||
{0x2440, 0x244A}, {0x24EA, 0x24EA}, {0x254C, 0x254F},
|
||||
{0x2574, 0x257F}, {0x2590, 0x2591}, {0x2596, 0x259F},
|
||||
{0x25A2, 0x25A2}, {0x25AA, 0x25B1}, {0x25B4, 0x25B5},
|
||||
{0x25B8, 0x25BB}, {0x25BE, 0x25BF}, {0x25C2, 0x25C5},
|
||||
{0x25C9, 0x25CA}, {0x25CC, 0x25CD}, {0x25D2, 0x25E1},
|
||||
{0x25E6, 0x25EE}, {0x25F0, 0x25FC}, {0x25FF, 0x2604},
|
||||
{0x2607, 0x2608}, {0x260A, 0x260D}, {0x2610, 0x2613},
|
||||
{0x2616, 0x261B}, {0x261D, 0x261D}, {0x261F, 0x263F},
|
||||
{0x2641, 0x2641}, {0x2643, 0x2647}, {0x2654, 0x265F},
|
||||
{0x2662, 0x2662}, {0x2666, 0x2666}, {0x266B, 0x266B},
|
||||
{0x266E, 0x266E}, {0x2670, 0x267E}, {0x2680, 0x2692},
|
||||
{0x2694, 0x269D}, {0x26A0, 0x26A0}, {0x26A2, 0x26A9},
|
||||
{0x26AC, 0x26BC}, {0x26C0, 0x26C3}, {0x26E2, 0x26E2},
|
||||
{0x26E4, 0x26E7}, {0x2700, 0x2704}, {0x2706, 0x2709},
|
||||
{0x270C, 0x2727}, {0x2729, 0x273C}, {0x273E, 0x274B},
|
||||
{0x274D, 0x274D}, {0x274F, 0x2752}, {0x2756, 0x2756},
|
||||
{0x2758, 0x2775}, {0x2780, 0x2794}, {0x2798, 0x27AF},
|
||||
{0x27B1, 0x27BE}, {0x27C0, 0x27E5}, {0x27EE, 0x2984},
|
||||
{0x2987, 0x2B1A}, {0x2B1D, 0x2B4F}, {0x2B51, 0x2B54},
|
||||
{0x2B5A, 0x2B73}, {0x2B76, 0x2B95}, {0x2B98, 0x2C2E},
|
||||
{0x2C30, 0x2C5E}, {0x2C60, 0x2CF3}, {0x2CF9, 0x2D25},
|
||||
{0x2D27, 0x2D27}, {0x2D2D, 0x2D2D}, {0x2D30, 0x2D67},
|
||||
{0x2D6F, 0x2D70}, {0x2D7F, 0x2D96}, {0x2DA0, 0x2DA6},
|
||||
{0x2DA8, 0x2DAE}, {0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE},
|
||||
{0x2DC0, 0x2DC6}, {0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6},
|
||||
{0x2DD8, 0x2DDE}, {0x2DE0, 0x2E4F}, {0x303F, 0x303F},
|
||||
{0x4DC0, 0x4DFF}, {0xA4D0, 0xA62B}, {0xA640, 0xA6F7},
|
||||
{0xA700, 0xA7BF}, {0xA7C2, 0xA7C6}, {0xA7F7, 0xA82B},
|
||||
{0xA830, 0xA839}, {0xA840, 0xA877}, {0xA880, 0xA8C5},
|
||||
{0xA8CE, 0xA8D9}, {0xA8E0, 0xA953}, {0xA95F, 0xA95F},
|
||||
{0xA980, 0xA9CD}, {0xA9CF, 0xA9D9}, {0xA9DE, 0xA9FE},
|
||||
{0xAA00, 0xAA36}, {0xAA40, 0xAA4D}, {0xAA50, 0xAA59},
|
||||
{0xAA5C, 0xAAC2}, {0xAADB, 0xAAF6}, {0xAB01, 0xAB06},
|
||||
{0xAB09, 0xAB0E}, {0xAB11, 0xAB16}, {0xAB20, 0xAB26},
|
||||
{0xAB28, 0xAB2E}, {0xAB30, 0xAB67}, {0xAB70, 0xABED},
|
||||
{0xABF0, 0xABF9}, {0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB},
|
||||
{0xD800, 0xDFFF}, {0xFB00, 0xFB06}, {0xFB13, 0xFB17},
|
||||
{0xFB1D, 0xFB36}, {0xFB38, 0xFB3C}, {0xFB3E, 0xFB3E},
|
||||
{0xFB40, 0xFB41}, {0xFB43, 0xFB44}, {0xFB46, 0xFBC1},
|
||||
{0xFBD3, 0xFD3F}, {0xFD50, 0xFD8F}, {0xFD92, 0xFDC7},
|
||||
{0xFDF0, 0xFDFD}, {0xFE20, 0xFE2F}, {0xFE70, 0xFE74},
|
||||
{0xFE76, 0xFEFC}, {0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFC},
|
||||
{0x10000, 0x1000B}, {0x1000D, 0x10026}, {0x10028, 0x1003A},
|
||||
{0x1003C, 0x1003D}, {0x1003F, 0x1004D}, {0x10050, 0x1005D},
|
||||
{0x10080, 0x100FA}, {0x10100, 0x10102}, {0x10107, 0x10133},
|
||||
{0x10137, 0x1018E}, {0x10190, 0x1019B}, {0x101A0, 0x101A0},
|
||||
{0x101D0, 0x101FD}, {0x10280, 0x1029C}, {0x102A0, 0x102D0},
|
||||
{0x102E0, 0x102FB}, {0x10300, 0x10323}, {0x1032D, 0x1034A},
|
||||
{0x10350, 0x1037A}, {0x10380, 0x1039D}, {0x1039F, 0x103C3},
|
||||
{0x103C8, 0x103D5}, {0x10400, 0x1049D}, {0x104A0, 0x104A9},
|
||||
{0x104B0, 0x104D3}, {0x104D8, 0x104FB}, {0x10500, 0x10527},
|
||||
{0x10530, 0x10563}, {0x1056F, 0x1056F}, {0x10600, 0x10736},
|
||||
{0x10740, 0x10755}, {0x10760, 0x10767}, {0x10800, 0x10805},
|
||||
{0x10808, 0x10808}, {0x1080A, 0x10835}, {0x10837, 0x10838},
|
||||
{0x1083C, 0x1083C}, {0x1083F, 0x10855}, {0x10857, 0x1089E},
|
||||
{0x108A7, 0x108AF}, {0x108E0, 0x108F2}, {0x108F4, 0x108F5},
|
||||
{0x108FB, 0x1091B}, {0x1091F, 0x10939}, {0x1093F, 0x1093F},
|
||||
{0x10980, 0x109B7}, {0x109BC, 0x109CF}, {0x109D2, 0x10A03},
|
||||
{0x10A05, 0x10A06}, {0x10A0C, 0x10A13}, {0x10A15, 0x10A17},
|
||||
{0x10A19, 0x10A35}, {0x10A38, 0x10A3A}, {0x10A3F, 0x10A48},
|
||||
{0x10A50, 0x10A58}, {0x10A60, 0x10A9F}, {0x10AC0, 0x10AE6},
|
||||
{0x10AEB, 0x10AF6}, {0x10B00, 0x10B35}, {0x10B39, 0x10B55},
|
||||
{0x10B58, 0x10B72}, {0x10B78, 0x10B91}, {0x10B99, 0x10B9C},
|
||||
{0x10BA9, 0x10BAF}, {0x10C00, 0x10C48}, {0x10C80, 0x10CB2},
|
||||
{0x10CC0, 0x10CF2}, {0x10CFA, 0x10D27}, {0x10D30, 0x10D39},
|
||||
{0x10E60, 0x10E7E}, {0x10F00, 0x10F27}, {0x10F30, 0x10F59},
|
||||
{0x10FE0, 0x10FF6}, {0x11000, 0x1104D}, {0x11052, 0x1106F},
|
||||
{0x1107F, 0x110C1}, {0x110CD, 0x110CD}, {0x110D0, 0x110E8},
|
||||
{0x110F0, 0x110F9}, {0x11100, 0x11134}, {0x11136, 0x11146},
|
||||
{0x11150, 0x11176}, {0x11180, 0x111CD}, {0x111D0, 0x111DF},
|
||||
{0x111E1, 0x111F4}, {0x11200, 0x11211}, {0x11213, 0x1123E},
|
||||
{0x11280, 0x11286}, {0x11288, 0x11288}, {0x1128A, 0x1128D},
|
||||
{0x1128F, 0x1129D}, {0x1129F, 0x112A9}, {0x112B0, 0x112EA},
|
||||
{0x112F0, 0x112F9}, {0x11300, 0x11303}, {0x11305, 0x1130C},
|
||||
{0x1130F, 0x11310}, {0x11313, 0x11328}, {0x1132A, 0x11330},
|
||||
{0x11332, 0x11333}, {0x11335, 0x11339}, {0x1133B, 0x11344},
|
||||
{0x11347, 0x11348}, {0x1134B, 0x1134D}, {0x11350, 0x11350},
|
||||
{0x11357, 0x11357}, {0x1135D, 0x11363}, {0x11366, 0x1136C},
|
||||
{0x11370, 0x11374}, {0x11400, 0x11459}, {0x1145B, 0x1145B},
|
||||
{0x1145D, 0x1145F}, {0x11480, 0x114C7}, {0x114D0, 0x114D9},
|
||||
{0x11580, 0x115B5}, {0x115B8, 0x115DD}, {0x11600, 0x11644},
|
||||
{0x11650, 0x11659}, {0x11660, 0x1166C}, {0x11680, 0x116B8},
|
||||
{0x116C0, 0x116C9}, {0x11700, 0x1171A}, {0x1171D, 0x1172B},
|
||||
{0x11730, 0x1173F}, {0x11800, 0x1183B}, {0x118A0, 0x118F2},
|
||||
{0x118FF, 0x118FF}, {0x119A0, 0x119A7}, {0x119AA, 0x119D7},
|
||||
{0x119DA, 0x119E4}, {0x11A00, 0x11A47}, {0x11A50, 0x11AA2},
|
||||
{0x11AC0, 0x11AF8}, {0x11C00, 0x11C08}, {0x11C0A, 0x11C36},
|
||||
{0x11C38, 0x11C45}, {0x11C50, 0x11C6C}, {0x11C70, 0x11C8F},
|
||||
{0x11C92, 0x11CA7}, {0x11CA9, 0x11CB6}, {0x11D00, 0x11D06},
|
||||
{0x11D08, 0x11D09}, {0x11D0B, 0x11D36}, {0x11D3A, 0x11D3A},
|
||||
{0x11D3C, 0x11D3D}, {0x11D3F, 0x11D47}, {0x11D50, 0x11D59},
|
||||
{0x11D60, 0x11D65}, {0x11D67, 0x11D68}, {0x11D6A, 0x11D8E},
|
||||
{0x11D90, 0x11D91}, {0x11D93, 0x11D98}, {0x11DA0, 0x11DA9},
|
||||
{0x11EE0, 0x11EF8}, {0x11FC0, 0x11FF1}, {0x11FFF, 0x12399},
|
||||
{0x12400, 0x1246E}, {0x12470, 0x12474}, {0x12480, 0x12543},
|
||||
{0x13000, 0x1342E}, {0x13430, 0x13438}, {0x14400, 0x14646},
|
||||
{0x16800, 0x16A38}, {0x16A40, 0x16A5E}, {0x16A60, 0x16A69},
|
||||
{0x16A6E, 0x16A6F}, {0x16AD0, 0x16AED}, {0x16AF0, 0x16AF5},
|
||||
{0x16B00, 0x16B45}, {0x16B50, 0x16B59}, {0x16B5B, 0x16B61},
|
||||
{0x16B63, 0x16B77}, {0x16B7D, 0x16B8F}, {0x16E40, 0x16E9A},
|
||||
{0x16F00, 0x16F4A}, {0x16F4F, 0x16F87}, {0x16F8F, 0x16F9F},
|
||||
{0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C}, {0x1BC80, 0x1BC88},
|
||||
{0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BCA3}, {0x1D000, 0x1D0F5},
|
||||
{0x1D100, 0x1D126}, {0x1D129, 0x1D1E8}, {0x1D200, 0x1D245},
|
||||
{0x1D2E0, 0x1D2F3}, {0x1D300, 0x1D356}, {0x1D360, 0x1D378},
|
||||
{0x1D400, 0x1D454}, {0x1D456, 0x1D49C}, {0x1D49E, 0x1D49F},
|
||||
{0x1D4A2, 0x1D4A2}, {0x1D4A5, 0x1D4A6}, {0x1D4A9, 0x1D4AC},
|
||||
{0x1D4AE, 0x1D4B9}, {0x1D4BB, 0x1D4BB}, {0x1D4BD, 0x1D4C3},
|
||||
{0x1D4C5, 0x1D505}, {0x1D507, 0x1D50A}, {0x1D50D, 0x1D514},
|
||||
{0x1D516, 0x1D51C}, {0x1D51E, 0x1D539}, {0x1D53B, 0x1D53E},
|
||||
{0x1D540, 0x1D544}, {0x1D546, 0x1D546}, {0x1D54A, 0x1D550},
|
||||
{0x1D552, 0x1D6A5}, {0x1D6A8, 0x1D7CB}, {0x1D7CE, 0x1DA8B},
|
||||
{0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006},
|
||||
{0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E023, 0x1E024},
|
||||
{0x1E026, 0x1E02A}, {0x1E100, 0x1E12C}, {0x1E130, 0x1E13D},
|
||||
{0x1E140, 0x1E149}, {0x1E14E, 0x1E14F}, {0x1E2C0, 0x1E2F9},
|
||||
{0x1E2FF, 0x1E2FF}, {0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6},
|
||||
{0x1E900, 0x1E94B}, {0x1E950, 0x1E959}, {0x1E95E, 0x1E95F},
|
||||
{0x1EC71, 0x1ECB4}, {0x1ED01, 0x1ED3D}, {0x1EE00, 0x1EE03},
|
||||
{0x1EE05, 0x1EE1F}, {0x1EE21, 0x1EE22}, {0x1EE24, 0x1EE24},
|
||||
{0x1EE27, 0x1EE27}, {0x1EE29, 0x1EE32}, {0x1EE34, 0x1EE37},
|
||||
{0x1EE39, 0x1EE39}, {0x1EE3B, 0x1EE3B}, {0x1EE42, 0x1EE42},
|
||||
{0x1EE47, 0x1EE47}, {0x1EE49, 0x1EE49}, {0x1EE4B, 0x1EE4B},
|
||||
{0x1EE4D, 0x1EE4F}, {0x1EE51, 0x1EE52}, {0x1EE54, 0x1EE54},
|
||||
{0x1EE57, 0x1EE57}, {0x1EE59, 0x1EE59}, {0x1EE5B, 0x1EE5B},
|
||||
{0x1EE5D, 0x1EE5D}, {0x1EE5F, 0x1EE5F}, {0x1EE61, 0x1EE62},
|
||||
{0x1EE64, 0x1EE64}, {0x1EE67, 0x1EE6A}, {0x1EE6C, 0x1EE72},
|
||||
{0x1EE74, 0x1EE77}, {0x1EE79, 0x1EE7C}, {0x1EE7E, 0x1EE7E},
|
||||
{0x1EE80, 0x1EE89}, {0x1EE8B, 0x1EE9B}, {0x1EEA1, 0x1EEA3},
|
||||
{0x1EEA5, 0x1EEA9}, {0x1EEAB, 0x1EEBB}, {0x1EEF0, 0x1EEF1},
|
||||
{0x1F000, 0x1F003}, {0x1F005, 0x1F02B}, {0x1F030, 0x1F093},
|
||||
{0x1F0A0, 0x1F0AE}, {0x1F0B1, 0x1F0BF}, {0x1F0C1, 0x1F0CE},
|
||||
{0x1F0D1, 0x1F0F5}, {0x1F10B, 0x1F10C}, {0x1F12E, 0x1F12F},
|
||||
{0x1F16A, 0x1F16C}, {0x1F1E6, 0x1F1FF}, {0x1F321, 0x1F32C},
|
||||
{0x1F336, 0x1F336}, {0x1F37D, 0x1F37D}, {0x1F394, 0x1F39F},
|
||||
{0x1F3CB, 0x1F3CE}, {0x1F3D4, 0x1F3DF}, {0x1F3F1, 0x1F3F3},
|
||||
{0x1F3F5, 0x1F3F7}, {0x1F43F, 0x1F43F}, {0x1F441, 0x1F441},
|
||||
{0x1F4FD, 0x1F4FE}, {0x1F53E, 0x1F54A}, {0x1F54F, 0x1F54F},
|
||||
{0x1F568, 0x1F579}, {0x1F57B, 0x1F594}, {0x1F597, 0x1F5A3},
|
||||
{0x1F5A5, 0x1F5FA}, {0x1F650, 0x1F67F}, {0x1F6C6, 0x1F6CB},
|
||||
{0x1F6CD, 0x1F6CF}, {0x1F6D3, 0x1F6D4}, {0x1F6E0, 0x1F6EA},
|
||||
{0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F773}, {0x1F780, 0x1F7D8},
|
||||
{0x1F800, 0x1F80B}, {0x1F810, 0x1F847}, {0x1F850, 0x1F859},
|
||||
{0x1F860, 0x1F887}, {0x1F890, 0x1F8AD}, {0x1F900, 0x1F90B},
|
||||
{0x1FA00, 0x1FA53}, {0x1FA60, 0x1FA6D}, {0xE0001, 0xE0001},
|
||||
{0xE0020, 0xE007F},
|
||||
}
|
||||
|
||||
var emoji = table{
|
||||
{0x203C, 0x203C}, {0x2049, 0x2049}, {0x2122, 0x2122},
|
||||
{0x2139, 0x2139}, {0x2194, 0x2199}, {0x21A9, 0x21AA},
|
||||
{0x231A, 0x231B}, {0x2328, 0x2328}, {0x2388, 0x2388},
|
||||
{0x23CF, 0x23CF}, {0x23E9, 0x23F3}, {0x23F8, 0x23FA},
|
||||
{0x24C2, 0x24C2}, {0x25AA, 0x25AB}, {0x25B6, 0x25B6},
|
||||
{0x25C0, 0x25C0}, {0x25FB, 0x25FE}, {0x2600, 0x2605},
|
||||
{0x2607, 0x2612}, {0x2614, 0x2685}, {0x2690, 0x2705},
|
||||
{0x2708, 0x2712}, {0x2714, 0x2714}, {0x2716, 0x2716},
|
||||
{0x271D, 0x271D}, {0x2721, 0x2721}, {0x2728, 0x2728},
|
||||
{0x2733, 0x2734}, {0x2744, 0x2744}, {0x2747, 0x2747},
|
||||
{0x274C, 0x274C}, {0x274E, 0x274E}, {0x2753, 0x2755},
|
||||
{0x2757, 0x2757}, {0x2763, 0x2767}, {0x2795, 0x2797},
|
||||
{0x27A1, 0x27A1}, {0x27B0, 0x27B0}, {0x27BF, 0x27BF},
|
||||
{0x2934, 0x2935}, {0x2B05, 0x2B07}, {0x2B1B, 0x2B1C},
|
||||
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x3030, 0x3030},
|
||||
{0x303D, 0x303D}, {0x3297, 0x3297}, {0x3299, 0x3299},
|
||||
{0x1F000, 0x1F0FF}, {0x1F10D, 0x1F10F}, {0x1F12F, 0x1F12F},
|
||||
{0x1F16C, 0x1F171}, {0x1F17E, 0x1F17F}, {0x1F18E, 0x1F18E},
|
||||
{0x1F191, 0x1F19A}, {0x1F1AD, 0x1F1E5}, {0x1F201, 0x1F20F},
|
||||
{0x1F21A, 0x1F21A}, {0x1F22F, 0x1F22F}, {0x1F232, 0x1F23A},
|
||||
{0x1F23C, 0x1F23F}, {0x1F249, 0x1F3FA}, {0x1F400, 0x1F53D},
|
||||
{0x1F546, 0x1F64F}, {0x1F680, 0x1F6FF}, {0x1F774, 0x1F77F},
|
||||
{0x1F7D5, 0x1F7FF}, {0x1F80C, 0x1F80F}, {0x1F848, 0x1F84F},
|
||||
{0x1F85A, 0x1F85F}, {0x1F888, 0x1F88F}, {0x1F8AE, 0x1F8FF},
|
||||
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1FFFD},
|
||||
}
|
3
vendor/github.com/mattn/go-runewidth/runewidth_windows.go
generated
vendored
3
vendor/github.com/mattn/go-runewidth/runewidth_windows.go
generated
vendored
@ -1,6 +1,3 @@
|
||||
// +build windows
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
|
21
vendor/github.com/nlopes/slack/.travis.yml
generated
vendored
Normal file
21
vendor/github.com/nlopes/slack/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- export PATH=$HOME/gopath/bin:$PATH
|
||||
|
||||
script:
|
||||
- go test -race ./...
|
||||
- go test -cover ./...
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
git:
|
||||
depth: 10
|
25
vendor/github.com/nlopes/slack/CHANGELOG.md
generated
vendored
Normal file
25
vendor/github.com/nlopes/slack/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
### 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.
|
||||
|
||||
Please check [0.2.0](https://github.com/nlopes/slack/releases/tag/v0.2.0)
|
||||
|
||||
### v0.1.0 - May 28, 2017
|
||||
|
||||
This is released before adding context support.
|
||||
As the used context package is the one from Go 1.7 this will be the last
|
||||
compatible with Go < 1.7.
|
||||
|
||||
Please check [0.1.0](https://github.com/nlopes/slack/releases/tag/v0.1.0)
|
||||
|
||||
### v0.0.1 - Jul 26, 2015
|
||||
|
||||
If you just updated from master and it broke your implementation, please
|
||||
check [0.0.1](https://github.com/nlopes/slack/releases/tag/v0.0.1)
|
33
vendor/github.com/nlopes/slack/Gopkg.lock
generated
vendored
Normal file
33
vendor/github.com/nlopes/slack/Gopkg.lock
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
|
||||
version = "v1.2.2"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "888307bf47ee004aaaa4c45e6139929b4984f2253e48e382246bfb8c66f3cd65"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
13
vendor/github.com/nlopes/slack/Gopkg.toml
generated
vendored
Normal file
13
vendor/github.com/nlopes/slack/Gopkg.toml
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
ignored = ["github.com/lusis/slack-test"]
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
version = "1.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.2.1"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
0
vendor/github.com/slack-go/slack/LICENSE → vendor/github.com/nlopes/slack/LICENSE
generated
vendored
0
vendor/github.com/slack-go/slack/LICENSE → vendor/github.com/nlopes/slack/LICENSE
generated
vendored
35
vendor/github.com/slack-go/slack/README.md → vendor/github.com/nlopes/slack/README.md
generated
vendored
35
vendor/github.com/slack-go/slack/README.md → vendor/github.com/nlopes/slack/README.md
generated
vendored
@ -1,6 +1,5 @@
|
||||
Slack API in Go [![GoDoc](https://godoc.org/github.com/slack-go/slack?status.svg)](https://godoc.org/github.com/slack-go/slack) [![Build Status](https://travis-ci.org/slack-go/slack.svg)](https://travis-ci.org/slack-go/slack)
|
||||
Slack API in Go [![GoDoc](https://godoc.org/github.com/nlopes/slack?status.svg)](https://godoc.org/github.com/nlopes/slack) [![Build Status](https://travis-ci.org/nlopes/slack.svg)](https://travis-ci.org/nlopes/slack)
|
||||
===============
|
||||
This is the original Slack library for Go created by Norberto Lopez, transferred to a Github organization.
|
||||
|
||||
[![Join the chat at https://gitter.im/go-slack/Lobby](https://badges.gitter.im/go-slack/Lobby.svg)](https://gitter.im/go-slack/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
@ -10,16 +9,24 @@ a fully managed way.
|
||||
|
||||
|
||||
|
||||
## Change log
|
||||
Support for the EventsAPI has recently been added. It is still in its early stages but nearly all events have been added and tested (except for those events in [Developer Preview](https://api.slack.com/slack-apps-preview) mode). API stability for events is not promised at this time.
|
||||
|
||||
## Changelog
|
||||
### v0.2.0 - Feb 10, 2018
|
||||
|
||||
[CHANGELOG.md](https://github.com/slack-go/slack/blob/master/CHANGELOG.md) is available. Please visit it for updates.
|
||||
Release adds a bunch of functionality and improvements, mainly to give people a recent version to vendor against.
|
||||
|
||||
Please check [0.2.0](https://github.com/nlopes/slack/releases/tag/v0.2.0)
|
||||
|
||||
### CHANGELOG.md
|
||||
|
||||
[CHANGELOG.md](https://github.com/nlopes/slack/blob/master/CHANGELOG.md) is available. Please visit it for updates.
|
||||
|
||||
## Installing
|
||||
|
||||
### *go get*
|
||||
|
||||
$ go get -u github.com/slack-go/slack
|
||||
$ go get -u github.com/nlopes/slack
|
||||
|
||||
## Example
|
||||
|
||||
@ -29,14 +36,14 @@ a fully managed way.
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/nlopes/slack"
|
||||
)
|
||||
|
||||
func main() {
|
||||
api := slack.New("YOUR_TOKEN_HERE")
|
||||
// If you set debugging, it will log all requests to the console
|
||||
// Useful when encountering issues
|
||||
// slack.New("YOUR_TOKEN_HERE", slack.OptionDebug(true))
|
||||
// api.SetDebug(true)
|
||||
groups, err := api.GetGroups(false)
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
@ -54,7 +61,7 @@ func main() {
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/nlopes/slack"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -70,12 +77,12 @@ func main() {
|
||||
|
||||
## Minimal RTM usage:
|
||||
|
||||
See https://github.com/slack-go/slack/blob/master/examples/websocket/websocket.go
|
||||
See https://github.com/nlopes/slack/blob/master/examples/websocket/websocket.go
|
||||
|
||||
|
||||
## Minimal EventsAPI usage:
|
||||
|
||||
See https://github.com/slack-go/slack/blob/master/examples/eventsapi/events.go
|
||||
See https://github.com/nlopes/slack/blob/master/examples/eventsapi/events.go
|
||||
|
||||
|
||||
## Contributing
|
||||
@ -83,14 +90,6 @@ See https://github.com/slack-go/slack/blob/master/examples/eventsapi/events.go
|
||||
You are more than welcome to contribute to this project. Fork and
|
||||
make a Pull Request, or create an Issue if you see any problem.
|
||||
|
||||
Before making any Pull Request please run the following:
|
||||
|
||||
```
|
||||
make pr-prep
|
||||
```
|
||||
|
||||
This will check/update code formatting, linting and then run all tests
|
||||
|
||||
## License
|
||||
|
||||
BSD 2 Clause license
|
0
vendor/github.com/slack-go/slack/TODO.txt → vendor/github.com/nlopes/slack/TODO.txt
generated
vendored
0
vendor/github.com/slack-go/slack/TODO.txt → vendor/github.com/nlopes/slack/TODO.txt
generated
vendored
49
vendor/github.com/slack-go/slack/admin.go → vendor/github.com/nlopes/slack/admin.go
generated
vendored
49
vendor/github.com/slack-go/slack/admin.go → vendor/github.com/nlopes/slack/admin.go
generated
vendored
@ -2,19 +2,28 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (api *Client) adminRequest(ctx context.Context, method string, teamName string, values url.Values) error {
|
||||
resp := &SlackResponse{}
|
||||
err := parseAdminResponse(ctx, api.httpclient, method, teamName, values, resp, api)
|
||||
type adminResponse struct {
|
||||
OK bool `json:"ok"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func adminRequest(ctx context.Context, client HTTPRequester, method string, teamName string, values url.Values, debug bool) (*adminResponse, error) {
|
||||
adminResponse := &adminResponse{}
|
||||
err := parseAdminResponse(ctx, client, method, teamName, values, adminResponse, debug)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp.Err()
|
||||
if !adminResponse.OK {
|
||||
return nil, errors.New(adminResponse.Error)
|
||||
}
|
||||
|
||||
return adminResponse, nil
|
||||
}
|
||||
|
||||
// DisableUser disabled a user account, given a user ID
|
||||
@ -31,8 +40,9 @@ func (api *Client) DisableUserContext(ctx context.Context, teamName string, uid
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
if err := api.adminRequest(ctx, "setInactive", teamName, values); err != nil {
|
||||
return fmt.Errorf("failed to disable user with id '%s': %s", uid, err)
|
||||
_, err := adminRequest(ctx, api.httpclient, "setInactive", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to disable user with id '%s': %s", uid, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -57,7 +67,7 @@ func (api *Client) InviteGuestContext(ctx context.Context, teamName, channel, fi
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
err := api.adminRequest(ctx, "invite", teamName, values)
|
||||
_, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to invite single-channel guest: %s", err)
|
||||
}
|
||||
@ -84,7 +94,7 @@ func (api *Client) InviteRestrictedContext(ctx context.Context, teamName, channe
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
err := api.adminRequest(ctx, "invite", teamName, values)
|
||||
_, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to restricted account: %s", err)
|
||||
}
|
||||
@ -108,7 +118,7 @@ func (api *Client) InviteToTeamContext(ctx context.Context, teamName, firstName,
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
err := api.adminRequest(ctx, "invite", teamName, values)
|
||||
_, err := adminRequest(ctx, api.httpclient, "invite", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to invite to team: %s", err)
|
||||
}
|
||||
@ -130,7 +140,7 @@ func (api *Client) SetRegularContext(ctx context.Context, teamName, user string)
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
err := api.adminRequest(ctx, "setRegular", teamName, values)
|
||||
_, err := adminRequest(ctx, api.httpclient, "setRegular", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to change the user (%s) to a regular user: %s", user, err)
|
||||
}
|
||||
@ -152,7 +162,7 @@ func (api *Client) SendSSOBindingEmailContext(ctx context.Context, teamName, use
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
err := api.adminRequest(ctx, "sendSSOBind", teamName, values)
|
||||
_, err := adminRequest(ctx, api.httpclient, "sendSSOBind", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to send SSO binding email for user (%s): %s", user, err)
|
||||
}
|
||||
@ -175,7 +185,7 @@ func (api *Client) SetUltraRestrictedContext(ctx context.Context, teamName, uid,
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
err := api.adminRequest(ctx, "setUltraRestricted", teamName, values)
|
||||
_, err := adminRequest(ctx, api.httpclient, "setUltraRestricted", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to ultra-restrict account: %s", err)
|
||||
}
|
||||
@ -184,23 +194,22 @@ func (api *Client) SetUltraRestrictedContext(ctx context.Context, teamName, uid,
|
||||
}
|
||||
|
||||
// SetRestricted converts a user into a restricted account
|
||||
func (api *Client) SetRestricted(teamName, uid string, channelIds ...string) error {
|
||||
return api.SetRestrictedContext(context.Background(), teamName, uid, channelIds...)
|
||||
func (api *Client) SetRestricted(teamName, uid string) error {
|
||||
return api.SetRestrictedContext(context.Background(), teamName, uid)
|
||||
}
|
||||
|
||||
// SetRestrictedContext converts a user into a restricted account with a custom context
|
||||
func (api *Client) SetRestrictedContext(ctx context.Context, teamName, uid string, channelIds ...string) error {
|
||||
func (api *Client) SetRestrictedContext(ctx context.Context, teamName, uid string) error {
|
||||
values := url.Values{
|
||||
"user": {uid},
|
||||
"token": {api.token},
|
||||
"set_active": {"true"},
|
||||
"_attempts": {"1"},
|
||||
"channels": {strings.Join(channelIds, ",")},
|
||||
}
|
||||
|
||||
err := api.adminRequest(ctx, "setRestricted", teamName, values)
|
||||
_, err := adminRequest(ctx, api.httpclient, "setRestricted", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to restrict account: %s", err)
|
||||
return fmt.Errorf("Failed to restrict account: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
@ -17,7 +17,7 @@ type AttachmentAction struct {
|
||||
Name string `json:"name"` // Required.
|
||||
Text string `json:"text"` // Required.
|
||||
Style string `json:"style,omitempty"` // Optional. Allowed values: "default", "primary", "danger".
|
||||
Type actionType `json:"type"` // Required. Must be set to "button" or "select".
|
||||
Type string `json:"type"` // Required. Must be set to "button" or "select".
|
||||
Value string `json:"value,omitempty"` // Optional.
|
||||
DataSource string `json:"data_source,omitempty"` // Optional.
|
||||
MinQueryLength int `json:"min_query_length,omitempty"` // Optional. Default value is 1.
|
||||
@ -28,11 +28,6 @@ type AttachmentAction struct {
|
||||
URL string `json:"url,omitempty"` // Optional.
|
||||
}
|
||||
|
||||
// actionType returns the type of the action
|
||||
func (a AttachmentAction) actionType() actionType {
|
||||
return a.Type
|
||||
}
|
||||
|
||||
// AttachmentActionOption the individual option to appear in action menu.
|
||||
type AttachmentActionOption struct {
|
||||
Text string `json:"text"` // Required.
|
||||
@ -47,8 +42,25 @@ type AttachmentActionOptionGroup struct {
|
||||
}
|
||||
|
||||
// AttachmentActionCallback is sent from Slack when a user clicks a button in an interactive message (aka AttachmentAction)
|
||||
// DEPRECATED: use InteractionCallback
|
||||
type AttachmentActionCallback InteractionCallback
|
||||
type AttachmentActionCallback struct {
|
||||
Actions []AttachmentAction `json:"actions"`
|
||||
CallbackID string `json:"callback_id"`
|
||||
Team Team `json:"team"`
|
||||
Channel Channel `json:"channel"`
|
||||
User User `json:"user"`
|
||||
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
|
||||
OriginalMessage Message `json:"original_message"`
|
||||
|
||||
ActionTs string `json:"action_ts"`
|
||||
MessageTs string `json:"message_ts"`
|
||||
AttachmentID string `json:"attachment_id"`
|
||||
Token string `json:"token"`
|
||||
ResponseURL string `json:"response_url"`
|
||||
TriggerID string `json:"trigger_id"`
|
||||
}
|
||||
|
||||
// ConfirmationField are used to ask users to confirm actions
|
||||
type ConfirmationField struct {
|
||||
@ -61,7 +73,7 @@ type ConfirmationField struct {
|
||||
// Attachment contains all the information for an attachment
|
||||
type Attachment struct {
|
||||
Color string `json:"color,omitempty"`
|
||||
Fallback string `json:"fallback,omitempty"`
|
||||
Fallback string `json:"fallback"`
|
||||
|
||||
CallbackID string `json:"callback_id,omitempty"`
|
||||
ID int `json:"id,omitempty"`
|
||||
@ -75,7 +87,7 @@ type Attachment struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
TitleLink string `json:"title_link,omitempty"`
|
||||
Pretext string `json:"pretext,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Text string `json:"text"`
|
||||
|
||||
ImageURL string `json:"image_url,omitempty"`
|
||||
ThumbURL string `json:"thumb_url,omitempty"`
|
||||
@ -84,8 +96,6 @@ type Attachment struct {
|
||||
Actions []AttachmentAction `json:"actions,omitempty"`
|
||||
MarkdownIn []string `json:"mrkdwn_in,omitempty"`
|
||||
|
||||
Blocks Blocks `json:"blocks,omitempty"`
|
||||
|
||||
Footer string `json:"footer,omitempty"`
|
||||
FooterIcon string `json:"footer_icon,omitempty"`
|
||||
|
57
vendor/github.com/nlopes/slack/backoff.go
generated
vendored
Normal file
57
vendor/github.com/nlopes/slack/backoff.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This one was ripped from https://github.com/jpillora/backoff/blob/master/backoff.go
|
||||
|
||||
// Backoff is a time.Duration counter. It starts at Min. After every
|
||||
// call to Duration() it is multiplied by Factor. It is capped at
|
||||
// Max. It returns to Min on every call to Reset(). Used in
|
||||
// conjunction with the time package.
|
||||
type backoff struct {
|
||||
attempts int
|
||||
//Factor is the multiplying factor for each increment step
|
||||
Factor float64
|
||||
//Jitter eases contention by randomizing backoff steps
|
||||
Jitter bool
|
||||
//Min and Max are the minimum and maximum values of the counter
|
||||
Min, Max time.Duration
|
||||
}
|
||||
|
||||
// Returns the current value of the counter and then multiplies it
|
||||
// Factor
|
||||
func (b *backoff) Duration() time.Duration {
|
||||
//Zero-values are nonsensical, so we use
|
||||
//them to apply defaults
|
||||
if b.Min == 0 {
|
||||
b.Min = 100 * time.Millisecond
|
||||
}
|
||||
if b.Max == 0 {
|
||||
b.Max = 10 * time.Second
|
||||
}
|
||||
if b.Factor == 0 {
|
||||
b.Factor = 2
|
||||
}
|
||||
//calculate this duration
|
||||
dur := float64(b.Min) * math.Pow(b.Factor, float64(b.attempts))
|
||||
if b.Jitter {
|
||||
dur = rand.Float64()*(dur-float64(b.Min)) + float64(b.Min)
|
||||
}
|
||||
//cap!
|
||||
if dur > float64(b.Max) {
|
||||
return b.Max
|
||||
}
|
||||
//bump attempts count
|
||||
b.attempts++
|
||||
//return as a time.Duration
|
||||
return time.Duration(dur)
|
||||
}
|
||||
|
||||
//Resets the current value of the counter back to Min
|
||||
func (b *backoff) Reset() {
|
||||
b.attempts = 0
|
||||
}
|
29
vendor/github.com/slack-go/slack/bots.go → vendor/github.com/nlopes/slack/bots.go
generated
vendored
29
vendor/github.com/slack-go/slack/bots.go → vendor/github.com/nlopes/slack/bots.go
generated
vendored
@ -2,18 +2,16 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Bot contains information about a bot
|
||||
type Bot struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Deleted bool `json:"deleted"`
|
||||
UserID string `json:"user_id"`
|
||||
AppID string `json:"app_id"`
|
||||
Updated JSONTime `json:"updated"`
|
||||
Icons Icons `json:"icons"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Icons Icons `json:"icons"`
|
||||
}
|
||||
|
||||
type botResponseFull struct {
|
||||
@ -21,17 +19,15 @@ type botResponseFull struct {
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
func (api *Client) botRequest(ctx context.Context, path string, values url.Values) (*botResponseFull, error) {
|
||||
func botRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*botResponseFull, error) {
|
||||
response := &botResponseFull{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := response.Err(); err != nil {
|
||||
return nil, err
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@ -44,13 +40,10 @@ func (api *Client) GetBotInfo(bot string) (*Bot, error) {
|
||||
func (api *Client) GetBotInfoContext(ctx context.Context, bot string) (*Bot, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"bot": {bot},
|
||||
}
|
||||
|
||||
if bot != "" {
|
||||
values.Add("bot", bot)
|
||||
}
|
||||
|
||||
response, err := api.botRequest(ctx, "bots.info", values)
|
||||
response, err := botRequest(ctx, api.httpclient, "bots.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
169
vendor/github.com/slack-go/slack/channels.go → vendor/github.com/nlopes/slack/channels.go
generated
vendored
169
vendor/github.com/slack-go/slack/channels.go → vendor/github.com/nlopes/slack/channels.go
generated
vendored
@ -2,9 +2,9 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type channelResponseFull struct {
|
||||
@ -15,45 +15,27 @@ type channelResponseFull struct {
|
||||
NotInChannel bool `json:"not_in_channel"`
|
||||
History
|
||||
SlackResponse
|
||||
Metadata ResponseMetadata `json:"response_metadata"`
|
||||
}
|
||||
|
||||
// Channel contains information about the channel
|
||||
type Channel struct {
|
||||
GroupConversation
|
||||
groupConversation
|
||||
IsChannel bool `json:"is_channel"`
|
||||
IsGeneral bool `json:"is_general"`
|
||||
IsMember bool `json:"is_member"`
|
||||
Locale string `json:"locale"`
|
||||
}
|
||||
|
||||
func (api *Client) channelRequest(ctx context.Context, path string, values url.Values) (*channelResponseFull, error) {
|
||||
func channelRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*channelResponseFull, error) {
|
||||
response := &channelResponseFull{}
|
||||
err := postForm(ctx, api.httpclient, api.endpoint+path, values, response, api)
|
||||
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
}
|
||||
|
||||
// GetChannelsOption option provided when getting channels.
|
||||
type GetChannelsOption func(*ChannelPagination) error
|
||||
|
||||
// GetChannelsOptionExcludeMembers excludes the members collection from each channel.
|
||||
func GetChannelsOptionExcludeMembers() GetChannelsOption {
|
||||
return func(p *ChannelPagination) error {
|
||||
p.excludeMembers = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetChannelsOptionExcludeArchived excludes archived channels from results.
|
||||
func GetChannelsOptionExcludeArchived() GetChannelsOption {
|
||||
return func(p *ChannelPagination) error {
|
||||
p.excludeArchived = true
|
||||
return nil
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ArchiveChannel archives the given channel
|
||||
@ -70,7 +52,7 @@ func (api *Client) ArchiveChannelContext(ctx context.Context, channelID string)
|
||||
"channel": {channelID},
|
||||
}
|
||||
|
||||
_, err = api.channelRequest(ctx, "channels.archive", values)
|
||||
_, err = channelRequest(ctx, api.httpclient, "channels.archive", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -88,7 +70,7 @@ func (api *Client) UnarchiveChannelContext(ctx context.Context, channelID string
|
||||
"channel": {channelID},
|
||||
}
|
||||
|
||||
_, err = api.channelRequest(ctx, "channels.unarchive", values)
|
||||
_, err = channelRequest(ctx, api.httpclient, "channels.unarchive", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -106,7 +88,7 @@ func (api *Client) CreateChannelContext(ctx context.Context, channelName string)
|
||||
"name": {channelName},
|
||||
}
|
||||
|
||||
response, err := api.channelRequest(ctx, "channels.create", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.create", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -151,7 +133,7 @@ func (api *Client) GetChannelHistoryContext(ctx context.Context, channelID strin
|
||||
}
|
||||
}
|
||||
|
||||
response, err := api.channelRequest(ctx, "channels.history", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.history", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -168,12 +150,11 @@ func (api *Client) GetChannelInfo(channelID string) (*Channel, error) {
|
||||
// see https://api.slack.com/methods/channels.info
|
||||
func (api *Client) GetChannelInfoContext(ctx context.Context, channelID string) (*Channel, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"channel": {channelID},
|
||||
"include_locale": {strconv.FormatBool(true)},
|
||||
"token": {api.token},
|
||||
"channel": {channelID},
|
||||
}
|
||||
|
||||
response, err := api.channelRequest(ctx, "channels.info", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -186,7 +167,7 @@ func (api *Client) InviteUserToChannel(channelID, user string) (*Channel, error)
|
||||
return api.InviteUserToChannelContext(context.Background(), channelID, user)
|
||||
}
|
||||
|
||||
// InviteUserToChannelContext invites a user to a given channel and returns a *Channel with a custom context
|
||||
// InviteUserToChannelCustom invites a user to a given channel and returns a *Channel with a custom context
|
||||
// see https://api.slack.com/methods/channels.invite
|
||||
func (api *Client) InviteUserToChannelContext(ctx context.Context, channelID, user string) (*Channel, error) {
|
||||
values := url.Values{
|
||||
@ -195,7 +176,7 @@ func (api *Client) InviteUserToChannelContext(ctx context.Context, channelID, us
|
||||
"user": {user},
|
||||
}
|
||||
|
||||
response, err := api.channelRequest(ctx, "channels.invite", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.invite", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -216,7 +197,7 @@ func (api *Client) JoinChannelContext(ctx context.Context, channelName string) (
|
||||
"name": {channelName},
|
||||
}
|
||||
|
||||
response, err := api.channelRequest(ctx, "channels.join", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.join", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -237,7 +218,7 @@ func (api *Client) LeaveChannelContext(ctx context.Context, channelID string) (b
|
||||
"channel": {channelID},
|
||||
}
|
||||
|
||||
response, err := api.channelRequest(ctx, "channels.leave", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.leave", values, api.debug)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -260,111 +241,31 @@ func (api *Client) KickUserFromChannelContext(ctx context.Context, channelID, us
|
||||
"user": {user},
|
||||
}
|
||||
|
||||
_, err = api.channelRequest(ctx, "channels.kick", values)
|
||||
_, err = channelRequest(ctx, api.httpclient, "channels.kick", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
func newChannelPagination(c *Client, options ...GetChannelsOption) (cp ChannelPagination) {
|
||||
cp = ChannelPagination{
|
||||
c: c,
|
||||
limit: 200, // per slack api documentation.
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(&cp)
|
||||
}
|
||||
|
||||
return cp
|
||||
}
|
||||
|
||||
// ChannelPagination allows for paginating over the channels
|
||||
type ChannelPagination struct {
|
||||
Channels []Channel
|
||||
limit int
|
||||
excludeArchived bool
|
||||
excludeMembers bool
|
||||
previousResp *ResponseMetadata
|
||||
c *Client
|
||||
}
|
||||
|
||||
// Done checks if the pagination has completed
|
||||
func (ChannelPagination) Done(err error) bool {
|
||||
return err == errPaginationComplete
|
||||
}
|
||||
|
||||
// Failure checks if pagination failed.
|
||||
func (t ChannelPagination) Failure(err error) error {
|
||||
if t.Done(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (t ChannelPagination) Next(ctx context.Context) (_ ChannelPagination, err error) {
|
||||
var (
|
||||
resp *channelResponseFull
|
||||
)
|
||||
|
||||
if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") {
|
||||
return t, errPaginationComplete
|
||||
}
|
||||
|
||||
t.previousResp = t.previousResp.initialize()
|
||||
|
||||
values := url.Values{
|
||||
"limit": {strconv.Itoa(t.limit)},
|
||||
"exclude_archived": {strconv.FormatBool(t.excludeArchived)},
|
||||
"exclude_members": {strconv.FormatBool(t.excludeMembers)},
|
||||
"token": {t.c.token},
|
||||
"cursor": {t.previousResp.Cursor},
|
||||
}
|
||||
|
||||
if resp, err = t.c.channelRequest(ctx, "channels.list", values); err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
t.c.Debugf("GetChannelsContext: got %d channels; metadata %v", len(resp.Channels), resp.Metadata)
|
||||
t.Channels = resp.Channels
|
||||
t.previousResp = &resp.Metadata
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// GetChannelsPaginated fetches channels in a paginated fashion, see GetChannelsContext for usage.
|
||||
func (api *Client) GetChannelsPaginated(options ...GetChannelsOption) ChannelPagination {
|
||||
return newChannelPagination(api, options...)
|
||||
}
|
||||
|
||||
// GetChannels retrieves all the channels
|
||||
// see https://api.slack.com/methods/channels.list
|
||||
func (api *Client) GetChannels(excludeArchived bool, options ...GetChannelsOption) ([]Channel, error) {
|
||||
return api.GetChannelsContext(context.Background(), excludeArchived, options...)
|
||||
func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
|
||||
return api.GetChannelsContext(context.Background(), excludeArchived)
|
||||
}
|
||||
|
||||
// GetChannelsContext retrieves all the channels with a custom context
|
||||
// see https://api.slack.com/methods/channels.list
|
||||
func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool, options ...GetChannelsOption) (results []Channel, err error) {
|
||||
func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool) ([]Channel, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
if excludeArchived {
|
||||
options = append(options, GetChannelsOptionExcludeArchived())
|
||||
values.Add("exclude_archived", "1")
|
||||
}
|
||||
|
||||
p := api.GetChannelsPaginated(options...)
|
||||
for err == nil {
|
||||
p, err = p.Next(ctx)
|
||||
if err == nil {
|
||||
results = append(results, p.Channels...)
|
||||
} else if rateLimitedError, ok := err.(*RateLimitedError); ok {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
case <-time.After(rateLimitedError.RetryAfter):
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, p.Failure(err)
|
||||
return response.Channels, nil
|
||||
}
|
||||
|
||||
// SetChannelReadMark sets the read mark of a given channel to a specific point
|
||||
@ -387,7 +288,7 @@ func (api *Client) SetChannelReadMarkContext(ctx context.Context, channelID, ts
|
||||
"ts": {ts},
|
||||
}
|
||||
|
||||
_, err = api.channelRequest(ctx, "channels.mark", values)
|
||||
_, err = channelRequest(ctx, api.httpclient, "channels.mark", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -408,7 +309,7 @@ func (api *Client) RenameChannelContext(ctx context.Context, channelID, name str
|
||||
|
||||
// XXX: the created entry in this call returns a string instead of a number
|
||||
// so I may have to do some workaround to solve it.
|
||||
response, err := api.channelRequest(ctx, "channels.rename", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.rename", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -430,7 +331,7 @@ func (api *Client) SetChannelPurposeContext(ctx context.Context, channelID, purp
|
||||
"purpose": {purpose},
|
||||
}
|
||||
|
||||
response, err := api.channelRequest(ctx, "channels.setPurpose", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.setPurpose", values, api.debug)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -452,7 +353,7 @@ func (api *Client) SetChannelTopicContext(ctx context.Context, channelID, topic
|
||||
"topic": {topic},
|
||||
}
|
||||
|
||||
response, err := api.channelRequest(ctx, "channels.setTopic", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.setTopic", values, api.debug)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -473,7 +374,7 @@ func (api *Client) GetChannelRepliesContext(ctx context.Context, channelID, thre
|
||||
"channel": {channelID},
|
||||
"thread_ts": {thread_ts},
|
||||
}
|
||||
response, err := api.channelRequest(ctx, "channels.replies", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "channels.replies", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
448
vendor/github.com/nlopes/slack/chat.go
generated
vendored
Normal file
448
vendor/github.com/nlopes/slack/chat.go
generated
vendored
Normal file
@ -0,0 +1,448 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_MESSAGE_USERNAME = ""
|
||||
DEFAULT_MESSAGE_REPLY_BROADCAST = false
|
||||
DEFAULT_MESSAGE_ASUSER = false
|
||||
DEFAULT_MESSAGE_PARSE = ""
|
||||
DEFAULT_MESSAGE_THREAD_TIMESTAMP = ""
|
||||
DEFAULT_MESSAGE_LINK_NAMES = 0
|
||||
DEFAULT_MESSAGE_UNFURL_LINKS = false
|
||||
DEFAULT_MESSAGE_UNFURL_MEDIA = true
|
||||
DEFAULT_MESSAGE_ICON_URL = ""
|
||||
DEFAULT_MESSAGE_ICON_EMOJI = ""
|
||||
DEFAULT_MESSAGE_MARKDOWN = true
|
||||
DEFAULT_MESSAGE_ESCAPE_TEXT = true
|
||||
)
|
||||
|
||||
type chatResponseFull struct {
|
||||
Channel string `json:"channel"`
|
||||
Timestamp string `json:"ts"` //Regualr message timestamp
|
||||
MessageTimeStamp string `json:"message_ts"` //Ephemeral message timestamp
|
||||
Text string `json:"text"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
// getMessageTimestamp will inspect the `chatResponseFull` to ruturn a timestamp value
|
||||
// in `chat.postMessage` its under `ts`
|
||||
// in `chat.postEphemeral` its under `message_ts`
|
||||
func (c chatResponseFull) getMessageTimestamp() string {
|
||||
if len(c.Timestamp) > 0 {
|
||||
return c.Timestamp
|
||||
}
|
||||
return c.MessageTimeStamp
|
||||
}
|
||||
|
||||
// PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request
|
||||
type PostMessageParameters struct {
|
||||
Username string `json:"username"`
|
||||
AsUser bool `json:"as_user"`
|
||||
Parse string `json:"parse"`
|
||||
ThreadTimestamp string `json:"thread_ts"`
|
||||
ReplyBroadcast bool `json:"reply_broadcast"`
|
||||
LinkNames int `json:"link_names"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
UnfurlLinks bool `json:"unfurl_links"`
|
||||
UnfurlMedia bool `json:"unfurl_media"`
|
||||
IconURL string `json:"icon_url"`
|
||||
IconEmoji string `json:"icon_emoji"`
|
||||
Markdown bool `json:"mrkdwn,omitempty"`
|
||||
EscapeText bool `json:"escape_text"`
|
||||
|
||||
// chat.postEphemeral support
|
||||
Channel string `json:"channel"`
|
||||
User string `json:"user"`
|
||||
}
|
||||
|
||||
// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set
|
||||
func NewPostMessageParameters() PostMessageParameters {
|
||||
return PostMessageParameters{
|
||||
Username: DEFAULT_MESSAGE_USERNAME,
|
||||
User: DEFAULT_MESSAGE_USERNAME,
|
||||
AsUser: DEFAULT_MESSAGE_ASUSER,
|
||||
Parse: DEFAULT_MESSAGE_PARSE,
|
||||
ThreadTimestamp: DEFAULT_MESSAGE_THREAD_TIMESTAMP,
|
||||
LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
|
||||
Attachments: nil,
|
||||
UnfurlLinks: DEFAULT_MESSAGE_UNFURL_LINKS,
|
||||
UnfurlMedia: DEFAULT_MESSAGE_UNFURL_MEDIA,
|
||||
IconURL: DEFAULT_MESSAGE_ICON_URL,
|
||||
IconEmoji: DEFAULT_MESSAGE_ICON_EMOJI,
|
||||
Markdown: DEFAULT_MESSAGE_MARKDOWN,
|
||||
EscapeText: DEFAULT_MESSAGE_ESCAPE_TEXT,
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteMessage deletes a message in a channel
|
||||
func (api *Client) DeleteMessage(channel, messageTimestamp string) (string, string, error) {
|
||||
respChannel, respTimestamp, _, err := api.SendMessageContext(context.Background(), channel, MsgOptionDelete(messageTimestamp))
|
||||
return respChannel, respTimestamp, err
|
||||
}
|
||||
|
||||
// DeleteMessageContext deletes a message in a channel with a custom context
|
||||
func (api *Client) DeleteMessageContext(ctx context.Context, channel, messageTimestamp string) (string, string, error) {
|
||||
respChannel, respTimestamp, _, err := api.SendMessageContext(ctx, channel, MsgOptionDelete(messageTimestamp))
|
||||
return respChannel, respTimestamp, err
|
||||
}
|
||||
|
||||
// PostMessage sends a message to a channel.
|
||||
// Message is escaped by default according to https://api.slack.com/docs/formatting
|
||||
// Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
|
||||
func (api *Client) PostMessage(channel, text string, params PostMessageParameters) (string, string, error) {
|
||||
respChannel, respTimestamp, _, err := api.SendMessageContext(
|
||||
context.Background(),
|
||||
channel,
|
||||
MsgOptionText(text, params.EscapeText),
|
||||
MsgOptionAttachments(params.Attachments...),
|
||||
MsgOptionPostMessageParameters(params),
|
||||
)
|
||||
return respChannel, respTimestamp, err
|
||||
}
|
||||
|
||||
// PostMessageContext sends a message to a channel with a custom context
|
||||
// For more details, see PostMessage documentation
|
||||
func (api *Client) PostMessageContext(ctx context.Context, channel, text string, params PostMessageParameters) (string, string, error) {
|
||||
respChannel, respTimestamp, _, err := api.SendMessageContext(
|
||||
ctx,
|
||||
channel,
|
||||
MsgOptionText(text, params.EscapeText),
|
||||
MsgOptionAttachments(params.Attachments...),
|
||||
MsgOptionPostMessageParameters(params),
|
||||
)
|
||||
return respChannel, respTimestamp, err
|
||||
}
|
||||
|
||||
// PostEphemeral sends an ephemeral message to a user in a channel.
|
||||
// Message is escaped by default according to https://api.slack.com/docs/formatting
|
||||
// Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
|
||||
func (api *Client) PostEphemeral(channelID, userID string, options ...MsgOption) (string, error) {
|
||||
return api.PostEphemeralContext(
|
||||
context.Background(),
|
||||
channelID,
|
||||
userID,
|
||||
options...,
|
||||
)
|
||||
}
|
||||
|
||||
// PostEphemeralContext sends an ephemeal message to a user in a channel with a custom context
|
||||
// For more details, see PostEphemeral documentation
|
||||
func (api *Client) PostEphemeralContext(ctx context.Context, channelID, userID string, options ...MsgOption) (timestamp string, err error) {
|
||||
_, timestamp, _, err = api.SendMessageContext(ctx, channelID, append(options, MsgOptionPostEphemeral2(userID))...)
|
||||
return timestamp, err
|
||||
}
|
||||
|
||||
// UpdateMessage updates a message in a channel
|
||||
func (api *Client) UpdateMessage(channelID, timestamp, text string) (string, string, string, error) {
|
||||
return api.UpdateMessageContext(context.Background(), channelID, timestamp, text)
|
||||
}
|
||||
|
||||
// UpdateMessageContext updates a message in a channel
|
||||
func (api *Client) UpdateMessageContext(ctx context.Context, channelID, timestamp, text string) (string, string, string, error) {
|
||||
return api.SendMessageContext(ctx, channelID, MsgOptionUpdate(timestamp), MsgOptionText(text, true))
|
||||
}
|
||||
|
||||
// SendMessage more flexible method for configuring messages.
|
||||
func (api *Client) SendMessage(channel string, options ...MsgOption) (string, string, string, error) {
|
||||
return api.SendMessageContext(context.Background(), channel, options...)
|
||||
}
|
||||
|
||||
// SendMessageContext more flexible method for configuring messages with a custom context.
|
||||
func (api *Client) SendMessageContext(ctx context.Context, channelID string, options ...MsgOption) (channel string, timestamp string, text string, err error) {
|
||||
var (
|
||||
config sendConfig
|
||||
response chatResponseFull
|
||||
)
|
||||
|
||||
if config, err = applyMsgOptions(api.token, channelID, options...); err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
if err = postSlackMethod(ctx, api.httpclient, string(config.mode), config.values, &response, api.debug); err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
return response.Channel, response.getMessageTimestamp(), response.Text, response.Err()
|
||||
}
|
||||
|
||||
// ApplyMsgOptions utility function for debugging/testing chat requests.
|
||||
func ApplyMsgOptions(token, channel string, options ...MsgOption) (string, url.Values, error) {
|
||||
config, err := applyMsgOptions(token, channel, options...)
|
||||
return string(config.mode), config.values, err
|
||||
}
|
||||
|
||||
func applyMsgOptions(token, channel string, options ...MsgOption) (sendConfig, error) {
|
||||
config := sendConfig{
|
||||
mode: chatPostMessage,
|
||||
values: url.Values{
|
||||
"token": {token},
|
||||
"channel": {channel},
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
if err := opt(&config); err != nil {
|
||||
return config, err
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func escapeMessage(message string) string {
|
||||
replacer := strings.NewReplacer("&", "&", "<", "<", ">", ">")
|
||||
return replacer.Replace(message)
|
||||
}
|
||||
|
||||
type sendMode string
|
||||
|
||||
const (
|
||||
chatUpdate sendMode = "chat.update"
|
||||
chatPostMessage sendMode = "chat.postMessage"
|
||||
chatDelete sendMode = "chat.delete"
|
||||
chatPostEphemeral sendMode = "chat.postEphemeral"
|
||||
chatMeMessage sendMode = "chat.meMessage"
|
||||
)
|
||||
|
||||
type sendConfig struct {
|
||||
mode sendMode
|
||||
values url.Values
|
||||
}
|
||||
|
||||
// MsgOption option provided when sending a message.
|
||||
type MsgOption func(*sendConfig) error
|
||||
|
||||
// MsgOptionPost posts a messages, this is the default.
|
||||
func MsgOptionPost() MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.mode = chatPostMessage
|
||||
config.values.Del("ts")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionPostEphemeral - DEPRECATED: use MsgOptionPostEphemeral2
|
||||
// posts an ephemeral message.
|
||||
func MsgOptionPostEphemeral() MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.mode = chatPostEphemeral
|
||||
config.values.Del("ts")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionPostEphemeral2 - posts an ephemeral message to the provided user.
|
||||
func MsgOptionPostEphemeral2(userID string) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.mode = chatPostEphemeral
|
||||
MsgOptionUser(userID)(config)
|
||||
config.values.Del("ts")
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
config.mode = chatUpdate
|
||||
config.values.Add("ts", timestamp)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionDelete deletes a message based on the timestamp.
|
||||
func MsgOptionDelete(timestamp string) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.mode = chatDelete
|
||||
config.values.Add("ts", timestamp)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionAsUser whether or not to send the message as the user.
|
||||
func MsgOptionAsUser(b bool) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
if b != DEFAULT_MESSAGE_ASUSER {
|
||||
config.values.Set("as_user", "true")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionUser set the user for the message.
|
||||
func MsgOptionUser(userID string) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.values.Set("user", userID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionText provide the text for the message, optionally escape the provided
|
||||
// text.
|
||||
func MsgOptionText(text string, escape bool) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
if escape {
|
||||
text = escapeMessage(text)
|
||||
}
|
||||
config.values.Add("text", text)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionAttachments provide attachments for the message.
|
||||
func MsgOptionAttachments(attachments ...Attachment) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
if attachments == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
attachments, err := json.Marshal(attachments)
|
||||
if err == nil {
|
||||
config.values.Set("attachments", string(attachments))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionEnableLinkUnfurl enables link unfurling
|
||||
func MsgOptionEnableLinkUnfurl() MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.values.Set("unfurl_links", "true")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionDisableLinkUnfurl disables link unfurling
|
||||
func MsgOptionDisableLinkUnfurl() MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.values.Set("unfurl_links", "false")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionDisableMediaUnfurl disables media unfurling.
|
||||
func MsgOptionDisableMediaUnfurl() MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.values.Set("unfurl_media", "false")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionDisableMarkdown disables markdown.
|
||||
func MsgOptionDisableMarkdown() MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.values.Set("mrkdwn", "false")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionTS sets the thread TS of the message to enable creating or replying to a thread
|
||||
func MsgOptionTS(ts string) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.values.Set("thread_ts", ts)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionBroadcast sets reply_broadcast to true
|
||||
func MsgOptionBroadcast() MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.values.Set("reply_broadcast", "true")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// this function combines multiple options into a single option.
|
||||
func MsgOptionCompose(options ...MsgOption) MsgOption {
|
||||
return func(c *sendConfig) error {
|
||||
for _, opt := range options {
|
||||
if err := opt(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func MsgOptionParse(b bool) MsgOption {
|
||||
return func(c *sendConfig) error {
|
||||
var v string
|
||||
if b {
|
||||
v = "1"
|
||||
} else {
|
||||
v = "0"
|
||||
}
|
||||
c.values.Set("parse", v)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionPostMessageParameters maintain backwards compatibility.
|
||||
func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
if params.Username != DEFAULT_MESSAGE_USERNAME {
|
||||
config.values.Set("username", params.Username)
|
||||
}
|
||||
|
||||
// chat.postEphemeral support
|
||||
if params.User != DEFAULT_MESSAGE_USERNAME {
|
||||
config.values.Set("user", params.User)
|
||||
}
|
||||
|
||||
// never generates an error.
|
||||
MsgOptionAsUser(params.AsUser)(config)
|
||||
|
||||
if params.Parse != DEFAULT_MESSAGE_PARSE {
|
||||
config.values.Set("parse", params.Parse)
|
||||
}
|
||||
if params.LinkNames != DEFAULT_MESSAGE_LINK_NAMES {
|
||||
config.values.Set("link_names", "1")
|
||||
}
|
||||
|
||||
if params.UnfurlLinks != DEFAULT_MESSAGE_UNFURL_LINKS {
|
||||
config.values.Set("unfurl_links", "true")
|
||||
}
|
||||
|
||||
// I want to send a message with explicit `as_user` `true` and `unfurl_links` `false` in request.
|
||||
// Because setting `as_user` to `true` will change the default value for `unfurl_links` to `true` on Slack API side.
|
||||
if params.AsUser != DEFAULT_MESSAGE_ASUSER && params.UnfurlLinks == DEFAULT_MESSAGE_UNFURL_LINKS {
|
||||
config.values.Set("unfurl_links", "false")
|
||||
}
|
||||
if params.UnfurlMedia != DEFAULT_MESSAGE_UNFURL_MEDIA {
|
||||
config.values.Set("unfurl_media", "false")
|
||||
}
|
||||
if params.IconURL != DEFAULT_MESSAGE_ICON_URL {
|
||||
config.values.Set("icon_url", params.IconURL)
|
||||
}
|
||||
if params.IconEmoji != DEFAULT_MESSAGE_ICON_EMOJI {
|
||||
config.values.Set("icon_emoji", params.IconEmoji)
|
||||
}
|
||||
if params.Markdown != DEFAULT_MESSAGE_MARKDOWN {
|
||||
config.values.Set("mrkdwn", "false")
|
||||
}
|
||||
|
||||
if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP {
|
||||
config.values.Set("thread_ts", params.ThreadTimestamp)
|
||||
}
|
||||
if params.ReplyBroadcast != DEFAULT_MESSAGE_REPLY_BROADCAST {
|
||||
config.values.Set("reply_broadcast", "true")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
@ -2,13 +2,14 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Conversation is the foundation for IM and BaseGroupConversation
|
||||
type Conversation struct {
|
||||
type conversation struct {
|
||||
ID string `json:"id"`
|
||||
Created JSONTime `json:"created"`
|
||||
IsOpen bool `json:"is_open"`
|
||||
@ -35,8 +36,8 @@ type Conversation struct {
|
||||
}
|
||||
|
||||
// GroupConversation is the foundation for Group and Channel
|
||||
type GroupConversation struct {
|
||||
Conversation
|
||||
type groupConversation struct {
|
||||
conversation
|
||||
Name string `json:"name"`
|
||||
Creator string `json:"creator"`
|
||||
IsArchived bool `json:"is_archived"`
|
||||
@ -65,14 +66,6 @@ type GetUsersInConversationParameters struct {
|
||||
Limit int
|
||||
}
|
||||
|
||||
type GetConversationsForUserParameters struct {
|
||||
UserID string
|
||||
Cursor string
|
||||
Types []string
|
||||
Limit int
|
||||
ExcludeArchived bool
|
||||
}
|
||||
|
||||
type responseMetaData struct {
|
||||
NextCursor string `json:"next_cursor"`
|
||||
}
|
||||
@ -99,57 +92,16 @@ func (api *Client) GetUsersInConversationContext(ctx context.Context, params *Ge
|
||||
ResponseMetaData responseMetaData `json:"response_metadata"`
|
||||
SlackResponse
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "conversations.members", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.members", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if err := response.Err(); err != nil {
|
||||
return nil, "", err
|
||||
if !response.Ok {
|
||||
return nil, "", errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response.Members, response.ResponseMetaData.NextCursor, nil
|
||||
}
|
||||
|
||||
// GetConversationsForUser returns the list conversations for a given user
|
||||
func (api *Client) GetConversationsForUser(params *GetConversationsForUserParameters) (channels []Channel, nextCursor string, err error) {
|
||||
return api.GetConversationsForUserContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// GetConversationsForUserContext returns the list conversations for a given user with a custom context
|
||||
func (api *Client) GetConversationsForUserContext(ctx context.Context, params *GetConversationsForUserParameters) (channels []Channel, nextCursor string, err error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
if params.UserID != "" {
|
||||
values.Add("user", params.UserID)
|
||||
}
|
||||
if params.Cursor != "" {
|
||||
values.Add("cursor", params.Cursor)
|
||||
}
|
||||
if params.Limit != 0 {
|
||||
values.Add("limit", strconv.Itoa(params.Limit))
|
||||
}
|
||||
if params.Types != nil {
|
||||
values.Add("types", strings.Join(params.Types, ","))
|
||||
}
|
||||
if params.ExcludeArchived {
|
||||
values.Add("exclude_archived", "true")
|
||||
}
|
||||
response := struct {
|
||||
Channels []Channel `json:"channels"`
|
||||
ResponseMetaData responseMetaData `json:"response_metadata"`
|
||||
SlackResponse
|
||||
}{}
|
||||
err = api.postMethod(ctx, "users.conversations", values, &response)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return response.Channels, response.ResponseMetaData.NextCursor, response.Err()
|
||||
}
|
||||
|
||||
// ArchiveConversation archives a conversation
|
||||
func (api *Client) ArchiveConversation(channelID string) error {
|
||||
return api.ArchiveConversationContext(context.Background(), channelID)
|
||||
@ -161,9 +113,8 @@ func (api *Client) ArchiveConversationContext(ctx context.Context, channelID str
|
||||
"token": {api.token},
|
||||
"channel": {channelID},
|
||||
}
|
||||
|
||||
response := SlackResponse{}
|
||||
err := api.postMethod(ctx, "conversations.archive", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.archive", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -183,7 +134,7 @@ func (api *Client) UnArchiveConversationContext(ctx context.Context, channelID s
|
||||
"channel": {channelID},
|
||||
}
|
||||
response := SlackResponse{}
|
||||
err := api.postMethod(ctx, "conversations.unarchive", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.unarchive", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -207,7 +158,7 @@ func (api *Client) SetTopicOfConversationContext(ctx context.Context, channelID,
|
||||
SlackResponse
|
||||
Channel *Channel `json:"channel"`
|
||||
}{}
|
||||
err := api.postMethod(ctx, "conversations.setTopic", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.setTopic", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -231,8 +182,7 @@ func (api *Client) SetPurposeOfConversationContext(ctx context.Context, channelI
|
||||
SlackResponse
|
||||
Channel *Channel `json:"channel"`
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "conversations.setPurpose", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.setPurpose", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -256,8 +206,7 @@ func (api *Client) RenameConversationContext(ctx context.Context, channelID, cha
|
||||
SlackResponse
|
||||
Channel *Channel `json:"channel"`
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "conversations.rename", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.rename", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -281,8 +230,7 @@ func (api *Client) InviteUsersToConversationContext(ctx context.Context, channel
|
||||
SlackResponse
|
||||
Channel *Channel `json:"channel"`
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "conversations.invite", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.invite", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -302,9 +250,8 @@ func (api *Client) KickUserFromConversationContext(ctx context.Context, channelI
|
||||
"channel": {channelID},
|
||||
"user": {user},
|
||||
}
|
||||
|
||||
response := SlackResponse{}
|
||||
err := api.postMethod(ctx, "conversations.kick", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.kick", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -329,7 +276,7 @@ func (api *Client) CloseConversationContext(ctx context.Context, channelID strin
|
||||
AlreadyClosed bool `json:"already_closed"`
|
||||
}{}
|
||||
|
||||
err = api.postMethod(ctx, "conversations.close", values, &response)
|
||||
err = postSlackMethod(ctx, api.httpclient, "conversations.close", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
@ -349,12 +296,13 @@ func (api *Client) CreateConversationContext(ctx context.Context, channelName st
|
||||
"name": {channelName},
|
||||
"is_private": {strconv.FormatBool(isPrivate)},
|
||||
}
|
||||
response, err := api.channelRequest(ctx, "conversations.create", values)
|
||||
response, err := channelRequest(
|
||||
ctx, api.httpclient, "conversations.create", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.Channel, nil
|
||||
return &response.Channel, response.Err()
|
||||
}
|
||||
|
||||
// GetConversationInfo retrieves information about a conversation
|
||||
@ -369,7 +317,8 @@ func (api *Client) GetConversationInfoContext(ctx context.Context, channelID str
|
||||
"channel": {channelID},
|
||||
"include_locale": {strconv.FormatBool(includeLocale)},
|
||||
}
|
||||
response, err := api.channelRequest(ctx, "conversations.info", values)
|
||||
response, err := channelRequest(
|
||||
ctx, api.httpclient, "conversations.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -389,7 +338,7 @@ func (api *Client) LeaveConversationContext(ctx context.Context, channelID strin
|
||||
"channel": {channelID},
|
||||
}
|
||||
|
||||
response, err := api.channelRequest(ctx, "conversations.leave", values)
|
||||
response, err := channelRequest(ctx, api.httpclient, "conversations.leave", values, api.debug)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -445,7 +394,7 @@ func (api *Client) GetConversationRepliesContext(ctx context.Context, params *Ge
|
||||
Messages []Message `json:"messages"`
|
||||
}{}
|
||||
|
||||
err = api.postMethod(ctx, "conversations.replies", values, &response)
|
||||
err = postSlackMethod(ctx, api.httpclient, "conversations.replies", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, false, "", err
|
||||
}
|
||||
@ -485,8 +434,7 @@ func (api *Client) GetConversationsContext(ctx context.Context, params *GetConve
|
||||
ResponseMetaData responseMetaData `json:"response_metadata"`
|
||||
SlackResponse
|
||||
}{}
|
||||
|
||||
err = api.postMethod(ctx, "conversations.list", values, &response)
|
||||
err = postSlackMethod(ctx, api.httpclient, "conversations.list", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -523,8 +471,7 @@ func (api *Client) OpenConversationContext(ctx context.Context, params *OpenConv
|
||||
AlreadyOpen bool `json:"already_open"`
|
||||
SlackResponse
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "conversations.open", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.open", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, false, false, err
|
||||
}
|
||||
@ -548,8 +495,7 @@ func (api *Client) JoinConversationContext(ctx context.Context, channelID string
|
||||
} `json:"response_metadata"`
|
||||
SlackResponse
|
||||
}{}
|
||||
|
||||
err := api.postMethod(ctx, "conversations.join", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.join", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
@ -611,10 +557,12 @@ func (api *Client) GetConversationHistoryContext(ctx context.Context, params *Ge
|
||||
|
||||
response := GetConversationHistoryResponse{}
|
||||
|
||||
err := api.postMethod(ctx, "conversations.history", values, &response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "conversations.history", values, &response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
107
vendor/github.com/nlopes/slack/dialog.go
generated
vendored
Normal file
107
vendor/github.com/nlopes/slack/dialog.go
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type DialogTrigger struct {
|
||||
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.
|
||||
}
|
||||
|
||||
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".
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
type DialogElementOption struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// OpenDialog opens a dialog window where the triggerId originated from
|
||||
func (api *Client) OpenDialog(triggerId string, dialog Dialog) (err error) {
|
||||
return api.OpenDialogContext(context.Background(), triggerId, dialog)
|
||||
}
|
||||
|
||||
// OpenDialogContext opens a dialog window where the triggerId originated from with a custom context
|
||||
func (api *Client) OpenDialogContext(ctx context.Context, triggerId string, dialog Dialog) (err error) {
|
||||
if triggerId == "" {
|
||||
return errors.New("received empty parameters")
|
||||
}
|
||||
|
||||
resp := DialogTrigger{
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Err()
|
||||
}
|
29
vendor/github.com/slack-go/slack/dnd.go → vendor/github.com/nlopes/slack/dnd.go
generated
vendored
29
vendor/github.com/slack-go/slack/dnd.go → vendor/github.com/nlopes/slack/dnd.go
generated
vendored
@ -2,6 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -35,14 +36,16 @@ type dndTeamInfoResponse struct {
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
func (api *Client) dndRequest(ctx context.Context, path string, values url.Values) (*dndResponseFull, error) {
|
||||
func dndRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*dndResponseFull, error) {
|
||||
response := &dndResponseFull{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// EndDND ends the user's scheduled Do Not Disturb session
|
||||
@ -58,7 +61,7 @@ func (api *Client) EndDNDContext(ctx context.Context) error {
|
||||
|
||||
response := &SlackResponse{}
|
||||
|
||||
if err := api.postMethod(ctx, "dnd.endDnd", values, response); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "dnd.endDnd", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -76,7 +79,7 @@ func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) {
|
||||
"token": {api.token},
|
||||
}
|
||||
|
||||
response, err := api.dndRequest(ctx, "dnd.endSnooze", values)
|
||||
response, err := dndRequest(ctx, api.httpclient, "dnd.endSnooze", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -97,7 +100,7 @@ func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDSta
|
||||
values.Set("user", *user)
|
||||
}
|
||||
|
||||
response, err := api.dndRequest(ctx, "dnd.info", values)
|
||||
response, err := dndRequest(ctx, api.httpclient, "dnd.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -117,14 +120,12 @@ func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (m
|
||||
}
|
||||
response := &dndTeamInfoResponse{}
|
||||
|
||||
if err := api.postMethod(ctx, "dnd.teamInfo", values, response); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "dnd.teamInfo", values, response, api.debug); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.Err() != nil {
|
||||
return nil, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response.Users, nil
|
||||
}
|
||||
|
||||
@ -135,7 +136,7 @@ func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) {
|
||||
return api.SetSnoozeContext(context.Background(), minutes)
|
||||
}
|
||||
|
||||
// SetSnoozeContext adjusts the snooze duration for a user's Do Not Disturb settings with a custom context.
|
||||
// SetSnooze adjusts the snooze duration for a user's Do Not Disturb settings with a custom context.
|
||||
// For more information see the SetSnooze docs
|
||||
func (api *Client) SetSnoozeContext(ctx context.Context, minutes int) (*DNDStatus, error) {
|
||||
values := url.Values{
|
||||
@ -143,7 +144,7 @@ func (api *Client) SetSnoozeContext(ctx context.Context, minutes int) (*DNDStatu
|
||||
"num_minutes": {strconv.Itoa(minutes)},
|
||||
}
|
||||
|
||||
response, err := api.dndRequest(ctx, "dnd.setSnooze", values)
|
||||
response, err := dndRequest(ctx, api.httpclient, "dnd.setSnooze", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
9
vendor/github.com/slack-go/slack/emoji.go → vendor/github.com/nlopes/slack/emoji.go
generated
vendored
9
vendor/github.com/slack-go/slack/emoji.go → vendor/github.com/nlopes/slack/emoji.go
generated
vendored
@ -2,6 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
@ -22,14 +23,12 @@ func (api *Client) GetEmojiContext(ctx context.Context) (map[string]string, erro
|
||||
}
|
||||
response := &emojiResponseFull{}
|
||||
|
||||
err := api.postMethod(ctx, "emoji.list", values, response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "emoji.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response.Err() != nil {
|
||||
return nil, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response.Emoji, nil
|
||||
}
|
134
vendor/github.com/slack-go/slack/files.go → vendor/github.com/nlopes/slack/files.go
generated
vendored
134
vendor/github.com/slack-go/slack/files.go → vendor/github.com/nlopes/slack/files.go
generated
vendored
@ -2,7 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -86,41 +86,21 @@ type File struct {
|
||||
CommentsCount int `json:"comments_count"`
|
||||
NumStars int `json:"num_stars"`
|
||||
IsStarred bool `json:"is_starred"`
|
||||
Shares Share `json:"shares"`
|
||||
}
|
||||
|
||||
type Share struct {
|
||||
Public map[string][]ShareFileInfo `json:"public"`
|
||||
Private map[string][]ShareFileInfo `json:"private"`
|
||||
}
|
||||
|
||||
type ShareFileInfo struct {
|
||||
ReplyUsers []string `json:"reply_users"`
|
||||
ReplyUsersCount int `json:"reply_users_count"`
|
||||
ReplyCount int `json:"reply_count"`
|
||||
Ts string `json:"ts"`
|
||||
ThreadTs string `json:"thread_ts"`
|
||||
LatestReply string `json:"latest_reply"`
|
||||
ChannelName string `json:"channel_name"`
|
||||
TeamID string `json:"team_id"`
|
||||
}
|
||||
|
||||
// FileUploadParameters contains all the parameters necessary (including the optional ones) for an UploadFile() request.
|
||||
//
|
||||
// There are three ways to upload a file. You can either set Content if file is small, set Reader if file is large,
|
||||
// or provide a local file path in File to upload it from your filesystem.
|
||||
//
|
||||
// Note that when using the Reader option, you *must* specify the Filename, otherwise the Slack API isn't happy.
|
||||
type FileUploadParameters struct {
|
||||
File string
|
||||
Content string
|
||||
Reader io.Reader
|
||||
Filetype string
|
||||
Filename string
|
||||
Title string
|
||||
InitialComment string
|
||||
Channels []string
|
||||
ThreadTimestamp string
|
||||
File string
|
||||
Content string
|
||||
Reader io.Reader
|
||||
Filetype string
|
||||
Filename string
|
||||
Title string
|
||||
InitialComment string
|
||||
Channels []string
|
||||
}
|
||||
|
||||
// GetFilesParameters contains all the parameters necessary (including the optional ones) for a GetFiles() request
|
||||
@ -134,21 +114,11 @@ type GetFilesParameters struct {
|
||||
Page int
|
||||
}
|
||||
|
||||
// ListFilesParameters contains all the parameters necessary (including the optional ones) for a ListFiles() request
|
||||
type ListFilesParameters struct {
|
||||
Limit int
|
||||
User string
|
||||
Channel string
|
||||
Types string
|
||||
Cursor string
|
||||
}
|
||||
|
||||
type fileResponseFull struct {
|
||||
File `json:"file"`
|
||||
Paging `json:"paging"`
|
||||
Comments []Comment `json:"comments"`
|
||||
Files []File `json:"files"`
|
||||
Metadata ResponseMetadata `json:"response_metadata"`
|
||||
Comments []Comment `json:"comments"`
|
||||
Files []File `json:"files"`
|
||||
|
||||
SlackResponse
|
||||
}
|
||||
@ -166,14 +136,16 @@ func NewGetFilesParameters() GetFilesParameters {
|
||||
}
|
||||
}
|
||||
|
||||
func (api *Client) fileRequest(ctx context.Context, path string, values url.Values) (*fileResponseFull, error) {
|
||||
func fileRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*fileResponseFull, error) {
|
||||
response := &fileResponseFull{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetFileInfo retrieves a file and related comments
|
||||
@ -190,57 +162,18 @@ func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count,
|
||||
"page": {strconv.Itoa(page)},
|
||||
}
|
||||
|
||||
response, err := api.fileRequest(ctx, "files.info", values)
|
||||
response, err := fileRequest(ctx, api.httpclient, "files.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return &response.File, response.Comments, &response.Paging, nil
|
||||
}
|
||||
|
||||
// GetFile retreives a given file from its private download URL
|
||||
func (api *Client) GetFile(downloadURL string, writer io.Writer) error {
|
||||
return downloadFile(api.httpclient, api.token, downloadURL, writer, api)
|
||||
}
|
||||
|
||||
// GetFiles retrieves all files according to the parameters given
|
||||
func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) {
|
||||
return api.GetFilesContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// ListFiles retrieves all files according to the parameters given. Uses cursor based pagination.
|
||||
func (api *Client) ListFiles(params ListFilesParameters) ([]File, *ListFilesParameters, error) {
|
||||
return api.ListFilesContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// ListFilesContext retrieves all files according to the parameters given with a custom context. Uses cursor based pagination.
|
||||
func (api *Client) ListFilesContext(ctx context.Context, params ListFilesParameters) ([]File, *ListFilesParameters, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
|
||||
if params.User != DEFAULT_FILES_USER {
|
||||
values.Add("user", params.User)
|
||||
}
|
||||
if params.Channel != DEFAULT_FILES_CHANNEL {
|
||||
values.Add("channel", params.Channel)
|
||||
}
|
||||
if params.Limit != DEFAULT_FILES_COUNT {
|
||||
values.Add("limit", strconv.Itoa(params.Limit))
|
||||
}
|
||||
if params.Cursor != "" {
|
||||
values.Add("cursor", params.Cursor)
|
||||
}
|
||||
|
||||
response, err := api.fileRequest(ctx, "files.list", values)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
params.Cursor = response.Metadata.Cursor
|
||||
|
||||
return response.Files, ¶ms, nil
|
||||
}
|
||||
|
||||
// GetFilesContext retrieves all files according to the parameters given with a custom context
|
||||
func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) {
|
||||
values := url.Values{
|
||||
@ -268,7 +201,7 @@ func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameter
|
||||
values.Add("page", strconv.Itoa(params.Page))
|
||||
}
|
||||
|
||||
response, err := api.fileRequest(ctx, "files.list", values)
|
||||
response, err := fileRequest(ctx, api.httpclient, "files.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -304,29 +237,24 @@ func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParam
|
||||
if params.InitialComment != "" {
|
||||
values.Add("initial_comment", params.InitialComment)
|
||||
}
|
||||
if params.ThreadTimestamp != "" {
|
||||
values.Add("thread_ts", params.ThreadTimestamp)
|
||||
}
|
||||
if len(params.Channels) != 0 {
|
||||
values.Add("channels", strings.Join(params.Channels, ","))
|
||||
}
|
||||
if params.Content != "" {
|
||||
values.Add("content", params.Content)
|
||||
err = api.postMethod(ctx, "files.upload", values, response)
|
||||
err = postForm(ctx, api.httpclient, SLACK_API+"files.upload", values, response, api.debug)
|
||||
} else if params.File != "" {
|
||||
err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.File, "file", values, response, api)
|
||||
err = postLocalWithMultipartResponse(ctx, api.httpclient, "files.upload", params.File, "file", values, response, api.debug)
|
||||
} else if params.Reader != nil {
|
||||
if params.Filename == "" {
|
||||
return nil, fmt.Errorf("files.upload: FileUploadParameters.Filename is mandatory when using FileUploadParameters.Reader")
|
||||
}
|
||||
err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.upload", params.Filename, "file", values, params.Reader, response, api)
|
||||
err = postWithMultipartResponse(ctx, api.httpclient, "files.upload", params.Filename, "file", values, params.Reader, response, api.debug)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.File, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return &response.File, nil
|
||||
}
|
||||
|
||||
// DeleteFileComment deletes a file's comment
|
||||
@ -337,7 +265,7 @@ func (api *Client) DeleteFileComment(commentID, fileID string) error {
|
||||
// DeleteFileCommentContext deletes a file's comment with a custom context
|
||||
func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, commentID string) (err error) {
|
||||
if fileID == "" || commentID == "" {
|
||||
return ErrParametersMissing
|
||||
return errors.New("received empty parameters")
|
||||
}
|
||||
|
||||
values := url.Values{
|
||||
@ -345,7 +273,7 @@ func (api *Client) DeleteFileCommentContext(ctx context.Context, fileID, comment
|
||||
"file": {fileID},
|
||||
"id": {commentID},
|
||||
}
|
||||
_, err = api.fileRequest(ctx, "files.comments.delete", values)
|
||||
_, err = fileRequest(ctx, api.httpclient, "files.comments.delete", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -361,7 +289,7 @@ func (api *Client) DeleteFileContext(ctx context.Context, fileID string) (err er
|
||||
"file": {fileID},
|
||||
}
|
||||
|
||||
_, err = api.fileRequest(ctx, "files.delete", values)
|
||||
_, err = fileRequest(ctx, api.httpclient, "files.delete", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -377,7 +305,7 @@ func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string
|
||||
"file": {fileID},
|
||||
}
|
||||
|
||||
response, err := api.fileRequest(ctx, "files.revokePublicURL", values)
|
||||
response, err := fileRequest(ctx, api.httpclient, "files.revokePublicURL", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -396,7 +324,7 @@ func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string)
|
||||
"file": {fileID},
|
||||
}
|
||||
|
||||
response, err := api.fileRequest(ctx, "files.sharedPublicURL", values)
|
||||
response, err := fileRequest(ctx, api.httpclient, "files.sharedPublicURL", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
67
vendor/github.com/slack-go/slack/groups.go → vendor/github.com/nlopes/slack/groups.go
generated
vendored
67
vendor/github.com/slack-go/slack/groups.go → vendor/github.com/nlopes/slack/groups.go
generated
vendored
@ -2,13 +2,14 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Group contains all the information for a group
|
||||
type Group struct {
|
||||
GroupConversation
|
||||
groupConversation
|
||||
IsGroup bool `json:"is_group"`
|
||||
}
|
||||
|
||||
@ -27,14 +28,16 @@ type groupResponseFull struct {
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
func (api *Client) groupRequest(ctx context.Context, path string, values url.Values) (*groupResponseFull, error) {
|
||||
func groupRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*groupResponseFull, error) {
|
||||
response := &groupResponseFull{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ArchiveGroup archives a private group
|
||||
@ -49,7 +52,7 @@ func (api *Client) ArchiveGroupContext(ctx context.Context, group string) error
|
||||
"channel": {group},
|
||||
}
|
||||
|
||||
_, err := api.groupRequest(ctx, "groups.archive", values)
|
||||
_, err := groupRequest(ctx, api.httpclient, "groups.archive", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -65,7 +68,7 @@ func (api *Client) UnarchiveGroupContext(ctx context.Context, group string) erro
|
||||
"channel": {group},
|
||||
}
|
||||
|
||||
_, err := api.groupRequest(ctx, "groups.unarchive", values)
|
||||
_, err := groupRequest(ctx, api.httpclient, "groups.unarchive", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -81,7 +84,7 @@ func (api *Client) CreateGroupContext(ctx context.Context, group string) (*Group
|
||||
"name": {group},
|
||||
}
|
||||
|
||||
response, err := api.groupRequest(ctx, "groups.create", values)
|
||||
response, err := groupRequest(ctx, api.httpclient, "groups.create", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -106,13 +109,32 @@ func (api *Client) CreateChildGroupContext(ctx context.Context, group string) (*
|
||||
"channel": {group},
|
||||
}
|
||||
|
||||
response, err := api.groupRequest(ctx, "groups.createChild", values)
|
||||
response, err := groupRequest(ctx, api.httpclient, "groups.createChild", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.Group, nil
|
||||
}
|
||||
|
||||
// CloseGroup closes a private group
|
||||
func (api *Client) CloseGroup(group string) (bool, bool, error) {
|
||||
return api.CloseGroupContext(context.Background(), group)
|
||||
}
|
||||
|
||||
// CloseGroupContext closes a private group with a custom context
|
||||
func (api *Client) CloseGroupContext(ctx context.Context, group string) (bool, bool, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"channel": {group},
|
||||
}
|
||||
|
||||
response, err := imRequest(ctx, api.httpclient, "groups.close", values, api.debug)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
return response.NoOp, response.AlreadyClosed, nil
|
||||
}
|
||||
|
||||
// GetGroupHistory fetches all the history for a private group
|
||||
func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*History, error) {
|
||||
return api.GetGroupHistoryContext(context.Background(), group, params)
|
||||
@ -148,7 +170,7 @@ func (api *Client) GetGroupHistoryContext(ctx context.Context, group string, par
|
||||
}
|
||||
}
|
||||
|
||||
response, err := api.groupRequest(ctx, "groups.history", values)
|
||||
response, err := groupRequest(ctx, api.httpclient, "groups.history", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -168,7 +190,7 @@ func (api *Client) InviteUserToGroupContext(ctx context.Context, group, user str
|
||||
"user": {user},
|
||||
}
|
||||
|
||||
response, err := api.groupRequest(ctx, "groups.invite", values)
|
||||
response, err := groupRequest(ctx, api.httpclient, "groups.invite", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@ -187,7 +209,7 @@ func (api *Client) LeaveGroupContext(ctx context.Context, group string) (err err
|
||||
"channel": {group},
|
||||
}
|
||||
|
||||
_, err = api.groupRequest(ctx, "groups.leave", values)
|
||||
_, err = groupRequest(ctx, api.httpclient, "groups.leave", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -204,7 +226,7 @@ func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user str
|
||||
"user": {user},
|
||||
}
|
||||
|
||||
_, err = api.groupRequest(ctx, "groups.kick", values)
|
||||
_, err = groupRequest(ctx, api.httpclient, "groups.kick", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -222,7 +244,7 @@ func (api *Client) GetGroupsContext(ctx context.Context, excludeArchived bool) (
|
||||
values.Add("exclude_archived", "1")
|
||||
}
|
||||
|
||||
response, err := api.groupRequest(ctx, "groups.list", values)
|
||||
response, err := groupRequest(ctx, api.httpclient, "groups.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -237,12 +259,11 @@ func (api *Client) GetGroupInfo(group string) (*Group, error) {
|
||||
// GetGroupInfoContext retrieves the given group with a custom context
|
||||
func (api *Client) GetGroupInfoContext(ctx context.Context, group string) (*Group, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"channel": {group},
|
||||
"include_locale": {strconv.FormatBool(true)},
|
||||
"token": {api.token},
|
||||
"channel": {group},
|
||||
}
|
||||
|
||||
response, err := api.groupRequest(ctx, "groups.info", values)
|
||||
response, err := groupRequest(ctx, api.httpclient, "groups.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -267,7 +288,7 @@ func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string
|
||||
"ts": {ts},
|
||||
}
|
||||
|
||||
_, err = api.groupRequest(ctx, "groups.mark", values)
|
||||
_, err = groupRequest(ctx, api.httpclient, "groups.mark", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -283,7 +304,7 @@ func (api *Client) OpenGroupContext(ctx context.Context, group string) (bool, bo
|
||||
"channel": {group},
|
||||
}
|
||||
|
||||
response, err := api.groupRequest(ctx, "groups.open", values)
|
||||
response, err := groupRequest(ctx, api.httpclient, "groups.open", values, api.debug)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
@ -307,7 +328,7 @@ func (api *Client) RenameGroupContext(ctx context.Context, group, name string) (
|
||||
|
||||
// XXX: the created entry in this call returns a string instead of a number
|
||||
// so I may have to do some workaround to solve it.
|
||||
response, err := api.groupRequest(ctx, "groups.rename", values)
|
||||
response, err := groupRequest(ctx, api.httpclient, "groups.rename", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -327,7 +348,7 @@ func (api *Client) SetGroupPurposeContext(ctx context.Context, group, purpose st
|
||||
"purpose": {purpose},
|
||||
}
|
||||
|
||||
response, err := api.groupRequest(ctx, "groups.setPurpose", values)
|
||||
response, err := groupRequest(ctx, api.httpclient, "groups.setPurpose", values, api.debug)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -347,7 +368,7 @@ func (api *Client) SetGroupTopicContext(ctx context.Context, group, topic string
|
||||
"topic": {topic},
|
||||
}
|
||||
|
||||
response, err := api.groupRequest(ctx, "groups.setTopic", values)
|
||||
response, err := groupRequest(ctx, api.httpclient, "groups.setTopic", values, api.debug)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
27
vendor/github.com/slack-go/slack/im.go → vendor/github.com/nlopes/slack/im.go
generated
vendored
27
vendor/github.com/slack-go/slack/im.go → vendor/github.com/nlopes/slack/im.go
generated
vendored
@ -2,6 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
@ -22,18 +23,22 @@ type imResponseFull struct {
|
||||
|
||||
// IM contains information related to the Direct Message channel
|
||||
type IM struct {
|
||||
Conversation
|
||||
IsUserDeleted bool `json:"is_user_deleted"`
|
||||
conversation
|
||||
IsIM bool `json:"is_im"`
|
||||
User string `json:"user"`
|
||||
IsUserDeleted bool `json:"is_user_deleted"`
|
||||
}
|
||||
|
||||
func (api *Client) imRequest(ctx context.Context, path string, values url.Values) (*imResponseFull, error) {
|
||||
func imRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*imResponseFull, error) {
|
||||
response := &imResponseFull{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// CloseIMChannel closes the direct message channel
|
||||
@ -48,7 +53,7 @@ func (api *Client) CloseIMChannelContext(ctx context.Context, channel string) (b
|
||||
"channel": {channel},
|
||||
}
|
||||
|
||||
response, err := api.imRequest(ctx, "im.close", values)
|
||||
response, err := imRequest(ctx, api.httpclient, "im.close", values, api.debug)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
@ -69,7 +74,7 @@ func (api *Client) OpenIMChannelContext(ctx context.Context, user string) (bool,
|
||||
"user": {user},
|
||||
}
|
||||
|
||||
response, err := api.imRequest(ctx, "im.open", values)
|
||||
response, err := imRequest(ctx, api.httpclient, "im.open", values, api.debug)
|
||||
if err != nil {
|
||||
return false, false, "", err
|
||||
}
|
||||
@ -89,7 +94,7 @@ func (api *Client) MarkIMChannelContext(ctx context.Context, channel, ts string)
|
||||
"ts": {ts},
|
||||
}
|
||||
|
||||
_, err := api.imRequest(ctx, "im.mark", values)
|
||||
_, err := imRequest(ctx, api.httpclient, "im.mark", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -128,7 +133,7 @@ func (api *Client) GetIMHistoryContext(ctx context.Context, channel string, para
|
||||
}
|
||||
}
|
||||
|
||||
response, err := api.imRequest(ctx, "im.history", values)
|
||||
response, err := imRequest(ctx, api.httpclient, "im.history", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -146,7 +151,7 @@ func (api *Client) GetIMChannelsContext(ctx context.Context) ([]IM, error) {
|
||||
"token": {api.token},
|
||||
}
|
||||
|
||||
response, err := api.imRequest(ctx, "im.list", values)
|
||||
response, err := imRequest(ctx, api.httpclient, "im.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
225
vendor/github.com/nlopes/slack/info.go
generated
vendored
Normal file
225
vendor/github.com/nlopes/slack/info.go
generated
vendored
Normal file
@ -0,0 +1,225 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UserPrefs needs to be implemented
|
||||
type UserPrefs struct {
|
||||
// "highlight_words":"",
|
||||
// "user_colors":"",
|
||||
// "color_names_in_list":true,
|
||||
// "growls_enabled":true,
|
||||
// "tz":"Europe\/London",
|
||||
// "push_dm_alert":true,
|
||||
// "push_mention_alert":true,
|
||||
// "push_everything":true,
|
||||
// "push_idle_wait":2,
|
||||
// "push_sound":"b2.mp3",
|
||||
// "push_loud_channels":"",
|
||||
// "push_mention_channels":"",
|
||||
// "push_loud_channels_set":"",
|
||||
// "email_alerts":"instant",
|
||||
// "email_alerts_sleep_until":0,
|
||||
// "email_misc":false,
|
||||
// "email_weekly":true,
|
||||
// "welcome_message_hidden":false,
|
||||
// "all_channels_loud":true,
|
||||
// "loud_channels":"",
|
||||
// "never_channels":"",
|
||||
// "loud_channels_set":"",
|
||||
// "show_member_presence":true,
|
||||
// "search_sort":"timestamp",
|
||||
// "expand_inline_imgs":true,
|
||||
// "expand_internal_inline_imgs":true,
|
||||
// "expand_snippets":false,
|
||||
// "posts_formatting_guide":true,
|
||||
// "seen_welcome_2":true,
|
||||
// "seen_ssb_prompt":false,
|
||||
// "search_only_my_channels":false,
|
||||
// "emoji_mode":"default",
|
||||
// "has_invited":true,
|
||||
// "has_uploaded":false,
|
||||
// "has_created_channel":true,
|
||||
// "search_exclude_channels":"",
|
||||
// "messages_theme":"default",
|
||||
// "webapp_spellcheck":true,
|
||||
// "no_joined_overlays":false,
|
||||
// "no_created_overlays":true,
|
||||
// "dropbox_enabled":false,
|
||||
// "seen_user_menu_tip_card":true,
|
||||
// "seen_team_menu_tip_card":true,
|
||||
// "seen_channel_menu_tip_card":true,
|
||||
// "seen_message_input_tip_card":true,
|
||||
// "seen_channels_tip_card":true,
|
||||
// "seen_domain_invite_reminder":false,
|
||||
// "seen_member_invite_reminder":false,
|
||||
// "seen_flexpane_tip_card":true,
|
||||
// "seen_search_input_tip_card":true,
|
||||
// "mute_sounds":false,
|
||||
// "arrow_history":false,
|
||||
// "tab_ui_return_selects":true,
|
||||
// "obey_inline_img_limit":true,
|
||||
// "new_msg_snd":"knock_brush.mp3",
|
||||
// "collapsible":false,
|
||||
// "collapsible_by_click":true,
|
||||
// "require_at":false,
|
||||
// "mac_ssb_bounce":"",
|
||||
// "mac_ssb_bullet":true,
|
||||
// "win_ssb_bullet":true,
|
||||
// "expand_non_media_attachments":true,
|
||||
// "show_typing":true,
|
||||
// "pagekeys_handled":true,
|
||||
// "last_snippet_type":"",
|
||||
// "display_real_names_override":0,
|
||||
// "time24":false,
|
||||
// "enter_is_special_in_tbt":false,
|
||||
// "graphic_emoticons":false,
|
||||
// "convert_emoticons":true,
|
||||
// "autoplay_chat_sounds":true,
|
||||
// "ss_emojis":true,
|
||||
// "sidebar_behavior":"",
|
||||
// "mark_msgs_read_immediately":true,
|
||||
// "start_scroll_at_oldest":true,
|
||||
// "snippet_editor_wrap_long_lines":false,
|
||||
// "ls_disabled":false,
|
||||
// "sidebar_theme":"default",
|
||||
// "sidebar_theme_custom_values":"",
|
||||
// "f_key_search":false,
|
||||
// "k_key_omnibox":true,
|
||||
// "speak_growls":false,
|
||||
// "mac_speak_voice":"com.apple.speech.synthesis.voice.Alex",
|
||||
// "mac_speak_speed":250,
|
||||
// "comma_key_prefs":false,
|
||||
// "at_channel_suppressed_channels":"",
|
||||
// "push_at_channel_suppressed_channels":"",
|
||||
// "prompted_for_email_disabling":false,
|
||||
// "full_text_extracts":false,
|
||||
// "no_text_in_notifications":false,
|
||||
// "muted_channels":"",
|
||||
// "no_macssb1_banner":false,
|
||||
// "privacy_policy_seen":true,
|
||||
// "search_exclude_bots":false,
|
||||
// "fuzzy_matching":false
|
||||
}
|
||||
|
||||
// UserDetails contains user details coming in the initial response from StartRTM
|
||||
type UserDetails struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Created JSONTime `json:"created"`
|
||||
ManualPresence string `json:"manual_presence"`
|
||||
Prefs UserPrefs `json:"prefs"`
|
||||
}
|
||||
|
||||
// JSONTime exists so that we can have a String method converting the date
|
||||
type JSONTime int64
|
||||
|
||||
// String converts the unix timestamp into a string
|
||||
func (t JSONTime) String() string {
|
||||
tm := t.Time()
|
||||
return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
|
||||
}
|
||||
|
||||
// Time returns a `time.Time` representation of this value.
|
||||
func (t JSONTime) Time() time.Time {
|
||||
return time.Unix(int64(t), 0)
|
||||
}
|
||||
|
||||
// UnmarshalJSON will unmarshal both string and int JSON values
|
||||
func (t *JSONTime) UnmarshalJSON(buf []byte) error {
|
||||
s := bytes.Trim(buf, `"`)
|
||||
|
||||
v, err := strconv.Atoi(string(s))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = JSONTime(int64(v))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Team contains details about a team
|
||||
type Team struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
// Icons XXX: needs further investigation
|
||||
type Icons struct {
|
||||
Image36 string `json:"image_36,omitempty"`
|
||||
Image48 string `json:"image_48,omitempty"`
|
||||
Image72 string `json:"image_72,omitempty"`
|
||||
}
|
||||
|
||||
// Info contains various details about Users, Channels, Bots and the authenticated user.
|
||||
// It is returned by StartRTM or included in the "ConnectedEvent" RTM event.
|
||||
type Info struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
User *UserDetails `json:"self,omitempty"`
|
||||
Team *Team `json:"team,omitempty"`
|
||||
Users []User `json:"users,omitempty"`
|
||||
Channels []Channel `json:"channels,omitempty"`
|
||||
Groups []Group `json:"groups,omitempty"`
|
||||
Bots []Bot `json:"bots,omitempty"`
|
||||
IMs []IM `json:"ims,omitempty"`
|
||||
}
|
||||
|
||||
type infoResponseFull struct {
|
||||
Info
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
// GetBotByID returns a bot given a bot id
|
||||
func (info Info) GetBotByID(botID string) *Bot {
|
||||
for _, bot := range info.Bots {
|
||||
if bot.ID == botID {
|
||||
return &bot
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByID returns a user given a user id
|
||||
func (info Info) GetUserByID(userID string) *User {
|
||||
for _, user := range info.Users {
|
||||
if user.ID == userID {
|
||||
return &user
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetChannelByID returns a channel given a channel id
|
||||
func (info Info) GetChannelByID(channelID string) *Channel {
|
||||
for _, channel := range info.Channels {
|
||||
if channel.ID == channelID {
|
||||
return &channel
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGroupByID returns a group given a group id
|
||||
func (info Info) GetGroupByID(groupID string) *Group {
|
||||
for _, group := range info.Groups {
|
||||
if group.ID == groupID {
|
||||
return &group
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIMByID returns an IM given an IM id
|
||||
func (info Info) GetIMByID(imID string) *IM {
|
||||
for _, im := range info.IMs {
|
||||
if im.ID == imID {
|
||||
return &im
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
0
vendor/github.com/slack-go/slack/item.go → vendor/github.com/nlopes/slack/item.go
generated
vendored
0
vendor/github.com/slack-go/slack/item.go → vendor/github.com/nlopes/slack/item.go
generated
vendored
53
vendor/github.com/nlopes/slack/logger.go
generated
vendored
Normal file
53
vendor/github.com/nlopes/slack/logger.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SetLogger let's library users supply a logger, so that api debugging
|
||||
// can be logged along with the application's debugging info.
|
||||
func SetLogger(l logProvider) {
|
||||
loggerMutex.Lock()
|
||||
logger = ilogger{logProvider: l}
|
||||
loggerMutex.Unlock()
|
||||
}
|
||||
|
||||
var (
|
||||
loggerMutex = new(sync.Mutex)
|
||||
logger logInternal // A logger that can be set by consumers
|
||||
)
|
||||
|
||||
// logProvider is a logger interface compatible with both stdlib and some
|
||||
// 3rd party loggers such as logrus.
|
||||
type logProvider interface {
|
||||
Output(int, string) error
|
||||
}
|
||||
|
||||
// logInternal represents the internal logging api we use.
|
||||
type logInternal interface {
|
||||
Print(...interface{})
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
Output(int, string) error
|
||||
}
|
||||
|
||||
// ilogger implements the additional methods used by our internal logging.
|
||||
type ilogger struct {
|
||||
logProvider
|
||||
}
|
||||
|
||||
// Println replicates the behaviour of the standard logger.
|
||||
func (t ilogger) Println(v ...interface{}) {
|
||||
t.Output(2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// Printf replicates the behaviour of the standard logger.
|
||||
func (t ilogger) Printf(format string, v ...interface{}) {
|
||||
t.Output(2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Print replicates the behaviour of the standard logger.
|
||||
func (t ilogger) Print(v ...interface{}) {
|
||||
t.Output(2, fmt.Sprint(v...))
|
||||
}
|
@ -4,25 +4,22 @@ package slack
|
||||
type OutgoingMessage struct {
|
||||
ID int `json:"id"`
|
||||
// channel ID
|
||||
Channel string `json:"channel,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
ThreadTimestamp string `json:"thread_ts,omitempty"`
|
||||
ThreadBroadcast bool `json:"reply_broadcast,omitempty"`
|
||||
IDs []string `json:"ids,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
ThreadTimestamp string `json:"thread_ts,omitempty"`
|
||||
ThreadBroadcast bool `json:"reply_broadcast,omitempty"`
|
||||
}
|
||||
|
||||
// Message is an auxiliary type to allow us to have a message containing sub messages
|
||||
type Message struct {
|
||||
Msg
|
||||
SubMessage *Msg `json:"message,omitempty"`
|
||||
PreviousMessage *Msg `json:"previous_message,omitempty"`
|
||||
SubMessage *Msg `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// Msg contains information about a slack message
|
||||
type Msg struct {
|
||||
// Basic Message
|
||||
ClientMsgID string `json:"client_msg_id"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
@ -94,18 +91,8 @@ type Msg struct {
|
||||
ResponseType string `json:"response_type,omitempty"`
|
||||
ReplaceOriginal bool `json:"replace_original"`
|
||||
DeleteOriginal bool `json:"delete_original"`
|
||||
|
||||
// Block type Message
|
||||
Blocks Blocks `json:"blocks,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
// ResponseTypeInChannel in channel response for slash commands.
|
||||
ResponseTypeInChannel = "in_channel"
|
||||
// ResponseTypeEphemeral ephemeral response for slash commands.
|
||||
ResponseTypeEphemeral = "ephemeral"
|
||||
)
|
||||
|
||||
// Icon is used for bot messages
|
||||
type Icon struct {
|
||||
IconURL string `json:"icon_url,omitempty"`
|
||||
@ -160,15 +147,6 @@ func (rtm *RTM) NewOutgoingMessage(text string, channelID string, options ...RTM
|
||||
return &msg
|
||||
}
|
||||
|
||||
// NewSubscribeUserPresence prepares an OutgoingMessage that the user can
|
||||
// use to subscribe presence events for the specified users.
|
||||
func (rtm *RTM) NewSubscribeUserPresence(ids []string) *OutgoingMessage {
|
||||
return &OutgoingMessage{
|
||||
Type: "presence_sub",
|
||||
IDs: ids,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTypingMessage prepares an OutgoingMessage that the user can
|
||||
// use to send as a typing indicator. Use this function to properly set the
|
||||
// messageID.
|
||||
@ -196,4 +174,5 @@ func RTMsgOptionBroadcast() RTMsgOption {
|
||||
return func(msg *OutgoingMessage) {
|
||||
msg.ThreadBroadcast = true
|
||||
}
|
||||
|
||||
}
|
240
vendor/github.com/nlopes/slack/misc.go
generated
vendored
Normal file
240
vendor/github.com/nlopes/slack/misc.go
generated
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SlackResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func (t SlackResponse) Err() error {
|
||||
if t.Ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// handle pure text based responses like chat.post
|
||||
// which while they have a slack response in their data structure
|
||||
// it doesn't actually get set during parsing.
|
||||
if strings.TrimSpace(t.Error) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New(t.Error)
|
||||
}
|
||||
|
||||
// StatusCodeError represents an http response error.
|
||||
// type httpStatusCode interface { HTTPStatusCode() int } to handle it.
|
||||
type statusCodeError struct {
|
||||
Code int
|
||||
Status string
|
||||
}
|
||||
|
||||
func (t statusCodeError) Error() string {
|
||||
// TODO: this is a bad error string, should clean it up with a breaking changes
|
||||
// merger.
|
||||
return fmt.Sprintf("Slack server error: %s.", t.Status)
|
||||
}
|
||||
|
||||
func (t statusCodeError) HTTPStatusCode() int {
|
||||
return t.Code
|
||||
}
|
||||
|
||||
type RateLimitedError struct {
|
||||
RetryAfter time.Duration
|
||||
}
|
||||
|
||||
func (e *RateLimitedError) Error() string {
|
||||
return fmt.Sprintf("Slack rate limit exceeded, retry after %s", e.RetryAfter)
|
||||
}
|
||||
|
||||
func fileUploadReq(ctx context.Context, path, fieldname, filename string, values url.Values, r io.Reader) (*http.Request, error) {
|
||||
body := &bytes.Buffer{}
|
||||
wr := multipart.NewWriter(body)
|
||||
|
||||
ioWriter, err := wr.CreateFormFile(fieldname, filename)
|
||||
if err != nil {
|
||||
wr.Close()
|
||||
return nil, err
|
||||
}
|
||||
_, err = io.Copy(ioWriter, r)
|
||||
if err != nil {
|
||||
wr.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Close the multipart writer or the footer won't be written
|
||||
wr.Close()
|
||||
req, err := http.NewRequest("POST", path, body)
|
||||
req = req.WithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Content-Type", wr.FormDataContentType())
|
||||
req.URL.RawQuery = (values).Encode()
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func parseResponseBody(body io.ReadCloser, intf interface{}, debug bool) error {
|
||||
response, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME: will be api.Debugf
|
||||
if debug {
|
||||
logger.Printf("parseResponseBody: %s\n", string(response))
|
||||
}
|
||||
|
||||
return json.Unmarshal(response, intf)
|
||||
}
|
||||
|
||||
func postLocalWithMultipartResponse(ctx context.Context, client HTTPRequester, path, fpath, fieldname string, values url.Values, intf interface{}, debug bool) error {
|
||||
fullpath, err := filepath.Abs(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.Open(fullpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return postWithMultipartResponse(ctx, client, path, filepath.Base(fpath), fieldname, values, file, intf, debug)
|
||||
}
|
||||
|
||||
func postWithMultipartResponse(ctx context.Context, client HTTPRequester, path, name, fieldname string, values url.Values, r io.Reader, intf interface{}, debug bool) error {
|
||||
req, err := fileUploadReq(ctx, SLACK_API+path, fieldname, name, values, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusTooManyRequests {
|
||||
retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return &RateLimitedError{time.Duration(retry) * time.Second}
|
||||
}
|
||||
|
||||
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logResponse(resp, debug)
|
||||
return statusCodeError{Code: resp.StatusCode, Status: resp.Status}
|
||||
}
|
||||
|
||||
return parseResponseBody(resp.Body, intf, debug)
|
||||
}
|
||||
|
||||
func doPost(ctx context.Context, client HTTPRequester, req *http.Request, intf interface{}, debug bool) error {
|
||||
req = req.WithContext(ctx)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusTooManyRequests {
|
||||
retry, err := strconv.ParseInt(resp.Header.Get("Retry-After"), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return &RateLimitedError{time.Duration(retry) * time.Second}
|
||||
}
|
||||
|
||||
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logResponse(resp, debug)
|
||||
return statusCodeError{Code: resp.StatusCode, Status: resp.Status}
|
||||
}
|
||||
|
||||
return parseResponseBody(resp.Body, intf, debug)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
return doPost(ctx, client, req, intf, debug)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func parseAdminResponse(ctx context.Context, client HTTPRequester, method string, teamName string, values url.Values, intf interface{}, debug bool) error {
|
||||
endpoint := fmt.Sprintf(SLACK_WEB_API_FORMAT, teamName, method, time.Now().Unix())
|
||||
return postForm(ctx, client, endpoint, values, intf, debug)
|
||||
}
|
||||
|
||||
func logResponse(resp *http.Response, debug bool) error {
|
||||
if debug {
|
||||
text, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Print(string(text))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func okJSONHandler(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
response, _ := json.Marshal(SlackResponse{
|
||||
Ok: true,
|
||||
})
|
||||
rw.Write(response)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
66
vendor/github.com/nlopes/slack/oauth.go
generated
vendored
Normal file
66
vendor/github.com/nlopes/slack/oauth.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type OAuthResponseIncomingWebhook struct {
|
||||
URL string `json:"url"`
|
||||
Channel string `json:"channel"`
|
||||
ChannelID string `json:"channel_id,omitempty"`
|
||||
ConfigurationURL string `json:"configuration_url"`
|
||||
}
|
||||
|
||||
type OAuthResponseBot struct {
|
||||
BotUserID string `json:"bot_user_id"`
|
||||
BotAccessToken string `json:"bot_access_token"`
|
||||
}
|
||||
|
||||
type OAuthResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
Scope string `json:"scope"`
|
||||
TeamName string `json:"team_name"`
|
||||
TeamID string `json:"team_id"`
|
||||
IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"`
|
||||
Bot OAuthResponseBot `json:"bot"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
// GetOAuthToken retrieves an AccessToken
|
||||
func GetOAuthToken(clientID, clientSecret, code, redirectURI string, debug bool) (accessToken string, scope string, err error) {
|
||||
return GetOAuthTokenContext(context.Background(), clientID, clientSecret, code, redirectURI, debug)
|
||||
}
|
||||
|
||||
// GetOAuthTokenContext retrieves an AccessToken with a custom context
|
||||
func GetOAuthTokenContext(ctx context.Context, clientID, clientSecret, code, redirectURI string, debug bool) (accessToken string, scope string, err error) {
|
||||
response, err := GetOAuthResponseContext(ctx, clientID, clientSecret, code, redirectURI, debug)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return response.AccessToken, response.Scope, nil
|
||||
}
|
||||
|
||||
func GetOAuthResponse(clientID, clientSecret, code, redirectURI string, debug bool) (resp *OAuthResponse, err error) {
|
||||
return GetOAuthResponseContext(context.Background(), clientID, clientSecret, code, redirectURI, debug)
|
||||
}
|
||||
|
||||
func GetOAuthResponseContext(ctx context.Context, clientID, clientSecret, code, redirectURI string, debug bool) (resp *OAuthResponse, err error) {
|
||||
values := url.Values{
|
||||
"client_id": {clientID},
|
||||
"client_secret": {clientSecret},
|
||||
"code": {code},
|
||||
"redirect_uri": {redirectURI},
|
||||
}
|
||||
response := &OAuthResponse{}
|
||||
err = postSlackMethod(ctx, customHTTPClient, "oauth.access", values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
6
vendor/github.com/slack-go/slack/pins.go → vendor/github.com/nlopes/slack/pins.go
generated
vendored
6
vendor/github.com/slack-go/slack/pins.go → 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 := api.postMethod(ctx, "pins.add", values, response); 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 := api.postMethod(ctx, "pins.remove", values, response); 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 := api.postMethod(ctx, "pins.list", values, response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "pins.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
@ -2,6 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
@ -154,7 +155,7 @@ func (api *Client) AddReactionContext(ctx context.Context, name string, item Ite
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := api.postMethod(ctx, "reactions.add", values, response); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "reactions.add", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -188,7 +189,7 @@ func (api *Client) RemoveReactionContext(ctx context.Context, name string, item
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := api.postMethod(ctx, "reactions.remove", values, response); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "reactions.remove", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -222,14 +223,12 @@ func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params
|
||||
}
|
||||
|
||||
response := &getReactionsResponseFull{}
|
||||
if err := api.postMethod(ctx, "reactions.get", values, response); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "reactions.get", values, response, api.debug); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := response.Err(); err != nil {
|
||||
return nil, err
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response.extractReactions(), nil
|
||||
}
|
||||
|
||||
@ -257,14 +256,12 @@ func (api *Client) ListReactionsContext(ctx context.Context, params ListReaction
|
||||
}
|
||||
|
||||
response := &listReactionsResponseFull{}
|
||||
err := api.postMethod(ctx, "reactions.list", values, response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "reactions.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := response.Err(); err != nil {
|
||||
return nil, nil, err
|
||||
if !response.Ok {
|
||||
return nil, nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response.extractReactedItems(), &response.Paging, nil
|
||||
}
|
29
vendor/github.com/slack-go/slack/rtm.go → vendor/github.com/nlopes/slack/rtm.go
generated
vendored
29
vendor/github.com/slack-go/slack/rtm.go → vendor/github.com/nlopes/slack/rtm.go
generated
vendored
@ -2,6 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
@ -37,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 = api.postMethod(ctx, "rtm.start", url.Values{"token": {api.token}}, response)
|
||||
err = postSlackMethod(ctx, api.httpclient, "rtm.start", url.Values{"token": {api.token}}, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -62,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 = api.postMethod(ctx, "rtm.connect", url.Values{"token": {api.token}}, response)
|
||||
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
|
||||
@ -99,13 +100,6 @@ func RTMOptionPingInterval(d time.Duration) RTMOption {
|
||||
}
|
||||
}
|
||||
|
||||
// RTMOptionConnParams installs parameters to embed into the connection URL.
|
||||
func RTMOptionConnParams(connParams url.Values) RTMOption {
|
||||
return func(rtm *RTM) {
|
||||
rtm.connParams = connParams
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -115,10 +109,12 @@ func (api *Client) NewRTM(options ...RTMOption) *RTM {
|
||||
outgoingMessages: make(chan OutgoingMessage, 20),
|
||||
pingInterval: defaultPingInterval,
|
||||
pingDeadman: time.NewTimer(deadmanDuration(defaultPingInterval)),
|
||||
isConnected: false,
|
||||
wasIntentional: true,
|
||||
killChannel: make(chan bool),
|
||||
disconnected: make(chan struct{}),
|
||||
disconnectedm: &sync.Once{},
|
||||
disconnected: make(chan struct{}, 1),
|
||||
forcePing: make(chan bool),
|
||||
rawEvents: make(chan json.RawMessage),
|
||||
idGen: NewSafeID(1),
|
||||
mu: &sync.Mutex{},
|
||||
}
|
||||
@ -129,3 +125,14 @@ func (api *Client) NewRTM(options ...RTMOption) *RTM {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// NewRTMWithOptions Deprecated just use NewRTM(RTMOptionsUseStart(true))
|
||||
// returns a RTM, which provides a fully managed connection to
|
||||
// Slack's websocket-based Real-Time Messaging protocol.
|
||||
// This also allows to configure various options available for RTM API.
|
||||
func (api *Client) NewRTMWithOptions(options *RTMOptions) *RTM {
|
||||
if options != nil {
|
||||
return api.NewRTM(RTMOptionUseStart(options.UseRTMStart))
|
||||
}
|
||||
return api.NewRTM()
|
||||
}
|
20
vendor/github.com/slack-go/slack/search.go → vendor/github.com/nlopes/slack/search.go
generated
vendored
20
vendor/github.com/slack-go/slack/search.go → vendor/github.com/nlopes/slack/search.go
generated
vendored
@ -2,6 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
@ -23,14 +24,8 @@ type SearchParameters struct {
|
||||
}
|
||||
|
||||
type CtxChannel struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
IsExtShared bool `json:"is_ext_shared"`
|
||||
IsMPIM bool `json:"is_mpim"`
|
||||
ISOrgShared bool `json:"is_org_shared"`
|
||||
IsPendingExtShared bool `json:"is_pending_ext_shared"`
|
||||
IsPrivate bool `json:"is_private"`
|
||||
IsShared bool `json:"is_shared"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type CtxMessage struct {
|
||||
@ -47,7 +42,6 @@ type SearchMessage struct {
|
||||
User string `json:"user"`
|
||||
Username string `json:"username"`
|
||||
Timestamp string `json:"ts"`
|
||||
Blocks Blocks `json:"blocks,omitempty"`
|
||||
Text string `json:"text"`
|
||||
Permalink string `json:"permalink"`
|
||||
Attachments []Attachment `json:"attachments"`
|
||||
@ -110,12 +104,14 @@ func (api *Client) _search(ctx context.Context, path, query string, params Searc
|
||||
}
|
||||
|
||||
response = &searchResponseFull{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postSlackMethod(ctx, api.httpclient, path, values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
|
||||
}
|
||||
|
140
vendor/github.com/nlopes/slack/slack.go
generated
vendored
Normal file
140
vendor/github.com/nlopes/slack/slack.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Added as a var so that we can change this for testing purposes
|
||||
var SLACK_API string = "https://slack.com/api/"
|
||||
var SLACK_WEB_API_FORMAT string = "https://%s.slack.com/api/users.admin.%s?t=%s"
|
||||
|
||||
// HTTPClient sets a custom http.Client
|
||||
// deprecated: in favor of SetHTTPClient()
|
||||
var HTTPClient = &http.Client{}
|
||||
|
||||
var customHTTPClient HTTPRequester = HTTPClient
|
||||
|
||||
// HTTPRequester defines the minimal interface needed for an http.Client to be implemented.
|
||||
//
|
||||
// Use it in conjunction with the SetHTTPClient function to allow for other capabilities
|
||||
// like a tracing http.Client
|
||||
type HTTPRequester interface {
|
||||
Do(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// SetHTTPClient allows you to specify a custom http.Client
|
||||
// Use this instead of the package level HTTPClient variable if you want to use a custom client like the
|
||||
// Stackdriver Trace HTTPClient https://godoc.org/cloud.google.com/go/trace#HTTPClient
|
||||
func SetHTTPClient(client HTTPRequester) {
|
||||
customHTTPClient = client
|
||||
}
|
||||
|
||||
// ResponseMetadata holds pagination metadata
|
||||
type ResponseMetadata struct {
|
||||
Cursor string `json:"next_cursor"`
|
||||
}
|
||||
|
||||
func (t *ResponseMetadata) initialize() *ResponseMetadata {
|
||||
if t != nil {
|
||||
return t
|
||||
}
|
||||
|
||||
return &ResponseMetadata{}
|
||||
}
|
||||
|
||||
type AuthTestResponse struct {
|
||||
URL string `json:"url"`
|
||||
Team string `json:"team"`
|
||||
User string `json:"user"`
|
||||
TeamID string `json:"team_id"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
type authTestResponseFull struct {
|
||||
SlackResponse
|
||||
AuthTestResponse
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
token string
|
||||
info Info
|
||||
debug bool
|
||||
httpclient HTTPRequester
|
||||
}
|
||||
|
||||
// Option defines an option for a Client
|
||||
type Option func(*Client)
|
||||
|
||||
// OptionHTTPClient - provide a custom http client to the slack client.
|
||||
func OptionHTTPClient(c HTTPRequester) func(*Client) {
|
||||
return func(s *Client) {
|
||||
s.httpclient = c
|
||||
}
|
||||
}
|
||||
|
||||
// New builds a slack client from the provided token and options.
|
||||
func New(token string, options ...Option) *Client {
|
||||
s := &Client{
|
||||
token: token,
|
||||
httpclient: customHTTPClient,
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// AuthTest tests if the user is able to do authenticated requests or not
|
||||
func (api *Client) AuthTest() (response *AuthTestResponse, error error) {
|
||||
return api.AuthTestContext(context.Background())
|
||||
}
|
||||
|
||||
// AuthTestContext tests if the user is able to do authenticated requests or not with a custom context
|
||||
func (api *Client) AuthTestContext(ctx context.Context) (response *AuthTestResponse, error error) {
|
||||
api.Debugf("Challenging auth...")
|
||||
responseFull := &authTestResponseFull{}
|
||||
err := 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
|
||||
}
|
||||
if !responseFull.Ok {
|
||||
api.Debugf("auth response was not Ok: %s", responseFull.Error)
|
||||
return nil, errors.New(responseFull.Error)
|
||||
}
|
||||
|
||||
api.Debugf("Auth challenge was successful with response %+v", responseFull.AuthTestResponse)
|
||||
return &responseFull.AuthTestResponse, nil
|
||||
}
|
||||
|
||||
// SetDebug switches the api into debug mode
|
||||
// When in debug mode, it logs various info about what its doing
|
||||
// If you ever use this in production, don't call SetDebug(true)
|
||||
func (api *Client) SetDebug(debug bool) {
|
||||
api.debug = debug
|
||||
if debug && logger == nil {
|
||||
SetLogger(log.New(os.Stdout, "nlopes/slack", log.LstdFlags|log.Lshortfile))
|
||||
}
|
||||
}
|
||||
|
||||
// Debugf print a formatted debug line.
|
||||
func (api *Client) Debugf(format string, v ...interface{}) {
|
||||
if api.debug {
|
||||
logger.Output(2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Debugln print a debug line.
|
||||
func (api *Client) Debugln(v ...interface{}) {
|
||||
if api.debug {
|
||||
logger.Output(2, fmt.Sprintln(v...))
|
||||
}
|
||||
}
|
0
vendor/github.com/slack-go/slack/slash.go → vendor/github.com/nlopes/slack/slash.go
generated
vendored
0
vendor/github.com/slack-go/slack/slash.go → vendor/github.com/nlopes/slack/slash.go
generated
vendored
116
vendor/github.com/slack-go/slack/stars.go → vendor/github.com/nlopes/slack/stars.go
generated
vendored
116
vendor/github.com/slack-go/slack/stars.go → vendor/github.com/nlopes/slack/stars.go
generated
vendored
@ -2,9 +2,9 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -58,7 +58,7 @@ func (api *Client) AddStarContext(ctx context.Context, channel string, item Item
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := api.postMethod(ctx, "stars.add", values, response); 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 := api.postMethod(ctx, "stars.remove", values, response); err != nil {
|
||||
if err := postSlackMethod(ctx, api.httpclient, "stars.remove", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -115,15 +115,13 @@ func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters)
|
||||
}
|
||||
|
||||
response := &listResponseFull{}
|
||||
err := api.postMethod(ctx, "stars.list", values, response)
|
||||
err := postSlackMethod(ctx, api.httpclient, "stars.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := response.Err(); err != nil {
|
||||
return nil, nil, err
|
||||
if !response.Ok {
|
||||
return nil, nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response.Items, &response.Paging, nil
|
||||
}
|
||||
|
||||
@ -159,105 +157,3 @@ func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters
|
||||
}
|
||||
return starredItems, paging, nil
|
||||
}
|
||||
|
||||
type listResponsePaginated struct {
|
||||
Items []Item `json:"items"`
|
||||
SlackResponse
|
||||
Metadata ResponseMetadata `json:"response_metadata"`
|
||||
}
|
||||
|
||||
// StarredItemPagination allows for paginating over the starred items
|
||||
type StarredItemPagination struct {
|
||||
Items []Item
|
||||
limit int
|
||||
previousResp *ResponseMetadata
|
||||
c *Client
|
||||
}
|
||||
|
||||
// ListStarsOption options for the GetUsers method call.
|
||||
type ListStarsOption func(*StarredItemPagination)
|
||||
|
||||
// ListAllStars returns the complete list of starred items
|
||||
func (api *Client) ListAllStars() ([]Item, error) {
|
||||
return api.ListAllStarsContext(context.Background())
|
||||
}
|
||||
|
||||
// ListAllStarsContext returns the list of users (with their detailed information) with a custom context
|
||||
func (api *Client) ListAllStarsContext(ctx context.Context) (results []Item, err error) {
|
||||
p := api.ListStarsPaginated()
|
||||
for err == nil {
|
||||
p, err = p.next(ctx)
|
||||
if err == nil {
|
||||
results = append(results, p.Items...)
|
||||
} else if rateLimitedError, ok := err.(*RateLimitedError); ok {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
case <-time.After(rateLimitedError.RetryAfter):
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results, p.failure(err)
|
||||
}
|
||||
|
||||
// ListStarsPaginated fetches users in a paginated fashion, see ListStarsPaginationContext for usage.
|
||||
func (api *Client) ListStarsPaginated(options ...ListStarsOption) StarredItemPagination {
|
||||
return newStarPagination(api, options...)
|
||||
}
|
||||
|
||||
func newStarPagination(c *Client, options ...ListStarsOption) (sip StarredItemPagination) {
|
||||
sip = StarredItemPagination{
|
||||
c: c,
|
||||
limit: 200, // per slack api documentation.
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(&sip)
|
||||
}
|
||||
|
||||
return sip
|
||||
}
|
||||
|
||||
// done checks if the pagination has completed
|
||||
func (StarredItemPagination) done(err error) bool {
|
||||
return err == errPaginationComplete
|
||||
}
|
||||
|
||||
// done checks if pagination failed.
|
||||
func (t StarredItemPagination) failure(err error) error {
|
||||
if t.done(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// next gets the next list of starred items based on the cursor value
|
||||
func (t StarredItemPagination) next(ctx context.Context) (_ StarredItemPagination, err error) {
|
||||
var (
|
||||
resp *listResponsePaginated
|
||||
)
|
||||
|
||||
if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") {
|
||||
return t, errPaginationComplete
|
||||
}
|
||||
|
||||
t.previousResp = t.previousResp.initialize()
|
||||
|
||||
values := url.Values{
|
||||
"limit": {strconv.Itoa(t.limit)},
|
||||
"token": {t.c.token},
|
||||
"cursor": {t.previousResp.Cursor},
|
||||
}
|
||||
|
||||
if err = t.c.postMethod(ctx, "stars.list", values, &resp); err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
t.previousResp = &resp.Metadata
|
||||
t.Items = resp.Items
|
||||
|
||||
return t, nil
|
||||
}
|
40
vendor/github.com/slack-go/slack/team.go → vendor/github.com/nlopes/slack/team.go
generated
vendored
40
vendor/github.com/slack-go/slack/team.go → vendor/github.com/nlopes/slack/team.go
generated
vendored
@ -2,6 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
@ -66,33 +67,44 @@ func NewAccessLogParameters() AccessLogParameters {
|
||||
}
|
||||
}
|
||||
|
||||
func (api *Client) teamRequest(ctx context.Context, path string, values url.Values) (*TeamResponse, error) {
|
||||
func teamRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*TeamResponse, error) {
|
||||
response := &TeamResponse{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (api *Client) billableInfoRequest(ctx context.Context, path string, values url.Values) (map[string]BillingActive, error) {
|
||||
func billableInfoRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (map[string]BillingActive, error) {
|
||||
response := &BillableInfoResponse{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response.BillableInfo, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response.BillableInfo, nil
|
||||
}
|
||||
|
||||
func (api *Client) accessLogsRequest(ctx context.Context, path string, values url.Values) (*LoginResponse, error) {
|
||||
func accessLogsRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*LoginResponse, error) {
|
||||
response := &LoginResponse{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetTeamInfo gets the Team Information of the user
|
||||
@ -106,7 +118,7 @@ func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) {
|
||||
"token": {api.token},
|
||||
}
|
||||
|
||||
response, err := api.teamRequest(ctx, "team.info", values)
|
||||
response, err := teamRequest(ctx, api.httpclient, "team.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -130,26 +142,24 @@ func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogPar
|
||||
values.Add("page", strconv.Itoa(params.Page))
|
||||
}
|
||||
|
||||
response, err := api.accessLogsRequest(ctx, "team.accessLogs", values)
|
||||
response, err := accessLogsRequest(ctx, api.httpclient, "team.accessLogs", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return response.Logins, &response.Paging, nil
|
||||
}
|
||||
|
||||
// GetBillableInfo ...
|
||||
func (api *Client) GetBillableInfo(user string) (map[string]BillingActive, error) {
|
||||
return api.GetBillableInfoContext(context.Background(), user)
|
||||
}
|
||||
|
||||
// GetBillableInfoContext ...
|
||||
func (api *Client) GetBillableInfoContext(ctx context.Context, user string) (map[string]BillingActive, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"user": {user},
|
||||
}
|
||||
|
||||
return api.billableInfoRequest(ctx, "team.billableInfo", values)
|
||||
return billableInfoRequest(ctx, api.httpclient, "team.billableInfo", values, api.debug)
|
||||
}
|
||||
|
||||
// GetBillableInfoForTeam returns the billing_active status of all users on the team.
|
||||
@ -163,5 +173,5 @@ func (api *Client) GetBillableInfoForTeamContext(ctx context.Context) (map[strin
|
||||
"token": {api.token},
|
||||
}
|
||||
|
||||
return api.billableInfoRequest(ctx, "team.billableInfo", values)
|
||||
return billableInfoRequest(ctx, api.httpclient, "team.billableInfo", values, api.debug)
|
||||
}
|
@ -2,6 +2,7 @@ package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
@ -24,7 +25,6 @@ type UserGroup struct {
|
||||
DeletedBy string `json:"deleted_by"`
|
||||
Prefs UserGroupPrefs `json:"prefs"`
|
||||
UserCount int `json:"user_count"`
|
||||
Users []string `json:"users"`
|
||||
}
|
||||
|
||||
// UserGroupPrefs contains default channels and groups (private channels)
|
||||
@ -40,14 +40,16 @@ type userGroupResponseFull struct {
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
func (api *Client) userGroupRequest(ctx context.Context, path string, values url.Values) (*userGroupResponseFull, error) {
|
||||
func userGroupRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*userGroupResponseFull, error) {
|
||||
response := &userGroupResponseFull{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postSlackMethod(ctx, client, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// CreateUserGroup creates a new user group
|
||||
@ -74,7 +76,7 @@ func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGro
|
||||
values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")}
|
||||
}
|
||||
|
||||
response, err := api.userGroupRequest(ctx, "usergroups.create", values)
|
||||
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.create", values, api.debug)
|
||||
if err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
||||
@ -93,7 +95,7 @@ func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string
|
||||
"usergroup": {userGroup},
|
||||
}
|
||||
|
||||
response, err := api.userGroupRequest(ctx, "usergroups.disable", values)
|
||||
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.disable", values, api.debug)
|
||||
if err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
||||
@ -112,71 +114,25 @@ func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string)
|
||||
"usergroup": {userGroup},
|
||||
}
|
||||
|
||||
response, err := api.userGroupRequest(ctx, "usergroups.enable", values)
|
||||
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.enable", values, api.debug)
|
||||
if err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
||||
return response.UserGroup, nil
|
||||
}
|
||||
|
||||
// GetUserGroupsOption options for the GetUserGroups method call.
|
||||
type GetUserGroupsOption func(*GetUserGroupsParams)
|
||||
|
||||
// GetUserGroupsOptionIncludeCount include the number of users in each User Group (default: false)
|
||||
func GetUserGroupsOptionIncludeCount(b bool) GetUserGroupsOption {
|
||||
return func(params *GetUserGroupsParams) {
|
||||
params.IncludeCount = b
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserGroupsOptionIncludeDisabled include disabled User Groups (default: false)
|
||||
func GetUserGroupsOptionIncludeDisabled(b bool) GetUserGroupsOption {
|
||||
return func(params *GetUserGroupsParams) {
|
||||
params.IncludeDisabled = b
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserGroupsOptionIncludeUsers include the list of users for each User Group (default: false)
|
||||
func GetUserGroupsOptionIncludeUsers(b bool) GetUserGroupsOption {
|
||||
return func(params *GetUserGroupsParams) {
|
||||
params.IncludeUsers = b
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserGroupsParams contains arguments for GetUserGroups method call
|
||||
type GetUserGroupsParams struct {
|
||||
IncludeCount bool
|
||||
IncludeDisabled bool
|
||||
IncludeUsers bool
|
||||
}
|
||||
|
||||
// GetUserGroups returns a list of user groups for the team
|
||||
func (api *Client) GetUserGroups(options ...GetUserGroupsOption) ([]UserGroup, error) {
|
||||
return api.GetUserGroupsContext(context.Background(), options...)
|
||||
func (api *Client) GetUserGroups() ([]UserGroup, error) {
|
||||
return api.GetUserGroupsContext(context.Background())
|
||||
}
|
||||
|
||||
// GetUserGroupsContext returns a list of user groups for the team with a custom context
|
||||
func (api *Client) GetUserGroupsContext(ctx context.Context, options ...GetUserGroupsOption) ([]UserGroup, error) {
|
||||
params := GetUserGroupsParams{}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(¶ms)
|
||||
}
|
||||
|
||||
func (api *Client) GetUserGroupsContext(ctx context.Context) ([]UserGroup, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
if params.IncludeCount {
|
||||
values.Add("include_count", "true")
|
||||
}
|
||||
if params.IncludeDisabled {
|
||||
values.Add("include_disabled", "true")
|
||||
}
|
||||
if params.IncludeUsers {
|
||||
values.Add("include_users", "true")
|
||||
}
|
||||
|
||||
response, err := api.userGroupRequest(ctx, "usergroups.list", values)
|
||||
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -207,11 +163,7 @@ func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGro
|
||||
values["description"] = []string{userGroup.Description}
|
||||
}
|
||||
|
||||
if len(userGroup.Prefs.Channels) > 0 {
|
||||
values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")}
|
||||
}
|
||||
|
||||
response, err := api.userGroupRequest(ctx, "usergroups.update", values)
|
||||
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.update", values, api.debug)
|
||||
if err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
||||
@ -230,7 +182,7 @@ func (api *Client) GetUserGroupMembersContext(ctx context.Context, userGroup str
|
||||
"usergroup": {userGroup},
|
||||
}
|
||||
|
||||
response, err := api.userGroupRequest(ctx, "usergroups.users.list", values)
|
||||
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.users.list", values, api.debug)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
@ -250,7 +202,7 @@ func (api *Client) UpdateUserGroupMembersContext(ctx context.Context, userGroup
|
||||
"users": {members},
|
||||
}
|
||||
|
||||
response, err := api.userGroupRequest(ctx, "usergroups.users.update", values)
|
||||
response, err := userGroupRequest(ctx, api.httpclient, "usergroups.users.update", values, api.debug)
|
||||
if err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
193
vendor/github.com/slack-go/slack/users.go → vendor/github.com/nlopes/slack/users.go
generated
vendored
193
vendor/github.com/slack-go/slack/users.go → vendor/github.com/nlopes/slack/users.go
generated
vendored
@ -3,15 +3,16 @@ package slack
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_USER_PHOTO_CROP_X = -1
|
||||
DEFAULT_USER_PHOTO_CROP_Y = -1
|
||||
DEFAULT_USER_PHOTO_CROP_W = -1
|
||||
errPaginationComplete = errorString("pagination complete")
|
||||
)
|
||||
|
||||
// UserProfile contains all the information details of a given user
|
||||
@ -36,7 +37,6 @@ type UserProfile struct {
|
||||
ApiAppID string `json:"api_app_id,omitempty"`
|
||||
StatusText string `json:"status_text,omitempty"`
|
||||
StatusEmoji string `json:"status_emoji,omitempty"`
|
||||
StatusExpiration int `json:"status_expiration"`
|
||||
Team string `json:"team"`
|
||||
Fields UserProfileCustomFields `json:"fields"`
|
||||
}
|
||||
@ -44,14 +44,14 @@ type UserProfile struct {
|
||||
// UserProfileCustomFields represents user profile's custom fields.
|
||||
// Slack API's response data type is inconsistent so we use the struct.
|
||||
// For detail, please see below.
|
||||
// https://github.com/slack-go/slack/pull/298#discussion_r185159233
|
||||
// https://github.com/nlopes/slack/pull/298#discussion_r185159233
|
||||
type UserProfileCustomFields struct {
|
||||
fields map[string]UserProfileCustomField
|
||||
}
|
||||
|
||||
// UnmarshalJSON is the implementation of the json.Unmarshaler interface.
|
||||
func (fields *UserProfileCustomFields) UnmarshalJSON(b []byte) error {
|
||||
// https://github.com/slack-go/slack/pull/298#discussion_r185159233
|
||||
// https://github.com/nlopes/slack/pull/298#discussion_r185159233
|
||||
if string(b) == "[]" {
|
||||
return nil
|
||||
}
|
||||
@ -100,31 +100,28 @@ type UserProfileCustomField struct {
|
||||
|
||||
// User contains all the information of a user
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
TeamID string `json:"team_id"`
|
||||
Name string `json:"name"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Color string `json:"color"`
|
||||
RealName string `json:"real_name"`
|
||||
TZ string `json:"tz,omitempty"`
|
||||
TZLabel string `json:"tz_label"`
|
||||
TZOffset int `json:"tz_offset"`
|
||||
Profile UserProfile `json:"profile"`
|
||||
IsBot bool `json:"is_bot"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
IsOwner bool `json:"is_owner"`
|
||||
IsPrimaryOwner bool `json:"is_primary_owner"`
|
||||
IsRestricted bool `json:"is_restricted"`
|
||||
IsUltraRestricted bool `json:"is_ultra_restricted"`
|
||||
IsStranger bool `json:"is_stranger"`
|
||||
IsAppUser bool `json:"is_app_user"`
|
||||
IsInvitedUser bool `json:"is_invited_user"`
|
||||
Has2FA bool `json:"has_2fa"`
|
||||
HasFiles bool `json:"has_files"`
|
||||
Presence string `json:"presence"`
|
||||
Locale string `json:"locale"`
|
||||
Updated JSONTime `json:"updated"`
|
||||
Enterprise EnterpriseUser `json:"enterprise_user,omitempty"`
|
||||
ID string `json:"id"`
|
||||
TeamID string `json:"team_id"`
|
||||
Name string `json:"name"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Color string `json:"color"`
|
||||
RealName string `json:"real_name"`
|
||||
TZ string `json:"tz,omitempty"`
|
||||
TZLabel string `json:"tz_label"`
|
||||
TZOffset int `json:"tz_offset"`
|
||||
Profile UserProfile `json:"profile"`
|
||||
IsBot bool `json:"is_bot"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
IsOwner bool `json:"is_owner"`
|
||||
IsPrimaryOwner bool `json:"is_primary_owner"`
|
||||
IsRestricted bool `json:"is_restricted"`
|
||||
IsUltraRestricted bool `json:"is_ultra_restricted"`
|
||||
IsStranger bool `json:"is_stranger"`
|
||||
IsAppUser bool `json:"is_app_user"`
|
||||
Has2FA bool `json:"has_2fa"`
|
||||
HasFiles bool `json:"has_files"`
|
||||
Presence string `json:"presence"`
|
||||
Locale string `json:"locale"`
|
||||
}
|
||||
|
||||
// UserPresence contains details about a user online status
|
||||
@ -155,17 +152,6 @@ type UserIdentity struct {
|
||||
Image512 string `json:"image_512"`
|
||||
}
|
||||
|
||||
// EnterpriseUser is present when a user is part of Slack Enterprise Grid
|
||||
// https://api.slack.com/types/user#enterprise_grid_user_objects
|
||||
type EnterpriseUser struct {
|
||||
ID string `json:"id"`
|
||||
EnterpriseID string `json:"enterprise_id"`
|
||||
EnterpriseName string `json:"enterprise_name"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
IsOwner bool `json:"is_owner"`
|
||||
Teams []string `json:"teams"`
|
||||
}
|
||||
|
||||
type TeamIdentity struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@ -203,14 +189,16 @@ func NewUserSetPhotoParams() UserSetPhotoParams {
|
||||
}
|
||||
}
|
||||
|
||||
func (api *Client) userRequest(ctx context.Context, path string, values url.Values) (*userResponseFull, error) {
|
||||
func userRequest(ctx context.Context, client HTTPRequester, path string, values url.Values, debug bool) (*userResponseFull, error) {
|
||||
response := &userResponseFull{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
err := postForm(ctx, client, SLACK_API+path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetUserPresence will retrieve the current presence status of given user.
|
||||
@ -225,7 +213,7 @@ func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*Us
|
||||
"user": {user},
|
||||
}
|
||||
|
||||
response, err := api.userRequest(ctx, "users.getPresence", values)
|
||||
response, err := userRequest(ctx, api.httpclient, "users.getPresence", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -240,12 +228,11 @@ func (api *Client) GetUserInfo(user string) (*User, error) {
|
||||
// GetUserInfoContext will retrieve the complete user information with a custom context
|
||||
func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"user": {user},
|
||||
"include_locale": {strconv.FormatBool(true)},
|
||||
"token": {api.token},
|
||||
"user": {user},
|
||||
}
|
||||
|
||||
response, err := api.userRequest(ctx, "users.info", values)
|
||||
response, err := userRequest(ctx, api.httpclient, "users.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -317,14 +304,13 @@ func (t UserPagination) Next(ctx context.Context) (_ UserPagination, err error)
|
||||
t.previousResp = t.previousResp.initialize()
|
||||
|
||||
values := url.Values{
|
||||
"limit": {strconv.Itoa(t.limit)},
|
||||
"presence": {strconv.FormatBool(t.presence)},
|
||||
"token": {t.c.token},
|
||||
"cursor": {t.previousResp.Cursor},
|
||||
"include_locale": {strconv.FormatBool(true)},
|
||||
"limit": {strconv.Itoa(t.limit)},
|
||||
"presence": {strconv.FormatBool(t.presence)},
|
||||
"token": {t.c.token},
|
||||
"cursor": {t.previousResp.Cursor},
|
||||
}
|
||||
|
||||
if resp, err = t.c.userRequest(ctx, "users.list", values); err != nil {
|
||||
if resp, err = userRequest(ctx, t.c.httpclient, "users.list", values, t.c.debug); err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
@ -347,19 +333,12 @@ func (api *Client) GetUsers() ([]User, error) {
|
||||
|
||||
// GetUsersContext returns the list of users (with their detailed information) with a custom context
|
||||
func (api *Client) GetUsersContext(ctx context.Context) (results []User, err error) {
|
||||
p := api.GetUsersPaginated()
|
||||
for err == nil {
|
||||
p, err = p.Next(ctx)
|
||||
if err == nil {
|
||||
results = append(results, p.Users...)
|
||||
} else if rateLimitedError, ok := err.(*RateLimitedError); ok {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
case <-time.After(rateLimitedError.RetryAfter):
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
var (
|
||||
p UserPagination
|
||||
)
|
||||
|
||||
for p = api.GetUsersPaginated(); !p.Done(err); p, err = p.Next(ctx) {
|
||||
results = append(results, p.Users...)
|
||||
}
|
||||
|
||||
return results, p.Failure(err)
|
||||
@ -376,7 +355,7 @@ func (api *Client) GetUserByEmailContext(ctx context.Context, email string) (*Us
|
||||
"token": {api.token},
|
||||
"email": {email},
|
||||
}
|
||||
response, err := api.userRequest(ctx, "users.lookupByEmail", values)
|
||||
response, err := userRequest(ctx, api.httpclient, "users.lookupByEmail", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -394,7 +373,7 @@ func (api *Client) SetUserAsActiveContext(ctx context.Context) (err error) {
|
||||
"token": {api.token},
|
||||
}
|
||||
|
||||
_, err = api.userRequest(ctx, "users.setActive", values)
|
||||
_, err = userRequest(ctx, api.httpclient, "users.setActive", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -410,7 +389,7 @@ func (api *Client) SetUserPresenceContext(ctx context.Context, presence string)
|
||||
"presence": {presence},
|
||||
}
|
||||
|
||||
_, err := api.userRequest(ctx, "users.setPresence", values)
|
||||
_, err := userRequest(ctx, api.httpclient, "users.setPresence", values, api.debug)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -420,21 +399,19 @@ func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) {
|
||||
}
|
||||
|
||||
// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context
|
||||
func (api *Client) GetUserIdentityContext(ctx context.Context) (response *UserIdentityResponse, err error) {
|
||||
func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityResponse, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
response = &UserIdentityResponse{}
|
||||
response := &UserIdentityResponse{}
|
||||
|
||||
err = api.postMethod(ctx, "users.identity", values, response)
|
||||
err := postForm(ctx, api.httpclient, SLACK_API+"users.identity", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := response.Err(); err != nil {
|
||||
return nil, err
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@ -444,7 +421,7 @@ func (api *Client) SetUserPhoto(image string, params UserSetPhotoParams) error {
|
||||
}
|
||||
|
||||
// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
|
||||
func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) (err error) {
|
||||
func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) error {
|
||||
response := &SlackResponse{}
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
@ -459,7 +436,7 @@ func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params
|
||||
values.Add("crop_w", strconv.Itoa(params.CropW))
|
||||
}
|
||||
|
||||
err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"users.setPhoto", image, "image", values, response, api)
|
||||
err := postLocalWithMultipartResponse(ctx, api.httpclient, "users.setPhoto", image, "image", values, response, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -473,13 +450,13 @@ func (api *Client) DeleteUserPhoto() error {
|
||||
}
|
||||
|
||||
// DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context
|
||||
func (api *Client) DeleteUserPhotoContext(ctx context.Context) (err error) {
|
||||
func (api *Client) DeleteUserPhotoContext(ctx context.Context) error {
|
||||
response := &SlackResponse{}
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
|
||||
err = api.postMethod(ctx, "users.deletePhoto", values, response)
|
||||
err := postForm(ctx, api.httpclient, SLACK_API+"users.deletePhoto", values, response, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -490,30 +467,15 @@ func (api *Client) DeleteUserPhotoContext(ctx context.Context) (err error) {
|
||||
// SetUserCustomStatus will set a custom status and emoji for the currently
|
||||
// authenticated user. If statusEmoji is "" and statusText is not, the Slack API
|
||||
// will automatically set it to ":speech_balloon:". Otherwise, if both are ""
|
||||
// the Slack API will unset the custom status/emoji. If statusExpiration is set to 0
|
||||
// the status will not expire.
|
||||
func (api *Client) SetUserCustomStatus(statusText, statusEmoji string, statusExpiration int64) error {
|
||||
return api.SetUserCustomStatusContextWithUser(context.Background(), "", statusText, statusEmoji, statusExpiration)
|
||||
// the Slack API will unset the custom status/emoji.
|
||||
func (api *Client) SetUserCustomStatus(statusText, statusEmoji string) error {
|
||||
return api.SetUserCustomStatusContext(context.Background(), statusText, statusEmoji)
|
||||
}
|
||||
|
||||
// SetUserCustomStatusContext will set a custom status and emoji for the currently authenticated user with a custom context
|
||||
//
|
||||
// For more information see SetUserCustomStatus
|
||||
func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, statusEmoji string, statusExpiration int64) error {
|
||||
return api.SetUserCustomStatusContextWithUser(context.Background(), "", statusText, statusEmoji, statusExpiration)
|
||||
}
|
||||
|
||||
// SetUserCustomStatusWithUser will set a custom status and emoji for the provided user.
|
||||
//
|
||||
// For more information see SetUserCustomStatus
|
||||
func (api *Client) SetUserCustomStatusWithUser(user, statusText, statusEmoji string, statusExpiration int64) error {
|
||||
return api.SetUserCustomStatusContextWithUser(context.Background(), user, statusText, statusEmoji, statusExpiration)
|
||||
}
|
||||
|
||||
// SetUserCustomStatusContextWithUser will set a custom status and emoji for the provided user with a custom context
|
||||
//
|
||||
// For more information see SetUserCustomStatus
|
||||
func (api *Client) SetUserCustomStatusContextWithUser(ctx context.Context, user, statusText, statusEmoji string, statusExpiration int64) error {
|
||||
func (api *Client) SetUserCustomStatusContext(ctx context.Context, statusText, statusEmoji string) error {
|
||||
// XXX(theckman): this anonymous struct is for making requests to the Slack
|
||||
// API for setting and unsetting a User's Custom Status/Emoji. To change
|
||||
// these values we must provide a JSON document as the profile POST field.
|
||||
@ -526,13 +488,11 @@ func (api *Client) SetUserCustomStatusContextWithUser(ctx context.Context, user,
|
||||
// - https://api.slack.com/docs/presence-and-status#custom_status
|
||||
profile, err := json.Marshal(
|
||||
&struct {
|
||||
StatusText string `json:"status_text"`
|
||||
StatusEmoji string `json:"status_emoji"`
|
||||
StatusExpiration int64 `json:"status_expiration"`
|
||||
StatusText string `json:"status_text"`
|
||||
StatusEmoji string `json:"status_emoji"`
|
||||
}{
|
||||
StatusText: statusText,
|
||||
StatusEmoji: statusEmoji,
|
||||
StatusExpiration: statusExpiration,
|
||||
StatusText: statusText,
|
||||
StatusEmoji: statusEmoji,
|
||||
},
|
||||
)
|
||||
|
||||
@ -541,17 +501,20 @@ func (api *Client) SetUserCustomStatusContextWithUser(ctx context.Context, user,
|
||||
}
|
||||
|
||||
values := url.Values{
|
||||
"user": {user},
|
||||
"token": {api.token},
|
||||
"profile": {string(profile)},
|
||||
}
|
||||
|
||||
response := &userResponseFull{}
|
||||
if err = api.postMethod(ctx, "users.profile.set", values, response); err != nil {
|
||||
if err = postForm(ctx, api.httpclient, SLACK_API+"users.profile.set", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Err()
|
||||
if !response.Ok {
|
||||
return errors.New(response.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsetUserCustomStatus removes the custom status message for the currently
|
||||
@ -563,7 +526,7 @@ func (api *Client) UnsetUserCustomStatus() error {
|
||||
// UnsetUserCustomStatusContext removes the custom status message for the currently authenticated user
|
||||
// with a custom context. This is a convenience method that wraps (*Client).SetUserCustomStatus().
|
||||
func (api *Client) UnsetUserCustomStatusContext(ctx context.Context) error {
|
||||
return api.SetUserCustomStatusContext(ctx, "", "", 0)
|
||||
return api.SetUserCustomStatusContext(ctx, "", "")
|
||||
}
|
||||
|
||||
// GetUserProfile retrieves a user's profile information.
|
||||
@ -584,14 +547,12 @@ func (api *Client) GetUserProfileContext(ctx context.Context, userID string, inc
|
||||
}
|
||||
resp := &getUserProfileResponse{}
|
||||
|
||||
err := api.postMethod(ctx, "users.profile.get", values, &resp)
|
||||
err := postSlackMethod(ctx, api.httpclient, "users.profile.get", values, &resp, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := resp.Err(); err != nil {
|
||||
return nil, err
|
||||
if !resp.Ok {
|
||||
return nil, errors.New(resp.Error)
|
||||
}
|
||||
|
||||
return resp.Profile, nil
|
||||
}
|
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
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -19,9 +20,6 @@ const (
|
||||
//
|
||||
// Create this element with Client's NewRTM() or NewRTMWithOptions(*RTMOptions)
|
||||
type RTM struct {
|
||||
// Client is the main API, embedded
|
||||
Client
|
||||
|
||||
idGen IDGenerator
|
||||
pingInterval time.Duration
|
||||
pingDeadman *time.Timer
|
||||
@ -31,9 +29,15 @@ type RTM struct {
|
||||
IncomingEvents chan RTMEvent
|
||||
outgoingMessages chan OutgoingMessage
|
||||
killChannel chan bool
|
||||
disconnected chan struct{}
|
||||
disconnectedm *sync.Once
|
||||
disconnected chan struct{} // disconnected is closed when Disconnect is invoked, regardless of connection state. Allows for ManagedConnection to not leak.
|
||||
forcePing chan bool
|
||||
rawEvents chan json.RawMessage
|
||||
wasIntentional bool
|
||||
isConnected bool
|
||||
|
||||
// Client is the main API, embedded
|
||||
Client
|
||||
websocketURL string
|
||||
|
||||
// UserDetails upon connection
|
||||
info *Info
|
||||
@ -49,35 +53,46 @@ type RTM struct {
|
||||
|
||||
// mu is mutex used to prevent RTM connection race conditions
|
||||
mu *sync.Mutex
|
||||
|
||||
// connParams is a map of flags for connection parameters.
|
||||
connParams url.Values
|
||||
}
|
||||
|
||||
// signal that we are disconnected by closing the channel.
|
||||
// protect it with a mutex to ensure it only happens once.
|
||||
func (rtm *RTM) disconnect() {
|
||||
rtm.disconnectedm.Do(func() {
|
||||
close(rtm.disconnected)
|
||||
})
|
||||
// RTMOptions allows configuration of various options available for RTM messaging
|
||||
//
|
||||
// This structure will evolve in time so please make sure you are always using the
|
||||
// named keys for every entry available as per Go 1 compatibility promise adding fields
|
||||
// to this structure should not be considered a breaking change.
|
||||
type RTMOptions struct {
|
||||
// UseRTMStart set to true in order to use rtm.start or false to use rtm.connect
|
||||
// As of 11th July 2017 you should prefer setting this to false, see:
|
||||
// https://api.slack.com/changelog/2017-04-start-using-rtm-connect-and-stop-using-rtm-start
|
||||
UseRTMStart bool
|
||||
}
|
||||
|
||||
// Disconnect and wait, blocking until a successful disconnection.
|
||||
func (rtm *RTM) Disconnect() error {
|
||||
// always push into the kill channel when invoked,
|
||||
// avoid RTM disconnect race conditions
|
||||
rtm.mu.Lock()
|
||||
defer rtm.mu.Unlock()
|
||||
|
||||
// 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.killChannel <- true:
|
||||
return nil
|
||||
case <-rtm.disconnected:
|
||||
return ErrAlreadyDisconnected
|
||||
case rtm.disconnected <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
if !rtm.isConnected {
|
||||
return errors.New("Invalid call to Disconnect - Slack API is already disconnected")
|
||||
}
|
||||
|
||||
rtm.killChannel <- true
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInfo returns the info structure received when calling
|
||||
// "startrtm", holding metadata needed to implement a full
|
||||
// chat client. It will be non-nil after a call to StartRTM().
|
||||
// "startrtm", holding all channels, groups and other metadata needed
|
||||
// to implement a full chat client. It will be non-nil after a call to
|
||||
// StartRTM().
|
||||
func (rtm *RTM) GetInfo() *Info {
|
||||
return rtm.info
|
||||
}
|
||||
@ -95,7 +110,7 @@ func (rtm *RTM) SendMessage(msg *OutgoingMessage) {
|
||||
}
|
||||
|
||||
func (rtm *RTM) resetDeadman() {
|
||||
rtm.pingDeadman.Reset(deadmanDuration(rtm.pingInterval))
|
||||
timerReset(rtm.pingDeadman, deadmanDuration(rtm.pingInterval))
|
||||
}
|
||||
|
||||
func deadmanDuration(d time.Duration) time.Duration {
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user