Merge branch 'v0.3.0'
* v0.3.0: (25 commits) Update readme Update readme and reset defaults Fix MarkAsRead Make search more robust Remove RankFind Start with improved search Implement theming for several components Start with theming functionality Start with adding colors for messages Fix notification for channels Fix input component overflow runewidth > 1 Refactor to get service reference out of components Add support for double width runes Fix Chat component border labels Speed up Channels component Add profiling endpoints Add overflow functionality Input component Update Debug component Update Debug component Refactor to create more loose coupling ...
This commit is contained in:
commit
ceb13292ac
84
README.md
84
README.md
@ -11,7 +11,7 @@ Installation
|
||||
#### Binary installation
|
||||
|
||||
[Download](https://github.com/erroneousboat/slack-term/releases) a
|
||||
compatible binary for your system. For convenience place `slack-term` in a
|
||||
compatible binary for your system. For convenience, place `slack-term` in a
|
||||
directory where you can access it from the command line. Usually this is
|
||||
`/usr/local/bin`.
|
||||
|
||||
@ -41,48 +41,70 @@ Setup
|
||||
{
|
||||
"slack_token": "yourslacktokenhere",
|
||||
|
||||
// OPTIONAL: add the following to use light theme, default is dark
|
||||
"theme": "light",
|
||||
|
||||
// OPTIONAL: set the width of the sidebar (between 1 and 11), default is 1
|
||||
"sidebar_width": 3,
|
||||
|
||||
// OPTIONAL: define custom key mappings, defaults are:
|
||||
"key_map": {
|
||||
"command": {
|
||||
"i": "mode-insert",
|
||||
"k": "channel-up",
|
||||
"j": "channel-down",
|
||||
"g": "channel-top",
|
||||
"G": "channel-bottom",
|
||||
"i": "mode-insert",
|
||||
"k": "channel-up",
|
||||
"j": "channel-down",
|
||||
"g": "channel-top",
|
||||
"G": "channel-bottom",
|
||||
"<previous>": "chat-up",
|
||||
"C-b": "chat-up",
|
||||
"C-u": "chat-up",
|
||||
"<next>": "chat-down",
|
||||
"C-f": "chat-down",
|
||||
"C-d": "chat-down",
|
||||
"q": "quit",
|
||||
"<f1>": "help"
|
||||
"C-b": "chat-up",
|
||||
"C-u": "chat-up",
|
||||
"<next>": "chat-down",
|
||||
"C-f": "chat-down",
|
||||
"C-d": "chat-down",
|
||||
"n": "channel-search-next",
|
||||
"N": "channel-search-previous",
|
||||
"q": "quit",
|
||||
"<f1>": "help"
|
||||
},
|
||||
"insert": {
|
||||
"<left>": "cursor-left",
|
||||
"<right>": "cursor-right",
|
||||
"<enter>": "send",
|
||||
"<escape>": "mode-command",
|
||||
"<left>": "cursor-left",
|
||||
"<right>": "cursor-right",
|
||||
"<enter>": "send",
|
||||
"<escape>": "mode-command",
|
||||
"<backspace>": "backspace",
|
||||
"C-8": "backspace",
|
||||
"<delete>": "delete",
|
||||
"<space>": "space"
|
||||
"C-8": "backspace",
|
||||
"<delete>": "delete",
|
||||
"<space>": "space"
|
||||
},
|
||||
"search": {
|
||||
"<left>": "cursor-left",
|
||||
"<right>": "cursor-right",
|
||||
"<escape>": "clear-input",
|
||||
"<enter>": "clear-input",
|
||||
"<left>": "cursor-left",
|
||||
"<right>": "cursor-right",
|
||||
"<escape>": "clear-input",
|
||||
"<enter>": "clear-input",
|
||||
"<backspace>": "backspace",
|
||||
"C-8": "backspace",
|
||||
"<delete>": "delete",
|
||||
"<space>": "space"
|
||||
"C-8": "backspace",
|
||||
"<delete>": "delete",
|
||||
"<space>": "space"
|
||||
}
|
||||
},
|
||||
|
||||
// OPTIONAL: override the default theme (see wiki for more information),
|
||||
// defaults are:
|
||||
"theme": {
|
||||
"view": {
|
||||
"fg": "white",
|
||||
"bg": "default",
|
||||
"border_fg": "white",
|
||||
"border_bg": "white",
|
||||
"par_fg": "white",
|
||||
"par_label_fg": "white"
|
||||
},
|
||||
"channel": {
|
||||
"prefix": "",
|
||||
"icon": "",
|
||||
"text": ""
|
||||
},
|
||||
"message": {
|
||||
"time": "",
|
||||
"name": "",
|
||||
"text": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,6 +148,8 @@ in your `slack-term.json` file.
|
||||
| command | `pg-down` | scroll chat pane down |
|
||||
| command | `ctrl-f` | scroll chat pane down |
|
||||
| command | `ctrl-d` | scroll chat pane down |
|
||||
| command | `n` | next search match |
|
||||
| command | `N` | previous search match |
|
||||
| command | `q` | quit |
|
||||
| command | `f1` | help |
|
||||
| insert | `left` | move input cursor left |
|
||||
|
@ -2,11 +2,10 @@ package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"html"
|
||||
|
||||
"github.com/erroneousboat/termui"
|
||||
|
||||
"github.com/erroneousboat/slack-term/service"
|
||||
"github.com/renstrom/fuzzysearch/fuzzy"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -15,22 +14,96 @@ const (
|
||||
IconChannel = "#"
|
||||
IconGroup = "☰"
|
||||
IconIM = "●"
|
||||
IconNotification = "🞷"
|
||||
IconNotification = "*"
|
||||
|
||||
PresenceAway = "away"
|
||||
PresenceActive = "active"
|
||||
|
||||
ChannelTypeChannel = "channel"
|
||||
ChannelTypeGroup = "group"
|
||||
ChannelTypeIM = "im"
|
||||
)
|
||||
|
||||
type ChannelItem struct {
|
||||
ID string
|
||||
Name string
|
||||
Topic string
|
||||
Type string
|
||||
UserID string
|
||||
Presence string
|
||||
Notification bool
|
||||
|
||||
StylePrefix string
|
||||
StyleIcon string
|
||||
StyleText string
|
||||
}
|
||||
|
||||
// ToString will set the label of the channel, how it will be
|
||||
// displayed on screen. Based on the type, different icons are
|
||||
// shown, as well as an optional notification icon.
|
||||
func (c ChannelItem) ToString() string {
|
||||
var prefix string
|
||||
if c.Notification {
|
||||
prefix = IconNotification
|
||||
} else {
|
||||
prefix = " "
|
||||
}
|
||||
|
||||
var icon string
|
||||
switch c.Type {
|
||||
case ChannelTypeChannel:
|
||||
icon = IconChannel
|
||||
case ChannelTypeGroup:
|
||||
icon = IconGroup
|
||||
case ChannelTypeIM:
|
||||
switch c.Presence {
|
||||
case PresenceActive:
|
||||
icon = IconOnline
|
||||
case PresenceAway:
|
||||
icon = IconOffline
|
||||
default:
|
||||
icon = IconIM
|
||||
}
|
||||
}
|
||||
|
||||
label := fmt.Sprintf(
|
||||
"[%s](%s) [%s](%s) [%s](%s)",
|
||||
prefix, c.StylePrefix,
|
||||
icon, c.StyleIcon,
|
||||
c.Name, c.StyleText,
|
||||
)
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
// GetChannelName will return a formatted representation of the
|
||||
// name of the channel
|
||||
func (c ChannelItem) GetChannelName() string {
|
||||
var channelName string
|
||||
if c.Topic != "" {
|
||||
channelName = fmt.Sprintf("%s - %s",
|
||||
html.UnescapeString(c.Name),
|
||||
html.UnescapeString(c.Topic),
|
||||
)
|
||||
} else {
|
||||
channelName = c.Name
|
||||
}
|
||||
return channelName
|
||||
}
|
||||
|
||||
// Channels is the definition of a Channels component
|
||||
type Channels struct {
|
||||
List *termui.List
|
||||
SelectedChannel int // index of which channel is selected from the List
|
||||
Offset int // from what offset are channels rendered
|
||||
CursorPosition int // the y position of the 'cursor'
|
||||
|
||||
SearchMatches []int // index of the search matches
|
||||
SearchPosition int // current position of a search match
|
||||
}
|
||||
|
||||
// CreateChannels is the constructor for the Channels component
|
||||
func CreateChannels(svc *service.SlackService, inputHeight int) *Channels {
|
||||
func CreateChannelsComponent(inputHeight int) *Channels {
|
||||
channels := &Channels{
|
||||
List: termui.NewList(),
|
||||
}
|
||||
@ -42,9 +115,6 @@ func CreateChannels(svc *service.SlackService, inputHeight int) *Channels {
|
||||
channels.Offset = 0
|
||||
channels.CursorPosition = channels.List.InnerBounds().Min.Y
|
||||
|
||||
channels.GetChannels(svc)
|
||||
channels.SetPresenceForIMChannels(svc)
|
||||
|
||||
return channels
|
||||
}
|
||||
|
||||
@ -60,6 +130,7 @@ func (c *Channels) Buffer() termui.Buffer {
|
||||
break
|
||||
}
|
||||
|
||||
// Set the visible cursor
|
||||
var cells []termui.Cell
|
||||
if y == c.CursorPosition {
|
||||
cells = termui.DefaultTxBuilder.Build(
|
||||
@ -125,27 +196,8 @@ func (c *Channels) SetY(y int) {
|
||||
c.List.SetY(y)
|
||||
}
|
||||
|
||||
// GetChannels will get all available channels from the SlackService
|
||||
func (c *Channels) GetChannels(svc *service.SlackService) {
|
||||
for _, slackChan := range svc.GetChannels() {
|
||||
label := setChannelLabel(slackChan, false)
|
||||
c.List.Items = append(c.List.Items, label)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// SetPresenceForIMChannels this will set the correct icon for
|
||||
// IM channels for when they're online of away
|
||||
func (c *Channels) SetPresenceForIMChannels(svc *service.SlackService) {
|
||||
for _, slackChan := range svc.GetChannels() {
|
||||
if slackChan.Type == service.ChannelTypeIM {
|
||||
presence, err := svc.GetUserPresence(slackChan.UserID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
c.SetPresence(svc, slackChan.UserID, presence)
|
||||
}
|
||||
}
|
||||
func (c *Channels) SetChannels(channels []string) {
|
||||
c.List.Items = channels
|
||||
}
|
||||
|
||||
// SetSelectedChannel sets the SelectedChannel given the index
|
||||
@ -153,12 +205,16 @@ func (c *Channels) SetSelectedChannel(index int) {
|
||||
c.SelectedChannel = index
|
||||
}
|
||||
|
||||
// GetSelectedChannel returns the SelectedChannel
|
||||
func (c *Channels) GetSelectedChannel() string {
|
||||
return c.List.Items[c.SelectedChannel]
|
||||
}
|
||||
|
||||
// MoveCursorUp will decrease the SelectedChannel by 1
|
||||
func (c *Channels) MoveCursorUp() {
|
||||
if c.SelectedChannel > 0 {
|
||||
c.SetSelectedChannel(c.SelectedChannel - 1)
|
||||
c.ScrollUp()
|
||||
c.ClearNewMessageIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +223,6 @@ func (c *Channels) MoveCursorDown() {
|
||||
if c.SelectedChannel < len(c.List.Items)-1 {
|
||||
c.SetSelectedChannel(c.SelectedChannel + 1)
|
||||
c.ScrollDown()
|
||||
c.ClearNewMessageIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,134 +276,71 @@ func (c *Channels) ScrollDown() {
|
||||
// when a match has been found the selected channel will then
|
||||
// be the channel that has been found
|
||||
func (c *Channels) Search(term string) {
|
||||
for i, item := range c.List.Items {
|
||||
if strings.Contains(item, term) {
|
||||
c.SearchMatches = make([]int, 0)
|
||||
|
||||
// The new position
|
||||
newPos := i
|
||||
matches := fuzzy.Find(term, c.List.Items)
|
||||
|
||||
// Is the new position in range of the current view?
|
||||
minRange := c.Offset
|
||||
maxRange := c.Offset + (c.List.InnerBounds().Max.Y - 2)
|
||||
|
||||
if newPos < minRange {
|
||||
// newPos is above, we need to scroll up.
|
||||
c.SetSelectedChannel(i)
|
||||
|
||||
// How much do we need to scroll to get it into range?
|
||||
c.Offset = c.Offset - (minRange - newPos)
|
||||
} else if newPos > maxRange {
|
||||
// newPos is below, we need to scroll down
|
||||
c.SetSelectedChannel(i)
|
||||
|
||||
// How much do we need to scroll to get it into range?
|
||||
c.Offset = c.Offset + (newPos - maxRange)
|
||||
} else {
|
||||
// newPos is inside range
|
||||
c.SetSelectedChannel(i)
|
||||
for _, m := range matches {
|
||||
for i, item := range c.List.Items {
|
||||
if m == item {
|
||||
c.SearchMatches = append(c.SearchMatches, i)
|
||||
break
|
||||
}
|
||||
|
||||
// Set cursor to correct position
|
||||
c.CursorPosition = (newPos - c.Offset) + 1
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetNotification will be called when a new message arrives and will
|
||||
// render an notification icon in front of the channel name
|
||||
func (c *Channels) SetNotification(svc *service.SlackService, channelID string) {
|
||||
// Get the correct Channel from svc.Channels
|
||||
var index int
|
||||
for i, channel := range svc.Channels {
|
||||
if channelID == channel.ID {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.Contains(c.List.Items[index], IconNotification) {
|
||||
// The order of svc.Channels relates to the order of
|
||||
// List.Items, index will be the index of the channel
|
||||
c.List.Items[index] = fmt.Sprintf(
|
||||
"%s %s", IconNotification, strings.TrimSpace(c.List.Items[index]),
|
||||
)
|
||||
if len(c.SearchMatches) > 0 {
|
||||
c.GotoPosition(0)
|
||||
c.SearchPosition = 0
|
||||
}
|
||||
|
||||
// Play terminal bell sound
|
||||
fmt.Print("\a")
|
||||
}
|
||||
|
||||
// ClearNewMessageIndicator will remove the notification icon in front of
|
||||
// a channel that received a new message. This will happen as one will
|
||||
// move up or down the cursor for Channels
|
||||
func (c *Channels) ClearNewMessageIndicator() {
|
||||
channelName := strings.Split(
|
||||
c.List.Items[c.SelectedChannel],
|
||||
fmt.Sprintf("%s ", IconNotification),
|
||||
)
|
||||
// GotoPosition is used by the search functionality to automatically
|
||||
// scroll to a specific location in the channels component
|
||||
func (c *Channels) GotoPosition(position int) {
|
||||
|
||||
if len(channelName) > 1 {
|
||||
c.List.Items[c.SelectedChannel] = fmt.Sprintf(" %s", channelName[1])
|
||||
// The new position
|
||||
newPos := c.SearchMatches[position]
|
||||
|
||||
// Is the new position in range of the current view?
|
||||
minRange := c.Offset
|
||||
maxRange := c.Offset + (c.List.InnerBounds().Max.Y - 2)
|
||||
|
||||
if newPos < minRange {
|
||||
// newPos is above, we need to scroll up.
|
||||
c.SetSelectedChannel(newPos)
|
||||
|
||||
// How much do we need to scroll to get it into range?
|
||||
c.Offset = c.Offset - (minRange - newPos)
|
||||
} else if newPos > maxRange {
|
||||
// newPos is below, we need to scroll down
|
||||
c.SetSelectedChannel(newPos)
|
||||
|
||||
// How much do we need to scroll to get it into range?
|
||||
c.Offset = c.Offset + (newPos - maxRange)
|
||||
} else {
|
||||
c.List.Items[c.SelectedChannel] = channelName[0]
|
||||
// newPos is inside range
|
||||
c.SetSelectedChannel(newPos)
|
||||
}
|
||||
|
||||
// Set cursor to correct position
|
||||
c.CursorPosition = (newPos - c.Offset) + 1
|
||||
}
|
||||
|
||||
// SearchNext allows us to cycle through the c.SearchMatches
|
||||
func (c *Channels) SearchNext() {
|
||||
newPosition := c.SearchPosition + 1
|
||||
if newPosition <= len(c.SearchMatches)-1 {
|
||||
c.GotoPosition(newPosition)
|
||||
c.SearchPosition = newPosition
|
||||
}
|
||||
}
|
||||
|
||||
// SetReadMark will send the ReadMark event on the service
|
||||
func (c *Channels) SetReadMark(svc *service.SlackService) {
|
||||
svc.SetChannelReadMark(svc.SlackChannels[c.SelectedChannel])
|
||||
}
|
||||
|
||||
// SetPresence will set the correct icon for a IM Channel
|
||||
func (c *Channels) SetPresence(svc *service.SlackService, userID string, presence string) {
|
||||
// Get the correct Channel from svc.Channels
|
||||
var index int
|
||||
for i, channel := range svc.Channels {
|
||||
if userID == channel.UserID {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch presence {
|
||||
case PresenceActive:
|
||||
c.List.Items[index] = strings.Replace(
|
||||
c.List.Items[index], IconOffline, IconOnline, 1,
|
||||
)
|
||||
case PresenceAway:
|
||||
c.List.Items[index] = strings.Replace(
|
||||
c.List.Items[index], IconOnline, IconOffline, 1,
|
||||
)
|
||||
default:
|
||||
c.List.Items[index] = strings.Replace(
|
||||
c.List.Items[index], IconOnline, IconOffline, 1,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// setChannelLabel will set the label of the channel, meaning, how it
|
||||
// is displayed on screen. Based on the type, different icons are
|
||||
// shown, as well as an optional notification icon.
|
||||
func setChannelLabel(channel service.Channel, notification bool) string {
|
||||
var prefix string
|
||||
if notification {
|
||||
prefix = IconNotification
|
||||
} else {
|
||||
prefix = " "
|
||||
}
|
||||
|
||||
var label string
|
||||
switch channel.Type {
|
||||
case service.ChannelTypeChannel:
|
||||
label = fmt.Sprintf("%s %s %s", prefix, IconChannel, channel.Name)
|
||||
case service.ChannelTypeGroup:
|
||||
label = fmt.Sprintf("%s %s %s", prefix, IconGroup, channel.Name)
|
||||
case service.ChannelTypeIM:
|
||||
label = fmt.Sprintf("%s %s %s", prefix, IconIM, channel.Name)
|
||||
}
|
||||
|
||||
return label
|
||||
// SearchPrev allows us to cycle through the c.SearchMatches
|
||||
func (c *Channels) SearchPrev() {
|
||||
newPosition := c.SearchPosition - 1
|
||||
if newPosition >= 0 {
|
||||
c.GotoPosition(newPosition)
|
||||
c.SearchPosition = newPosition
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,44 @@ import (
|
||||
"html"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/erroneousboat/termui"
|
||||
|
||||
"github.com/erroneousboat/slack-term/config"
|
||||
"github.com/erroneousboat/slack-term/service"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Time time.Time
|
||||
Name string
|
||||
Content string
|
||||
|
||||
StyleTime string
|
||||
StyleName string
|
||||
StyleText string
|
||||
}
|
||||
|
||||
func (m Message) ToString() string {
|
||||
if (m.Time != time.Time{} && m.Name != "") {
|
||||
|
||||
return html.UnescapeString(
|
||||
fmt.Sprintf(
|
||||
"[[%s]](%s) [<%s>](%s) [%s](%s)",
|
||||
m.Time.Format("15:04"),
|
||||
m.StyleTime,
|
||||
m.Name,
|
||||
m.StyleName,
|
||||
m.Content,
|
||||
m.StyleText,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
return html.UnescapeString(
|
||||
fmt.Sprintf("[%s](%s)", m.Content, m.StyleText),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Chat is the definition of a Chat component
|
||||
type Chat struct {
|
||||
List *termui.List
|
||||
@ -19,7 +50,7 @@ type Chat struct {
|
||||
}
|
||||
|
||||
// CreateChat is the constructor for the Chat struct
|
||||
func CreateChat(svc *service.SlackService, inputHeight int, selectedSlackChannel interface{}, selectedChannel service.Channel) *Chat {
|
||||
func CreateChatComponent(inputHeight int) *Chat {
|
||||
chat := &Chat{
|
||||
List: termui.NewList(),
|
||||
Offset: 0,
|
||||
@ -28,9 +59,6 @@ func CreateChat(svc *service.SlackService, inputHeight int, selectedSlackChannel
|
||||
chat.List.Height = termui.TermHeight() - inputHeight
|
||||
chat.List.Overflow = "wrap"
|
||||
|
||||
chat.GetMessages(svc, selectedSlackChannel)
|
||||
chat.SetBorderLabel(selectedChannel)
|
||||
|
||||
return chat
|
||||
}
|
||||
|
||||
@ -153,15 +181,17 @@ func (c *Chat) SetY(y int) {
|
||||
c.List.SetY(y)
|
||||
}
|
||||
|
||||
// GetMessages will get an array of strings for a specific channel which will
|
||||
// contain messages in turn all these messages will be added to List.Items
|
||||
func (c *Chat) GetMessages(svc *service.SlackService, channel interface{}) {
|
||||
// Get the count of message that fit in the pane
|
||||
count := c.List.InnerBounds().Max.Y - c.List.InnerBounds().Min.Y
|
||||
messages := svc.GetMessages(channel, count)
|
||||
// GetMaxItems return the maximal amount of items can fit in the Chat
|
||||
// component
|
||||
func (c *Chat) GetMaxItems() int {
|
||||
return c.List.InnerBounds().Max.Y - c.List.InnerBounds().Min.Y
|
||||
}
|
||||
|
||||
for _, message := range messages {
|
||||
c.AddMessage(message)
|
||||
// SetMessages will put the provided messages into the Items field of the
|
||||
// Chat view
|
||||
func (c *Chat) SetMessages(messages []string) {
|
||||
for _, msg := range messages {
|
||||
c.List.Items = append(c.List.Items, html.UnescapeString(msg))
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,16 +238,7 @@ func (c *Chat) ScrollDown() {
|
||||
}
|
||||
|
||||
// SetBorderLabel will set Label of the Chat pane to the specified string
|
||||
func (c *Chat) SetBorderLabel(channel service.Channel) {
|
||||
var channelName string
|
||||
if channel.Topic != "" {
|
||||
channelName = fmt.Sprintf("%s - %s",
|
||||
channel.Name,
|
||||
channel.Topic,
|
||||
)
|
||||
} else {
|
||||
channelName = channel.Name
|
||||
}
|
||||
func (c *Chat) SetBorderLabel(channelName string) {
|
||||
c.List.BorderLabel = channelName
|
||||
}
|
||||
|
||||
|
58
components/debug.go
Normal file
58
components/debug.go
Normal file
@ -0,0 +1,58 @@
|
||||
package components
|
||||
|
||||
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
|
||||
type Debug struct {
|
||||
Par *termui.Par
|
||||
List *termui.List
|
||||
}
|
||||
|
||||
func CreateDebugComponent(inputHeight int) *Debug {
|
||||
debug := &Debug{
|
||||
List: termui.NewList(),
|
||||
}
|
||||
|
||||
debug.List.BorderLabel = "Debug"
|
||||
debug.List.Height = termui.TermHeight() - inputHeight
|
||||
|
||||
return debug
|
||||
}
|
||||
|
||||
// Buffer implements interface termui.Bufferer
|
||||
func (d *Debug) Buffer() termui.Buffer {
|
||||
return d.List.Buffer()
|
||||
}
|
||||
|
||||
// GetHeight implements interface termui.GridBufferer
|
||||
func (d *Debug) GetHeight() int {
|
||||
return d.List.Block.GetHeight()
|
||||
}
|
||||
|
||||
// SetWidth implements interface termui.GridBufferer
|
||||
func (d *Debug) SetWidth(w int) {
|
||||
d.List.SetWidth(w)
|
||||
}
|
||||
|
||||
// SetX implements interface termui.GridBufferer
|
||||
func (d *Debug) SetX(x int) {
|
||||
d.List.SetX(x)
|
||||
}
|
||||
|
||||
// SetY implements interface termui.GridBufferer
|
||||
func (d *Debug) SetY(y int) {
|
||||
d.List.SetY(y)
|
||||
}
|
||||
|
||||
// Println will add the text to the Debug component
|
||||
func (d *Debug) Println(text string) {
|
||||
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)
|
||||
}
|
@ -2,23 +2,26 @@ package components
|
||||
|
||||
import (
|
||||
"github.com/erroneousboat/termui"
|
||||
|
||||
"github.com/erroneousboat/slack-term/service"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// Input is the definition of an Input component
|
||||
type Input struct {
|
||||
Par *termui.Par
|
||||
Text []rune
|
||||
CursorPosition int
|
||||
Par *termui.Par
|
||||
Text []rune
|
||||
CursorPositionScreen int
|
||||
CursorPositionText int
|
||||
Offset int
|
||||
}
|
||||
|
||||
// CreateInput is the constructor of the Input struct
|
||||
func CreateInput() *Input {
|
||||
func CreateInputComponent() *Input {
|
||||
input := &Input{
|
||||
Par: termui.NewPar(""),
|
||||
Text: make([]rune, 0),
|
||||
CursorPosition: 0,
|
||||
Par: termui.NewPar(""),
|
||||
Text: make([]rune, 0),
|
||||
CursorPositionScreen: 0,
|
||||
CursorPositionText: 0,
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
input.Par.Height = 3
|
||||
@ -30,10 +33,11 @@ func CreateInput() *Input {
|
||||
func (i *Input) Buffer() termui.Buffer {
|
||||
buf := i.Par.Buffer()
|
||||
|
||||
// Set visible cursor
|
||||
char := buf.At(i.Par.InnerX()+i.CursorPosition, i.Par.Block.InnerY())
|
||||
// Set visible cursor, get char at screen cursor position
|
||||
char := buf.At(i.Par.InnerX()+i.CursorPositionScreen, i.Par.Block.InnerY())
|
||||
|
||||
buf.Set(
|
||||
i.Par.InnerX()+i.CursorPosition,
|
||||
i.Par.InnerX()+i.CursorPositionScreen,
|
||||
i.Par.Block.InnerY(),
|
||||
termui.Cell{
|
||||
Ch: char.Ch,
|
||||
@ -65,57 +69,117 @@ func (i *Input) SetY(y int) {
|
||||
i.Par.SetY(y)
|
||||
}
|
||||
|
||||
// SendMessage send the input text through the SlackService
|
||||
func (i *Input) SendMessage(svc *service.SlackService, channel string, message string) {
|
||||
svc.SendMessage(channel, message)
|
||||
}
|
||||
|
||||
// Insert will insert a given key at the place of the current CursorPosition
|
||||
// Insert will insert a given key at the place of the current CursorPositionText
|
||||
func (i *Input) Insert(key rune) {
|
||||
if len(i.Text) < i.Par.InnerBounds().Dx()-1 {
|
||||
// Append key to the left side
|
||||
left := make([]rune, len(i.Text[0:i.CursorPositionText]))
|
||||
copy(left, i.Text[0:i.CursorPositionText])
|
||||
left = append(left, key)
|
||||
|
||||
left := make([]rune, len(i.Text[0:i.CursorPosition]))
|
||||
copy(left, i.Text[0:i.CursorPosition])
|
||||
left = append(left, key)
|
||||
// Combine left and right side
|
||||
i.Text = append(left, i.Text[i.CursorPositionText:]...)
|
||||
|
||||
i.Text = append(left, i.Text[i.CursorPosition:]...)
|
||||
|
||||
i.Par.Text = string(i.Text)
|
||||
i.MoveCursorRight()
|
||||
}
|
||||
i.MoveCursorRight()
|
||||
}
|
||||
|
||||
// Backspace will remove a character in front of the CursorPosition
|
||||
// Backspace will remove a character in front of the CursorPositionText
|
||||
func (i *Input) Backspace() {
|
||||
if i.CursorPosition > 0 {
|
||||
i.Text = append(i.Text[0:i.CursorPosition-1], i.Text[i.CursorPosition:]...)
|
||||
i.Par.Text = string(i.Text)
|
||||
if i.CursorPositionText > 0 {
|
||||
i.MoveCursorLeft()
|
||||
i.Text = append(i.Text[0:i.CursorPositionText], i.Text[i.CursorPositionText+1:]...)
|
||||
i.Par.Text = string(i.Text[i.Offset:])
|
||||
}
|
||||
}
|
||||
|
||||
// Delete will remove a character at the CursorPosition
|
||||
// Delete will remove a character at the CursorPositionText
|
||||
func (i *Input) Delete() {
|
||||
if i.CursorPosition < len(i.Text) {
|
||||
i.Text = append(i.Text[0:i.CursorPosition], i.Text[i.CursorPosition+1:]...)
|
||||
i.Par.Text = string(i.Text)
|
||||
if i.CursorPositionText < len(i.Text) {
|
||||
i.Text = append(i.Text[0:i.CursorPositionText], i.Text[i.CursorPositionText+1:]...)
|
||||
i.Par.Text = string(i.Text[i.Offset:])
|
||||
}
|
||||
}
|
||||
|
||||
// MoveCursorRight will increase the current CursorPosition with 1
|
||||
// MoveCursorRight will increase the current CursorPositionText with 1
|
||||
func (i *Input) MoveCursorRight() {
|
||||
if i.CursorPosition < len(i.Text) {
|
||||
i.CursorPosition++
|
||||
if i.CursorPositionText < len(i.Text) {
|
||||
i.CursorPositionText++
|
||||
i.ScrollRight()
|
||||
}
|
||||
|
||||
i.Par.Text = string(i.Text[i.Offset:])
|
||||
}
|
||||
|
||||
// MoveCursorLeft will decrease the current CursorPositionText with 1
|
||||
func (i *Input) MoveCursorLeft() {
|
||||
if i.CursorPositionText > 0 {
|
||||
i.CursorPositionText--
|
||||
i.ScrollLeft()
|
||||
}
|
||||
|
||||
i.Par.Text = string(i.Text[i.Offset:])
|
||||
}
|
||||
|
||||
func (i *Input) ScrollLeft() {
|
||||
// Is the cursor at the far left of the Input component?
|
||||
if i.CursorPositionScreen == 0 {
|
||||
|
||||
// Decrease offset to show what is on the left side
|
||||
if i.Offset > 0 {
|
||||
i.Offset--
|
||||
}
|
||||
} else {
|
||||
i.CursorPositionScreen -= i.GetRuneWidthRight()
|
||||
}
|
||||
}
|
||||
|
||||
// MoveCursorLeft will decrease the current CursorPosition with 1
|
||||
func (i *Input) MoveCursorLeft() {
|
||||
if i.CursorPosition > 0 {
|
||||
i.CursorPosition--
|
||||
func (i *Input) ScrollRight() {
|
||||
// Is the cursor at the far right of the Input component, cursor
|
||||
// isn't at the end of the text
|
||||
if (i.CursorPositionScreen + i.GetRuneWidthLeft()) > i.Par.InnerBounds().Dx()-1 {
|
||||
|
||||
// Increase offset to show what is on the right side
|
||||
if i.Offset < len(i.Text) {
|
||||
i.Offset = i.CalculateOffset()
|
||||
i.CursorPositionScreen = i.GetRuneWidthOffsetToCursor()
|
||||
}
|
||||
} else {
|
||||
i.CursorPositionScreen += i.GetRuneWidthLeft()
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateOffset will, based on the width of the runes on the
|
||||
// left of the text cursor, calculate the offset that needs to
|
||||
// be used by the Inpute Component
|
||||
func (i *Input) CalculateOffset() int {
|
||||
var offset int
|
||||
|
||||
var currentRuneWidth int
|
||||
for j := (i.CursorPositionText - 1); currentRuneWidth < i.GetMaxWidth()-1; j-- {
|
||||
currentRuneWidth += runewidth.RuneWidth(i.Text[j])
|
||||
offset = j
|
||||
}
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
// GetRunWidthOffsetToCursor will get the rune width of all
|
||||
// the runes from the offset until the text cursor
|
||||
func (i *Input) GetRuneWidthOffsetToCursor() int {
|
||||
return runewidth.StringWidth(string(i.Text[i.Offset:i.CursorPositionText]))
|
||||
}
|
||||
|
||||
// GetRuneWidthLeft will get the width of a rune on the left side
|
||||
// of the CursorPositionText
|
||||
func (i *Input) GetRuneWidthLeft() int {
|
||||
return runewidth.RuneWidth(i.Text[i.CursorPositionText-1])
|
||||
}
|
||||
|
||||
// GetRuneWidthLeft will get the width of a rune on the right side
|
||||
// of the CursorPositionText
|
||||
func (i *Input) GetRuneWidthRight() int {
|
||||
return runewidth.RuneWidth(i.Text[i.CursorPositionText])
|
||||
}
|
||||
|
||||
// IsEmpty will return true when the input is empty
|
||||
func (i *Input) IsEmpty() bool {
|
||||
if i.Par.Text == "" {
|
||||
@ -128,10 +192,17 @@ func (i *Input) IsEmpty() bool {
|
||||
func (i *Input) Clear() {
|
||||
i.Text = make([]rune, 0)
|
||||
i.Par.Text = ""
|
||||
i.CursorPosition = 0
|
||||
i.CursorPositionScreen = 0
|
||||
i.CursorPositionText = 0
|
||||
}
|
||||
|
||||
// GetText returns the text currently in the input
|
||||
func (i *Input) GetText() string {
|
||||
return i.Par.Text
|
||||
}
|
||||
|
||||
// GetMaxWidth returns the maximum number of positions
|
||||
// the Input component can display
|
||||
func (i *Input) GetMaxWidth() int {
|
||||
return i.Par.InnerBounds().Dx() - 1
|
||||
}
|
||||
|
@ -1,6 +1,14 @@
|
||||
package components
|
||||
|
||||
import "github.com/erroneousboat/termui"
|
||||
import (
|
||||
"github.com/erroneousboat/termui"
|
||||
)
|
||||
|
||||
const (
|
||||
CommandMode = "NORMAL"
|
||||
InsertMode = "INSERT"
|
||||
SearchMode = "SEARCH"
|
||||
)
|
||||
|
||||
// Mode is the definition of Mode component
|
||||
type Mode struct {
|
||||
@ -8,12 +16,13 @@ type Mode struct {
|
||||
}
|
||||
|
||||
// CreateMode is the constructor of the Mode struct
|
||||
func CreateMode() *Mode {
|
||||
func CreateModeComponent() *Mode {
|
||||
mode := &Mode{
|
||||
Par: termui.NewPar("NORMAL"),
|
||||
Par: termui.NewPar(CommandMode),
|
||||
}
|
||||
|
||||
mode.Par.Height = 3
|
||||
mode.SetCommandMode()
|
||||
|
||||
return mode
|
||||
}
|
||||
@ -80,3 +89,18 @@ func (m *Mode) SetX(x int) {
|
||||
func (m *Mode) SetY(y int) {
|
||||
m.Par.SetY(y)
|
||||
}
|
||||
|
||||
func (m *Mode) SetInsertMode() {
|
||||
m.Par.Text = InsertMode
|
||||
termui.Render(m)
|
||||
}
|
||||
|
||||
func (m *Mode) SetCommandMode() {
|
||||
m.Par.Text = CommandMode
|
||||
termui.Render(m)
|
||||
}
|
||||
|
||||
func (m *Mode) SetSearchMode() {
|
||||
m.Par.Text = SearchMode
|
||||
termui.Render(m)
|
||||
}
|
||||
|
@ -11,18 +11,51 @@ import (
|
||||
// Config is the definition of a Config struct
|
||||
type Config struct {
|
||||
SlackToken string `json:"slack_token"`
|
||||
Theme string `json:"theme"`
|
||||
SidebarWidth int `json:"sidebar_width"`
|
||||
MainWidth int `json:"-"`
|
||||
KeyMap map[string]keyMapping `json:"key_map"`
|
||||
Theme Theme `json:"theme"`
|
||||
}
|
||||
|
||||
type keyMapping map[string]string
|
||||
|
||||
// NewConfig loads the config file and returns a Config struct
|
||||
func NewConfig(filepath string) (*Config, error) {
|
||||
cfg := Config{
|
||||
Theme: "dark",
|
||||
cfg := getDefaultConfig()
|
||||
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(file).Decode(&cfg); err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
if cfg.SlackToken == "" {
|
||||
return &cfg, errors.New("couldn't find 'slack_token' parameter")
|
||||
}
|
||||
|
||||
if cfg.SidebarWidth < 1 || cfg.SidebarWidth > 11 {
|
||||
return &cfg, errors.New("please specify the 'sidebar_width' between 1 and 11")
|
||||
}
|
||||
|
||||
cfg.MainWidth = 12 - cfg.SidebarWidth
|
||||
|
||||
termui.ColorMap = map[string]termui.Attribute{
|
||||
"fg": termui.StringToAttribute(cfg.Theme.View.Fg),
|
||||
"bg": termui.StringToAttribute(cfg.Theme.View.Bg),
|
||||
"border.fg": termui.StringToAttribute(cfg.Theme.View.BorderFg),
|
||||
"label.fg": termui.StringToAttribute(cfg.Theme.View.LabelFg),
|
||||
"par.fg": termui.StringToAttribute(cfg.Theme.View.ParFg),
|
||||
"par.label.bg": termui.StringToAttribute(cfg.Theme.View.ParLabelFg),
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func getDefaultConfig() Config {
|
||||
return Config{
|
||||
SidebarWidth: 1,
|
||||
MainWidth: 11,
|
||||
KeyMap: map[string]keyMapping{
|
||||
@ -39,6 +72,8 @@ func NewConfig(filepath string) (*Config, error) {
|
||||
"<next>": "chat-down",
|
||||
"C-f": "chat-down",
|
||||
"C-d": "chat-down",
|
||||
"n": "channel-search-next",
|
||||
"N": "channel-search-prev",
|
||||
"q": "quit",
|
||||
"<f1>": "help",
|
||||
},
|
||||
@ -63,37 +98,25 @@ func NewConfig(filepath string) (*Config, error) {
|
||||
"<space>": "space",
|
||||
},
|
||||
},
|
||||
Theme: Theme{
|
||||
View: View{
|
||||
Fg: "white",
|
||||
Bg: "default",
|
||||
BorderFg: "white",
|
||||
LabelFg: "green,bold",
|
||||
ParFg: "white",
|
||||
ParLabelFg: "white",
|
||||
},
|
||||
Channel: Channel{
|
||||
Prefix: "",
|
||||
Icon: "",
|
||||
Text: "",
|
||||
},
|
||||
Message: Message{
|
||||
Time: "",
|
||||
Name: "",
|
||||
Text: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(file).Decode(&cfg); err != nil {
|
||||
return &cfg, err
|
||||
}
|
||||
|
||||
if cfg.SlackToken == "" {
|
||||
return &cfg, errors.New("couldn't find 'slack_token' parameter")
|
||||
}
|
||||
|
||||
if cfg.SidebarWidth < 1 || cfg.SidebarWidth > 11 {
|
||||
return &cfg, errors.New("please specify the 'sidebar_width' between 1 and 11")
|
||||
}
|
||||
|
||||
cfg.MainWidth = 12 - cfg.SidebarWidth
|
||||
|
||||
if cfg.Theme == "light" {
|
||||
termui.ColorMap = map[string]termui.Attribute{
|
||||
"fg": termui.ColorBlack,
|
||||
"bg": termui.ColorWhite,
|
||||
"border.fg": termui.ColorBlack,
|
||||
"label.fg": termui.ColorBlue,
|
||||
"par.fg": termui.ColorYellow,
|
||||
"par.label.bg": termui.ColorWhite,
|
||||
}
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
28
config/theme.go
Normal file
28
config/theme.go
Normal file
@ -0,0 +1,28 @@
|
||||
package config
|
||||
|
||||
type Theme struct {
|
||||
View View `json:"view"`
|
||||
Channel Channel `json:"channel"`
|
||||
Message Message `json:"message"`
|
||||
}
|
||||
|
||||
type View struct {
|
||||
Fg string `json:"fg"`
|
||||
Bg string `json:"bg"`
|
||||
BorderFg string `json:"border_fg"`
|
||||
LabelFg string `json:"border_fg"`
|
||||
ParFg string `json:"par_fg"`
|
||||
ParLabelFg string `json:"par_label_fg"`
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Time string `json:"time"`
|
||||
Name string `json:"name"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type Channel struct {
|
||||
Prefix string `json:"prefix"`
|
||||
Icon string `json:"icon"`
|
||||
Text string `json:"text"`
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/erroneousboat/termui"
|
||||
termbox "github.com/nsf/termbox-go"
|
||||
|
||||
@ -21,12 +24,19 @@ type AppContext struct {
|
||||
Body *termui.Grid
|
||||
View *views.View
|
||||
Config *config.Config
|
||||
Debug bool
|
||||
Mode string
|
||||
}
|
||||
|
||||
// CreateAppContext creates an application context which can be passed
|
||||
// and referenced througout the application
|
||||
func CreateAppContext(flgConfig string) (*AppContext, error) {
|
||||
func CreateAppContext(flgConfig string, flgDebug bool) (*AppContext, error) {
|
||||
if flgDebug {
|
||||
go func() {
|
||||
http.ListenAndServe(":6060", nil)
|
||||
}()
|
||||
}
|
||||
|
||||
// Load config
|
||||
config, err := config.NewConfig(flgConfig)
|
||||
if err != nil {
|
||||
@ -34,16 +44,50 @@ func CreateAppContext(flgConfig string) (*AppContext, error) {
|
||||
}
|
||||
|
||||
// Create Service
|
||||
svc := service.NewSlackService(config.SlackToken)
|
||||
svc, err := service.NewSlackService(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create ChatView
|
||||
view := views.CreateChatView(svc)
|
||||
// Create the main view
|
||||
view := views.CreateView(config, svc)
|
||||
|
||||
// Setup the interface
|
||||
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 {
|
||||
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.Align()
|
||||
termui.Render(termui.Body)
|
||||
|
||||
return &AppContext{
|
||||
EventQueue: make(chan termbox.Event, 20),
|
||||
Service: svc,
|
||||
Body: termui.Body,
|
||||
View: view,
|
||||
Config: config,
|
||||
Debug: flgDebug,
|
||||
Mode: CommandMode,
|
||||
}, nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -19,24 +20,26 @@ var timer *time.Timer
|
||||
// these action names can then be used to bind them to specific keys
|
||||
// in the Config.
|
||||
var actionMap = map[string]func(*context.AppContext){
|
||||
"space": actionSpace,
|
||||
"backspace": actionBackSpace,
|
||||
"delete": actionDelete,
|
||||
"cursor-right": actionMoveCursorRight,
|
||||
"cursor-left": actionMoveCursorLeft,
|
||||
"send": actionSend,
|
||||
"quit": actionQuit,
|
||||
"mode-insert": actionInsertMode,
|
||||
"mode-command": actionCommandMode,
|
||||
"mode-search": actionSearchMode,
|
||||
"clear-input": actionClearInput,
|
||||
"channel-up": actionMoveCursorUpChannels,
|
||||
"channel-down": actionMoveCursorDownChannels,
|
||||
"channel-top": actionMoveCursorTopChannels,
|
||||
"channel-bottom": actionMoveCursorBottomChannels,
|
||||
"chat-up": actionScrollUpChat,
|
||||
"chat-down": actionScrollDownChat,
|
||||
"help": actionHelp,
|
||||
"space": actionSpace,
|
||||
"backspace": actionBackSpace,
|
||||
"delete": actionDelete,
|
||||
"cursor-right": actionMoveCursorRight,
|
||||
"cursor-left": actionMoveCursorLeft,
|
||||
"send": actionSend,
|
||||
"quit": actionQuit,
|
||||
"mode-insert": actionInsertMode,
|
||||
"mode-command": actionCommandMode,
|
||||
"mode-search": actionSearchMode,
|
||||
"clear-input": actionClearInput,
|
||||
"channel-up": actionMoveCursorUpChannels,
|
||||
"channel-down": actionMoveCursorDownChannels,
|
||||
"channel-top": actionMoveCursorTopChannels,
|
||||
"channel-bottom": actionMoveCursorBottomChannels,
|
||||
"channel-search-next": actionSearchNextChannels,
|
||||
"channel-search-prev": actionSearchPrevChannels,
|
||||
"chat-up": actionScrollUpChat,
|
||||
"chat-down": actionScrollDownChat,
|
||||
"help": actionHelp,
|
||||
}
|
||||
|
||||
func RegisterEventHandlers(ctx *context.AppContext) {
|
||||
@ -56,6 +59,13 @@ func eventHandler(ctx *context.AppContext) {
|
||||
ev := <-ctx.EventQueue
|
||||
handleTermboxEvents(ctx, ev)
|
||||
handleMoreTermboxEvents(ctx, ev)
|
||||
|
||||
// Place your debugging statements here
|
||||
if ctx.Debug {
|
||||
ctx.View.Debug.Println(
|
||||
"event received",
|
||||
)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -101,7 +111,9 @@ func messageHandler(ctx *context.AppContext) {
|
||||
// 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])
|
||||
ctx.View.Chat.AddMessage(
|
||||
msg[i].ToString(),
|
||||
)
|
||||
}
|
||||
|
||||
termui.Render(ctx.View.Chat)
|
||||
@ -202,9 +214,8 @@ func actionSend(ctx *context.AppContext) {
|
||||
ctx.View.Input.Clear()
|
||||
ctx.View.Refresh()
|
||||
|
||||
ctx.View.Input.SendMessage(
|
||||
ctx.Service,
|
||||
ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID,
|
||||
ctx.Service.SendMessage(
|
||||
ctx.View.Channels.SelectedChannel,
|
||||
message,
|
||||
)
|
||||
}
|
||||
@ -238,31 +249,38 @@ func actionQuit(ctx *context.AppContext) {
|
||||
|
||||
func actionInsertMode(ctx *context.AppContext) {
|
||||
ctx.Mode = context.InsertMode
|
||||
ctx.View.Mode.Par.Text = "INSERT"
|
||||
termui.Render(ctx.View.Mode)
|
||||
ctx.View.Mode.SetInsertMode()
|
||||
}
|
||||
|
||||
func actionCommandMode(ctx *context.AppContext) {
|
||||
ctx.Mode = context.CommandMode
|
||||
ctx.View.Mode.Par.Text = "NORMAL"
|
||||
termui.Render(ctx.View.Mode)
|
||||
ctx.View.Mode.SetCommandMode()
|
||||
}
|
||||
|
||||
func actionSearchMode(ctx *context.AppContext) {
|
||||
ctx.Mode = context.SearchMode
|
||||
ctx.View.Mode.Par.Text = "SEARCH"
|
||||
termui.Render(ctx.View.Mode)
|
||||
ctx.View.Mode.SetSearchMode()
|
||||
}
|
||||
|
||||
func actionGetMessages(ctx *context.AppContext) {
|
||||
ctx.View.Chat.GetMessages(
|
||||
ctx.Service,
|
||||
msgs := ctx.Service.GetMessages(
|
||||
ctx.Service.Channels[ctx.View.Channels.SelectedChannel],
|
||||
ctx.View.Chat.GetMaxItems(),
|
||||
)
|
||||
|
||||
var strMsgs []string
|
||||
for _, msg := range msgs {
|
||||
strMsgs = append(strMsgs, msg.ToString())
|
||||
}
|
||||
|
||||
ctx.View.Chat.SetMessages(strMsgs)
|
||||
|
||||
termui.Render(ctx.View.Chat)
|
||||
}
|
||||
|
||||
// actionMoveCursorUpChannels will execute the actionChangeChannel
|
||||
// function. A time is implemented to support fast scrolling through
|
||||
// the list without executing the actionChangeChannel event
|
||||
func actionMoveCursorUpChannels(ctx *context.AppContext) {
|
||||
go func() {
|
||||
if timer != nil {
|
||||
@ -275,10 +293,14 @@ func actionMoveCursorUpChannels(ctx *context.AppContext) {
|
||||
timer = time.NewTimer(time.Second / 4)
|
||||
<-timer.C
|
||||
|
||||
// Only actually change channel when the timer expires
|
||||
actionChangeChannel(ctx)
|
||||
}()
|
||||
}
|
||||
|
||||
// actionMoveCursorDownChannels will execute the actionChangeChannel
|
||||
// function. A time is implemented to support fast scrolling through
|
||||
// the list without executing the actionChangeChannel event
|
||||
func actionMoveCursorDownChannels(ctx *context.AppContext) {
|
||||
go func() {
|
||||
if timer != nil {
|
||||
@ -291,6 +313,7 @@ func actionMoveCursorDownChannels(ctx *context.AppContext) {
|
||||
timer = time.NewTimer(time.Second / 4)
|
||||
<-timer.C
|
||||
|
||||
// Only actually change channel when the timer expires
|
||||
actionChangeChannel(ctx)
|
||||
}()
|
||||
}
|
||||
@ -305,35 +328,58 @@ func actionMoveCursorBottomChannels(ctx *context.AppContext) {
|
||||
actionChangeChannel(ctx)
|
||||
}
|
||||
|
||||
func actionSearchNextChannels(ctx *context.AppContext) {
|
||||
ctx.View.Channels.SearchNext()
|
||||
actionChangeChannel(ctx)
|
||||
}
|
||||
|
||||
func actionSearchPrevChannels(ctx *context.AppContext) {
|
||||
ctx.View.Channels.SearchPrev()
|
||||
actionChangeChannel(ctx)
|
||||
}
|
||||
|
||||
func actionChangeChannel(ctx *context.AppContext) {
|
||||
// Clear messages from Chat pane
|
||||
ctx.View.Chat.ClearMessages()
|
||||
|
||||
// Get message for the new channel
|
||||
ctx.View.Chat.GetMessages(
|
||||
ctx.Service,
|
||||
ctx.Service.SlackChannels[ctx.View.Channels.SelectedChannel],
|
||||
// Get messages of the SelectedChannel, and get the count of messages
|
||||
// that fit into the Chat component
|
||||
msgs := ctx.Service.GetMessages(
|
||||
ctx.Service.GetSlackChannel(ctx.View.Channels.SelectedChannel),
|
||||
ctx.View.Chat.GetMaxItems(),
|
||||
)
|
||||
|
||||
var strMsgs []string
|
||||
for _, msg := range msgs {
|
||||
strMsgs = append(strMsgs, msg.ToString())
|
||||
}
|
||||
|
||||
// Set messages for the channel
|
||||
ctx.View.Chat.SetMessages(strMsgs)
|
||||
|
||||
// Set channel name for the Chat pane
|
||||
ctx.View.Chat.SetBorderLabel(
|
||||
ctx.Service.Channels[ctx.View.Channels.SelectedChannel],
|
||||
ctx.Service.Channels[ctx.View.Channels.SelectedChannel].GetChannelName(),
|
||||
)
|
||||
|
||||
// Set read mark
|
||||
ctx.View.Channels.SetReadMark(ctx.Service)
|
||||
// Clear notification icon if there is any
|
||||
ctx.Service.MarkAsRead(ctx.View.Channels.SelectedChannel)
|
||||
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
||||
|
||||
termui.Render(ctx.View.Channels)
|
||||
termui.Render(ctx.View.Chat)
|
||||
}
|
||||
|
||||
func actionNewMessage(ctx *context.AppContext, channelID string) {
|
||||
ctx.View.Channels.SetNotification(ctx.Service, channelID)
|
||||
ctx.Service.MarkAsUnread(channelID)
|
||||
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
||||
termui.Render(ctx.View.Channels)
|
||||
fmt.Print("\a")
|
||||
}
|
||||
|
||||
func actionSetPresence(ctx *context.AppContext, channelID string, presence string) {
|
||||
ctx.View.Channels.SetPresence(ctx.Service, channelID, presence)
|
||||
ctx.Service.SetPresenceChannelEvent(channelID, presence)
|
||||
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
||||
termui.Render(ctx.View.Channels)
|
||||
}
|
||||
|
||||
|
29
main.go
29
main.go
@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
VERSION = "v0.2.3"
|
||||
VERSION = "v0.3.0"
|
||||
USAGE = `NAME:
|
||||
slack-term - slack client for your terminal
|
||||
|
||||
@ -33,6 +33,7 @@ GLOBAL OPTIONS:
|
||||
|
||||
var (
|
||||
flgConfig string
|
||||
flgDebug bool
|
||||
flgUsage bool
|
||||
)
|
||||
|
||||
@ -51,6 +52,13 @@ func init() {
|
||||
"location of config file",
|
||||
)
|
||||
|
||||
flag.BoolVar(
|
||||
&flgDebug,
|
||||
"debug",
|
||||
false,
|
||||
"turn on debugging",
|
||||
)
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Printf(USAGE, VERSION)
|
||||
}
|
||||
@ -76,30 +84,13 @@ func main() {
|
||||
termui.DefaultEvtStream = customEvtStream
|
||||
|
||||
// Create context
|
||||
ctx, err := context.CreateAppContext(flgConfig)
|
||||
ctx, err := context.CreateAppContext(flgConfig, flgDebug)
|
||||
if err != nil {
|
||||
termbox.Close()
|
||||
log.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Setup body
|
||||
termui.Body.AddRows(
|
||||
termui.NewRow(
|
||||
termui.NewCol(ctx.Config.SidebarWidth, 0, ctx.View.Channels),
|
||||
termui.NewCol(ctx.Config.MainWidth, 0, ctx.View.Chat),
|
||||
),
|
||||
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)
|
||||
|
||||
// Set body in context
|
||||
ctx.Body = termui.Body
|
||||
|
||||
// Register handlers
|
||||
handlers.RegisterEventHandlers(ctx)
|
||||
|
||||
|
269
service/slack.go
269
service/slack.go
@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/nlopes/slack"
|
||||
|
||||
"github.com/erroneousboat/slack-term/components"
|
||||
"github.com/erroneousboat/slack-term/config"
|
||||
)
|
||||
|
||||
@ -20,27 +22,22 @@ const (
|
||||
)
|
||||
|
||||
type SlackService struct {
|
||||
Client *slack.Client
|
||||
RTM *slack.RTM
|
||||
SlackChannels []interface{}
|
||||
Channels []Channel
|
||||
UserCache map[string]string
|
||||
CurrentUserID string
|
||||
}
|
||||
|
||||
type Channel struct {
|
||||
ID string
|
||||
Name string
|
||||
Topic string
|
||||
Type string
|
||||
UserID string
|
||||
Config *config.Config
|
||||
Client *slack.Client
|
||||
RTM *slack.RTM
|
||||
SlackChannels []interface{}
|
||||
Channels []components.ChannelItem
|
||||
UserCache map[string]string
|
||||
CurrentUserID string
|
||||
CurrentUsername string
|
||||
}
|
||||
|
||||
// NewSlackService is the constructor for the SlackService and will initialize
|
||||
// the RTM and a Client
|
||||
func NewSlackService(token string) *SlackService {
|
||||
func NewSlackService(config *config.Config) (*SlackService, error) {
|
||||
svc := &SlackService{
|
||||
Client: slack.New(token),
|
||||
Config: config,
|
||||
Client: slack.New(config.SlackToken),
|
||||
UserCache: make(map[string]string),
|
||||
}
|
||||
|
||||
@ -49,7 +46,7 @@ func NewSlackService(token string) *SlackService {
|
||||
// arrives
|
||||
authTest, err := svc.Client.AuthTest()
|
||||
if err != nil {
|
||||
log.Fatal("ERROR: not able to authorize client, check your connection and/or slack-token")
|
||||
return nil, errors.New("not able to authorize client, check your connection and or slack-token")
|
||||
}
|
||||
svc.CurrentUserID = authTest.UserID
|
||||
|
||||
@ -67,48 +64,64 @@ func NewSlackService(token string) *SlackService {
|
||||
}
|
||||
}
|
||||
|
||||
return svc
|
||||
// Get name of current user
|
||||
currentUser, err := svc.Client.GetUserInfo(svc.CurrentUserID)
|
||||
if err != nil {
|
||||
svc.CurrentUsername = "slack-term"
|
||||
}
|
||||
svc.CurrentUsername = currentUser.Name
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
// GetChannels will retrieve all available channels, groups, and im channels.
|
||||
// Because the channels are of different types, we will append them to
|
||||
// an []interface as well as to a []Channel which will give us easy access
|
||||
// to the id and name of the Channel.
|
||||
func (s *SlackService) GetChannels() []Channel {
|
||||
var chans []Channel
|
||||
func (s *SlackService) GetChannels() []string {
|
||||
var chans []components.ChannelItem
|
||||
|
||||
// Channel
|
||||
slackChans, err := s.Client.GetChannels(true)
|
||||
if err != nil {
|
||||
chans = append(chans, Channel{})
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
|
||||
for _, chn := range slackChans {
|
||||
s.SlackChannels = append(s.SlackChannels, chn)
|
||||
chans = append(
|
||||
chans, Channel{
|
||||
ID: chn.ID,
|
||||
Name: chn.Name,
|
||||
Topic: chn.Topic.Value,
|
||||
Type: ChannelTypeChannel,
|
||||
UserID: "",
|
||||
},
|
||||
)
|
||||
if chn.IsMember {
|
||||
s.SlackChannels = append(s.SlackChannels, chn)
|
||||
chans = append(
|
||||
chans, components.ChannelItem{
|
||||
ID: chn.ID,
|
||||
Name: chn.Name,
|
||||
Topic: chn.Topic.Value,
|
||||
Type: components.ChannelTypeChannel,
|
||||
UserID: "",
|
||||
StylePrefix: s.Config.Theme.Channel.Prefix,
|
||||
StyleIcon: s.Config.Theme.Channel.Icon,
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Groups
|
||||
slackGroups, err := s.Client.GetGroups(true)
|
||||
if err != nil {
|
||||
chans = append(chans, Channel{})
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
for _, grp := range slackGroups {
|
||||
s.SlackChannels = append(s.SlackChannels, grp)
|
||||
chans = append(
|
||||
chans, Channel{
|
||||
ID: grp.ID,
|
||||
Name: grp.Name,
|
||||
Topic: grp.Topic.Value,
|
||||
Type: ChannelTypeGroup,
|
||||
UserID: "",
|
||||
chans, components.ChannelItem{
|
||||
ID: grp.ID,
|
||||
Name: grp.Name,
|
||||
Topic: grp.Topic.Value,
|
||||
Type: components.ChannelTypeGroup,
|
||||
UserID: "",
|
||||
StylePrefix: s.Config.Theme.Channel.Prefix,
|
||||
StyleIcon: s.Config.Theme.Channel.Icon,
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -116,10 +129,13 @@ func (s *SlackService) GetChannels() []Channel {
|
||||
// IM
|
||||
slackIM, err := s.Client.GetIMChannels()
|
||||
if err != nil {
|
||||
chans = append(chans, Channel{})
|
||||
chans = append(chans, components.ChannelItem{})
|
||||
}
|
||||
for _, im := range slackIM {
|
||||
|
||||
// FIXME: err
|
||||
presence, _ := s.GetUserPresence(im.User)
|
||||
|
||||
// Uncover name, when we can't uncover name for
|
||||
// IM channel this is then probably a deleted
|
||||
// user, because we won't add deleted users
|
||||
@ -129,12 +145,16 @@ func (s *SlackService) GetChannels() []Channel {
|
||||
if ok {
|
||||
chans = append(
|
||||
chans,
|
||||
Channel{
|
||||
ID: im.ID,
|
||||
Name: name,
|
||||
Topic: "",
|
||||
Type: ChannelTypeIM,
|
||||
UserID: im.User,
|
||||
components.ChannelItem{
|
||||
ID: im.ID,
|
||||
Name: name,
|
||||
Topic: "",
|
||||
Type: components.ChannelTypeIM,
|
||||
UserID: im.User,
|
||||
Presence: presence,
|
||||
StylePrefix: s.Config.Theme.Channel.Prefix,
|
||||
StyleIcon: s.Config.Theme.Channel.Icon,
|
||||
StyleText: s.Config.Theme.Channel.Text,
|
||||
},
|
||||
)
|
||||
s.SlackChannels = append(s.SlackChannels, im)
|
||||
@ -143,7 +163,38 @@ func (s *SlackService) GetChannels() []Channel {
|
||||
|
||||
s.Channels = chans
|
||||
|
||||
return chans
|
||||
var channels []string
|
||||
for _, chn := range s.Channels {
|
||||
channels = append(channels, chn.ToString())
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
// ChannelsToString will relay the string representation for a channel
|
||||
func (s *SlackService) ChannelsToString() []string {
|
||||
var channels []string
|
||||
for _, chn := range s.Channels {
|
||||
channels = append(channels, chn.ToString())
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
// SetPresenceChannelEvent will set the presence of a IM channel
|
||||
func (s *SlackService) SetPresenceChannelEvent(userID string, presence string) {
|
||||
// Get the correct Channel from svc.Channels
|
||||
var index int
|
||||
for i, channel := range s.Channels {
|
||||
if userID == channel.UserID {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
s.Channels[index].Presence = presence
|
||||
}
|
||||
|
||||
// GetSlackChannel returns the representation of a slack channel
|
||||
func (s *SlackService) GetSlackChannel(selectedChannel int) interface{} {
|
||||
return s.SlackChannels[selectedChannel]
|
||||
}
|
||||
|
||||
// GetUserPresence will get the presence of a specific user
|
||||
@ -178,20 +229,61 @@ func (s *SlackService) SetChannelReadMark(channel interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAsRead will set the channel as read
|
||||
func (s *SlackService) MarkAsRead(channelID int) {
|
||||
channel := s.Channels[channelID]
|
||||
|
||||
if channel.Notification {
|
||||
s.Channels[channelID].Notification = false
|
||||
|
||||
switch channel.Type {
|
||||
case ChannelTypeChannel:
|
||||
s.Client.SetChannelReadMark(
|
||||
channel.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
case ChannelTypeGroup:
|
||||
s.Client.SetGroupReadMark(
|
||||
channel.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
case ChannelTypeIM:
|
||||
s.Client.MarkIMChannel(
|
||||
channel.ID, fmt.Sprintf("%f",
|
||||
float64(time.Now().Unix())),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAsUnread will set the channel as unread
|
||||
func (s *SlackService) MarkAsUnread(channelID string) {
|
||||
var index int
|
||||
for i, channel := range s.Channels {
|
||||
if channel.ID == channelID {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
s.Channels[index].Notification = true
|
||||
}
|
||||
|
||||
// SendMessage will send a message to a particular channel
|
||||
func (s *SlackService) SendMessage(channel string, message string) {
|
||||
func (s *SlackService) SendMessage(channelID int, message string) {
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#PostMessageParameters
|
||||
postParams := slack.PostMessageParameters{
|
||||
AsUser: true,
|
||||
AsUser: true,
|
||||
Username: s.CurrentUsername,
|
||||
}
|
||||
|
||||
// https://godoc.org/github.com/nlopes/slack#Client.PostMessage
|
||||
s.Client.PostMessage(channel, message, postParams)
|
||||
s.Client.PostMessage(s.Channels[channelID].ID, message, postParams)
|
||||
}
|
||||
|
||||
// GetMessages will get messages for a channel, group or im channel delimited
|
||||
// by a count.
|
||||
func (s *SlackService) GetMessages(channel interface{}, count int) []string {
|
||||
func (s *SlackService) GetMessages(channel interface{}, count int) []components.Message {
|
||||
// https://api.slack.com/methods/channels.history
|
||||
historyParams := slack.HistoryParameters{
|
||||
Count: count,
|
||||
@ -221,7 +313,7 @@ func (s *SlackService) GetMessages(channel interface{}, count int) []string {
|
||||
}
|
||||
|
||||
// Construct the messages
|
||||
var messages []string
|
||||
var messages []components.Message
|
||||
for _, message := range history.Messages {
|
||||
msg := s.CreateMessage(message)
|
||||
messages = append(messages, msg...)
|
||||
@ -229,7 +321,7 @@ func (s *SlackService) GetMessages(channel interface{}, count int) []string {
|
||||
|
||||
// Reverse the order of the messages, we want the newest in
|
||||
// the last place
|
||||
var messagesReversed []string
|
||||
var messagesReversed []components.Message
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
messagesReversed = append(messagesReversed, messages[i])
|
||||
}
|
||||
@ -244,8 +336,8 @@ func (s *SlackService) GetMessages(channel interface{}, count int) []string {
|
||||
//
|
||||
// This returns an array of string because we will try to uncover attachments
|
||||
// associated with messages.
|
||||
func (s *SlackService) CreateMessage(message slack.Message) []string {
|
||||
var msgs []string
|
||||
func (s *SlackService) CreateMessage(message slack.Message) []components.Message {
|
||||
var msgs []components.Message
|
||||
var name string
|
||||
|
||||
// Get username from cache
|
||||
@ -280,7 +372,7 @@ func (s *SlackService) CreateMessage(message slack.Message) []string {
|
||||
|
||||
// When there are attachments append them
|
||||
if len(message.Attachments) > 0 {
|
||||
msgs = append(msgs, createMessageFromAttachments(message.Attachments)...)
|
||||
msgs = append(msgs, s.CreateMessageFromAttachments(message.Attachments)...)
|
||||
}
|
||||
|
||||
// Parse time
|
||||
@ -291,21 +383,23 @@ func (s *SlackService) CreateMessage(message slack.Message) []string {
|
||||
intTime := int64(floatTime)
|
||||
|
||||
// Format message
|
||||
msg := fmt.Sprintf(
|
||||
"[%s] <%s> %s",
|
||||
time.Unix(intTime, 0).Format("15:04"),
|
||||
name,
|
||||
parseMessage(s, message.Text),
|
||||
)
|
||||
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,
|
||||
}
|
||||
|
||||
msgs = append(msgs, msg)
|
||||
|
||||
return msgs
|
||||
}
|
||||
|
||||
func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent) []string {
|
||||
func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent) []components.Message {
|
||||
|
||||
var msgs []string
|
||||
var msgs []components.Message
|
||||
var name string
|
||||
|
||||
// Append (edited) when an edited message is received
|
||||
@ -346,7 +440,7 @@ func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent
|
||||
|
||||
// When there are attachments append them
|
||||
if len(message.Attachments) > 0 {
|
||||
msgs = append(msgs, createMessageFromAttachments(message.Attachments)...)
|
||||
msgs = append(msgs, s.CreateMessageFromAttachments(message.Attachments)...)
|
||||
}
|
||||
|
||||
// Parse time
|
||||
@ -357,12 +451,14 @@ func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent
|
||||
intTime := int64(floatTime)
|
||||
|
||||
// Format message
|
||||
msg := fmt.Sprintf(
|
||||
"[%s] <%s> %s",
|
||||
time.Unix(intTime, 0).Format("15:04"),
|
||||
name,
|
||||
parseMessage(s, message.Text),
|
||||
)
|
||||
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,
|
||||
}
|
||||
|
||||
msgs = append(msgs, msg)
|
||||
|
||||
@ -391,13 +487,14 @@ func parseMessage(s *SlackService, msg string) string {
|
||||
// <@U12345>
|
||||
func parseMentions(s *SlackService, msg string) string {
|
||||
r := regexp.MustCompile(`\<@(\w+\|*\w+)\>`)
|
||||
rs := r.FindStringSubmatch(msg)
|
||||
if len(rs) < 1 {
|
||||
return msg
|
||||
}
|
||||
|
||||
return r.ReplaceAllStringFunc(
|
||||
msg, func(str string) string {
|
||||
rs := r.FindStringSubmatch(str)
|
||||
if len(rs) < 1 {
|
||||
return str
|
||||
}
|
||||
|
||||
var userID string
|
||||
split := strings.Split(rs[1], "|")
|
||||
if len(split) > 0 {
|
||||
@ -443,27 +540,37 @@ func parseEmoji(msg string) string {
|
||||
)
|
||||
}
|
||||
|
||||
// createMessageFromAttachments will construct a array of string of the Field
|
||||
// CreateMessageFromAttachments will construct a array of string of the Field
|
||||
// values of Attachments from a Message.
|
||||
func createMessageFromAttachments(atts []slack.Attachment) []string {
|
||||
var msgs []string
|
||||
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,
|
||||
fmt.Sprintf(
|
||||
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,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if att.Text != "" {
|
||||
msgs = append(msgs, att.Text)
|
||||
msgs = append(
|
||||
msgs,
|
||||
components.Message{Content: fmt.Sprintf("%s", att.Text)},
|
||||
)
|
||||
}
|
||||
|
||||
if att.Title != "" {
|
||||
msgs = append(msgs, att.Title)
|
||||
msgs = append(
|
||||
msgs,
|
||||
components.Message{Content: fmt.Sprintf("%s", att.Title)},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
37
vendor/github.com/maruel/panicparse/stack/source.go
generated
vendored
37
vendor/github.com/maruel/panicparse/stack/source.go
generated
vendored
@ -53,7 +53,7 @@ func (c *cache) augmentGoroutine(goroutine *Goroutine) {
|
||||
}
|
||||
|
||||
// Once all loaded, we can look at the next call when available.
|
||||
for i := 1; i < len(goroutine.Stack.Calls); i++ {
|
||||
for i := 0; i < len(goroutine.Stack.Calls)-1; i++ {
|
||||
// Get the AST from the previous call and process the call line with it.
|
||||
if f := c.getFuncAST(&goroutine.Stack.Calls[i]); f != nil {
|
||||
processCall(&goroutine.Stack.Calls[i], f)
|
||||
@ -115,6 +115,15 @@ type parsedFile struct {
|
||||
// getFuncAST gets the callee site function AST representation for the code
|
||||
// inside the function f at line l.
|
||||
func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) {
|
||||
if len(p.lineToByteOffset) <= l {
|
||||
// The line number in the stack trace line does not exist in the file. That
|
||||
// can only mean that the sources on disk do not match the sources used to
|
||||
// build the binary.
|
||||
// TODO(maruel): This should be surfaced, so that source parsing is
|
||||
// completely ignored.
|
||||
return
|
||||
}
|
||||
|
||||
// Walk the AST to find the lineToByteOffset that fits the line number.
|
||||
var lastFunc *ast.FuncDecl
|
||||
var found ast.Node
|
||||
@ -155,20 +164,18 @@ func (p *parsedFile) getFuncAST(f string, l int) (d *ast.FuncDecl) {
|
||||
}
|
||||
|
||||
func name(n ast.Node) string {
|
||||
if _, ok := n.(*ast.InterfaceType); ok {
|
||||
switch t := n.(type) {
|
||||
case *ast.InterfaceType:
|
||||
return "interface{}"
|
||||
case *ast.Ident:
|
||||
return t.Name
|
||||
case *ast.SelectorExpr:
|
||||
return t.Sel.Name
|
||||
case *ast.StarExpr:
|
||||
return "*" + name(t.X)
|
||||
default:
|
||||
return "<unknown>"
|
||||
}
|
||||
if i, ok := n.(*ast.Ident); ok {
|
||||
return i.Name
|
||||
}
|
||||
if _, ok := n.(*ast.FuncType); ok {
|
||||
return "func"
|
||||
}
|
||||
if s, ok := n.(*ast.SelectorExpr); ok {
|
||||
return s.Sel.Name
|
||||
}
|
||||
// TODO(maruel): Implement anything missing.
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
// fieldToType returns the type name and whether if it's an ellipsis.
|
||||
@ -189,6 +196,10 @@ func fieldToType(f *ast.Field) (string, bool) {
|
||||
return arg.Sel.Name, false
|
||||
case *ast.StarExpr:
|
||||
return "*" + name(arg.X), false
|
||||
case *ast.MapType:
|
||||
return fmt.Sprintf("map[%s]%s", name(arg.Key), name(arg.Value)), false
|
||||
case *ast.ChanType:
|
||||
return fmt.Sprintf("chan %s", name(arg.Value)), false
|
||||
default:
|
||||
// TODO(maruel): Implement anything missing.
|
||||
return "<unknown>", false
|
||||
|
12
vendor/github.com/maruel/panicparse/stack/stack.go
generated
vendored
12
vendor/github.com/maruel/panicparse/stack/stack.go
generated
vendored
@ -35,7 +35,7 @@ var (
|
||||
// - found next stack barrier at 0x123; expected
|
||||
// - runtime: unexpected return pc for FUNC_NAME called from 0x123
|
||||
|
||||
reRoutineHeader = regexp.MustCompile("^goroutine (\\d+) \\[([^\\]]+)\\]\\:\n$")
|
||||
reRoutineHeader = regexp.MustCompile("^goroutine (\\d+) \\[([^\\]]+)\\]\\:\r?\n$")
|
||||
reMinutes = regexp.MustCompile("^(\\d+) minutes$")
|
||||
reUnavail = regexp.MustCompile("^(?:\t| +)goroutine running on other thread; stack unavailable")
|
||||
// See gentraceback() in src/runtime/traceback.go for more information.
|
||||
@ -54,12 +54,12 @@ var (
|
||||
// when a signal is not correctly handled. It is printed with m.throwing>0.
|
||||
// These are discarded.
|
||||
// - For cgo, the source file may be "??".
|
||||
reFile = regexp.MustCompile("^(?:\t| +)(\\?\\?|\\<autogenerated\\>|.+\\.(?:c|go|s))\\:(\\d+)(?:| \\+0x[0-9a-f]+)(?:| fp=0x[0-9a-f]+ sp=0x[0-9a-f]+)\n$")
|
||||
reFile = regexp.MustCompile("^(?:\t| +)(\\?\\?|\\<autogenerated\\>|.+\\.(?:c|go|s))\\:(\\d+)(?:| \\+0x[0-9a-f]+)(?:| fp=0x[0-9a-f]+ sp=0x[0-9a-f]+)\r?\n$")
|
||||
// Sadly, it doesn't note the goroutine number so we could cascade them per
|
||||
// parenthood.
|
||||
reCreated = regexp.MustCompile("^created by (.+)\n$")
|
||||
reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\n$")
|
||||
reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\n$")
|
||||
reCreated = regexp.MustCompile("^created by (.+)\r?\n$")
|
||||
reFunc = regexp.MustCompile("^(.+)\\((.*)\\)\r?\n$")
|
||||
reElided = regexp.MustCompile("^\\.\\.\\.additional frames elided\\.\\.\\.\r?\n$")
|
||||
// Include frequent GOROOT value on Windows, distro provided and user
|
||||
// installed path. This simplifies the user's life when processing a trace
|
||||
// generated on another VM.
|
||||
@ -656,7 +656,7 @@ func ParseDump(r io.Reader, out io.Writer) ([]Goroutine, error) {
|
||||
firstLine := false
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line == "\n" {
|
||||
if line == "\n" || line == "\r\n" {
|
||||
if goroutine != nil {
|
||||
goroutine = nil
|
||||
continue
|
||||
|
1
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
1
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
@ -55,6 +55,7 @@ var private = table{
|
||||
var nonprint = table{
|
||||
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
|
||||
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
|
||||
{0x2028, 0x2029},
|
||||
{0x202A, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
|
||||
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
|
||||
}
|
||||
|
12
vendor/github.com/nlopes/slack/CHANGELOG.md
generated
vendored
Normal file
12
vendor/github.com/nlopes/slack/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
### 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)
|
15
vendor/github.com/nlopes/slack/README.md
generated
vendored
15
vendor/github.com/nlopes/slack/README.md
generated
vendored
@ -5,14 +5,25 @@ This library supports most if not all of the `api.slack.com` REST
|
||||
calls, as well as the Real-Time Messaging protocol over websocket, in
|
||||
a fully managed way.
|
||||
|
||||
## Change log
|
||||
|
||||
Note: 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)
|
||||
### 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)
|
||||
|
||||
### CHANGELOG.md
|
||||
|
||||
As of this version a [CHANGELOG.md](https://github.com/nlopes/slack/blob/master/CHANGELOG.md) is available. Please visit it for updates.
|
||||
|
||||
## Installing
|
||||
|
||||
### *go get*
|
||||
|
||||
$ go get github.com/nlopes/slack
|
||||
$ go get -u github.com/nlopes/slack
|
||||
|
||||
## Example
|
||||
|
||||
|
88
vendor/github.com/nlopes/slack/admin.go
generated
vendored
88
vendor/github.com/nlopes/slack/admin.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
@ -11,9 +12,9 @@ type adminResponse struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func adminRequest(method string, teamName string, values url.Values, debug bool) (*adminResponse, error) {
|
||||
func adminRequest(ctx context.Context, method string, teamName string, values url.Values, debug bool) (*adminResponse, error) {
|
||||
adminResponse := &adminResponse{}
|
||||
err := parseAdminResponse(method, teamName, values, adminResponse, debug)
|
||||
err := parseAdminResponse(ctx, method, teamName, values, adminResponse, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -27,6 +28,11 @@ func adminRequest(method string, teamName string, values url.Values, debug bool)
|
||||
|
||||
// DisableUser disabled a user account, given a user ID
|
||||
func (api *Client) DisableUser(teamName string, uid string) error {
|
||||
return api.DisableUserContext(context.Background(), teamName, uid)
|
||||
}
|
||||
|
||||
// DisableUserContext disabled a user account, given a user ID with a custom context
|
||||
func (api *Client) DisableUserContext(ctx context.Context, teamName string, uid string) error {
|
||||
values := url.Values{
|
||||
"user": {uid},
|
||||
"token": {api.config.token},
|
||||
@ -34,7 +40,7 @@ func (api *Client) DisableUser(teamName string, uid string) error {
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
_, err := adminRequest("setInactive", teamName, values, api.debug)
|
||||
_, err := adminRequest(ctx, "setInactive", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to disable user with id '%s': %s", uid, err)
|
||||
}
|
||||
@ -43,13 +49,12 @@ func (api *Client) DisableUser(teamName string, uid string) error {
|
||||
}
|
||||
|
||||
// InviteGuest invites a user to Slack as a single-channel guest
|
||||
func (api *Client) InviteGuest(
|
||||
teamName string,
|
||||
channel string,
|
||||
firstName string,
|
||||
lastName string,
|
||||
emailAddress string,
|
||||
) error {
|
||||
func (api *Client) InviteGuest(teamName, channel, firstName, lastName, emailAddress string) error {
|
||||
return api.InviteGuestContext(context.Background(), teamName, channel, firstName, lastName, emailAddress)
|
||||
}
|
||||
|
||||
// InviteGuestContext invites a user to Slack as a single-channel guest with a custom context
|
||||
func (api *Client) InviteGuestContext(ctx context.Context, teamName, channel, firstName, lastName, emailAddress string) error {
|
||||
values := url.Values{
|
||||
"email": {emailAddress},
|
||||
"channels": {channel},
|
||||
@ -61,7 +66,7 @@ func (api *Client) InviteGuest(
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
_, err := adminRequest("invite", teamName, values, api.debug)
|
||||
_, err := adminRequest(ctx, "invite", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to invite single-channel guest: %s", err)
|
||||
}
|
||||
@ -70,13 +75,12 @@ func (api *Client) InviteGuest(
|
||||
}
|
||||
|
||||
// InviteRestricted invites a user to Slack as a restricted account
|
||||
func (api *Client) InviteRestricted(
|
||||
teamName string,
|
||||
channel string,
|
||||
firstName string,
|
||||
lastName string,
|
||||
emailAddress string,
|
||||
) error {
|
||||
func (api *Client) InviteRestricted(teamName, channel, firstName, lastName, emailAddress string) error {
|
||||
return api.InviteRestrictedContext(context.Background(), teamName, channel, firstName, lastName, emailAddress)
|
||||
}
|
||||
|
||||
// InviteRestrictedContext invites a user to Slack as a restricted account with a custom context
|
||||
func (api *Client) InviteRestrictedContext(ctx context.Context, teamName, channel, firstName, lastName, emailAddress string) error {
|
||||
values := url.Values{
|
||||
"email": {emailAddress},
|
||||
"channels": {channel},
|
||||
@ -88,7 +92,7 @@ func (api *Client) InviteRestricted(
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
_, err := adminRequest("invite", teamName, values, api.debug)
|
||||
_, err := adminRequest(ctx, "invite", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to restricted account: %s", err)
|
||||
}
|
||||
@ -97,12 +101,12 @@ func (api *Client) InviteRestricted(
|
||||
}
|
||||
|
||||
// InviteToTeam invites a user to a Slack team
|
||||
func (api *Client) InviteToTeam(
|
||||
teamName string,
|
||||
firstName string,
|
||||
lastName string,
|
||||
emailAddress string,
|
||||
) error {
|
||||
func (api *Client) InviteToTeam(teamName, firstName, lastName, emailAddress string) error {
|
||||
return api.InviteToTeamContext(context.Background(), teamName, firstName, lastName, emailAddress)
|
||||
}
|
||||
|
||||
// InviteToTeamContext invites a user to a Slack team with a custom context
|
||||
func (api *Client) InviteToTeamContext(ctx context.Context, teamName, firstName, lastName, emailAddress string) error {
|
||||
values := url.Values{
|
||||
"email": {emailAddress},
|
||||
"first_name": {firstName},
|
||||
@ -112,7 +116,7 @@ func (api *Client) InviteToTeam(
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
_, err := adminRequest("invite", teamName, values, api.debug)
|
||||
_, err := adminRequest(ctx, "invite", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to invite to team: %s", err)
|
||||
}
|
||||
@ -121,7 +125,12 @@ func (api *Client) InviteToTeam(
|
||||
}
|
||||
|
||||
// SetRegular enables the specified user
|
||||
func (api *Client) SetRegular(teamName string, user string) error {
|
||||
func (api *Client) SetRegular(teamName, user string) error {
|
||||
return api.SetRegularContext(context.Background(), teamName, user)
|
||||
}
|
||||
|
||||
// SetRegularContext enables the specified user with a custom context
|
||||
func (api *Client) SetRegularContext(ctx context.Context, teamName, user string) error {
|
||||
values := url.Values{
|
||||
"user": {user},
|
||||
"token": {api.config.token},
|
||||
@ -129,7 +138,7 @@ func (api *Client) SetRegular(teamName string, user string) error {
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
_, err := adminRequest("setRegular", teamName, values, api.debug)
|
||||
_, err := adminRequest(ctx, "setRegular", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to change the user (%s) to a regular user: %s", user, err)
|
||||
}
|
||||
@ -138,7 +147,12 @@ func (api *Client) SetRegular(teamName string, user string) error {
|
||||
}
|
||||
|
||||
// SendSSOBindingEmail sends an SSO binding email to the specified user
|
||||
func (api *Client) SendSSOBindingEmail(teamName string, user string) error {
|
||||
func (api *Client) SendSSOBindingEmail(teamName, user string) error {
|
||||
return api.SendSSOBindingEmailContext(context.Background(), teamName, user)
|
||||
}
|
||||
|
||||
// SendSSOBindingEmailContext sends an SSO binding email to the specified user with a custom context
|
||||
func (api *Client) SendSSOBindingEmailContext(ctx context.Context, teamName, user string) error {
|
||||
values := url.Values{
|
||||
"user": {user},
|
||||
"token": {api.config.token},
|
||||
@ -146,7 +160,7 @@ func (api *Client) SendSSOBindingEmail(teamName string, user string) error {
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
_, err := adminRequest("sendSSOBind", teamName, values, api.debug)
|
||||
_, err := adminRequest(ctx, "sendSSOBind", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to send SSO binding email for user (%s): %s", user, err)
|
||||
}
|
||||
@ -156,6 +170,11 @@ func (api *Client) SendSSOBindingEmail(teamName string, user string) error {
|
||||
|
||||
// SetUltraRestricted converts a user into a single-channel guest
|
||||
func (api *Client) SetUltraRestricted(teamName, uid, channel string) error {
|
||||
return api.SetUltraRestrictedContext(context.Background(), teamName, uid, channel)
|
||||
}
|
||||
|
||||
// SetUltraRestrictedContext converts a user into a single-channel guest with a custom context
|
||||
func (api *Client) SetUltraRestrictedContext(ctx context.Context, teamName, uid, channel string) error {
|
||||
values := url.Values{
|
||||
"user": {uid},
|
||||
"channel": {channel},
|
||||
@ -164,7 +183,7 @@ func (api *Client) SetUltraRestricted(teamName, uid, channel string) error {
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
_, err := adminRequest("setUltraRestricted", teamName, values, api.debug)
|
||||
_, err := adminRequest(ctx, "setUltraRestricted", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to ultra-restrict account: %s", err)
|
||||
}
|
||||
@ -174,6 +193,11 @@ func (api *Client) SetUltraRestricted(teamName, uid, channel string) error {
|
||||
|
||||
// SetRestricted converts a user into a restricted account
|
||||
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) error {
|
||||
values := url.Values{
|
||||
"user": {uid},
|
||||
"token": {api.config.token},
|
||||
@ -181,7 +205,7 @@ func (api *Client) SetRestricted(teamName, uid string) error {
|
||||
"_attempts": {"1"},
|
||||
}
|
||||
|
||||
_, err := adminRequest("setRestricted", teamName, values, api.debug)
|
||||
_, err := adminRequest(ctx, "setRestricted", teamName, values, api.debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to restrict account: %s", err)
|
||||
}
|
||||
|
34
vendor/github.com/nlopes/slack/attachments.go
generated
vendored
34
vendor/github.com/nlopes/slack/attachments.go
generated
vendored
@ -10,16 +10,34 @@ type AttachmentField struct {
|
||||
Short bool `json:"short"`
|
||||
}
|
||||
|
||||
// AttachmentAction is a button to be included in the attachment. Required when
|
||||
// using message buttons and otherwise not useful. A maximum of 5 actions may be
|
||||
// AttachmentAction is a button or menu to be included in the attachment. Required when
|
||||
// using message buttons or menus and otherwise not useful. A maximum of 5 actions may be
|
||||
// provided per attachment.
|
||||
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 string `json:"type"` // Required. Must be set to "button"
|
||||
Value string `json:"value,omitempty"` // Optional.
|
||||
Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
|
||||
Name string `json:"name"` // Required.
|
||||
Text string `json:"text"` // Required.
|
||||
Style string `json:"style,omitempty"` // Optional. Allowed values: "default", "primary", "danger".
|
||||
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.
|
||||
Options []AttachmentActionOption `json:"options,omitempty"` // Optional. Maximum of 100 options can be provided in each menu.
|
||||
SelectedOptions []AttachmentActionOption `json:"selected_options,omitempty"` // Optional. The first element of this array will be set as the pre-selected option for this menu.
|
||||
OptionGroups []AttachmentActionOptionGroup `json:"option_groups,omitempty"` // Optional.
|
||||
Confirm *ConfirmationField `json:"confirm,omitempty"` // Optional.
|
||||
}
|
||||
|
||||
// AttachmentActionOption the individual option to appear in action menu.
|
||||
type AttachmentActionOption struct {
|
||||
Text string `json:"text"` // Required.
|
||||
Value string `json:"value"` // Required.
|
||||
Description string `json:"description,omitempty"` // Optional. Up to 30 characters.
|
||||
}
|
||||
|
||||
// AttachmentActionOptionGroup is a semi-hierarchal way to list available options to appear in action menu.
|
||||
type AttachmentActionOptionGroup struct {
|
||||
Text string `json:"text"` // Required.
|
||||
Options []AttachmentActionOption `json:"options"` // Required.
|
||||
}
|
||||
|
||||
// AttachmentActionCallback is sent from Slack when a user clicks a button in an interactive message (aka AttachmentAction)
|
||||
|
12
vendor/github.com/nlopes/slack/bots.go
generated
vendored
12
vendor/github.com/nlopes/slack/bots.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
@ -18,9 +19,9 @@ type botResponseFull struct {
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
func botRequest(path string, values url.Values, debug bool) (*botResponseFull, error) {
|
||||
func botRequest(ctx context.Context, path string, values url.Values, debug bool) (*botResponseFull, error) {
|
||||
response := &botResponseFull{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -32,11 +33,16 @@ func botRequest(path string, values url.Values, debug bool) (*botResponseFull, e
|
||||
|
||||
// GetBotInfo will retrieve the complete bot information
|
||||
func (api *Client) GetBotInfo(bot string) (*Bot, error) {
|
||||
return api.GetBotInfoContext(context.Background(), bot)
|
||||
}
|
||||
|
||||
// GetBotInfoContext will retrieve the complete bot information using a custom context
|
||||
func (api *Client) GetBotInfoContext(ctx context.Context, bot string) (*Bot, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"bot": {bot},
|
||||
}
|
||||
response, err := botRequest("bots.info", values, api.debug)
|
||||
response, err := botRequest(ctx, "bots.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
127
vendor/github.com/nlopes/slack/channels.go
generated
vendored
127
vendor/github.com/nlopes/slack/channels.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -24,9 +25,9 @@ type Channel struct {
|
||||
IsMember bool `json:"is_member"`
|
||||
}
|
||||
|
||||
func channelRequest(path string, values url.Values, debug bool) (*channelResponseFull, error) {
|
||||
func channelRequest(ctx context.Context, path string, values url.Values, debug bool) (*channelResponseFull, error) {
|
||||
response := &channelResponseFull{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -38,11 +39,16 @@ func channelRequest(path string, values url.Values, debug bool) (*channelRespons
|
||||
|
||||
// ArchiveChannel archives the given channel
|
||||
func (api *Client) ArchiveChannel(channel string) error {
|
||||
return api.ArchiveChannelContext(context.Background(), channel)
|
||||
}
|
||||
|
||||
// ArchiveChannelContext archives the given channel with a custom context
|
||||
func (api *Client) ArchiveChannelContext(ctx context.Context, channel string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
}
|
||||
_, err := channelRequest("channels.archive", values, api.debug)
|
||||
_, err := channelRequest(ctx, "channels.archive", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -51,11 +57,16 @@ func (api *Client) ArchiveChannel(channel string) error {
|
||||
|
||||
// UnarchiveChannel unarchives the given channel
|
||||
func (api *Client) UnarchiveChannel(channel string) error {
|
||||
return api.UnarchiveChannelContext(context.Background(), channel)
|
||||
}
|
||||
|
||||
// UnarchiveChannelContext unarchives the given channel with a custom context
|
||||
func (api *Client) UnarchiveChannelContext(ctx context.Context, channel string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
}
|
||||
_, err := channelRequest("channels.unarchive", values, api.debug)
|
||||
_, err := channelRequest(ctx, "channels.unarchive", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -64,11 +75,16 @@ func (api *Client) UnarchiveChannel(channel string) error {
|
||||
|
||||
// CreateChannel creates a channel with the given name and returns a *Channel
|
||||
func (api *Client) CreateChannel(channel string) (*Channel, error) {
|
||||
return api.CreateChannelContext(context.Background(), channel)
|
||||
}
|
||||
|
||||
// CreateChannelContext creates a channel with the given name and returns a *Channel with a custom context
|
||||
func (api *Client) CreateChannelContext(ctx context.Context, channel string) (*Channel, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"name": {channel},
|
||||
}
|
||||
response, err := channelRequest("channels.create", values, api.debug)
|
||||
response, err := channelRequest(ctx, "channels.create", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -77,6 +93,11 @@ func (api *Client) CreateChannel(channel string) (*Channel, error) {
|
||||
|
||||
// GetChannelHistory retrieves the channel history
|
||||
func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (*History, error) {
|
||||
return api.GetChannelHistoryContext(context.Background(), channel, params)
|
||||
}
|
||||
|
||||
// GetChannelHistoryContext retrieves the channel history with a custom context
|
||||
func (api *Client) GetChannelHistoryContext(ctx context.Context, channel string, params HistoryParameters) (*History, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
@ -104,7 +125,7 @@ func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (
|
||||
values.Add("unreads", "0")
|
||||
}
|
||||
}
|
||||
response, err := channelRequest("channels.history", values, api.debug)
|
||||
response, err := channelRequest(ctx, "channels.history", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -113,11 +134,16 @@ func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (
|
||||
|
||||
// GetChannelInfo retrieves the given channel
|
||||
func (api *Client) GetChannelInfo(channel string) (*Channel, error) {
|
||||
return api.GetChannelInfoContext(context.Background(), channel)
|
||||
}
|
||||
|
||||
// GetChannelInfoContext retrieves the given channel with a custom context
|
||||
func (api *Client) GetChannelInfoContext(ctx context.Context, channel string) (*Channel, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
}
|
||||
response, err := channelRequest("channels.info", values, api.debug)
|
||||
response, err := channelRequest(ctx, "channels.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -126,12 +152,17 @@ func (api *Client) GetChannelInfo(channel string) (*Channel, error) {
|
||||
|
||||
// InviteUserToChannel invites a user to a given channel and returns a *Channel
|
||||
func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) {
|
||||
return api.InviteUserToChannelContext(context.Background(), channel, user)
|
||||
}
|
||||
|
||||
// InviteUserToChannelCustom invites a user to a given channel and returns a *Channel with a custom context
|
||||
func (api *Client) InviteUserToChannelContext(ctx context.Context, channel, user string) (*Channel, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"user": {user},
|
||||
}
|
||||
response, err := channelRequest("channels.invite", values, api.debug)
|
||||
response, err := channelRequest(ctx, "channels.invite", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -140,11 +171,16 @@ func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) {
|
||||
|
||||
// JoinChannel joins the currently authenticated user to a channel
|
||||
func (api *Client) JoinChannel(channel string) (*Channel, error) {
|
||||
return api.JoinChannelContext(context.Background(), channel)
|
||||
}
|
||||
|
||||
// JoinChannelContext joins the currently authenticated user to a channel with a custom context
|
||||
func (api *Client) JoinChannelContext(ctx context.Context, channel string) (*Channel, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"name": {channel},
|
||||
}
|
||||
response, err := channelRequest("channels.join", values, api.debug)
|
||||
response, err := channelRequest(ctx, "channels.join", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -153,11 +189,16 @@ func (api *Client) JoinChannel(channel string) (*Channel, error) {
|
||||
|
||||
// LeaveChannel makes the authenticated user leave the given channel
|
||||
func (api *Client) LeaveChannel(channel string) (bool, error) {
|
||||
return api.LeaveChannelContext(context.Background(), channel)
|
||||
}
|
||||
|
||||
// LeaveChannelContext makes the authenticated user leave the given channel with a custom context
|
||||
func (api *Client) LeaveChannelContext(ctx context.Context, channel string) (bool, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
}
|
||||
response, err := channelRequest("channels.leave", values, api.debug)
|
||||
response, err := channelRequest(ctx, "channels.leave", values, api.debug)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -169,12 +210,17 @@ func (api *Client) LeaveChannel(channel string) (bool, error) {
|
||||
|
||||
// KickUserFromChannel kicks a user from a given channel
|
||||
func (api *Client) KickUserFromChannel(channel, user string) error {
|
||||
return api.KickUserFromChannelContext(context.Background(), channel, user)
|
||||
}
|
||||
|
||||
// KickUserFromChannelContext kicks a user from a given channel with a custom context
|
||||
func (api *Client) KickUserFromChannelContext(ctx context.Context, channel, user string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"user": {user},
|
||||
}
|
||||
_, err := channelRequest("channels.kick", values, api.debug)
|
||||
_, err := channelRequest(ctx, "channels.kick", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -183,13 +229,18 @@ func (api *Client) KickUserFromChannel(channel, user string) error {
|
||||
|
||||
// GetChannels retrieves all the channels
|
||||
func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
|
||||
return api.GetChannelsContext(context.Background(), excludeArchived)
|
||||
}
|
||||
|
||||
// GetChannelsContext retrieves all the channels with a custom context
|
||||
func (api *Client) GetChannelsContext(ctx context.Context, excludeArchived bool) ([]Channel, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
if excludeArchived {
|
||||
values.Add("exclude_archived", "1")
|
||||
}
|
||||
response, err := channelRequest("channels.list", values, api.debug)
|
||||
response, err := channelRequest(ctx, "channels.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -202,12 +253,18 @@ func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
|
||||
// (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A
|
||||
// timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
|
||||
func (api *Client) SetChannelReadMark(channel, ts string) error {
|
||||
return api.SetChannelReadMarkContext(context.Background(), channel, ts)
|
||||
}
|
||||
|
||||
// SetChannelReadMarkContext sets the read mark of a given channel to a specific point with a custom context
|
||||
// For more details see SetChannelReadMark documentation
|
||||
func (api *Client) SetChannelReadMarkContext(ctx context.Context, channel, ts string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"ts": {ts},
|
||||
}
|
||||
_, err := channelRequest("channels.mark", values, api.debug)
|
||||
_, err := channelRequest(ctx, "channels.mark", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -216,6 +273,11 @@ func (api *Client) SetChannelReadMark(channel, ts string) error {
|
||||
|
||||
// RenameChannel renames a given channel
|
||||
func (api *Client) RenameChannel(channel, name string) (*Channel, error) {
|
||||
return api.RenameChannelContext(context.Background(), channel, name)
|
||||
}
|
||||
|
||||
// RenameChannelContext renames a given channel with a custom context
|
||||
func (api *Client) RenameChannelContext(ctx context.Context, channel, name string) (*Channel, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
@ -223,23 +285,26 @@ func (api *Client) RenameChannel(channel, name string) (*Channel, error) {
|
||||
}
|
||||
// XXX: the created entry in this call returns a string instead of a number
|
||||
// so I may have to do some workaround to solve it.
|
||||
response, err := channelRequest("channels.rename", values, api.debug)
|
||||
response, err := channelRequest(ctx, "channels.rename", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.Channel, nil
|
||||
|
||||
}
|
||||
|
||||
// SetChannelPurpose sets the channel purpose and returns the purpose that was
|
||||
// successfully set
|
||||
// SetChannelPurpose sets the channel purpose and returns the purpose that was successfully set
|
||||
func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) {
|
||||
return api.SetChannelPurposeContext(context.Background(), channel, purpose)
|
||||
}
|
||||
|
||||
// SetChannelPurposeContext sets the channel purpose and returns the purpose that was successfully set with a custom context
|
||||
func (api *Client) SetChannelPurposeContext(ctx context.Context, channel, purpose string) (string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"purpose": {purpose},
|
||||
}
|
||||
response, err := channelRequest("channels.setPurpose", values, api.debug)
|
||||
response, err := channelRequest(ctx, "channels.setPurpose", values, api.debug)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -248,14 +313,38 @@ func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) {
|
||||
|
||||
// SetChannelTopic sets the channel topic and returns the topic that was successfully set
|
||||
func (api *Client) SetChannelTopic(channel, topic string) (string, error) {
|
||||
return api.SetChannelTopicContext(context.Background(), channel, topic)
|
||||
}
|
||||
|
||||
// SetChannelTopicContext sets the channel topic and returns the topic that was successfully set with a custom context
|
||||
func (api *Client) SetChannelTopicContext(ctx context.Context, channel, topic string) (string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"topic": {topic},
|
||||
}
|
||||
response, err := channelRequest("channels.setTopic", values, api.debug)
|
||||
response, err := channelRequest(ctx, "channels.setTopic", values, api.debug)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.Topic, nil
|
||||
}
|
||||
|
||||
// GetChannelReplies gets an entire thread (a message plus all the messages in reply to it).
|
||||
func (api *Client) GetChannelReplies(channel, thread_ts string) ([]Message, error) {
|
||||
return api.GetChannelRepliesContext(context.Background(), channel, thread_ts)
|
||||
}
|
||||
|
||||
// GetChannelRepliesContext gets an entire thread (a message plus all the messages in reply to it) with a custom context
|
||||
func (api *Client) GetChannelRepliesContext(ctx context.Context, channel, thread_ts string) ([]Message, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"thread_ts": {thread_ts},
|
||||
}
|
||||
response, err := channelRequest(ctx, "channels.replies", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.History.Messages, nil
|
||||
}
|
||||
|
319
vendor/github.com/nlopes/slack/chat.go
generated
vendored
319
vendor/github.com/nlopes/slack/chat.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
@ -62,9 +63,102 @@ func NewPostMessageParameters() PostMessageParameters {
|
||||
}
|
||||
}
|
||||
|
||||
func chatRequest(path string, values url.Values, debug bool) (*chatResponseFull, error) {
|
||||
// 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
|
||||
}
|
||||
|
||||
// UpdateMessage updates a message in a channel
|
||||
func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
|
||||
return api.UpdateMessageContext(context.Background(), channel, timestamp, text)
|
||||
}
|
||||
|
||||
// UpdateMessage updates a message in a channel
|
||||
func (api *Client) UpdateMessageContext(ctx context.Context, channel, timestamp, text string) (string, string, string, error) {
|
||||
return api.SendMessageContext(ctx, channel, MsgOptionUpdate(timestamp), MsgOptionText(text, true))
|
||||
}
|
||||
|
||||
// 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, channel string, options ...MsgOption) (string, string, string, error) {
|
||||
channel, values, err := ApplyMsgOptions(api.config.token, channel, options...)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
response, err := chatRequest(ctx, channel, values, api.debug)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
return response.Channel, response.Timestamp, response.Text, nil
|
||||
}
|
||||
|
||||
// ApplyMsgOptions utility function for debugging/testing chat requests.
|
||||
func ApplyMsgOptions(token, channel string, options ...MsgOption) (string, url.Values, error) {
|
||||
config := sendConfig{
|
||||
mode: chatPostMessage,
|
||||
values: url.Values{
|
||||
"token": {token},
|
||||
"channel": {channel},
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
if err := opt(&config); err != nil {
|
||||
return string(config.mode), config.values, err
|
||||
}
|
||||
}
|
||||
|
||||
return string(config.mode), config.values, nil
|
||||
}
|
||||
|
||||
func escapeMessage(message string) string {
|
||||
replacer := strings.NewReplacer("&", "&", "<", "<", ">", ">")
|
||||
return replacer.Replace(message)
|
||||
}
|
||||
|
||||
func chatRequest(ctx context.Context, path string, values url.Values, debug bool) (*chatResponseFull, error) {
|
||||
response := &chatResponseFull{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -74,98 +168,153 @@ func chatRequest(path string, values url.Values, debug bool) (*chatResponseFull,
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// DeleteMessage deletes a message in a channel
|
||||
func (api *Client) DeleteMessage(channel, messageTimestamp string) (string, string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"ts": {messageTimestamp},
|
||||
}
|
||||
response, err := chatRequest("chat.delete", values, api.debug)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return response.Channel, response.Timestamp, nil
|
||||
type sendMode string
|
||||
|
||||
const (
|
||||
chatUpdate sendMode = "chat.update"
|
||||
chatPostMessage sendMode = "chat.postMessage"
|
||||
chatDelete sendMode = "chat.delete"
|
||||
)
|
||||
|
||||
type sendConfig struct {
|
||||
mode sendMode
|
||||
values url.Values
|
||||
}
|
||||
|
||||
func escapeMessage(message string) string {
|
||||
replacer := strings.NewReplacer("&", "&", "<", "<", ">", ">")
|
||||
return replacer.Replace(message)
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if params.EscapeText {
|
||||
text = escapeMessage(text)
|
||||
// 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
|
||||
}
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"text": {text},
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if params.Username != DEFAULT_MESSAGE_USERNAME {
|
||||
values.Set("username", string(params.Username))
|
||||
}
|
||||
if params.AsUser != DEFAULT_MESSAGE_ASUSER {
|
||||
values.Set("as_user", "true")
|
||||
}
|
||||
if params.Parse != DEFAULT_MESSAGE_PARSE {
|
||||
values.Set("parse", string(params.Parse))
|
||||
}
|
||||
if params.LinkNames != DEFAULT_MESSAGE_LINK_NAMES {
|
||||
values.Set("link_names", "1")
|
||||
}
|
||||
if params.Attachments != nil {
|
||||
attachments, err := json.Marshal(params.Attachments)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
values.Set("attachments", string(attachments))
|
||||
return nil
|
||||
}
|
||||
if params.UnfurlLinks != DEFAULT_MESSAGE_UNFURL_LINKS {
|
||||
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 {
|
||||
values.Set("unfurl_links", "false")
|
||||
}
|
||||
if params.UnfurlMedia != DEFAULT_MESSAGE_UNFURL_MEDIA {
|
||||
values.Set("unfurl_media", "false")
|
||||
}
|
||||
if params.IconURL != DEFAULT_MESSAGE_ICON_URL {
|
||||
values.Set("icon_url", params.IconURL)
|
||||
}
|
||||
if params.IconEmoji != DEFAULT_MESSAGE_ICON_EMOJI {
|
||||
values.Set("icon_emoji", params.IconEmoji)
|
||||
}
|
||||
if params.Markdown != DEFAULT_MESSAGE_MARKDOWN {
|
||||
values.Set("mrkdwn", "false")
|
||||
}
|
||||
if params.ThreadTimestamp != DEFAULT_MESSAGE_THREAD_TIMESTAMP {
|
||||
values.Set("thread_ts", params.ThreadTimestamp)
|
||||
}
|
||||
|
||||
response, err := chatRequest("chat.postMessage", values, api.debug)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return response.Channel, response.Timestamp, nil
|
||||
}
|
||||
|
||||
// UpdateMessage updates a message in a channel
|
||||
func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"text": {escapeMessage(text)},
|
||||
"ts": {timestamp},
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionPostMessageParameters maintain backwards compatibility.
|
||||
func MsgOptionPostMessageParameters(params PostMessageParameters) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
if params.Username != DEFAULT_MESSAGE_USERNAME {
|
||||
config.values.Set("username", string(params.Username))
|
||||
}
|
||||
|
||||
// never generates an error.
|
||||
MsgOptionAsUser(params.AsUser)(config)
|
||||
|
||||
if params.Parse != DEFAULT_MESSAGE_PARSE {
|
||||
config.values.Set("parse", string(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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
response, err := chatRequest("chat.update", values, api.debug)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
return response.Channel, response.Timestamp, response.Text, nil
|
||||
}
|
||||
|
41
vendor/github.com/nlopes/slack/dnd.go
generated
vendored
41
vendor/github.com/nlopes/slack/dnd.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -35,9 +36,9 @@ type dndTeamInfoResponse struct {
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
func dndRequest(path string, values url.Values, debug bool) (*dndResponseFull, error) {
|
||||
func dndRequest(ctx context.Context, path string, values url.Values, debug bool) (*dndResponseFull, error) {
|
||||
response := &dndResponseFull{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -49,12 +50,17 @@ func dndRequest(path string, values url.Values, debug bool) (*dndResponseFull, e
|
||||
|
||||
// EndDND ends the user's scheduled Do Not Disturb session
|
||||
func (api *Client) EndDND() error {
|
||||
return api.EndDNDContext(context.Background())
|
||||
}
|
||||
|
||||
// EndDNDContext ends the user's scheduled Do Not Disturb session with a custom context
|
||||
func (api *Client) EndDNDContext(ctx context.Context) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := post("dnd.endDnd", values, response, api.debug); err != nil {
|
||||
if err := post(ctx, "dnd.endDnd", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
if !response.Ok {
|
||||
@ -65,11 +71,16 @@ func (api *Client) EndDND() error {
|
||||
|
||||
// EndSnooze ends the current user's snooze mode
|
||||
func (api *Client) EndSnooze() (*DNDStatus, error) {
|
||||
return api.EndSnoozeContext(context.Background())
|
||||
}
|
||||
|
||||
// EndSnoozeContext ends the current user's snooze mode with a custom context
|
||||
func (api *Client) EndSnoozeContext(ctx context.Context) (*DNDStatus, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
|
||||
response, err := dndRequest("dnd.endSnooze", values, api.debug)
|
||||
response, err := dndRequest(ctx, "dnd.endSnooze", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -78,13 +89,18 @@ func (api *Client) EndSnooze() (*DNDStatus, error) {
|
||||
|
||||
// GetDNDInfo provides information about a user's current Do Not Disturb settings.
|
||||
func (api *Client) GetDNDInfo(user *string) (*DNDStatus, error) {
|
||||
return api.GetDNDInfoContext(context.Background(), user)
|
||||
}
|
||||
|
||||
// GetDNDInfoContext provides information about a user's current Do Not Disturb settings with a custom context.
|
||||
func (api *Client) GetDNDInfoContext(ctx context.Context, user *string) (*DNDStatus, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
if user != nil {
|
||||
values.Set("user", *user)
|
||||
}
|
||||
response, err := dndRequest("dnd.info", values, api.debug)
|
||||
response, err := dndRequest(ctx, "dnd.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -93,12 +109,17 @@ func (api *Client) GetDNDInfo(user *string) (*DNDStatus, error) {
|
||||
|
||||
// GetDNDTeamInfo provides information about a user's current Do Not Disturb settings.
|
||||
func (api *Client) GetDNDTeamInfo(users []string) (map[string]DNDStatus, error) {
|
||||
return api.GetDNDTeamInfoContext(context.Background(), users)
|
||||
}
|
||||
|
||||
// GetDNDTeamInfoContext provides information about a user's current Do Not Disturb settings with a custom context.
|
||||
func (api *Client) GetDNDTeamInfoContext(ctx context.Context, users []string) (map[string]DNDStatus, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"users": {strings.Join(users, ",")},
|
||||
}
|
||||
response := &dndTeamInfoResponse{}
|
||||
if err := post("dnd.teamInfo", values, response, api.debug); err != nil {
|
||||
if err := post(ctx, "dnd.teamInfo", values, response, api.debug); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !response.Ok {
|
||||
@ -111,11 +132,17 @@ func (api *Client) GetDNDTeamInfo(users []string) (map[string]DNDStatus, error)
|
||||
// settings. If a snooze session is not already active for the user, invoking
|
||||
// this method will begin one for the specified duration.
|
||||
func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) {
|
||||
return api.SetSnoozeContext(context.Background(), minutes)
|
||||
}
|
||||
|
||||
// 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{
|
||||
"token": {api.config.token},
|
||||
"num_minutes": {strconv.Itoa(minutes)},
|
||||
}
|
||||
response, err := dndRequest("dnd.setSnooze", values, api.debug)
|
||||
response, err := dndRequest(ctx, "dnd.setSnooze", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
8
vendor/github.com/nlopes/slack/emoji.go
generated
vendored
8
vendor/github.com/nlopes/slack/emoji.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
@ -12,11 +13,16 @@ type emojiResponseFull struct {
|
||||
|
||||
// GetEmoji retrieves all the emojis
|
||||
func (api *Client) GetEmoji() (map[string]string, error) {
|
||||
return api.GetEmojiContext(context.Background())
|
||||
}
|
||||
|
||||
// GetEmojiContext retrieves all the emojis with a custom context
|
||||
func (api *Client) GetEmojiContext(ctx context.Context) (map[string]string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
response := &emojiResponseFull{}
|
||||
err := post("emoji.list", values, response, api.debug)
|
||||
err := post(ctx, "emoji.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
63
vendor/github.com/nlopes/slack/files.go
generated
vendored
63
vendor/github.com/nlopes/slack/files.go
generated
vendored
@ -1,7 +1,9 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -86,10 +88,14 @@ type File struct {
|
||||
IsStarred bool `json:"is_starred"`
|
||||
}
|
||||
|
||||
// FileUploadParameters contains all the parameters necessary (including the optional ones) for an UploadFile() request
|
||||
// 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.
|
||||
type FileUploadParameters struct {
|
||||
File string
|
||||
Content string
|
||||
Reader io.Reader
|
||||
Filetype string
|
||||
Filename string
|
||||
Title string
|
||||
@ -130,9 +136,9 @@ func NewGetFilesParameters() GetFilesParameters {
|
||||
}
|
||||
}
|
||||
|
||||
func fileRequest(path string, values url.Values, debug bool) (*fileResponseFull, error) {
|
||||
func fileRequest(ctx context.Context, path string, values url.Values, debug bool) (*fileResponseFull, error) {
|
||||
response := &fileResponseFull{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -144,13 +150,18 @@ func fileRequest(path string, values url.Values, debug bool) (*fileResponseFull,
|
||||
|
||||
// GetFileInfo retrieves a file and related comments
|
||||
func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) {
|
||||
return api.GetFileInfoContext(context.Background(), fileID, count, page)
|
||||
}
|
||||
|
||||
// GetFileInfoContext retrieves a file and related comments with a custom context
|
||||
func (api *Client) GetFileInfoContext(ctx context.Context, fileID string, count, page int) (*File, []Comment, *Paging, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"file": {fileID},
|
||||
"count": {strconv.Itoa(count)},
|
||||
"page": {strconv.Itoa(page)},
|
||||
}
|
||||
response, err := fileRequest("files.info", values, api.debug)
|
||||
response, err := fileRequest(ctx, "files.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@ -159,6 +170,11 @@ func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment
|
||||
|
||||
// GetFiles retrieves all files according to the parameters given
|
||||
func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) {
|
||||
return api.GetFilesContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// GetFilesContext retrieves all files according to the parameters given with a custom context
|
||||
func (api *Client) GetFilesContext(ctx context.Context, params GetFilesParameters) ([]File, *Paging, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
@ -168,12 +184,11 @@ func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error)
|
||||
if params.Channel != DEFAULT_FILES_CHANNEL {
|
||||
values.Add("channel", params.Channel)
|
||||
}
|
||||
// XXX: this is broken. fix it with a proper unix timestamp
|
||||
if params.TimestampFrom != DEFAULT_FILES_TS_FROM {
|
||||
values.Add("ts_from", params.TimestampFrom.String())
|
||||
values.Add("ts_from", strconv.FormatInt(int64(params.TimestampFrom), 10))
|
||||
}
|
||||
if params.TimestampTo != DEFAULT_FILES_TS_TO {
|
||||
values.Add("ts_to", params.TimestampTo.String())
|
||||
values.Add("ts_to", strconv.FormatInt(int64(params.TimestampTo), 10))
|
||||
}
|
||||
if params.Types != DEFAULT_FILES_TYPES {
|
||||
values.Add("types", params.Types)
|
||||
@ -184,7 +199,7 @@ func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error)
|
||||
if params.Page != DEFAULT_FILES_PAGE {
|
||||
values.Add("page", strconv.Itoa(params.Page))
|
||||
}
|
||||
response, err := fileRequest("files.list", values, api.debug)
|
||||
response, err := fileRequest(ctx, "files.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -193,6 +208,11 @@ func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error)
|
||||
|
||||
// UploadFile uploads a file
|
||||
func (api *Client) UploadFile(params FileUploadParameters) (file *File, err error) {
|
||||
return api.UploadFileContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// UploadFileContext uploads a file and setting a custom context
|
||||
func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParameters) (file *File, err error) {
|
||||
// Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More
|
||||
// investigation needed, but for now this will do.
|
||||
_, err = api.AuthTest()
|
||||
@ -220,9 +240,11 @@ func (api *Client) UploadFile(params FileUploadParameters) (file *File, err erro
|
||||
}
|
||||
if params.Content != "" {
|
||||
values.Add("content", params.Content)
|
||||
err = post("files.upload", values, response, api.debug)
|
||||
err = post(ctx, "files.upload", values, response, api.debug)
|
||||
} else if params.File != "" {
|
||||
err = postWithMultipartResponse("files.upload", params.File, values, response, api.debug)
|
||||
err = postLocalWithMultipartResponse(ctx, "files.upload", params.File, "file", values, response, api.debug)
|
||||
} else if params.Reader != nil {
|
||||
err = postWithMultipartResponse(ctx, "files.upload", params.Filename, "file", values, params.Reader, response, api.debug)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -235,11 +257,16 @@ func (api *Client) UploadFile(params FileUploadParameters) (file *File, err erro
|
||||
|
||||
// DeleteFile deletes a file
|
||||
func (api *Client) DeleteFile(fileID string) error {
|
||||
return api.DeleteFileContext(context.Background(), fileID)
|
||||
}
|
||||
|
||||
// DeleteFileContext deletes a file with a custom context
|
||||
func (api *Client) DeleteFileContext(ctx context.Context, fileID string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"file": {fileID},
|
||||
}
|
||||
_, err := fileRequest("files.delete", values, api.debug)
|
||||
_, err := fileRequest(ctx, "files.delete", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -249,11 +276,16 @@ func (api *Client) DeleteFile(fileID string) error {
|
||||
|
||||
// RevokeFilePublicURL disables public/external sharing for a file
|
||||
func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) {
|
||||
return api.RevokeFilePublicURLContext(context.Background(), fileID)
|
||||
}
|
||||
|
||||
// RevokeFilePublicURLContext disables public/external sharing for a file with a custom context
|
||||
func (api *Client) RevokeFilePublicURLContext(ctx context.Context, fileID string) (*File, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"file": {fileID},
|
||||
}
|
||||
response, err := fileRequest("files.revokePublicURL", values, api.debug)
|
||||
response, err := fileRequest(ctx, "files.revokePublicURL", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -262,11 +294,16 @@ func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) {
|
||||
|
||||
// ShareFilePublicURL enabled public/external sharing for a file
|
||||
func (api *Client) ShareFilePublicURL(fileID string) (*File, []Comment, *Paging, error) {
|
||||
return api.ShareFilePublicURLContext(context.Background(), fileID)
|
||||
}
|
||||
|
||||
// ShareFilePublicURLContext enabled public/external sharing for a file with a custom context
|
||||
func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string) (*File, []Comment, *Paging, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"file": {fileID},
|
||||
}
|
||||
response, err := fileRequest("files.sharedPublicURL", values, api.debug)
|
||||
response, err := fileRequest(ctx, "files.sharedPublicURL", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
120
vendor/github.com/nlopes/slack/groups.go
generated
vendored
120
vendor/github.com/nlopes/slack/groups.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -27,9 +28,9 @@ type groupResponseFull struct {
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
func groupRequest(path string, values url.Values, debug bool) (*groupResponseFull, error) {
|
||||
func groupRequest(ctx context.Context, path string, values url.Values, debug bool) (*groupResponseFull, error) {
|
||||
response := &groupResponseFull{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -41,11 +42,16 @@ func groupRequest(path string, values url.Values, debug bool) (*groupResponseFul
|
||||
|
||||
// ArchiveGroup archives a private group
|
||||
func (api *Client) ArchiveGroup(group string) error {
|
||||
return api.ArchiveGroupContext(context.Background(), group)
|
||||
}
|
||||
|
||||
// ArchiveGroup archives a private group
|
||||
func (api *Client) ArchiveGroupContext(ctx context.Context, group string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
}
|
||||
_, err := groupRequest("groups.archive", values, api.debug)
|
||||
_, err := groupRequest(ctx, "groups.archive", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -54,11 +60,16 @@ func (api *Client) ArchiveGroup(group string) error {
|
||||
|
||||
// UnarchiveGroup unarchives a private group
|
||||
func (api *Client) UnarchiveGroup(group string) error {
|
||||
return api.UnarchiveGroupContext(context.Background(), group)
|
||||
}
|
||||
|
||||
// UnarchiveGroup unarchives a private group
|
||||
func (api *Client) UnarchiveGroupContext(ctx context.Context, group string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
}
|
||||
_, err := groupRequest("groups.unarchive", values, api.debug)
|
||||
_, err := groupRequest(ctx, "groups.unarchive", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -67,11 +78,16 @@ func (api *Client) UnarchiveGroup(group string) error {
|
||||
|
||||
// CreateGroup creates a private group
|
||||
func (api *Client) CreateGroup(group string) (*Group, error) {
|
||||
return api.CreateGroupContext(context.Background(), group)
|
||||
}
|
||||
|
||||
// CreateGroup creates a private group
|
||||
func (api *Client) CreateGroupContext(ctx context.Context, group string) (*Group, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"name": {group},
|
||||
}
|
||||
response, err := groupRequest("groups.create", values, api.debug)
|
||||
response, err := groupRequest(ctx, "groups.create", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -85,11 +101,17 @@ func (api *Client) CreateGroup(group string) (*Group, error) {
|
||||
// 3. Creates a new group with the name of the existing group.
|
||||
// 4. Adds all members of the existing group to the new group.
|
||||
func (api *Client) CreateChildGroup(group string) (*Group, error) {
|
||||
return api.CreateChildGroupContext(context.Background(), group)
|
||||
}
|
||||
|
||||
// CreateChildGroup creates a new private group archiving the old one with a custom context
|
||||
// For more information see CreateChildGroup
|
||||
func (api *Client) CreateChildGroupContext(ctx context.Context, group string) (*Group, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
}
|
||||
response, err := groupRequest("groups.createChild", values, api.debug)
|
||||
response, err := groupRequest(ctx, "groups.createChild", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -98,11 +120,16 @@ func (api *Client) CreateChildGroup(group string) (*Group, error) {
|
||||
|
||||
// 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.config.token},
|
||||
"channel": {group},
|
||||
}
|
||||
response, err := imRequest("groups.close", values, api.debug)
|
||||
response, err := imRequest(ctx, "groups.close", values, api.debug)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
@ -111,6 +138,11 @@ func (api *Client) CloseGroup(group string) (bool, bool, error) {
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// GetGroupHistoryContext fetches all the history for a private group with a custom context
|
||||
func (api *Client) GetGroupHistoryContext(ctx context.Context, group string, params HistoryParameters) (*History, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
@ -138,7 +170,7 @@ func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*His
|
||||
values.Add("unreads", "0")
|
||||
}
|
||||
}
|
||||
response, err := groupRequest("groups.history", values, api.debug)
|
||||
response, err := groupRequest(ctx, "groups.history", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -147,12 +179,17 @@ func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*His
|
||||
|
||||
// InviteUserToGroup invites a specific user to a private group
|
||||
func (api *Client) InviteUserToGroup(group, user string) (*Group, bool, error) {
|
||||
return api.InviteUserToGroupContext(context.Background(), group, user)
|
||||
}
|
||||
|
||||
// InviteUserToGroupContext invites a specific user to a private group with a custom context
|
||||
func (api *Client) InviteUserToGroupContext(ctx context.Context, group, user string) (*Group, bool, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
"user": {user},
|
||||
}
|
||||
response, err := groupRequest("groups.invite", values, api.debug)
|
||||
response, err := groupRequest(ctx, "groups.invite", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@ -161,11 +198,16 @@ func (api *Client) InviteUserToGroup(group, user string) (*Group, bool, error) {
|
||||
|
||||
// LeaveGroup makes authenticated user leave the group
|
||||
func (api *Client) LeaveGroup(group string) error {
|
||||
return api.LeaveGroupContext(context.Background(), group)
|
||||
}
|
||||
|
||||
// LeaveGroupContext makes authenticated user leave the group with a custom context
|
||||
func (api *Client) LeaveGroupContext(ctx context.Context, group string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
}
|
||||
_, err := groupRequest("groups.leave", values, api.debug)
|
||||
_, err := groupRequest(ctx, "groups.leave", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -174,12 +216,17 @@ func (api *Client) LeaveGroup(group string) error {
|
||||
|
||||
// KickUserFromGroup kicks a user from a group
|
||||
func (api *Client) KickUserFromGroup(group, user string) error {
|
||||
return api.KickUserFromGroupContext(context.Background(), group, user)
|
||||
}
|
||||
|
||||
// KickUserFromGroupContext kicks a user from a group with a custom context
|
||||
func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
"user": {user},
|
||||
}
|
||||
_, err := groupRequest("groups.kick", values, api.debug)
|
||||
_, err := groupRequest(ctx, "groups.kick", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -188,13 +235,18 @@ func (api *Client) KickUserFromGroup(group, user string) error {
|
||||
|
||||
// GetGroups retrieves all groups
|
||||
func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) {
|
||||
return api.GetGroupsContext(context.Background(), excludeArchived)
|
||||
}
|
||||
|
||||
// GetGroupsContext retrieves all groups with a custom context
|
||||
func (api *Client) GetGroupsContext(ctx context.Context, excludeArchived bool) ([]Group, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
if excludeArchived {
|
||||
values.Add("exclude_archived", "1")
|
||||
}
|
||||
response, err := groupRequest("groups.list", values, api.debug)
|
||||
response, err := groupRequest(ctx, "groups.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -203,11 +255,16 @@ func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) {
|
||||
|
||||
// GetGroupInfo retrieves the given group
|
||||
func (api *Client) GetGroupInfo(group string) (*Group, error) {
|
||||
return api.GetGroupInfoContext(context.Background(), group)
|
||||
}
|
||||
|
||||
// GetGroupInfoContext retrieves the given group with a custom context
|
||||
func (api *Client) GetGroupInfoContext(ctx context.Context, group string) (*Group, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
}
|
||||
response, err := groupRequest("groups.info", values, api.debug)
|
||||
response, err := groupRequest(ctx, "groups.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -220,12 +277,18 @@ func (api *Client) GetGroupInfo(group string) (*Group, error) {
|
||||
// calls (just one per channel). This is useful for when reading scroll-back history, or following a busy live
|
||||
// channel. A timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
|
||||
func (api *Client) SetGroupReadMark(group, ts string) error {
|
||||
return api.SetGroupReadMarkContext(context.Background(), group, ts)
|
||||
}
|
||||
|
||||
// SetGroupReadMarkContext sets the read mark on a private group with a custom context
|
||||
// For more details see SetGroupReadMark
|
||||
func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
"ts": {ts},
|
||||
}
|
||||
_, err := groupRequest("groups.mark", values, api.debug)
|
||||
_, err := groupRequest(ctx, "groups.mark", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -234,11 +297,16 @@ func (api *Client) SetGroupReadMark(group, ts string) error {
|
||||
|
||||
// OpenGroup opens a private group
|
||||
func (api *Client) OpenGroup(group string) (bool, bool, error) {
|
||||
return api.OpenGroupContext(context.Background(), group)
|
||||
}
|
||||
|
||||
// OpenGroupContext opens a private group with a custom context
|
||||
func (api *Client) OpenGroupContext(ctx context.Context, group string) (bool, bool, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
}
|
||||
response, err := groupRequest("groups.open", values, api.debug)
|
||||
response, err := groupRequest(ctx, "groups.open", values, api.debug)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
@ -249,6 +317,11 @@ func (api *Client) OpenGroup(group string) (bool, bool, error) {
|
||||
// XXX: They return a channel, not a group. What is this crap? :(
|
||||
// Inconsistent api it seems.
|
||||
func (api *Client) RenameGroup(group, name string) (*Channel, error) {
|
||||
return api.RenameGroupContext(context.Background(), group, name)
|
||||
}
|
||||
|
||||
// RenameGroupContext renames a group with a custom context
|
||||
func (api *Client) RenameGroupContext(ctx context.Context, group, name string) (*Channel, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
@ -256,22 +329,26 @@ func (api *Client) RenameGroup(group, name string) (*Channel, error) {
|
||||
}
|
||||
// XXX: the created entry in this call returns a string instead of a number
|
||||
// so I may have to do some workaround to solve it.
|
||||
response, err := groupRequest("groups.rename", values, api.debug)
|
||||
response, err := groupRequest(ctx, "groups.rename", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.Channel, nil
|
||||
|
||||
}
|
||||
|
||||
// SetGroupPurpose sets the group purpose
|
||||
func (api *Client) SetGroupPurpose(group, purpose string) (string, error) {
|
||||
return api.SetGroupPurposeContext(context.Background(), group, purpose)
|
||||
}
|
||||
|
||||
// SetGroupPurposeContext sets the group purpose with a custom context
|
||||
func (api *Client) SetGroupPurposeContext(ctx context.Context, group, purpose string) (string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
"purpose": {purpose},
|
||||
}
|
||||
response, err := groupRequest("groups.setPurpose", values, api.debug)
|
||||
response, err := groupRequest(ctx, "groups.setPurpose", values, api.debug)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -280,12 +357,17 @@ func (api *Client) SetGroupPurpose(group, purpose string) (string, error) {
|
||||
|
||||
// SetGroupTopic sets the group topic
|
||||
func (api *Client) SetGroupTopic(group, topic string) (string, error) {
|
||||
return api.SetGroupTopicContext(context.Background(), group, topic)
|
||||
}
|
||||
|
||||
// SetGroupTopicContext sets the group topic with a custom context
|
||||
func (api *Client) SetGroupTopicContext(ctx context.Context, group, topic string) (string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {group},
|
||||
"topic": {topic},
|
||||
}
|
||||
response, err := groupRequest("groups.setTopic", values, api.debug)
|
||||
response, err := groupRequest(ctx, "groups.setTopic", values, api.debug)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
41
vendor/github.com/nlopes/slack/im.go
generated
vendored
41
vendor/github.com/nlopes/slack/im.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -28,9 +29,9 @@ type IM struct {
|
||||
IsUserDeleted bool `json:"is_user_deleted"`
|
||||
}
|
||||
|
||||
func imRequest(path string, values url.Values, debug bool) (*imResponseFull, error) {
|
||||
func imRequest(ctx context.Context, path string, values url.Values, debug bool) (*imResponseFull, error) {
|
||||
response := &imResponseFull{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -42,11 +43,16 @@ func imRequest(path string, values url.Values, debug bool) (*imResponseFull, err
|
||||
|
||||
// CloseIMChannel closes the direct message channel
|
||||
func (api *Client) CloseIMChannel(channel string) (bool, bool, error) {
|
||||
return api.CloseIMChannelContext(context.Background(), channel)
|
||||
}
|
||||
|
||||
// CloseIMChannelContext closes the direct message channel with a custom context
|
||||
func (api *Client) CloseIMChannelContext(ctx context.Context, channel string) (bool, bool, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
}
|
||||
response, err := imRequest("im.close", values, api.debug)
|
||||
response, err := imRequest(ctx, "im.close", values, api.debug)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
@ -56,11 +62,17 @@ func (api *Client) CloseIMChannel(channel string) (bool, bool, error) {
|
||||
// OpenIMChannel opens a direct message channel to the user provided as argument
|
||||
// Returns some status and the channel ID
|
||||
func (api *Client) OpenIMChannel(user string) (bool, bool, string, error) {
|
||||
return api.OpenIMChannelContext(context.Background(), user)
|
||||
}
|
||||
|
||||
// OpenIMChannelContext opens a direct message channel to the user provided as argument with a custom context
|
||||
// Returns some status and the channel ID
|
||||
func (api *Client) OpenIMChannelContext(ctx context.Context, user string) (bool, bool, string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"user": {user},
|
||||
}
|
||||
response, err := imRequest("im.open", values, api.debug)
|
||||
response, err := imRequest(ctx, "im.open", values, api.debug)
|
||||
if err != nil {
|
||||
return false, false, "", err
|
||||
}
|
||||
@ -69,12 +81,17 @@ func (api *Client) OpenIMChannel(user string) (bool, bool, string, error) {
|
||||
|
||||
// MarkIMChannel sets the read mark of a direct message channel to a specific point
|
||||
func (api *Client) MarkIMChannel(channel, ts string) (err error) {
|
||||
return api.MarkIMChannelContext(context.Background(), channel, ts)
|
||||
}
|
||||
|
||||
// MarkIMChannelContext sets the read mark of a direct message channel to a specific point with a custom context
|
||||
func (api *Client) MarkIMChannelContext(ctx context.Context, channel, ts string) (err error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
"ts": {ts},
|
||||
}
|
||||
_, err = imRequest("im.mark", values, api.debug)
|
||||
_, err = imRequest(ctx, "im.mark", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -83,6 +100,11 @@ func (api *Client) MarkIMChannel(channel, ts string) (err error) {
|
||||
|
||||
// GetIMHistory retrieves the direct message channel history
|
||||
func (api *Client) GetIMHistory(channel string, params HistoryParameters) (*History, error) {
|
||||
return api.GetIMHistoryContext(context.Background(), channel, params)
|
||||
}
|
||||
|
||||
// GetIMHistoryContext retrieves the direct message channel history with a custom context
|
||||
func (api *Client) GetIMHistoryContext(ctx context.Context, channel string, params HistoryParameters) (*History, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"channel": {channel},
|
||||
@ -110,7 +132,7 @@ func (api *Client) GetIMHistory(channel string, params HistoryParameters) (*Hist
|
||||
values.Add("unreads", "0")
|
||||
}
|
||||
}
|
||||
response, err := imRequest("im.history", values, api.debug)
|
||||
response, err := imRequest(ctx, "im.history", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -119,10 +141,15 @@ func (api *Client) GetIMHistory(channel string, params HistoryParameters) (*Hist
|
||||
|
||||
// GetIMChannels returns the list of direct message channels
|
||||
func (api *Client) GetIMChannels() ([]IM, error) {
|
||||
return api.GetIMChannelsContext(context.Background())
|
||||
}
|
||||
|
||||
// GetIMChannelsContext returns the list of direct message channels with a custom context
|
||||
func (api *Client) GetIMChannelsContext(ctx context.Context) ([]IM, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
response, err := imRequest("im.list", values, api.debug)
|
||||
response, err := imRequest(ctx, "im.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
39
vendor/github.com/nlopes/slack/messages.go
generated
vendored
39
vendor/github.com/nlopes/slack/messages.go
generated
vendored
@ -2,10 +2,11 @@ package slack
|
||||
|
||||
// OutgoingMessage is used for the realtime API, and seems incomplete.
|
||||
type OutgoingMessage struct {
|
||||
ID int `json:"id"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
ID int `json:"id"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
ThreadTimestamp string `json:"thread_ts,omitempty"`
|
||||
}
|
||||
|
||||
// Message is an auxiliary type to allow us to have a message containing sub messages
|
||||
@ -17,15 +18,16 @@ type Message struct {
|
||||
// Msg contains information about a slack message
|
||||
type Msg struct {
|
||||
// Basic Message
|
||||
Type string `json:"type,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Timestamp string `json:"ts,omitempty"`
|
||||
IsStarred bool `json:"is_starred,omitempty"`
|
||||
PinnedTo []string `json:"pinned_to, omitempty"`
|
||||
Attachments []Attachment `json:"attachments,omitempty"`
|
||||
Edited *Edited `json:"edited,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Channel string `json:"channel,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Timestamp string `json:"ts,omitempty"`
|
||||
ThreadTimestamp string `json:"thread_ts,omitempty"`
|
||||
IsStarred bool `json:"is_starred,omitempty"`
|
||||
PinnedTo []string `json:"pinned_to, omitempty"`
|
||||
Attachments []Attachment `json:"attachments,omitempty"`
|
||||
Edited *Edited `json:"edited,omitempty"`
|
||||
|
||||
// Message Subtypes
|
||||
SubType string `json:"subtype,omitempty"`
|
||||
@ -56,6 +58,11 @@ type Msg struct {
|
||||
// channel_archive, group_archive
|
||||
Members []string `json:"members,omitempty"`
|
||||
|
||||
// channels.replies, groups.replies, im.replies, mpim.replies
|
||||
ReplyCount int `json:"reply_count,omitempty"`
|
||||
Replies []Reply `json:"replies,omitempty"`
|
||||
ParentUserId string `json:"parent_user_id,omitempty"`
|
||||
|
||||
// file_share, file_comment, file_mention
|
||||
File *File `json:"file,omitempty"`
|
||||
|
||||
@ -88,6 +95,12 @@ type Edited struct {
|
||||
Timestamp string `json:"ts,omitempty"`
|
||||
}
|
||||
|
||||
// Reply contains information about a reply for a thread
|
||||
type Reply struct {
|
||||
User string `json:"user,omitempty"`
|
||||
Timestamp string `json:"ts,omitempty"`
|
||||
}
|
||||
|
||||
// Event contains the event type
|
||||
type Event struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
|
105
vendor/github.com/nlopes/slack/misc.go
generated
vendored
105
vendor/github.com/nlopes/slack/misc.go
generated
vendored
@ -2,8 +2,8 @@ package slack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -13,9 +13,22 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTPRequester defines the minimal interface needed for an http.Client to be implemented.
|
||||
//
|
||||
// Use it in conjunction with the SetHTTPClient function to allow for other capabilities
|
||||
// like a tracing http.Client
|
||||
type HTTPRequester interface {
|
||||
Do(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
var customHTTPClient HTTPRequester
|
||||
|
||||
// HTTPClient sets a custom http.Client
|
||||
// deprecated: in favor of SetHTTPClient()
|
||||
var HTTPClient = &http.Client{}
|
||||
|
||||
type WebResponse struct {
|
||||
@ -29,40 +42,24 @@ func (s WebError) Error() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func fileUploadReq(path, fpath string, values url.Values) (*http.Request, error) {
|
||||
fullpath, err := filepath.Abs(fpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file, err := os.Open(fullpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
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("file", filepath.Base(fullpath))
|
||||
ioWriter, err := wr.CreateFormFile(fieldname, filename)
|
||||
if err != nil {
|
||||
wr.Close()
|
||||
return nil, err
|
||||
}
|
||||
bytes, err := io.Copy(ioWriter, file)
|
||||
_, 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()
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bytes != stat.Size() {
|
||||
return nil, errors.New("could not read the whole file")
|
||||
}
|
||||
req, err := http.NewRequest("POST", path, body)
|
||||
req = req.WithContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -90,9 +87,26 @@ func parseResponseBody(body io.ReadCloser, intf *interface{}, debug bool) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func postWithMultipartResponse(path string, filepath string, values url.Values, intf interface{}, debug bool) error {
|
||||
req, err := fileUploadReq(SLACK_API+path, filepath, values)
|
||||
resp, err := HTTPClient.Do(req)
|
||||
func postLocalWithMultipartResponse(ctx context.Context, 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, path, filepath.Base(fpath), fieldname, values, file, intf, debug)
|
||||
}
|
||||
|
||||
func postWithMultipartResponse(ctx context.Context, path, name, fieldname string, values url.Values, r io.Reader, intf interface{}, debug bool) error {
|
||||
req, err := fileUploadReq(ctx, SLACK_API+path, fieldname, name, values, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
resp, err := getHTTPClient().Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -107,23 +121,37 @@ func postWithMultipartResponse(path string, filepath string, values url.Values,
|
||||
return parseResponseBody(resp.Body, &intf, debug)
|
||||
}
|
||||
|
||||
func postForm(endpoint string, values url.Values, intf interface{}, debug bool) error {
|
||||
resp, err := HTTPClient.PostForm(endpoint, values)
|
||||
func postForm(ctx context.Context, 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")
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
resp, err := getHTTPClient().Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
|
||||
if resp.StatusCode != 200 {
|
||||
logResponse(resp, debug)
|
||||
return fmt.Errorf("Slack server error: %s.", resp.Status)
|
||||
}
|
||||
|
||||
return parseResponseBody(resp.Body, &intf, debug)
|
||||
}
|
||||
|
||||
func post(path string, values url.Values, intf interface{}, debug bool) error {
|
||||
return postForm(SLACK_API+path, values, intf, debug)
|
||||
func post(ctx context.Context, path string, values url.Values, intf interface{}, debug bool) error {
|
||||
return postForm(ctx, SLACK_API+path, values, intf, debug)
|
||||
}
|
||||
|
||||
func parseAdminResponse(method string, teamName string, values url.Values, intf interface{}, debug bool) error {
|
||||
func parseAdminResponse(ctx context.Context, 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(endpoint, values, intf, debug)
|
||||
return postForm(ctx, endpoint, values, intf, debug)
|
||||
}
|
||||
|
||||
func logResponse(resp *http.Response, debug bool) error {
|
||||
@ -133,8 +161,23 @@ func logResponse(resp *http.Response, debug bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Print(text)
|
||||
logger.Print(string(text))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHTTPClient() HTTPRequester {
|
||||
if customHTTPClient != nil {
|
||||
return customHTTPClient
|
||||
}
|
||||
|
||||
return HTTPClient
|
||||
}
|
||||
|
||||
// SetHTTPClient allows you to specify a custom http.Client
|
||||
// Use this instead of the package level HTTPClient variable if you want to use a custom client like the
|
||||
// Stackdriver Trace HTTPClient https://godoc.org/cloud.google.com/go/trace#HTTPClient
|
||||
func SetHTTPClient(client HTTPRequester) {
|
||||
customHTTPClient = client
|
||||
}
|
||||
|
14
vendor/github.com/nlopes/slack/oauth.go
generated
vendored
14
vendor/github.com/nlopes/slack/oauth.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
@ -30,7 +31,12 @@ type OAuthResponse struct {
|
||||
|
||||
// GetOAuthToken retrieves an AccessToken
|
||||
func GetOAuthToken(clientID, clientSecret, code, redirectURI string, debug bool) (accessToken string, scope string, err error) {
|
||||
response, err := GetOAuthResponse(clientID, clientSecret, code, redirectURI, debug)
|
||||
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
|
||||
}
|
||||
@ -38,6 +44,10 @@ func GetOAuthToken(clientID, clientSecret, code, redirectURI string, debug bool)
|
||||
}
|
||||
|
||||
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},
|
||||
@ -45,7 +55,7 @@ func GetOAuthResponse(clientID, clientSecret, code, redirectURI string, debug bo
|
||||
"redirect_uri": {redirectURI},
|
||||
}
|
||||
response := &OAuthResponse{}
|
||||
err = post("oauth.access", values, response, debug)
|
||||
err = post(ctx, "oauth.access", values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
22
vendor/github.com/nlopes/slack/pins.go
generated
vendored
22
vendor/github.com/nlopes/slack/pins.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
@ -13,6 +14,11 @@ type listPinsResponseFull struct {
|
||||
|
||||
// AddPin pins an item in a channel
|
||||
func (api *Client) AddPin(channel string, item ItemRef) error {
|
||||
return api.AddPinContext(context.Background(), channel, item)
|
||||
}
|
||||
|
||||
// AddPinContext pins an item in a channel with a custom context
|
||||
func (api *Client) AddPinContext(ctx context.Context, channel string, item ItemRef) error {
|
||||
values := url.Values{
|
||||
"channel": {channel},
|
||||
"token": {api.config.token},
|
||||
@ -27,7 +33,7 @@ func (api *Client) AddPin(channel string, item ItemRef) error {
|
||||
values.Set("file_comment", string(item.Comment))
|
||||
}
|
||||
response := &SlackResponse{}
|
||||
if err := post("pins.add", values, response, api.debug); err != nil {
|
||||
if err := post(ctx, "pins.add", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
if !response.Ok {
|
||||
@ -38,6 +44,11 @@ func (api *Client) AddPin(channel string, item ItemRef) error {
|
||||
|
||||
// RemovePin un-pins an item from a channel
|
||||
func (api *Client) RemovePin(channel string, item ItemRef) error {
|
||||
return api.RemovePinContext(context.Background(), channel, item)
|
||||
}
|
||||
|
||||
// RemovePinContext un-pins an item from a channel with a custom context
|
||||
func (api *Client) RemovePinContext(ctx context.Context, channel string, item ItemRef) error {
|
||||
values := url.Values{
|
||||
"channel": {channel},
|
||||
"token": {api.config.token},
|
||||
@ -52,7 +63,7 @@ func (api *Client) RemovePin(channel string, item ItemRef) error {
|
||||
values.Set("file_comment", string(item.Comment))
|
||||
}
|
||||
response := &SlackResponse{}
|
||||
if err := post("pins.remove", values, response, api.debug); err != nil {
|
||||
if err := post(ctx, "pins.remove", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
if !response.Ok {
|
||||
@ -63,12 +74,17 @@ func (api *Client) RemovePin(channel string, item ItemRef) error {
|
||||
|
||||
// ListPins returns information about the items a user reacted to.
|
||||
func (api *Client) ListPins(channel string) ([]Item, *Paging, error) {
|
||||
return api.ListPinsContext(context.Background(), channel)
|
||||
}
|
||||
|
||||
// ListPinsContext returns information about the items a user reacted to with a custom context.
|
||||
func (api *Client) ListPinsContext(ctx context.Context, channel string) ([]Item, *Paging, error) {
|
||||
values := url.Values{
|
||||
"channel": {channel},
|
||||
"token": {api.config.token},
|
||||
}
|
||||
response := &listPinsResponseFull{}
|
||||
err := post("pins.list", values, response, api.debug)
|
||||
err := post(ctx, "pins.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
29
vendor/github.com/nlopes/slack/reactions.go
generated
vendored
29
vendor/github.com/nlopes/slack/reactions.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -129,6 +130,11 @@ func (res listReactionsResponseFull) extractReactedItems() []ReactedItem {
|
||||
|
||||
// AddReaction adds a reaction emoji to a message, file or file comment.
|
||||
func (api *Client) AddReaction(name string, item ItemRef) error {
|
||||
return api.AddReactionContext(context.Background(), name, item)
|
||||
}
|
||||
|
||||
// AddReactionContext adds a reaction emoji to a message, file or file comment with a custom context.
|
||||
func (api *Client) AddReactionContext(ctx context.Context, name string, item ItemRef) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
@ -148,7 +154,7 @@ func (api *Client) AddReaction(name string, item ItemRef) error {
|
||||
values.Set("file_comment", string(item.Comment))
|
||||
}
|
||||
response := &SlackResponse{}
|
||||
if err := post("reactions.add", values, response, api.debug); err != nil {
|
||||
if err := post(ctx, "reactions.add", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
if !response.Ok {
|
||||
@ -159,6 +165,11 @@ func (api *Client) AddReaction(name string, item ItemRef) error {
|
||||
|
||||
// RemoveReaction removes a reaction emoji from a message, file or file comment.
|
||||
func (api *Client) RemoveReaction(name string, item ItemRef) error {
|
||||
return api.RemoveReactionContext(context.Background(), name, item)
|
||||
}
|
||||
|
||||
// RemoveReactionContext removes a reaction emoji from a message, file or file comment with a custom context.
|
||||
func (api *Client) RemoveReactionContext(ctx context.Context, name string, item ItemRef) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
@ -178,7 +189,7 @@ func (api *Client) RemoveReaction(name string, item ItemRef) error {
|
||||
values.Set("file_comment", string(item.Comment))
|
||||
}
|
||||
response := &SlackResponse{}
|
||||
if err := post("reactions.remove", values, response, api.debug); err != nil {
|
||||
if err := post(ctx, "reactions.remove", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
if !response.Ok {
|
||||
@ -189,6 +200,11 @@ func (api *Client) RemoveReaction(name string, item ItemRef) error {
|
||||
|
||||
// GetReactions returns details about the reactions on an item.
|
||||
func (api *Client) GetReactions(item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) {
|
||||
return api.GetReactionsContext(context.Background(), item, params)
|
||||
}
|
||||
|
||||
// GetReactionsContext returns details about the reactions on an item with a custom context
|
||||
func (api *Client) GetReactionsContext(ctx context.Context, item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
@ -208,7 +224,7 @@ func (api *Client) GetReactions(item ItemRef, params GetReactionsParameters) ([]
|
||||
values.Set("full", strconv.FormatBool(params.Full))
|
||||
}
|
||||
response := &getReactionsResponseFull{}
|
||||
if err := post("reactions.get", values, response, api.debug); err != nil {
|
||||
if err := post(ctx, "reactions.get", values, response, api.debug); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !response.Ok {
|
||||
@ -219,6 +235,11 @@ func (api *Client) GetReactions(item ItemRef, params GetReactionsParameters) ([]
|
||||
|
||||
// ListReactions returns information about the items a user reacted to.
|
||||
func (api *Client) ListReactions(params ListReactionsParameters) ([]ReactedItem, *Paging, error) {
|
||||
return api.ListReactionsContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// ListReactionsContext returns information about the items a user reacted to with a custom context.
|
||||
func (api *Client) ListReactionsContext(ctx context.Context, params ListReactionsParameters) ([]ReactedItem, *Paging, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
@ -235,7 +256,7 @@ func (api *Client) ListReactions(params ListReactionsParameters) ([]ReactedItem,
|
||||
values.Add("full", strconv.FormatBool(params.Full))
|
||||
}
|
||||
response := &listReactionsResponseFull{}
|
||||
err := post("reactions.list", values, response, api.debug)
|
||||
err := post(ctx, "reactions.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
80
vendor/github.com/nlopes/slack/rtm.go
generated
vendored
80
vendor/github.com/nlopes/slack/rtm.go
generated
vendored
@ -1,18 +1,58 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StartRTM calls the "rtm.start" endpoint and returns the provided URL and the full Info
|
||||
// block.
|
||||
// StartRTM calls the "rtm.start" endpoint and returns the provided URL and the full Info block.
|
||||
//
|
||||
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()`
|
||||
// on it.
|
||||
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
|
||||
func (api *Client) StartRTM() (info *Info, websocketURL string, err error) {
|
||||
return api.StartRTMContext(context.Background())
|
||||
}
|
||||
|
||||
// StartRTMContext calls the "rtm.start" endpoint and returns the provided URL and the full Info block with a custom context.
|
||||
//
|
||||
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
|
||||
func (api *Client) StartRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) {
|
||||
response := &infoResponseFull{}
|
||||
err = post("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
|
||||
err = post(ctx, "rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("post: %s", err)
|
||||
}
|
||||
if !response.Ok {
|
||||
return nil, "", response.Error
|
||||
}
|
||||
|
||||
// websocket.Dial does not accept url without the port (yet)
|
||||
// Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
|
||||
// but slack returns the address with no port, so we have to fix it
|
||||
api.Debugln("Using URL:", response.Info.URL)
|
||||
websocketURL, err = websocketizeURLPort(response.Info.URL)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("parsing response URL: %s", err)
|
||||
}
|
||||
|
||||
return &response.Info, websocketURL, nil
|
||||
}
|
||||
|
||||
// ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block.
|
||||
//
|
||||
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
|
||||
func (api *Client) ConnectRTM() (info *Info, websocketURL string, err error) {
|
||||
return api.ConnectRTMContext(context.Background())
|
||||
}
|
||||
|
||||
// ConnectRTM calls the "rtm.connect" endpoint and returns the provided URL and the compact Info block with a custom context.
|
||||
//
|
||||
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()` on it.
|
||||
func (api *Client) ConnectRTMContext(ctx context.Context) (info *Info, websocketURL string, err error) {
|
||||
response := &infoResponseFull{}
|
||||
err = post(ctx, "rtm.connect", url.Values{"token": {api.config.token}}, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("post: %s", err)
|
||||
}
|
||||
@ -33,7 +73,33 @@ func (api *Client) StartRTM() (info *Info, websocketURL string, err error) {
|
||||
}
|
||||
|
||||
// NewRTM returns a RTM, which provides a fully managed connection to
|
||||
// Slack's websocket-based Real-Time Messaging protocol./
|
||||
// Slack's websocket-based Real-Time Messaging protocol.
|
||||
func (api *Client) NewRTM() *RTM {
|
||||
return newRTM(api)
|
||||
return api.NewRTMWithOptions(nil)
|
||||
}
|
||||
|
||||
// NewRTMWithOptions 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 {
|
||||
result := &RTM{
|
||||
Client: *api,
|
||||
IncomingEvents: make(chan RTMEvent, 50),
|
||||
outgoingMessages: make(chan OutgoingMessage, 20),
|
||||
pings: make(map[int]time.Time),
|
||||
isConnected: false,
|
||||
wasIntentional: true,
|
||||
killChannel: make(chan bool),
|
||||
forcePing: make(chan bool),
|
||||
rawEvents: make(chan json.RawMessage),
|
||||
idGen: NewSafeID(1),
|
||||
}
|
||||
|
||||
if options != nil {
|
||||
result.useRTMStart = options.UseRTMStart
|
||||
} else {
|
||||
result.useRTMStart = true
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
23
vendor/github.com/nlopes/slack/search.go
generated
vendored
23
vendor/github.com/nlopes/slack/search.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -80,7 +81,7 @@ func NewSearchParameters() SearchParameters {
|
||||
}
|
||||
}
|
||||
|
||||
func (api *Client) _search(path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) {
|
||||
func (api *Client) _search(ctx context.Context, path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"query": {query},
|
||||
@ -101,7 +102,7 @@ func (api *Client) _search(path, query string, params SearchParameters, files, m
|
||||
values.Add("page", strconv.Itoa(params.Page))
|
||||
}
|
||||
response = &searchResponseFull{}
|
||||
err := post(path, values, response, api.debug)
|
||||
err := post(ctx, path, values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -113,7 +114,11 @@ func (api *Client) _search(path, query string, params SearchParameters, files, m
|
||||
}
|
||||
|
||||
func (api *Client) Search(query string, params SearchParameters) (*SearchMessages, *SearchFiles, error) {
|
||||
response, err := api._search("search.all", query, params, true, true)
|
||||
return api.SearchContext(context.Background(), query, params)
|
||||
}
|
||||
|
||||
func (api *Client) SearchContext(ctx context.Context, query string, params SearchParameters) (*SearchMessages, *SearchFiles, error) {
|
||||
response, err := api._search(ctx, "search.all", query, params, true, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -121,7 +126,11 @@ func (api *Client) Search(query string, params SearchParameters) (*SearchMessage
|
||||
}
|
||||
|
||||
func (api *Client) SearchFiles(query string, params SearchParameters) (*SearchFiles, error) {
|
||||
response, err := api._search("search.files", query, params, true, false)
|
||||
return api.SearchFilesContext(context.Background(), query, params)
|
||||
}
|
||||
|
||||
func (api *Client) SearchFilesContext(ctx context.Context, query string, params SearchParameters) (*SearchFiles, error) {
|
||||
response, err := api._search(ctx, "search.files", query, params, true, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -129,7 +138,11 @@ func (api *Client) SearchFiles(query string, params SearchParameters) (*SearchFi
|
||||
}
|
||||
|
||||
func (api *Client) SearchMessages(query string, params SearchParameters) (*SearchMessages, error) {
|
||||
response, err := api._search("search.messages", query, params, false, true)
|
||||
return api.SearchMessagesContext(context.Background(), query, params)
|
||||
}
|
||||
|
||||
func (api *Client) SearchMessagesContext(ctx context.Context, query string, params SearchParameters) (*SearchMessages, error) {
|
||||
response, err := api._search(ctx, "search.messages", query, params, false, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
10
vendor/github.com/nlopes/slack/slack.go
generated
vendored
10
vendor/github.com/nlopes/slack/slack.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"net/url"
|
||||
@ -54,8 +55,13 @@ func New(token string) *Client {
|
||||
|
||||
// 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) {
|
||||
responseFull := &authTestResponseFull{}
|
||||
err := post("auth.test", url.Values{"token": {api.config.token}}, responseFull, api.debug)
|
||||
err := post(ctx, "auth.test", url.Values{"token": {api.config.token}}, responseFull, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -71,7 +77,7 @@ func (api *Client) AuthTest() (response *AuthTestResponse, error error) {
|
||||
func (api *Client) SetDebug(debug bool) {
|
||||
api.debug = debug
|
||||
if debug && logger == nil {
|
||||
logger = log.New(os.Stdout, "nlopes/slack", log.LstdFlags | log.Lshortfile)
|
||||
logger = log.New(os.Stdout, "nlopes/slack", log.LstdFlags|log.Lshortfile)
|
||||
}
|
||||
}
|
||||
|
||||
|
35
vendor/github.com/nlopes/slack/stars.go
generated
vendored
35
vendor/github.com/nlopes/slack/stars.go
generated
vendored
@ -1,6 +1,7 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -37,6 +38,11 @@ func NewStarsParameters() StarsParameters {
|
||||
|
||||
// AddStar stars an item in a channel
|
||||
func (api *Client) AddStar(channel string, item ItemRef) error {
|
||||
return api.AddStarContext(context.Background(), channel, item)
|
||||
}
|
||||
|
||||
// AddStarContext stars an item in a channel with a custom context
|
||||
func (api *Client) AddStarContext(ctx context.Context, channel string, item ItemRef) error {
|
||||
values := url.Values{
|
||||
"channel": {channel},
|
||||
"token": {api.config.token},
|
||||
@ -51,7 +57,7 @@ func (api *Client) AddStar(channel string, item ItemRef) error {
|
||||
values.Set("file_comment", string(item.Comment))
|
||||
}
|
||||
response := &SlackResponse{}
|
||||
if err := post("stars.add", values, response, api.debug); err != nil {
|
||||
if err := post(ctx, "stars.add", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
if !response.Ok {
|
||||
@ -62,6 +68,11 @@ func (api *Client) AddStar(channel string, item ItemRef) error {
|
||||
|
||||
// RemoveStar removes a starred item from a channel
|
||||
func (api *Client) RemoveStar(channel string, item ItemRef) error {
|
||||
return api.RemoveStarContext(context.Background(), channel, item)
|
||||
}
|
||||
|
||||
// RemoveStarContext removes a starred item from a channel with a custom context
|
||||
func (api *Client) RemoveStarContext(ctx context.Context, channel string, item ItemRef) error {
|
||||
values := url.Values{
|
||||
"channel": {channel},
|
||||
"token": {api.config.token},
|
||||
@ -76,7 +87,7 @@ func (api *Client) RemoveStar(channel string, item ItemRef) error {
|
||||
values.Set("file_comment", string(item.Comment))
|
||||
}
|
||||
response := &SlackResponse{}
|
||||
if err := post("stars.remove", values, response, api.debug); err != nil {
|
||||
if err := post(ctx, "stars.remove", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
if !response.Ok {
|
||||
@ -87,6 +98,11 @@ func (api *Client) RemoveStar(channel string, item ItemRef) error {
|
||||
|
||||
// ListStars returns information about the stars a user added
|
||||
func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
|
||||
return api.ListStarsContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// ListStarsContext returns information about the stars a user added with a custom context
|
||||
func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) ([]Item, *Paging, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
@ -100,7 +116,7 @@ func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
|
||||
values.Add("page", strconv.Itoa(params.Page))
|
||||
}
|
||||
response := &listResponseFull{}
|
||||
err := post("stars.list", values, response, api.debug)
|
||||
err := post(ctx, "stars.list", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -110,7 +126,9 @@ func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
|
||||
return response.Items, &response.Paging, nil
|
||||
}
|
||||
|
||||
// GetStarred returns a list of StarredItem items. The user then has to iterate over them and figure out what they should
|
||||
// GetStarred returns a list of StarredItem items.
|
||||
//
|
||||
// The user then has to iterate over them and figure out what they should
|
||||
// be looking at according to what is in the Type.
|
||||
// for _, item := range items {
|
||||
// switch c.Type {
|
||||
@ -123,7 +141,14 @@ func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
|
||||
// This function still exists to maintain backwards compatibility.
|
||||
// I exposed it as returning []StarredItem, so it shall stay as StarredItem
|
||||
func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) {
|
||||
items, paging, err := api.ListStars(params)
|
||||
return api.GetStarredContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// GetStarredContext returns a list of StarredItem items with a custom context
|
||||
//
|
||||
// For more details see GetStarred
|
||||
func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters) ([]StarredItem, *Paging, error) {
|
||||
items, paging, err := api.ListStarsContext(ctx, params)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
56
vendor/github.com/nlopes/slack/team.go
generated
vendored
56
vendor/github.com/nlopes/slack/team.go
generated
vendored
@ -1,14 +1,15 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_LOGINS_COUNT = 100
|
||||
DEFAULT_LOGINS_PAGE = 1
|
||||
DEFAULT_LOGINS_COUNT = 100
|
||||
DEFAULT_LOGINS_PAGE = 1
|
||||
)
|
||||
|
||||
type TeamResponse struct {
|
||||
@ -26,11 +27,10 @@ type TeamInfo struct {
|
||||
|
||||
type LoginResponse struct {
|
||||
Logins []Login `json:"logins"`
|
||||
Paging `json:"paging"`
|
||||
Paging `json:"paging"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
|
||||
type Login struct {
|
||||
UserID string `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
@ -47,7 +47,6 @@ type Login struct {
|
||||
type BillableInfoResponse struct {
|
||||
BillableInfo map[string]BillingActive `json:"billable_info"`
|
||||
SlackResponse
|
||||
|
||||
}
|
||||
|
||||
type BillingActive struct {
|
||||
@ -56,8 +55,8 @@ type BillingActive struct {
|
||||
|
||||
// AccessLogParameters contains all the parameters necessary (including the optional ones) for a GetAccessLogs() request
|
||||
type AccessLogParameters struct {
|
||||
Count int
|
||||
Page int
|
||||
Count int
|
||||
Page int
|
||||
}
|
||||
|
||||
// NewAccessLogParameters provides an instance of AccessLogParameters with all the sane default values set
|
||||
@ -68,10 +67,9 @@ func NewAccessLogParameters() AccessLogParameters {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func teamRequest(path string, values url.Values, debug bool) (*TeamResponse, error) {
|
||||
func teamRequest(ctx context.Context, path string, values url.Values, debug bool) (*TeamResponse, error) {
|
||||
response := &TeamResponse{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -83,9 +81,9 @@ func teamRequest(path string, values url.Values, debug bool) (*TeamResponse, err
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func billableInfoRequest(path string, values url.Values, debug bool) (map[string]BillingActive, error) {
|
||||
func billableInfoRequest(ctx context.Context, path string, values url.Values, debug bool) (map[string]BillingActive, error) {
|
||||
response := &BillableInfoResponse{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -97,9 +95,9 @@ func billableInfoRequest(path string, values url.Values, debug bool) (map[string
|
||||
return response.BillableInfo, nil
|
||||
}
|
||||
|
||||
func accessLogsRequest(path string, values url.Values, debug bool) (*LoginResponse, error) {
|
||||
func accessLogsRequest(ctx context.Context, path string, values url.Values, debug bool) (*LoginResponse, error) {
|
||||
response := &LoginResponse{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -109,14 +107,18 @@ func accessLogsRequest(path string, values url.Values, debug bool) (*LoginRespon
|
||||
return response, nil
|
||||
}
|
||||
|
||||
|
||||
// GetTeamInfo gets the Team Information of the user
|
||||
func (api *Client) GetTeamInfo() (*TeamInfo, error) {
|
||||
return api.GetTeamInfoContext(context.Background())
|
||||
}
|
||||
|
||||
// GetTeamInfoContext gets the Team Information of the user with a custom context
|
||||
func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
|
||||
response, err := teamRequest("team.info", values, api.debug)
|
||||
response, err := teamRequest(ctx, "team.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -125,6 +127,11 @@ func (api *Client) GetTeamInfo() (*TeamInfo, error) {
|
||||
|
||||
// GetAccessLogs retrieves a page of logins according to the parameters given
|
||||
func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging, error) {
|
||||
return api.GetAccessLogsContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// GetAccessLogsContext retrieves a page of logins according to the parameters given with a custom context
|
||||
func (api *Client) GetAccessLogsContext(ctx context.Context, params AccessLogParameters) ([]Login, *Paging, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
@ -134,7 +141,7 @@ func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging,
|
||||
if params.Page != DEFAULT_LOGINS_PAGE {
|
||||
values.Add("page", strconv.Itoa(params.Page))
|
||||
}
|
||||
response, err := accessLogsRequest("team.accessLogs", values, api.debug)
|
||||
response, err := accessLogsRequest(ctx, "team.accessLogs", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -142,19 +149,28 @@ func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging,
|
||||
}
|
||||
|
||||
func (api *Client) GetBillableInfo(user string) (map[string]BillingActive, error) {
|
||||
return api.GetBillableInfoContext(context.Background(), user)
|
||||
}
|
||||
|
||||
func (api *Client) GetBillableInfoContext(ctx context.Context, user string) (map[string]BillingActive, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"user": {user},
|
||||
"user": {user},
|
||||
}
|
||||
|
||||
return billableInfoRequest("team.billableInfo", values, api.debug)
|
||||
return billableInfoRequest(ctx, "team.billableInfo", values, api.debug)
|
||||
}
|
||||
|
||||
// GetBillableInfoForTeam returns the billing_active status of all users on the team.
|
||||
func (api *Client) GetBillableInfoForTeam() (map[string]BillingActive, error) {
|
||||
return api.GetBillableInfoForTeamContext(context.Background())
|
||||
}
|
||||
|
||||
// GetBillableInfoForTeamContext returns the billing_active status of all users on the team with a custom context
|
||||
func (api *Client) GetBillableInfoForTeamContext(ctx context.Context) (map[string]BillingActive, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
|
||||
return billableInfoRequest("team.billableInfo", values, api.debug)
|
||||
return billableInfoRequest(ctx, "team.billableInfo", values, api.debug)
|
||||
}
|
||||
|
210
vendor/github.com/nlopes/slack/usergroups.go
generated
vendored
Normal file
210
vendor/github.com/nlopes/slack/usergroups.go
generated
vendored
Normal file
@ -0,0 +1,210 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UserGroup contains all the information of a user group
|
||||
type UserGroup struct {
|
||||
ID string `json:"id"`
|
||||
TeamID string `json:"team_id"`
|
||||
IsUserGroup bool `json:"is_usergroup"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Handle string `json:"handle"`
|
||||
IsExternal bool `json:"is_external"`
|
||||
DateCreate JSONTime `json:"date_create"`
|
||||
DateUpdate JSONTime `json:"date_update"`
|
||||
DateDelete JSONTime `json:"date_delete"`
|
||||
AutoType string `json:"auto_type"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
UpdatedBy string `json:"updated_by"`
|
||||
DeletedBy string `json:"deleted_by"`
|
||||
Prefs UserGroupPrefs `json:"prefs"`
|
||||
UserCount int `json:"user_count"`
|
||||
}
|
||||
|
||||
// UserGroupPrefs contains default channels and groups (private channels)
|
||||
type UserGroupPrefs struct {
|
||||
Channels []string `json:"channels"`
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
|
||||
type userGroupResponseFull struct {
|
||||
UserGroups []UserGroup `json:"usergroups"`
|
||||
UserGroup UserGroup `json:"usergroup"`
|
||||
Users []string `json:"users"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
func userGroupRequest(ctx context.Context, path string, values url.Values, debug bool) (*userGroupResponseFull, error) {
|
||||
response := &userGroupResponseFull{}
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !response.Ok {
|
||||
return nil, errors.New(response.Error)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// CreateUserGroup creates a new user group
|
||||
func (api *Client) CreateUserGroup(userGroup UserGroup) (UserGroup, error) {
|
||||
return api.CreateUserGroupContext(context.Background(), userGroup)
|
||||
}
|
||||
|
||||
// CreateUserGroupContext creates a new user group with a custom context
|
||||
func (api *Client) CreateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"name": {userGroup.Name},
|
||||
}
|
||||
|
||||
if userGroup.Handle != "" {
|
||||
values["handle"] = []string{userGroup.Handle}
|
||||
}
|
||||
|
||||
if userGroup.Description != "" {
|
||||
values["description"] = []string{userGroup.Description}
|
||||
}
|
||||
|
||||
if len(userGroup.Prefs.Channels) > 0 {
|
||||
values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")}
|
||||
}
|
||||
|
||||
response, err := userGroupRequest(ctx, "usergroups.create", values, api.debug)
|
||||
if err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
||||
return response.UserGroup, nil
|
||||
}
|
||||
|
||||
// DisableUserGroup disables an existing user group
|
||||
func (api *Client) DisableUserGroup(userGroup string) (UserGroup, error) {
|
||||
return api.DisableUserGroupContext(context.Background(), userGroup)
|
||||
}
|
||||
|
||||
// DisableUserGroupContext disables an existing user group with a custom context
|
||||
func (api *Client) DisableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"usergroup": {userGroup},
|
||||
}
|
||||
|
||||
response, err := userGroupRequest(ctx, "usergroups.disable", values, api.debug)
|
||||
if err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
||||
return response.UserGroup, nil
|
||||
}
|
||||
|
||||
// EnableUserGroup enables an existing user group
|
||||
func (api *Client) EnableUserGroup(userGroup string) (UserGroup, error) {
|
||||
return api.EnableUserGroupContext(context.Background(), userGroup)
|
||||
}
|
||||
|
||||
// EnableUserGroupContext enables an existing user group with a custom context
|
||||
func (api *Client) EnableUserGroupContext(ctx context.Context, userGroup string) (UserGroup, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"usergroup": {userGroup},
|
||||
}
|
||||
|
||||
response, err := userGroupRequest(ctx, "usergroups.enable", values, api.debug)
|
||||
if err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
||||
return response.UserGroup, nil
|
||||
}
|
||||
|
||||
// GetUserGroups returns a list of user groups for the team
|
||||
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) ([]UserGroup, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
|
||||
response, err := userGroupRequest(ctx, "usergroups.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.UserGroups, nil
|
||||
}
|
||||
|
||||
// UpdateUserGroup will update an existing user group
|
||||
func (api *Client) UpdateUserGroup(userGroup UserGroup) (UserGroup, error) {
|
||||
return api.UpdateUserGroupContext(context.Background(), userGroup)
|
||||
}
|
||||
|
||||
// UpdateUserGroupContext will update an existing user group with a custom context
|
||||
func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"usergroup": {userGroup.ID},
|
||||
}
|
||||
|
||||
if userGroup.Name != "" {
|
||||
values["name"] = []string{userGroup.Name}
|
||||
}
|
||||
|
||||
if userGroup.Handle != "" {
|
||||
values["handle"] = []string{userGroup.Handle}
|
||||
}
|
||||
|
||||
if userGroup.Description != "" {
|
||||
values["description"] = []string{userGroup.Description}
|
||||
}
|
||||
|
||||
response, err := userGroupRequest(ctx, "usergroups.update", values, api.debug)
|
||||
if err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
||||
return response.UserGroup, nil
|
||||
}
|
||||
|
||||
// GetUserGroupMembers will retrieve the current list of users in a group
|
||||
func (api *Client) GetUserGroupMembers(userGroup string) ([]string, error) {
|
||||
return api.GetUserGroupMembersContext(context.Background(), userGroup)
|
||||
}
|
||||
|
||||
// GetUserGroupMembersContext will retrieve the current list of users in a group with a custom context
|
||||
func (api *Client) GetUserGroupMembersContext(ctx context.Context, userGroup string) ([]string, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"usergroup": {userGroup},
|
||||
}
|
||||
|
||||
response, err := userGroupRequest(ctx, "usergroups.users.list", values, api.debug)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return response.Users, nil
|
||||
}
|
||||
|
||||
// UpdateUserGroupMembers will update the members of an existing user group
|
||||
func (api *Client) UpdateUserGroupMembers(userGroup string, members string) (UserGroup, error) {
|
||||
return api.UpdateUserGroupMembersContext(context.Background(), userGroup, members)
|
||||
}
|
||||
|
||||
// UpdateUserGroupMembersContext will update the members of an existing user group with a custom context
|
||||
func (api *Client) UpdateUserGroupMembersContext(ctx context.Context, userGroup string, members string) (UserGroup, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"usergroup": {userGroup},
|
||||
"users": {members},
|
||||
}
|
||||
|
||||
response, err := userGroupRequest(ctx, "usergroups.users.update", values, api.debug)
|
||||
if err != nil {
|
||||
return UserGroup{}, err
|
||||
}
|
||||
return response.UserGroup, nil
|
||||
}
|
187
vendor/github.com/nlopes/slack/users.go
generated
vendored
187
vendor/github.com/nlopes/slack/users.go
generated
vendored
@ -1,10 +1,18 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_USER_PHOTO_CROP_X = -1
|
||||
DEFAULT_USER_PHOTO_CROP_Y = -1
|
||||
DEFAULT_USER_PHOTO_CROP_W = -1
|
||||
)
|
||||
|
||||
// UserProfile contains all the information details of a given user
|
||||
type UserProfile struct {
|
||||
FirstName string `json:"first_name"`
|
||||
@ -23,6 +31,8 @@ type UserProfile struct {
|
||||
Title string `json:"title"`
|
||||
BotID string `json:"bot_id,omitempty"`
|
||||
ApiAppID string `json:"api_app_id,omitempty"`
|
||||
StatusText string `json:"status_text,omitempty"`
|
||||
StatusEmoji string `json:"status_emoji,omitempty"`
|
||||
}
|
||||
|
||||
// User contains all the information of a user
|
||||
@ -97,9 +107,23 @@ type userResponseFull struct {
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
func userRequest(path string, values url.Values, debug bool) (*userResponseFull, error) {
|
||||
type UserSetPhotoParams struct {
|
||||
CropX int
|
||||
CropY int
|
||||
CropW int
|
||||
}
|
||||
|
||||
func NewUserSetPhotoParams() UserSetPhotoParams {
|
||||
return UserSetPhotoParams{
|
||||
CropX: DEFAULT_USER_PHOTO_CROP_X,
|
||||
CropY: DEFAULT_USER_PHOTO_CROP_Y,
|
||||
CropW: DEFAULT_USER_PHOTO_CROP_W,
|
||||
}
|
||||
}
|
||||
|
||||
func userRequest(ctx context.Context, path string, values url.Values, debug bool) (*userResponseFull, error) {
|
||||
response := &userResponseFull{}
|
||||
err := post(path, values, response, debug)
|
||||
err := post(ctx, path, values, response, debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -111,11 +135,16 @@ func userRequest(path string, values url.Values, debug bool) (*userResponseFull,
|
||||
|
||||
// GetUserPresence will retrieve the current presence status of given user.
|
||||
func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
|
||||
return api.GetUserPresenceContext(context.Background(), user)
|
||||
}
|
||||
|
||||
// GetUserPresenceContext will retrieve the current presence status of given user with a custom context.
|
||||
func (api *Client) GetUserPresenceContext(ctx context.Context, user string) (*UserPresence, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"user": {user},
|
||||
}
|
||||
response, err := userRequest("users.getPresence", values, api.debug)
|
||||
response, err := userRequest(ctx, "users.getPresence", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -124,11 +153,16 @@ func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
|
||||
|
||||
// GetUserInfo will retrieve the complete user information
|
||||
func (api *Client) GetUserInfo(user string) (*User, error) {
|
||||
return api.GetUserInfoContext(context.Background(), user)
|
||||
}
|
||||
|
||||
// GetUserInfoContext will retrieve the complete user information with a custom context
|
||||
func (api *Client) GetUserInfoContext(ctx context.Context, user string) (*User, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"user": {user},
|
||||
}
|
||||
response, err := userRequest("users.info", values, api.debug)
|
||||
response, err := userRequest(ctx, "users.info", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -137,11 +171,16 @@ func (api *Client) GetUserInfo(user string) (*User, error) {
|
||||
|
||||
// GetUsers returns the list of users (with their detailed information)
|
||||
func (api *Client) GetUsers() ([]User, error) {
|
||||
return api.GetUsersContext(context.Background())
|
||||
}
|
||||
|
||||
// GetUsersContext returns the list of users (with their detailed information) with a custom context
|
||||
func (api *Client) GetUsersContext(ctx context.Context) ([]User, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"presence": {"1"},
|
||||
}
|
||||
response, err := userRequest("users.list", values, api.debug)
|
||||
response, err := userRequest(ctx, "users.list", values, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -150,10 +189,15 @@ func (api *Client) GetUsers() ([]User, error) {
|
||||
|
||||
// SetUserAsActive marks the currently authenticated user as active
|
||||
func (api *Client) SetUserAsActive() error {
|
||||
return api.SetUserAsActiveContext(context.Background())
|
||||
}
|
||||
|
||||
// SetUserAsActiveContext marks the currently authenticated user as active with a custom context
|
||||
func (api *Client) SetUserAsActiveContext(ctx context.Context) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
_, err := userRequest("users.setActive", values, api.debug)
|
||||
_, err := userRequest(ctx, "users.setActive", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -162,11 +206,16 @@ func (api *Client) SetUserAsActive() error {
|
||||
|
||||
// SetUserPresence changes the currently authenticated user presence
|
||||
func (api *Client) SetUserPresence(presence string) error {
|
||||
return api.SetUserPresenceContext(context.Background(), presence)
|
||||
}
|
||||
|
||||
// SetUserPresenceContext changes the currently authenticated user presence with a custom context
|
||||
func (api *Client) SetUserPresenceContext(ctx context.Context, presence string) error {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"presence": {presence},
|
||||
}
|
||||
_, err := userRequest("users.setPresence", values, api.debug)
|
||||
_, err := userRequest(ctx, "users.setPresence", values, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -176,11 +225,16 @@ func (api *Client) SetUserPresence(presence string) error {
|
||||
|
||||
// GetUserIdentity will retrieve user info available per identity scopes
|
||||
func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) {
|
||||
return api.GetUserIdentityContext(context.Background())
|
||||
}
|
||||
|
||||
// GetUserIdentityContext will retrieve user info available per identity scopes with a custom context
|
||||
func (api *Client) GetUserIdentityContext(ctx context.Context) (*UserIdentityResponse, error) {
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
response := &UserIdentityResponse{}
|
||||
err := post("users.identity", values, response, api.debug)
|
||||
err := post(ctx, "users.identity", values, response, api.debug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -189,3 +243,120 @@ func (api *Client) GetUserIdentity() (*UserIdentityResponse, error) {
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// SetUserPhoto changes the currently authenticated user's profile image
|
||||
func (api *Client) SetUserPhoto(ctx context.Context, image string, params UserSetPhotoParams) error {
|
||||
return api.SetUserPhoto(context.Background(), image, params)
|
||||
}
|
||||
|
||||
// SetUserPhotoContext changes the currently authenticated user's profile image using a custom context
|
||||
func (api *Client) SetUserPhotoContext(ctx context.Context, image string, params UserSetPhotoParams) error {
|
||||
response := &SlackResponse{}
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
if params.CropX != DEFAULT_USER_PHOTO_CROP_X {
|
||||
values.Add("crop_x", string(params.CropX))
|
||||
}
|
||||
if params.CropY != DEFAULT_USER_PHOTO_CROP_Y {
|
||||
values.Add("crop_y", string(params.CropY))
|
||||
}
|
||||
if params.CropW != DEFAULT_USER_PHOTO_CROP_W {
|
||||
values.Add("crop_w", string(params.CropW))
|
||||
}
|
||||
err := postLocalWithMultipartResponse(ctx, "users.setPhoto", image, "image", values, response, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !response.Ok {
|
||||
return errors.New(response.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUserPhoto deletes the current authenticated user's profile image
|
||||
func (api *Client) DeleteUserPhoto() error {
|
||||
return api.DeleteUserPhotoContext(context.Background())
|
||||
}
|
||||
|
||||
// DeleteUserPhotoContext deletes the current authenticated user's profile image with a custom context
|
||||
func (api *Client) DeleteUserPhotoContext(ctx context.Context) error {
|
||||
response := &SlackResponse{}
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
}
|
||||
err := post(ctx, "users.deletePhoto", values, response, api.debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !response.Ok {
|
||||
return errors.New(response.Error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
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) 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.
|
||||
//
|
||||
// We use an anonymous struct over UserProfile because to unset the values
|
||||
// on the User's profile we cannot use the `json:"omitempty"` tag. This is
|
||||
// because an empty string ("") is what's used to unset the values. Check
|
||||
// out the API docs for more details:
|
||||
//
|
||||
// - 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"`
|
||||
}{
|
||||
StatusText: statusText,
|
||||
StatusEmoji: statusEmoji,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
values := url.Values{
|
||||
"token": {api.config.token},
|
||||
"profile": {string(profile)},
|
||||
}
|
||||
|
||||
response := &userResponseFull{}
|
||||
|
||||
if err = post(ctx, "users.profile.set", values, response, api.debug); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !response.Ok {
|
||||
return errors.New(response.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnsetUserCustomStatus removes the custom status message for the currently
|
||||
// authenticated user. This is a convenience method that wraps (*Client).SetUserCustomStatus().
|
||||
func (api *Client) UnsetUserCustomStatus() error {
|
||||
return api.UnsetUserCustomStatusContext(context.Background())
|
||||
}
|
||||
|
||||
// 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, "", "")
|
||||
}
|
||||
|
32
vendor/github.com/nlopes/slack/websocket.go
generated
vendored
32
vendor/github.com/nlopes/slack/websocket.go
generated
vendored
@ -17,7 +17,7 @@ const (
|
||||
// RTM represents a managed websocket connection. It also supports
|
||||
// all the methods of the `Client` type.
|
||||
//
|
||||
// Create this element with Client's NewRTM().
|
||||
// Create this element with Client's NewRTM() or NewRTMWithOptions(*RTMOptions)
|
||||
type RTM struct {
|
||||
idGen IDGenerator
|
||||
pings map[int]time.Time
|
||||
@ -38,23 +38,23 @@ type RTM struct {
|
||||
|
||||
// UserDetails upon connection
|
||||
info *Info
|
||||
|
||||
// useRTMStart should be set to true if you want to use
|
||||
// rtm.start to connect to Slack, otherwise it will use
|
||||
// rtm.connect
|
||||
useRTMStart bool
|
||||
}
|
||||
|
||||
// NewRTM returns a RTM, which provides a fully managed connection to
|
||||
// Slack's websocket-based Real-Time Messaging protocol.
|
||||
func newRTM(api *Client) *RTM {
|
||||
return &RTM{
|
||||
Client: *api,
|
||||
IncomingEvents: make(chan RTMEvent, 50),
|
||||
outgoingMessages: make(chan OutgoingMessage, 20),
|
||||
pings: make(map[int]time.Time),
|
||||
isConnected: false,
|
||||
wasIntentional: true,
|
||||
killChannel: make(chan bool),
|
||||
forcePing: make(chan bool),
|
||||
rawEvents: make(chan json.RawMessage),
|
||||
idGen: NewSafeID(1),
|
||||
}
|
||||
// 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.
|
||||
|
27
vendor/github.com/nlopes/slack/websocket_managed_conn.go
generated
vendored
27
vendor/github.com/nlopes/slack/websocket_managed_conn.go
generated
vendored
@ -29,7 +29,7 @@ func (rtm *RTM) ManageConnection() {
|
||||
connectionCount++
|
||||
// start trying to connect
|
||||
// the returned err is already passed onto the IncomingEvents channel
|
||||
info, conn, err := rtm.connect(connectionCount)
|
||||
info, conn, err := rtm.connect(connectionCount, rtm.useRTMStart)
|
||||
// if err != nil then the connection is sucessful - otherwise it is
|
||||
// fatal
|
||||
if err != nil {
|
||||
@ -64,7 +64,9 @@ func (rtm *RTM) ManageConnection() {
|
||||
// connect attempts to connect to the slack websocket API. It handles any
|
||||
// errors that occur while connecting and will return once a connection
|
||||
// has been successfully opened.
|
||||
func (rtm *RTM) connect(connectionCount int) (*Info, *websocket.Conn, error) {
|
||||
// If useRTMStart is false then it uses rtm.connect to create the connection,
|
||||
// otherwise it uses rtm.start.
|
||||
func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocket.Conn, error) {
|
||||
// used to provide exponential backoff wait time with jitter before trying
|
||||
// to connect to slack again
|
||||
boff := &backoff{
|
||||
@ -81,7 +83,7 @@ func (rtm *RTM) connect(connectionCount int) (*Info, *websocket.Conn, error) {
|
||||
ConnectionCount: connectionCount,
|
||||
}}
|
||||
// attempt to start the connection
|
||||
info, conn, err := rtm.startRTMAndDial()
|
||||
info, conn, err := rtm.startRTMAndDial(useRTMStart)
|
||||
if err == nil {
|
||||
return info, conn, nil
|
||||
}
|
||||
@ -105,10 +107,19 @@ func (rtm *RTM) connect(connectionCount int) (*Info, *websocket.Conn, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// startRTMAndDial attemps to connect to the slack websocket. It returns the
|
||||
// full information returned by the "rtm.start" method on the slack API.
|
||||
func (rtm *RTM) startRTMAndDial() (*Info, *websocket.Conn, error) {
|
||||
info, url, err := rtm.StartRTM()
|
||||
// startRTMAndDial attempts to connect to the slack websocket. If useRTMStart is true,
|
||||
// then it returns the full information returned by the "rtm.start" method on the
|
||||
// slack API. Else it uses the "rtm.connect" method to connect
|
||||
func (rtm *RTM) startRTMAndDial(useRTMStart bool) (*Info, *websocket.Conn, error) {
|
||||
var info *Info
|
||||
var url string
|
||||
var err error
|
||||
|
||||
if useRTMStart {
|
||||
info, url, err = rtm.StartRTM()
|
||||
} else {
|
||||
info, url, err = rtm.ConnectRTM()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -291,6 +302,8 @@ func (rtm *RTM) handleRawEvent(rawEvent json.RawMessage) {
|
||||
rtm.IncomingEvents <- RTMEvent{"hello", &HelloEvent{}}
|
||||
case "pong":
|
||||
rtm.handlePong(rawEvent)
|
||||
case "desktop_notification":
|
||||
rtm.Debugln("Received desktop notification, ignoring")
|
||||
default:
|
||||
rtm.handleEvent(event.Type, rawEvent)
|
||||
}
|
||||
|
8
vendor/github.com/nlopes/slack/websocket_misc.go
generated
vendored
8
vendor/github.com/nlopes/slack/websocket_misc.go
generated
vendored
@ -76,8 +76,12 @@ type UserChangeEvent struct {
|
||||
|
||||
// EmojiChangedEvent represents the emoji changed event
|
||||
type EmojiChangedEvent struct {
|
||||
Type string `json:"type"`
|
||||
EventTimestamp string `json:"event_ts"`
|
||||
Type string `json:"type"`
|
||||
SubType string `json:"subtype"`
|
||||
Name string `json:"name"`
|
||||
Names []string `json:"names"`
|
||||
Value string `json:"value"`
|
||||
EventTimestamp string `json:"event_ts"`
|
||||
}
|
||||
|
||||
// CommandsChangedEvent represents the commands changed event
|
||||
|
8
vendor/github.com/nsf/termbox-go/README.md
generated
vendored
8
vendor/github.com/nsf/termbox-go/README.md
generated
vendored
@ -14,7 +14,7 @@ There are also some interesting projects using termbox-go:
|
||||
- [sokoban-go](https://github.com/rn2dy/sokoban-go) is an implementation of sokoban game.
|
||||
- [hecate](https://github.com/evanmiller/hecate) is a hex editor designed by Satan.
|
||||
- [httopd](https://github.com/verdverm/httopd) is top for httpd logs.
|
||||
- [mop](https://github.com/michaeldv/mop) is stock market tracker for hackers.
|
||||
- [mop](https://github.com/mop-tracker/mop) is stock market tracker for hackers.
|
||||
- [termui](https://github.com/gizak/termui) is a terminal dashboard.
|
||||
- [termloop](https://github.com/JoelOtter/termloop) is a terminal game engine.
|
||||
- [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) is a XTerm 256 color chart.
|
||||
@ -27,6 +27,12 @@ There are also some interesting projects using termbox-go:
|
||||
- [lf](https://github.com/gokcehan/lf) is a terminal file manager
|
||||
- [rat](https://github.com/ericfreese/rat) lets you compose shell commands to build terminal applications.
|
||||
- [httplab](https://github.com/gchaincl/httplab) An interactive web server.
|
||||
- [tetris](https://github.com/MichaelS11/tetris) Go Tetris with AI option
|
||||
- [wot](https://github.com/kyu-suke/wot) Wait time during command is completed.
|
||||
- [2048-go](https://github.com/1984weed/2048-go) is 2048 in Go
|
||||
- [jv](https://github.com/maxzender/jv) helps you view JSON on the command-line.
|
||||
- [pinger](https://github.com/hirose31/pinger) helps you to monitor numerous hosts using ICMP ECHO_REQUEST.
|
||||
- [vixl44](https://github.com/sebashwa/vixl44) lets you create pixel art inside your terminal using vim movements
|
||||
|
||||
### API reference
|
||||
[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go)
|
||||
|
4
vendor/github.com/nsf/termbox-go/api.go
generated
vendored
4
vendor/github.com/nsf/termbox-go/api.go
generated
vendored
@ -418,12 +418,12 @@ func SetInputMode(mode InputMode) InputMode {
|
||||
//
|
||||
// 3. Output216 => [1..216]
|
||||
// This mode supports the 3rd range of the 256 mode only.
|
||||
// But you dont need to provide an offset.
|
||||
// But you don't need to provide an offset.
|
||||
//
|
||||
// 4. OutputGrayscale => [1..26]
|
||||
// This mode supports the 4th range of the 256 mode
|
||||
// and black and white colors from 3th range of the 256 mode
|
||||
// But you dont need to provide an offset.
|
||||
// But you don't need to provide an offset.
|
||||
//
|
||||
// In all modes, 0x00 represents the default color.
|
||||
//
|
||||
|
35
vendor/github.com/nsf/termbox-go/termbox_windows.go
generated
vendored
35
vendor/github.com/nsf/termbox-go/termbox_windows.go
generated
vendored
@ -63,6 +63,8 @@ const (
|
||||
mouse_lmb = 0x1
|
||||
mouse_rmb = 0x2
|
||||
mouse_mmb = 0x4 | 0x8 | 0x10
|
||||
SM_CXMIN = 28
|
||||
SM_CYMIN = 29
|
||||
)
|
||||
|
||||
func (this coord) uintptr() uintptr {
|
||||
@ -70,6 +72,7 @@ func (this coord) uintptr() uintptr {
|
||||
}
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
var moduser32 = syscall.NewLazyDLL("user32.dll")
|
||||
var is_cjk = runewidth.IsEastAsian()
|
||||
|
||||
var (
|
||||
@ -91,6 +94,7 @@ var (
|
||||
proc_create_event = kernel32.NewProc("CreateEventW")
|
||||
proc_wait_for_multiple_objects = kernel32.NewProc("WaitForMultipleObjects")
|
||||
proc_set_event = kernel32.NewProc("SetEvent")
|
||||
get_system_metrics = moduser32.NewProc("GetSystemMetrics")
|
||||
)
|
||||
|
||||
func set_console_active_screen_buffer(h syscall.Handle) (err error) {
|
||||
@ -397,15 +401,44 @@ func get_term_size(out syscall.Handle) coord {
|
||||
return tmp_info.size
|
||||
}
|
||||
|
||||
func get_win_min_size(out syscall.Handle) coord {
|
||||
x, _, err := get_system_metrics.Call(SM_CXMIN)
|
||||
y, _, err := get_system_metrics.Call(SM_CYMIN)
|
||||
|
||||
if x == 0 || y == 0 {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return coord{
|
||||
x: short(x),
|
||||
y: short(y),
|
||||
}
|
||||
}
|
||||
|
||||
func get_win_size(out syscall.Handle) coord {
|
||||
err := get_console_screen_buffer_info(out, &tmp_info)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return coord{
|
||||
|
||||
min_size := get_win_min_size(out)
|
||||
|
||||
size := coord{
|
||||
x: tmp_info.window.right - tmp_info.window.left + 1,
|
||||
y: tmp_info.window.bottom - tmp_info.window.top + 1,
|
||||
}
|
||||
|
||||
if size.x < min_size.x {
|
||||
size.x = min_size.x
|
||||
}
|
||||
|
||||
if size.y < min_size.y {
|
||||
size.y = min_size.y
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
func update_size_maybe() {
|
||||
|
21
vendor/github.com/renstrom/fuzzysearch/LICENSE
generated
vendored
Normal file
21
vendor/github.com/renstrom/fuzzysearch/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Peter Renström
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
167
vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go
generated
vendored
Normal file
167
vendor/github.com/renstrom/fuzzysearch/fuzzy/fuzzy.go
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
// 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 (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var noop = func(r rune) rune { return r }
|
||||
|
||||
// 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, noop)
|
||||
}
|
||||
|
||||
// MatchFold is a case-insensitive version of Match.
|
||||
func MatchFold(source, target string) bool {
|
||||
return match(source, target, unicode.ToLower)
|
||||
}
|
||||
|
||||
func match(source, target string, fn func(rune) rune) bool {
|
||||
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 fn(r1) == fn(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, noop)
|
||||
}
|
||||
|
||||
// FindFold is a case-insensitive version of Find.
|
||||
func FindFold(source string, targets []string) []string {
|
||||
return find(source, targets, unicode.ToLower)
|
||||
}
|
||||
|
||||
func find(source string, targets []string, fn func(rune) rune) []string {
|
||||
var matches []string
|
||||
|
||||
for _, target := range targets {
|
||||
if match(source, target, fn) {
|
||||
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, noop)
|
||||
}
|
||||
|
||||
// RankMatchFold is a case-insensitive version of RankMatch.
|
||||
func RankMatchFold(source, target string) int {
|
||||
return rank(source, target, unicode.ToLower)
|
||||
}
|
||||
|
||||
func rank(source, target string, fn func(rune) rune) int {
|
||||
lenDiff := len(target) - len(source)
|
||||
|
||||
if lenDiff < 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
if lenDiff == 0 && source == target {
|
||||
return 0
|
||||
}
|
||||
|
||||
runeDiff := 0
|
||||
|
||||
Outer:
|
||||
for _, r1 := range source {
|
||||
for i, r2 := range target {
|
||||
if fn(r1) == fn(r2) {
|
||||
target = target[i+utf8.RuneLen(r2):]
|
||||
continue Outer
|
||||
} else {
|
||||
runeDiff++
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Count up remaining char
|
||||
for len(target) > 0 {
|
||||
target = target[utf8.RuneLen(rune(target[0])):]
|
||||
runeDiff++
|
||||
}
|
||||
|
||||
return runeDiff
|
||||
}
|
||||
|
||||
// RankFind is similar to Find, except it will also rank all matches using
|
||||
// Levenshtein distance.
|
||||
func RankFind(source string, targets []string) Ranks {
|
||||
var r Ranks
|
||||
for _, target := range find(source, targets, noop) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// RankFindFold is a case-insensitive version of RankFind.
|
||||
func RankFindFold(source string, targets []string) Ranks {
|
||||
var r Ranks
|
||||
for _, target := range find(source, targets, unicode.ToLower) {
|
||||
distance := LevenshteinDistance(source, target)
|
||||
r = append(r, Rank{source, target, distance})
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
43
vendor/github.com/renstrom/fuzzysearch/fuzzy/levenshtein.go
generated
vendored
Normal file
43
vendor/github.com/renstrom/fuzzysearch/fuzzy/levenshtein.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package fuzzy
|
||||
|
||||
// LevenshteinDistance measures the difference between two strings.
|
||||
// The Levenshtein distance between two words is the minimum number of
|
||||
// single-character edits (i.e. insertions, deletions or substitutions)
|
||||
// required to change one word into the other.
|
||||
//
|
||||
// This implemention is optimized to use O(min(m,n)) space and is based on the
|
||||
// optimized C version found here:
|
||||
// http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Levenshtein_distance#C
|
||||
func LevenshteinDistance(s, t string) int {
|
||||
r1, r2 := []rune(s), []rune(t)
|
||||
column := make([]int, len(r1)+1)
|
||||
|
||||
for y := 1; y <= len(r1); y++ {
|
||||
column[y] = y
|
||||
}
|
||||
|
||||
for x := 1; x <= len(r2); x++ {
|
||||
column[0] = x
|
||||
|
||||
for y, lastDiag := 1, x-1; y <= len(r1); y++ {
|
||||
oldDiag := column[y]
|
||||
cost := 0
|
||||
if r1[y-1] != r2[x-1] {
|
||||
cost = 1
|
||||
}
|
||||
column[y] = min(column[y]+1, column[y-1]+1, lastDiag+cost)
|
||||
lastDiag = oldDiag
|
||||
}
|
||||
}
|
||||
|
||||
return column[len(r1)]
|
||||
}
|
||||
|
||||
func min(a, b, c int) int {
|
||||
if a < b && a < c {
|
||||
return a
|
||||
} else if b < c {
|
||||
return b
|
||||
}
|
||||
return c
|
||||
}
|
210
vendor/vendor.json
vendored
210
vendor/vendor.json
vendored
@ -2,6 +2,58 @@
|
||||
"comment": "",
|
||||
"ignore": "test",
|
||||
"package": [
|
||||
{
|
||||
"path": "bufio",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "bytes",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "context",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "crypto/rand",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "crypto/sha1",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "crypto/tls",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "encoding/base64",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "encoding/binary",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "encoding/hex",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "encoding/json",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "errors",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "flag",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "fmt",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "CbpC2ha+GTTuROMyyLVd/L3O+8Y=",
|
||||
"path": "github.com/erroneousboat/termui",
|
||||
@ -9,10 +61,10 @@
|
||||
"revisionTime": "2017-09-23T11:51:41Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "7hln62oZPZmyqEmgXaybf9WxQ7A=",
|
||||
"checksumSHA1": "zpFCi2nWiwR5F2INAJOvQqsj7lY=",
|
||||
"path": "github.com/maruel/panicparse/stack",
|
||||
"revision": "868abbf1ebac0fb2760cd9a410a5bd2f5afb2f76",
|
||||
"revisionTime": "2017-07-16T23:31:26Z"
|
||||
"revision": "766956aceb8ff49664065ae50bef0ae8a0a83ec4",
|
||||
"revisionTime": "2017-11-29T15:16:18Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "cJE7dphDlam/i7PhnsyosNWtbd4=",
|
||||
@ -33,16 +85,158 @@
|
||||
"revisionTime": "2017-07-25T12:17:30Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "2si62NpJ4Rw8vVb7+LOfnKJ3y2Q=",
|
||||
"checksumSHA1": "Zi8hWUMkKtii1fc6YaGgoYAssIw=",
|
||||
"path": "github.com/nsf/termbox-go",
|
||||
"revision": "4ed959e0540971545eddb8c75514973d670cf739",
|
||||
"revisionTime": "2017-07-10T10:34:07Z"
|
||||
"revision": "aa4a75b1c20a2b03751b1a9f7e41d58bd6f71c43",
|
||||
"revisionTime": "2017-11-04T16:23:16Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "DF3jZEw4lCq/SEaC7DIl/R+7S70=",
|
||||
"path": "github.com/renstrom/fuzzysearch/fuzzy",
|
||||
"revision": "2d205ac6ec17a839a94bdbfd16d2fa6c6dada2e0",
|
||||
"revisionTime": "2016-03-31T20:48:55Z"
|
||||
},
|
||||
{
|
||||
"path": "go/ast",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "go/parser",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "go/token",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "7EZyXN0EmZLgGxZxK01IJua4c8o=",
|
||||
"path": "golang.org/x/net/websocket",
|
||||
"revision": "f5079bd7f6f74e23c4d65efa0f4ce14cbd6a3c0f",
|
||||
"revisionTime": "2017-07-19T21:11:51Z"
|
||||
"revision": "a8b9294777976932365dabb6640cf1468d95c70f",
|
||||
"revisionTime": "2017-11-29T19:21:16Z"
|
||||
},
|
||||
{
|
||||
"path": "html",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "image",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "io",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "io/ioutil",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "log",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "math",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "math/rand",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "mime/multipart",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "net",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "net/http",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "net/http/httputil",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "net/url",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "os",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "os/signal",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "os/user",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "path",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "path/filepath",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "reflect",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "regexp",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "runtime",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "runtime/debug",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "sort",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "strconv",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "strings",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "sync",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "syscall",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "time",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "unicode",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "unicode/utf16",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "unicode/utf8",
|
||||
"revision": ""
|
||||
},
|
||||
{
|
||||
"path": "unsafe",
|
||||
"revision": ""
|
||||
}
|
||||
],
|
||||
"rootPath": "github.com/erroneousboat/slack-term"
|
||||
|
@ -1,48 +0,0 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"github.com/erroneousboat/termui"
|
||||
|
||||
"github.com/erroneousboat/slack-term/components"
|
||||
"github.com/erroneousboat/slack-term/service"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
Input *components.Input
|
||||
Chat *components.Chat
|
||||
Channels *components.Channels
|
||||
Mode *components.Mode
|
||||
}
|
||||
|
||||
func CreateChatView(svc *service.SlackService) *View {
|
||||
input := components.CreateInput()
|
||||
|
||||
channels := components.CreateChannels(svc, input.Par.Height)
|
||||
|
||||
chat := components.CreateChat(
|
||||
svc,
|
||||
input.Par.Height,
|
||||
svc.SlackChannels[channels.SelectedChannel],
|
||||
svc.Channels[channels.SelectedChannel],
|
||||
)
|
||||
|
||||
mode := components.CreateMode()
|
||||
|
||||
view := &View{
|
||||
Input: input,
|
||||
Channels: channels,
|
||||
Chat: chat,
|
||||
Mode: mode,
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
func (v *View) Refresh() {
|
||||
termui.Render(
|
||||
v.Input,
|
||||
v.Chat,
|
||||
v.Channels,
|
||||
v.Mode,
|
||||
)
|
||||
}
|
73
views/view.go
Normal file
73
views/view.go
Normal file
@ -0,0 +1,73 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"github.com/erroneousboat/termui"
|
||||
|
||||
"github.com/erroneousboat/slack-term/components"
|
||||
"github.com/erroneousboat/slack-term/config"
|
||||
"github.com/erroneousboat/slack-term/service"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
Config *config.Config
|
||||
Input *components.Input
|
||||
Chat *components.Chat
|
||||
Channels *components.Channels
|
||||
Mode *components.Mode
|
||||
Debug *components.Debug
|
||||
}
|
||||
|
||||
func CreateView(config *config.Config, svc *service.SlackService) *View {
|
||||
// Create Input component
|
||||
input := components.CreateInputComponent()
|
||||
|
||||
// Channels: create the component
|
||||
channels := components.CreateChannelsComponent(input.Par.Height)
|
||||
|
||||
// Channels: fill the component
|
||||
slackChans := svc.GetChannels()
|
||||
channels.SetChannels(slackChans)
|
||||
|
||||
// Chat: create the component
|
||||
chat := components.CreateChatComponent(input.Par.Height)
|
||||
|
||||
// Chat: fill the component
|
||||
msgs := svc.GetMessages(
|
||||
svc.GetSlackChannel(channels.SelectedChannel),
|
||||
chat.GetMaxItems(),
|
||||
)
|
||||
|
||||
var strMsgs []string
|
||||
for _, msg := range msgs {
|
||||
strMsgs = append(strMsgs, msg.ToString())
|
||||
}
|
||||
|
||||
chat.SetMessages(strMsgs)
|
||||
chat.SetBorderLabel(svc.Channels[channels.SelectedChannel].GetChannelName())
|
||||
|
||||
// Debug: create the component
|
||||
debug := components.CreateDebugComponent(input.Par.Height)
|
||||
|
||||
// Mode: create the component
|
||||
mode := components.CreateModeComponent()
|
||||
|
||||
view := &View{
|
||||
Config: config,
|
||||
Input: input,
|
||||
Channels: channels,
|
||||
Chat: chat,
|
||||
Mode: mode,
|
||||
Debug: debug,
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
func (v *View) Refresh() {
|
||||
termui.Render(
|
||||
v.Input,
|
||||
v.Chat,
|
||||
v.Channels,
|
||||
v.Mode,
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user