mirror of
https://github.com/42wim/matterbridge.git
synced 2024-11-15 08:29:25 +01:00
Add Slack support
This commit is contained in:
parent
e449a97bd0
commit
b30e85836e
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/42wim/matterbridge/bridge/gitter"
|
"github.com/42wim/matterbridge/bridge/gitter"
|
||||||
"github.com/42wim/matterbridge/bridge/irc"
|
"github.com/42wim/matterbridge/bridge/irc"
|
||||||
"github.com/42wim/matterbridge/bridge/mattermost"
|
"github.com/42wim/matterbridge/bridge/mattermost"
|
||||||
|
"github.com/42wim/matterbridge/bridge/slack"
|
||||||
"github.com/42wim/matterbridge/bridge/xmpp"
|
"github.com/42wim/matterbridge/bridge/xmpp"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"strings"
|
"strings"
|
||||||
@ -42,6 +43,9 @@ func NewBridge(cfg *config.Config) error {
|
|||||||
if cfg.Gitter.Enable {
|
if cfg.Gitter.Enable {
|
||||||
b.Bridges = append(b.Bridges, bgitter.New(cfg, c))
|
b.Bridges = append(b.Bridges, bgitter.New(cfg, c))
|
||||||
}
|
}
|
||||||
|
if cfg.Slack.Enable {
|
||||||
|
b.Bridges = append(b.Bridges, bslack.New(cfg, c))
|
||||||
|
}
|
||||||
if len(b.Bridges) < 2 {
|
if len(b.Bridges) < 2 {
|
||||||
log.Fatalf("only %d sections enabled. Need at least 2 sections enabled (eg [IRC] and [mattermost]", len(b.Bridges))
|
log.Fatalf("only %d sections enabled. Need at least 2 sections enabled (eg [IRC] and [mattermost]", len(b.Bridges))
|
||||||
}
|
}
|
||||||
@ -72,6 +76,7 @@ func (b *Bridge) mapChannels() error {
|
|||||||
m["mattermost"] = val.Mattermost
|
m["mattermost"] = val.Mattermost
|
||||||
m["xmpp"] = val.Xmpp
|
m["xmpp"] = val.Xmpp
|
||||||
m["gitter"] = val.Gitter
|
m["gitter"] = val.Gitter
|
||||||
|
m["slack"] = val.Slack
|
||||||
b.Channels = append(b.Channels, m)
|
b.Channels = append(b.Channels, m)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -83,6 +88,7 @@ func (b *Bridge) mapIgnores() {
|
|||||||
m["mattermost"] = strings.Fields(b.Config.Mattermost.IgnoreNicks)
|
m["mattermost"] = strings.Fields(b.Config.Mattermost.IgnoreNicks)
|
||||||
m["xmpp"] = strings.Fields(b.Config.Xmpp.IgnoreNicks)
|
m["xmpp"] = strings.Fields(b.Config.Xmpp.IgnoreNicks)
|
||||||
m["gitter"] = strings.Fields(b.Config.Gitter.IgnoreNicks)
|
m["gitter"] = strings.Fields(b.Config.Gitter.IgnoreNicks)
|
||||||
|
m["slack"] = strings.Fields(b.Config.Slack.IgnoreNicks)
|
||||||
b.ignoreNicks = m
|
b.ignoreNicks = m
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +111,7 @@ func (b *Bridge) handleMessage(msg config.Message, dest Bridger) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.modifyMessage(&msg, dest.Name())
|
b.modifyMessage(&msg, dest.Name())
|
||||||
|
log.Debugf("sending %#v from %s to %s", msg, msg.Origin, dest.Name())
|
||||||
dest.Send(msg)
|
dest.Send(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,5 +145,7 @@ func (b *Bridge) modifyMessage(msg *config.Message, dest string) {
|
|||||||
setNickFormat(msg, b.Config.Xmpp.RemoteNickFormat)
|
setNickFormat(msg, b.Config.Xmpp.RemoteNickFormat)
|
||||||
case "mattermost":
|
case "mattermost":
|
||||||
setNickFormat(msg, b.Config.Mattermost.RemoteNickFormat)
|
setNickFormat(msg, b.Config.Mattermost.RemoteNickFormat)
|
||||||
|
case "slack":
|
||||||
|
setNickFormat(msg, b.Config.Slack.RemoteNickFormat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ type Config struct {
|
|||||||
RemoteNickFormat string
|
RemoteNickFormat string
|
||||||
Token string
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
Mattermost struct {
|
Mattermost struct {
|
||||||
URL string
|
URL string
|
||||||
ShowJoinPart bool
|
ShowJoinPart bool
|
||||||
@ -55,6 +54,19 @@ type Config struct {
|
|||||||
NoTLS bool
|
NoTLS bool
|
||||||
Enable bool
|
Enable bool
|
||||||
}
|
}
|
||||||
|
Slack struct {
|
||||||
|
BindAddress string
|
||||||
|
Enable bool
|
||||||
|
IconURL string
|
||||||
|
IgnoreNicks string
|
||||||
|
NickFormatter string
|
||||||
|
NicksPerRow int
|
||||||
|
PrefixMessagesWithNick bool
|
||||||
|
RemoteNickFormat string
|
||||||
|
Token string
|
||||||
|
URL string
|
||||||
|
UseAPI bool
|
||||||
|
}
|
||||||
Xmpp struct {
|
Xmpp struct {
|
||||||
IgnoreNicks string
|
IgnoreNicks string
|
||||||
Jid string
|
Jid string
|
||||||
@ -70,6 +82,7 @@ type Config struct {
|
|||||||
Mattermost string
|
Mattermost string
|
||||||
Xmpp string
|
Xmpp string
|
||||||
Gitter string
|
Gitter string
|
||||||
|
Slack string
|
||||||
}
|
}
|
||||||
General struct {
|
General struct {
|
||||||
GiphyAPIKey string
|
GiphyAPIKey string
|
||||||
|
180
bridge/slack/slack.go
Normal file
180
bridge/slack/slack.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package bslack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MMMessage struct {
|
||||||
|
Text string
|
||||||
|
Channel string
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
|
type bslack struct {
|
||||||
|
mh *matterhook.Client
|
||||||
|
sc *slack.Client
|
||||||
|
// MMapi
|
||||||
|
*config.Config
|
||||||
|
rtm *slack.RTM
|
||||||
|
Plus bool
|
||||||
|
Remote chan config.Message
|
||||||
|
channels []slack.Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
var flog *log.Entry
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flog = log.WithFields(log.Fields{"module": "slack"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg *config.Config, c chan config.Message) *bslack {
|
||||||
|
b := &bslack{}
|
||||||
|
b.Config = cfg
|
||||||
|
b.Remote = c
|
||||||
|
b.Plus = cfg.Slack.UseAPI
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bslack) Command(cmd string) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bslack) Connect() error {
|
||||||
|
if !b.Plus {
|
||||||
|
b.mh = matterhook.New(b.Config.Slack.URL,
|
||||||
|
matterhook.Config{BindAddress: b.Config.Slack.BindAddress})
|
||||||
|
} else {
|
||||||
|
b.sc = slack.New(b.Config.Slack.Token)
|
||||||
|
flog.Infof("Trying login on slack with Token")
|
||||||
|
/*
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
flog.Info("Login ok")
|
||||||
|
}
|
||||||
|
b.rtm = b.sc.NewRTM()
|
||||||
|
go b.rtm.ManageConnection()
|
||||||
|
go b.handleSlack()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bslack) Name() string {
|
||||||
|
return "slack"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bslack) Send(msg config.Message) error {
|
||||||
|
flog.Infof("slack send %#v", msg)
|
||||||
|
if msg.Origin != "slack" {
|
||||||
|
return b.SendType(msg.Username, msg.Text, msg.Channel, "")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bslack) SendType(nick string, message string, channel string, mtype string) error {
|
||||||
|
if b.Config.Slack.PrefixMessagesWithNick {
|
||||||
|
message = nick + " " + message
|
||||||
|
}
|
||||||
|
if !b.Plus {
|
||||||
|
matterMessage := matterhook.OMessage{IconURL: b.Config.Slack.IconURL}
|
||||||
|
matterMessage.Channel = channel
|
||||||
|
matterMessage.UserName = nick
|
||||||
|
matterMessage.Type = mtype
|
||||||
|
matterMessage.Text = message
|
||||||
|
err := b.mh.Send(matterMessage)
|
||||||
|
if err != nil {
|
||||||
|
flog.Info(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
flog.Debug("->slack channel: ", channel, " ", message)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
flog.Debugf("sent to slack channel API: %s %s", channel, message)
|
||||||
|
newmsg := b.rtm.NewOutgoingMessage(message, b.getChannelByName(channel).ID)
|
||||||
|
b.rtm.SendMessage(newmsg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bslack) getChannelByName(name string) *slack.Channel {
|
||||||
|
if b.channels == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, channel := range b.channels {
|
||||||
|
if channel.Name == name {
|
||||||
|
return &channel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bslack) handleSlack() {
|
||||||
|
flog.Infof("Choosing API based slack connection: %t", b.Plus)
|
||||||
|
mchan := make(chan *MMMessage)
|
||||||
|
if b.Plus {
|
||||||
|
go b.handleSlackClient(mchan)
|
||||||
|
} else {
|
||||||
|
go b.handleMatterHook(mchan)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
flog.Info("Start listening for Slack messages")
|
||||||
|
for message := range mchan {
|
||||||
|
texts := strings.Split(message.Text, "\n")
|
||||||
|
for _, text := range texts {
|
||||||
|
flog.Debug("Sending message from " + message.Username + " to " + message.Channel)
|
||||||
|
b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: "slack"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bslack) handleSlackClient(mchan chan *MMMessage) {
|
||||||
|
for msg := range b.rtm.IncomingEvents {
|
||||||
|
switch ev := msg.Data.(type) {
|
||||||
|
case *slack.MessageEvent:
|
||||||
|
flog.Debugf("%#v", ev)
|
||||||
|
channel, err := b.rtm.GetChannelInfo(ev.Channel)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
user, err := b.rtm.GetUserInfo(ev.User)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m := &MMMessage{}
|
||||||
|
m.Username = user.Name
|
||||||
|
m.Channel = channel.Name
|
||||||
|
m.Text = ev.Text
|
||||||
|
mchan <- m
|
||||||
|
case *slack.OutgoingErrorEvent:
|
||||||
|
flog.Debugf("%#v", ev.Error())
|
||||||
|
case *slack.ConnectedEvent:
|
||||||
|
b.channels = ev.Info.Channels
|
||||||
|
for _, val := range b.Config.Channel {
|
||||||
|
channel := b.getChannelByName(val.Slack)
|
||||||
|
if channel != nil && !channel.IsMember {
|
||||||
|
flog.Infof("Joining %s", val.Slack)
|
||||||
|
b.sc.JoinChannel(channel.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *slack.InvalidAuthEvent:
|
||||||
|
flog.Fatalf("Invalid Token %#v", ev)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bslack) handleMatterHook(mchan chan *MMMessage) {
|
||||||
|
for {
|
||||||
|
message := b.mh.Receive()
|
||||||
|
flog.Debugf("receiving from slack %#v", message)
|
||||||
|
m := &MMMessage{}
|
||||||
|
m.Username = message.UserName
|
||||||
|
m.Text = message.Text
|
||||||
|
m.Channel = message.ChannelName
|
||||||
|
mchan <- m
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,8 @@ type OMessage struct {
|
|||||||
|
|
||||||
// IMessage for mattermost outgoing webhook. (received from mattermost)
|
// IMessage for mattermost outgoing webhook. (received from mattermost)
|
||||||
type IMessage struct {
|
type IMessage struct {
|
||||||
|
BotID string `schema:"bot_id"`
|
||||||
|
BotName string `schema:"bot_name"`
|
||||||
Token string `schema:"token"`
|
Token string `schema:"token"`
|
||||||
TeamID string `schema:"team_id"`
|
TeamID string `schema:"team_id"`
|
||||||
TeamDomain string `schema:"team_domain"`
|
TeamDomain string `schema:"team_domain"`
|
||||||
@ -36,6 +38,8 @@ type IMessage struct {
|
|||||||
UserID string `schema:"user_id"`
|
UserID string `schema:"user_id"`
|
||||||
UserName string `schema:"user_name"`
|
UserName string `schema:"user_name"`
|
||||||
PostId string `schema:"post_id"`
|
PostId string `schema:"post_id"`
|
||||||
|
RawText string `schema:"raw_text"`
|
||||||
|
ServiceId string `schema:"service_id"`
|
||||||
Text string `schema:"text"`
|
Text string `schema:"text"`
|
||||||
TriggerWord string `schema:"trigger_word"`
|
TriggerWord string `schema:"trigger_word"`
|
||||||
}
|
}
|
||||||
|
23
vendor/github.com/nlopes/slack/LICENSE
generated
vendored
Normal file
23
vendor/github.com/nlopes/slack/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2015, Norberto Lopes
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation and/or
|
||||||
|
other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
190
vendor/github.com/nlopes/slack/admin.go
generated
vendored
Normal file
190
vendor/github.com/nlopes/slack/admin.go
generated
vendored
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type adminResponse struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func adminRequest(method string, teamName string, values url.Values, debug bool) (*adminResponse, error) {
|
||||||
|
adminResponse := &adminResponse{}
|
||||||
|
err := parseAdminResponse(method, teamName, values, adminResponse, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !adminResponse.OK {
|
||||||
|
return nil, errors.New(adminResponse.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adminResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableUser disabled a user account, given a user ID
|
||||||
|
func (api *Client) DisableUser(teamName string, uid string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"user": {uid},
|
||||||
|
"token": {api.config.token},
|
||||||
|
"set_active": {"true"},
|
||||||
|
"_attempts": {"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adminRequest("setInactive", teamName, values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to disable user with id '%s': %s", uid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
values := url.Values{
|
||||||
|
"email": {emailAddress},
|
||||||
|
"channels": {channel},
|
||||||
|
"first_name": {firstName},
|
||||||
|
"last_name": {lastName},
|
||||||
|
"ultra_restricted": {"1"},
|
||||||
|
"token": {api.config.token},
|
||||||
|
"set_active": {"true"},
|
||||||
|
"_attempts": {"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adminRequest("invite", teamName, values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to invite single-channel guest: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
values := url.Values{
|
||||||
|
"email": {emailAddress},
|
||||||
|
"channels": {channel},
|
||||||
|
"first_name": {firstName},
|
||||||
|
"last_name": {lastName},
|
||||||
|
"restricted": {"1"},
|
||||||
|
"token": {api.config.token},
|
||||||
|
"set_active": {"true"},
|
||||||
|
"_attempts": {"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adminRequest("invite", teamName, values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to restricted account: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteToTeam invites a user to a Slack team
|
||||||
|
func (api *Client) InviteToTeam(
|
||||||
|
teamName string,
|
||||||
|
firstName string,
|
||||||
|
lastName string,
|
||||||
|
emailAddress string,
|
||||||
|
) error {
|
||||||
|
values := url.Values{
|
||||||
|
"email": {emailAddress},
|
||||||
|
"first_name": {firstName},
|
||||||
|
"last_name": {lastName},
|
||||||
|
"token": {api.config.token},
|
||||||
|
"set_active": {"true"},
|
||||||
|
"_attempts": {"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adminRequest("invite", teamName, values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to invite to team: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRegular enables the specified user
|
||||||
|
func (api *Client) SetRegular(teamName string, user string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"user": {user},
|
||||||
|
"token": {api.config.token},
|
||||||
|
"set_active": {"true"},
|
||||||
|
"_attempts": {"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adminRequest("setRegular", teamName, values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to change the user (%s) to a regular user: %s", user, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendSSOBindingEmail sends an SSO binding email to the specified user
|
||||||
|
func (api *Client) SendSSOBindingEmail(teamName string, user string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"user": {user},
|
||||||
|
"token": {api.config.token},
|
||||||
|
"set_active": {"true"},
|
||||||
|
"_attempts": {"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adminRequest("sendSSOBind", teamName, values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to send SSO binding email for user (%s): %s", user, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUltraRestricted converts a user into a single-channel guest
|
||||||
|
func (api *Client) SetUltraRestricted(teamName, uid, channel string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"user": {uid},
|
||||||
|
"channel": {channel},
|
||||||
|
"token": {api.config.token},
|
||||||
|
"set_active": {"true"},
|
||||||
|
"_attempts": {"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adminRequest("setUltraRestricted", teamName, values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to ultra-restrict account: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRestricted converts a user into a restricted account
|
||||||
|
func (api *Client) SetRestricted(teamName, uid string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"user": {uid},
|
||||||
|
"token": {api.config.token},
|
||||||
|
"set_active": {"true"},
|
||||||
|
"_attempts": {"1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := adminRequest("setRestricted", teamName, values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to restrict account: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
76
vendor/github.com/nlopes/slack/attachments.go
generated
vendored
Normal file
76
vendor/github.com/nlopes/slack/attachments.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// AttachmentField contains information for an attachment field
|
||||||
|
// An Attachment can contain multiple of these
|
||||||
|
type AttachmentField struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
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
|
||||||
|
// 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.
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachmentActionCallback is sent from Slack when a user clicks a button in an interactive message (aka AttachmentAction)
|
||||||
|
type AttachmentActionCallback struct {
|
||||||
|
Actions []AttachmentAction `json:"actions"`
|
||||||
|
CallbackID string `json:"callback_id"`
|
||||||
|
Team Team `json:"team"`
|
||||||
|
Channel Channel `json:"channel"`
|
||||||
|
User User `json:"user"`
|
||||||
|
|
||||||
|
OriginalMessage Message `json:"original_message"`
|
||||||
|
|
||||||
|
ActionTs string `json:"action_ts"`
|
||||||
|
MessageTs string `json:"message_ts"`
|
||||||
|
AttachmentID string `json:"attachment_id"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
ResponseURL string `json:"response_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfirmationField are used to ask users to confirm actions
|
||||||
|
type ConfirmationField struct {
|
||||||
|
Title string `json:"title,omitempty"` // Optional.
|
||||||
|
Text string `json:"text"` // Required.
|
||||||
|
OkText string `json:"ok_text,omitempty"` // Optional. Defaults to "Okay"
|
||||||
|
DismissText string `json:"dismiss_text,omitempty"` // Optional. Defaults to "Cancel"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachment contains all the information for an attachment
|
||||||
|
type Attachment struct {
|
||||||
|
Color string `json:"color,omitempty"`
|
||||||
|
Fallback string `json:"fallback"`
|
||||||
|
|
||||||
|
CallbackID string `json:"callback_id,omitempty"`
|
||||||
|
|
||||||
|
AuthorName string `json:"author_name,omitempty"`
|
||||||
|
AuthorSubname string `json:"author_subname,omitempty"`
|
||||||
|
AuthorLink string `json:"author_link,omitempty"`
|
||||||
|
AuthorIcon string `json:"author_icon,omitempty"`
|
||||||
|
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
TitleLink string `json:"title_link,omitempty"`
|
||||||
|
Pretext string `json:"pretext,omitempty"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
|
||||||
|
ImageURL string `json:"image_url,omitempty"`
|
||||||
|
ThumbURL string `json:"thumb_url,omitempty"`
|
||||||
|
|
||||||
|
Fields []AttachmentField `json:"fields,omitempty"`
|
||||||
|
Actions []AttachmentAction `json:"actions,omitempty"`
|
||||||
|
MarkdownIn []string `json:"mrkdwn_in,omitempty"`
|
||||||
|
|
||||||
|
Footer string `json:"footer,omitempty"`
|
||||||
|
FooterIcon string `json:"footer_icon,omitempty"`
|
||||||
|
|
||||||
|
Ts int64 `json:"ts,omitempty"`
|
||||||
|
}
|
57
vendor/github.com/nlopes/slack/backoff.go
generated
vendored
Normal file
57
vendor/github.com/nlopes/slack/backoff.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This one was ripped from https://github.com/jpillora/backoff/blob/master/backoff.go
|
||||||
|
|
||||||
|
// Backoff is a time.Duration counter. It starts at Min. After every
|
||||||
|
// call to Duration() it is multiplied by Factor. It is capped at
|
||||||
|
// Max. It returns to Min on every call to Reset(). Used in
|
||||||
|
// conjunction with the time package.
|
||||||
|
type backoff struct {
|
||||||
|
attempts int
|
||||||
|
//Factor is the multiplying factor for each increment step
|
||||||
|
Factor float64
|
||||||
|
//Jitter eases contention by randomizing backoff steps
|
||||||
|
Jitter bool
|
||||||
|
//Min and Max are the minimum and maximum values of the counter
|
||||||
|
Min, Max time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the current value of the counter and then multiplies it
|
||||||
|
// Factor
|
||||||
|
func (b *backoff) Duration() time.Duration {
|
||||||
|
//Zero-values are nonsensical, so we use
|
||||||
|
//them to apply defaults
|
||||||
|
if b.Min == 0 {
|
||||||
|
b.Min = 100 * time.Millisecond
|
||||||
|
}
|
||||||
|
if b.Max == 0 {
|
||||||
|
b.Max = 10 * time.Second
|
||||||
|
}
|
||||||
|
if b.Factor == 0 {
|
||||||
|
b.Factor = 2
|
||||||
|
}
|
||||||
|
//calculate this duration
|
||||||
|
dur := float64(b.Min) * math.Pow(b.Factor, float64(b.attempts))
|
||||||
|
if b.Jitter == true {
|
||||||
|
dur = rand.Float64()*(dur-float64(b.Min)) + float64(b.Min)
|
||||||
|
}
|
||||||
|
//cap!
|
||||||
|
if dur > float64(b.Max) {
|
||||||
|
return b.Max
|
||||||
|
}
|
||||||
|
//bump attempts count
|
||||||
|
b.attempts++
|
||||||
|
//return as a time.Duration
|
||||||
|
return time.Duration(dur)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Resets the current value of the counter back to Min
|
||||||
|
func (b *backoff) Reset() {
|
||||||
|
b.attempts = 0
|
||||||
|
}
|
261
vendor/github.com/nlopes/slack/channels.go
generated
vendored
Normal file
261
vendor/github.com/nlopes/slack/channels.go
generated
vendored
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type channelResponseFull struct {
|
||||||
|
Channel Channel `json:"channel"`
|
||||||
|
Channels []Channel `json:"channels"`
|
||||||
|
Purpose string `json:"purpose"`
|
||||||
|
Topic string `json:"topic"`
|
||||||
|
NotInChannel bool `json:"not_in_channel"`
|
||||||
|
History
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel contains information about the channel
|
||||||
|
type Channel struct {
|
||||||
|
groupConversation
|
||||||
|
IsChannel bool `json:"is_channel"`
|
||||||
|
IsGeneral bool `json:"is_general"`
|
||||||
|
IsMember bool `json:"is_member"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func channelRequest(path string, values url.Values, debug bool) (*channelResponseFull, error) {
|
||||||
|
response := &channelResponseFull{}
|
||||||
|
err := post(path, values, response, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveChannel archives the given channel
|
||||||
|
func (api *Client) ArchiveChannel(channel string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
}
|
||||||
|
_, err := channelRequest("channels.archive", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnarchiveChannel unarchives the given channel
|
||||||
|
func (api *Client) UnarchiveChannel(channel string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
}
|
||||||
|
_, err := channelRequest("channels.unarchive", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateChannel creates a channel with the given name and returns a *Channel
|
||||||
|
func (api *Client) CreateChannel(channel string) (*Channel, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"name": {channel},
|
||||||
|
}
|
||||||
|
response, err := channelRequest("channels.create", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.Channel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChannelHistory retrieves the channel history
|
||||||
|
func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (*History, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
}
|
||||||
|
if params.Latest != DEFAULT_HISTORY_LATEST {
|
||||||
|
values.Add("latest", params.Latest)
|
||||||
|
}
|
||||||
|
if params.Oldest != DEFAULT_HISTORY_OLDEST {
|
||||||
|
values.Add("oldest", params.Oldest)
|
||||||
|
}
|
||||||
|
if params.Count != DEFAULT_HISTORY_COUNT {
|
||||||
|
values.Add("count", strconv.Itoa(params.Count))
|
||||||
|
}
|
||||||
|
if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE {
|
||||||
|
if params.Inclusive {
|
||||||
|
values.Add("inclusive", "1")
|
||||||
|
} else {
|
||||||
|
values.Add("inclusive", "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if params.Unreads != DEFAULT_HISTORY_UNREADS {
|
||||||
|
if params.Unreads {
|
||||||
|
values.Add("unreads", "1")
|
||||||
|
} else {
|
||||||
|
values.Add("unreads", "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response, err := channelRequest("channels.history", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.History, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChannelInfo retrieves the given channel
|
||||||
|
func (api *Client) GetChannelInfo(channel string) (*Channel, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
}
|
||||||
|
response, err := channelRequest("channels.info", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.Channel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteUserToChannel invites a user to a given channel and returns a *Channel
|
||||||
|
func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
"user": {user},
|
||||||
|
}
|
||||||
|
response, err := channelRequest("channels.invite", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.Channel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinChannel joins the currently authenticated user to a channel
|
||||||
|
func (api *Client) JoinChannel(channel string) (*Channel, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"name": {channel},
|
||||||
|
}
|
||||||
|
response, err := channelRequest("channels.join", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.Channel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaveChannel makes the authenticated user leave the given channel
|
||||||
|
func (api *Client) LeaveChannel(channel string) (bool, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
}
|
||||||
|
response, err := channelRequest("channels.leave", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if response.NotInChannel {
|
||||||
|
return response.NotInChannel, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KickUserFromChannel kicks a user from a given channel
|
||||||
|
func (api *Client) KickUserFromChannel(channel, user string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
"user": {user},
|
||||||
|
}
|
||||||
|
_, err := channelRequest("channels.kick", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChannels retrieves all the channels
|
||||||
|
func (api *Client) GetChannels(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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return response.Channels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChannelReadMark sets the read mark of a given channel to a specific point
|
||||||
|
// Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a
|
||||||
|
// timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls
|
||||||
|
// (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A
|
||||||
|
// timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
|
||||||
|
func (api *Client) SetChannelReadMark(channel, ts string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
"ts": {ts},
|
||||||
|
}
|
||||||
|
_, err := channelRequest("channels.mark", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenameChannel renames a given channel
|
||||||
|
func (api *Client) RenameChannel(channel, name string) (*Channel, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
"name": {name},
|
||||||
|
}
|
||||||
|
// XXX: the created entry in this call returns a string instead of a number
|
||||||
|
// so I may have to do some workaround to solve it.
|
||||||
|
response, err := channelRequest("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
|
||||||
|
func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
"purpose": {purpose},
|
||||||
|
}
|
||||||
|
response, err := channelRequest("channels.setPurpose", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return response.Purpose, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetChannelTopic sets the channel topic and returns the topic that was successfully set
|
||||||
|
func (api *Client) SetChannelTopic(channel, topic string) (string, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
"topic": {topic},
|
||||||
|
}
|
||||||
|
response, err := channelRequest("channels.setTopic", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return response.Topic, nil
|
||||||
|
}
|
166
vendor/github.com/nlopes/slack/chat.go
generated
vendored
Normal file
166
vendor/github.com/nlopes/slack/chat.go
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_MESSAGE_USERNAME = ""
|
||||||
|
DEFAULT_MESSAGE_ASUSER = false
|
||||||
|
DEFAULT_MESSAGE_PARSE = ""
|
||||||
|
DEFAULT_MESSAGE_LINK_NAMES = 0
|
||||||
|
DEFAULT_MESSAGE_UNFURL_LINKS = false
|
||||||
|
DEFAULT_MESSAGE_UNFURL_MEDIA = true
|
||||||
|
DEFAULT_MESSAGE_ICON_URL = ""
|
||||||
|
DEFAULT_MESSAGE_ICON_EMOJI = ""
|
||||||
|
DEFAULT_MESSAGE_MARKDOWN = true
|
||||||
|
DEFAULT_MESSAGE_ESCAPE_TEXT = true
|
||||||
|
)
|
||||||
|
|
||||||
|
type chatResponseFull struct {
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
Timestamp string `json:"ts"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request
|
||||||
|
type PostMessageParameters struct {
|
||||||
|
Text string
|
||||||
|
Username string
|
||||||
|
AsUser bool
|
||||||
|
Parse string
|
||||||
|
LinkNames int
|
||||||
|
Attachments []Attachment
|
||||||
|
UnfurlLinks bool
|
||||||
|
UnfurlMedia bool
|
||||||
|
IconURL string
|
||||||
|
IconEmoji string
|
||||||
|
Markdown bool `json:"mrkdwn,omitempty"`
|
||||||
|
EscapeText bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set
|
||||||
|
func NewPostMessageParameters() PostMessageParameters {
|
||||||
|
return PostMessageParameters{
|
||||||
|
Username: DEFAULT_MESSAGE_USERNAME,
|
||||||
|
AsUser: DEFAULT_MESSAGE_ASUSER,
|
||||||
|
Parse: DEFAULT_MESSAGE_PARSE,
|
||||||
|
LinkNames: DEFAULT_MESSAGE_LINK_NAMES,
|
||||||
|
Attachments: nil,
|
||||||
|
UnfurlLinks: DEFAULT_MESSAGE_UNFURL_LINKS,
|
||||||
|
UnfurlMedia: DEFAULT_MESSAGE_UNFURL_MEDIA,
|
||||||
|
IconURL: DEFAULT_MESSAGE_ICON_URL,
|
||||||
|
IconEmoji: DEFAULT_MESSAGE_ICON_EMOJI,
|
||||||
|
Markdown: DEFAULT_MESSAGE_MARKDOWN,
|
||||||
|
EscapeText: DEFAULT_MESSAGE_ESCAPE_TEXT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func chatRequest(path string, values url.Values, debug bool) (*chatResponseFull, error) {
|
||||||
|
response := &chatResponseFull{}
|
||||||
|
err := post(path, values, response, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeMessage(message string) string {
|
||||||
|
replacer := strings.NewReplacer("&", "&", "<", "<", ">", ">")
|
||||||
|
return replacer.Replace(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
"text": {text},
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
values.Set("attachments", string(attachments))
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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},
|
||||||
|
}
|
||||||
|
response, err := chatRequest("chat.update", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
return response.Channel, response.Timestamp, response.Text, nil
|
||||||
|
}
|
10
vendor/github.com/nlopes/slack/comment.go
generated
vendored
Normal file
10
vendor/github.com/nlopes/slack/comment.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// Comment contains all the information relative to a comment
|
||||||
|
type Comment struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Created JSONTime `json:"created,omitempty"`
|
||||||
|
Timestamp JSONTime `json:"timestamp,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Comment string `json:"comment,omitempty"`
|
||||||
|
}
|
38
vendor/github.com/nlopes/slack/conversation.go
generated
vendored
Normal file
38
vendor/github.com/nlopes/slack/conversation.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// Conversation is the foundation for IM and BaseGroupConversation
|
||||||
|
type conversation struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Created JSONTime `json:"created"`
|
||||||
|
IsOpen bool `json:"is_open"`
|
||||||
|
LastRead string `json:"last_read,omitempty"`
|
||||||
|
Latest *Message `json:"latest,omitempty"`
|
||||||
|
UnreadCount int `json:"unread_count,omitempty"`
|
||||||
|
UnreadCountDisplay int `json:"unread_count_display,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupConversation is the foundation for Group and Channel
|
||||||
|
type groupConversation struct {
|
||||||
|
conversation
|
||||||
|
Name string `json:"name"`
|
||||||
|
Creator string `json:"creator"`
|
||||||
|
IsArchived bool `json:"is_archived"`
|
||||||
|
Members []string `json:"members"`
|
||||||
|
NumMembers int `json:"num_members,omitempty"`
|
||||||
|
Topic Topic `json:"topic"`
|
||||||
|
Purpose Purpose `json:"purpose"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Topic contains information about the topic
|
||||||
|
type Topic struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
Creator string `json:"creator"`
|
||||||
|
LastSet JSONTime `json:"last_set"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purpose contains information about the purpose
|
||||||
|
type Purpose struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
Creator string `json:"creator"`
|
||||||
|
LastSet JSONTime `json:"last_set"`
|
||||||
|
}
|
123
vendor/github.com/nlopes/slack/dnd.go
generated
vendored
Normal file
123
vendor/github.com/nlopes/slack/dnd.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SnoozeDebug struct {
|
||||||
|
SnoozeEndDate string `json:"snooze_end_date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnoozeInfo struct {
|
||||||
|
SnoozeEnabled bool `json:"snooze_enabled,omitempty"`
|
||||||
|
SnoozeEndTime int `json:"snooze_endtime,omitempty"`
|
||||||
|
SnoozeRemaining int `json:"snooze_remaining,omitempty"`
|
||||||
|
SnoozeDebug SnoozeDebug `json:"snooze_debug,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNDStatus struct {
|
||||||
|
Enabled bool `json:"dnd_enabled"`
|
||||||
|
NextStartTimestamp int `json:"next_dnd_start_ts"`
|
||||||
|
NextEndTimestamp int `json:"next_dnd_end_ts"`
|
||||||
|
SnoozeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type dndResponseFull struct {
|
||||||
|
DNDStatus
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type dndTeamInfoResponse struct {
|
||||||
|
Users map[string]DNDStatus `json:"users"`
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func dndRequest(path string, values url.Values, debug bool) (*dndResponseFull, error) {
|
||||||
|
response := &dndResponseFull{}
|
||||||
|
err := post(path, values, response, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndDND ends the user's scheduled Do Not Disturb session
|
||||||
|
func (api *Client) EndDND() error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &SlackResponse{}
|
||||||
|
if err := post("dnd.endDnd", values, response, api.debug); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndSnooze ends the current user's snooze mode
|
||||||
|
func (api *Client) EndSnooze() (*DNDStatus, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := dndRequest("dnd.endSnooze", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.DNDStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDNDInfo provides information about a user's current Do Not Disturb settings.
|
||||||
|
func (api *Client) GetDNDInfo(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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.DNDStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDNDTeamInfo provides information about a user's current Do Not Disturb settings.
|
||||||
|
func (api *Client) GetDNDTeamInfo(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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response.Users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSnooze adjusts the snooze duration for a user's Do Not Disturb
|
||||||
|
// 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) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"num_minutes": {strconv.Itoa(minutes)},
|
||||||
|
}
|
||||||
|
response, err := dndRequest("dnd.setSnooze", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.DNDStatus, nil
|
||||||
|
}
|
27
vendor/github.com/nlopes/slack/emoji.go
generated
vendored
Normal file
27
vendor/github.com/nlopes/slack/emoji.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type emojiResponseFull struct {
|
||||||
|
Emoji map[string]string `json:"emoji"`
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmoji retrieves all the emojis
|
||||||
|
func (api *Client) GetEmoji() (map[string]string, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
response := &emojiResponseFull{}
|
||||||
|
err := post("emoji.list", values, response, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response.Emoji, nil
|
||||||
|
}
|
19
vendor/github.com/nlopes/slack/examples/channels/channels.go
generated
vendored
Normal file
19
vendor/github.com/nlopes/slack/examples/channels/channels.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
channels, err := api.GetChannels(false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, channel := range channels {
|
||||||
|
fmt.Println(channel.ID)
|
||||||
|
}
|
||||||
|
}
|
30
vendor/github.com/nlopes/slack/examples/files/files.go
generated
vendored
Normal file
30
vendor/github.com/nlopes/slack/examples/files/files.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
params := slack.FileUploadParameters{
|
||||||
|
Title: "Batman Example",
|
||||||
|
//Filetype: "txt",
|
||||||
|
File: "example.txt",
|
||||||
|
//Content: "Nan Nan Nan Nan Nan Nan Nan Nan Batman",
|
||||||
|
}
|
||||||
|
file, err := api.UploadFile(params)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Name: %s, URL: %s\n", file.Name, file.URL)
|
||||||
|
|
||||||
|
err = api.DeleteFile(file.ID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("File %s deleted successfully.\n", file.Name)
|
||||||
|
}
|
22
vendor/github.com/nlopes/slack/examples/groups/groups.go
generated
vendored
Normal file
22
vendor/github.com/nlopes/slack/examples/groups/groups.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
// If you set debugging, it will log all requests to the console
|
||||||
|
// Useful when encountering issues
|
||||||
|
// api.SetDebug(true)
|
||||||
|
groups, err := api.GetGroups(false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, group := range groups {
|
||||||
|
fmt.Printf("ID: %s, Name: %s\n", group.ID, group.Name)
|
||||||
|
}
|
||||||
|
}
|
32
vendor/github.com/nlopes/slack/examples/messages/messages.go
generated
vendored
Normal file
32
vendor/github.com/nlopes/slack/examples/messages/messages.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
params := slack.PostMessageParameters{}
|
||||||
|
attachment := slack.Attachment{
|
||||||
|
Pretext: "some pretext",
|
||||||
|
Text: "some text",
|
||||||
|
// Uncomment the following part to send a field too
|
||||||
|
/*
|
||||||
|
Fields: []slack.AttachmentField{
|
||||||
|
slack.AttachmentField{
|
||||||
|
Title: "a",
|
||||||
|
Value: "no",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
params.Attachments = []slack.Attachment{attachment}
|
||||||
|
channelID, timestamp, err := api.PostMessage("CHANNEL_ID", "Some text", params)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Message successfully sent to channel %s at %s", channelID, timestamp)
|
||||||
|
}
|
123
vendor/github.com/nlopes/slack/examples/pins/pins.go
generated
vendored
Normal file
123
vendor/github.com/nlopes/slack/examples/pins/pins.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
WARNING: This example is destructive in the sense that it create a channel called testpinning
|
||||||
|
*/
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
apiToken string
|
||||||
|
debug bool
|
||||||
|
)
|
||||||
|
|
||||||
|
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
|
||||||
|
flag.BoolVar(&debug, "debug", false, "Show JSON output")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
api := slack.New(apiToken)
|
||||||
|
if debug {
|
||||||
|
api.SetDebug(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
postAsUserName string
|
||||||
|
postAsUserID string
|
||||||
|
postToChannelID string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the user to post as.
|
||||||
|
authTest, err := api.AuthTest()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting channels: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channelName := "testpinning"
|
||||||
|
|
||||||
|
// Post as the authenticated user.
|
||||||
|
postAsUserName = authTest.User
|
||||||
|
postAsUserID = authTest.UserID
|
||||||
|
|
||||||
|
// Create a temporary channel
|
||||||
|
channel, err := api.CreateChannel(channelName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// If the channel exists, that means we just need to unarchive it
|
||||||
|
if err.Error() == "name_taken" {
|
||||||
|
err = nil
|
||||||
|
channels, err := api.GetChannels(false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Could not retrieve channels")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, archivedChannel := range channels {
|
||||||
|
if archivedChannel.Name == channelName {
|
||||||
|
if archivedChannel.IsArchived {
|
||||||
|
err = api.UnarchiveChannel(archivedChannel.ID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Could not unarchive %s: %s\n", archivedChannel.ID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel = &archivedChannel
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error setting test channel for pinning: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
postToChannelID = channel.ID
|
||||||
|
|
||||||
|
fmt.Printf("Posting as %s (%s) in channel %s\n", postAsUserName, postAsUserID, postToChannelID)
|
||||||
|
|
||||||
|
// Post a message.
|
||||||
|
postParams := slack.PostMessageParameters{}
|
||||||
|
channelID, timestamp, err := api.PostMessage(postToChannelID, "Is this any good?", postParams)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error posting message: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab a reference to the message.
|
||||||
|
msgRef := slack.NewRefToMessage(channelID, timestamp)
|
||||||
|
|
||||||
|
// Add message pin to channel
|
||||||
|
if err := api.AddPin(channelID, msgRef); err != nil {
|
||||||
|
fmt.Printf("Error adding pin: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all of the users pins.
|
||||||
|
listPins, _, err := api.ListPins(channelID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error listing pins: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("All pins by %s...\n", authTest.User)
|
||||||
|
for _, item := range listPins {
|
||||||
|
fmt.Printf(" > Item type: %s\n", item.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the pin.
|
||||||
|
err = api.RemovePin(channelID, msgRef)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error remove pin: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = api.ArchiveChannel(channelID); err != nil {
|
||||||
|
fmt.Printf("Error archiving channel: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
126
vendor/github.com/nlopes/slack/examples/reactions/reactions.go
generated
vendored
Normal file
126
vendor/github.com/nlopes/slack/examples/reactions/reactions.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
apiToken string
|
||||||
|
debug bool
|
||||||
|
)
|
||||||
|
|
||||||
|
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
|
||||||
|
flag.BoolVar(&debug, "debug", false, "Show JSON output")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
api := slack.New(apiToken)
|
||||||
|
if debug {
|
||||||
|
api.SetDebug(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
postAsUserName string
|
||||||
|
postAsUserID string
|
||||||
|
postToUserName string
|
||||||
|
postToUserID string
|
||||||
|
postToChannelID string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the user to post as.
|
||||||
|
authTest, err := api.AuthTest()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting channels: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post as the authenticated user.
|
||||||
|
postAsUserName = authTest.User
|
||||||
|
postAsUserID = authTest.UserID
|
||||||
|
|
||||||
|
// Posting to DM with self causes a conversation with slackbot.
|
||||||
|
postToUserName = authTest.User
|
||||||
|
postToUserID = authTest.UserID
|
||||||
|
|
||||||
|
// Find the channel.
|
||||||
|
_, _, chanID, err := api.OpenIMChannel(postToUserID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error opening IM: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
postToChannelID = chanID
|
||||||
|
|
||||||
|
fmt.Printf("Posting as %s (%s) in DM with %s (%s), channel %s\n", postAsUserName, postAsUserID, postToUserName, postToUserID, postToChannelID)
|
||||||
|
|
||||||
|
// Post a message.
|
||||||
|
postParams := slack.PostMessageParameters{}
|
||||||
|
channelID, timestamp, err := api.PostMessage(postToChannelID, "Is this any good?", postParams)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error posting message: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grab a reference to the message.
|
||||||
|
msgRef := slack.NewRefToMessage(channelID, timestamp)
|
||||||
|
|
||||||
|
// React with :+1:
|
||||||
|
if err := api.AddReaction("+1", msgRef); err != nil {
|
||||||
|
fmt.Printf("Error adding reaction: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// React with :-1:
|
||||||
|
if err := api.AddReaction("cry", msgRef); err != nil {
|
||||||
|
fmt.Printf("Error adding reaction: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all reactions on the message.
|
||||||
|
msgReactions, err := api.GetReactions(msgRef, slack.NewGetReactionsParameters())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting reactions: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("%d reactions to message...\n", len(msgReactions))
|
||||||
|
for _, r := range msgReactions {
|
||||||
|
fmt.Printf(" %d users say %s\n", r.Count, r.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all of the users reactions.
|
||||||
|
listReactions, _, err := api.ListReactions(slack.NewListReactionsParameters())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error listing reactions: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("All reactions by %s...\n", authTest.User)
|
||||||
|
for _, item := range listReactions {
|
||||||
|
fmt.Printf("%d on a %s...\n", len(item.Reactions), item.Type)
|
||||||
|
for _, r := range item.Reactions {
|
||||||
|
fmt.Printf(" %s (along with %d others)\n", r.Name, r.Count-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the :cry: reaction.
|
||||||
|
err = api.RemoveReaction("cry", msgRef)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error remove reaction: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all reactions on the message.
|
||||||
|
msgReactions, err = api.GetReactions(msgRef, slack.NewGetReactionsParameters())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting reactions: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("%d reactions to message after removing cry...\n", len(msgReactions))
|
||||||
|
for _, r := range msgReactions {
|
||||||
|
fmt.Printf(" %d users say %s\n", r.Count, r.Name)
|
||||||
|
}
|
||||||
|
}
|
46
vendor/github.com/nlopes/slack/examples/stars/stars.go
generated
vendored
Normal file
46
vendor/github.com/nlopes/slack/examples/stars/stars.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
apiToken string
|
||||||
|
debug bool
|
||||||
|
)
|
||||||
|
|
||||||
|
flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token")
|
||||||
|
flag.BoolVar(&debug, "debug", false, "Show JSON output")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
api := slack.New(apiToken)
|
||||||
|
if debug {
|
||||||
|
api.SetDebug(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all stars for the usr.
|
||||||
|
params := slack.NewStarsParameters()
|
||||||
|
starredItems, _, err := api.GetStarred(params)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting stars: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, s := range starredItems {
|
||||||
|
var desc string
|
||||||
|
switch s.Type {
|
||||||
|
case slack.TYPE_MESSAGE:
|
||||||
|
desc = s.Message.Text
|
||||||
|
case slack.TYPE_FILE:
|
||||||
|
desc = s.File.Name
|
||||||
|
case slack.TYPE_FILE_COMMENT:
|
||||||
|
desc = s.File.Name + " - " + s.Comment.Comment
|
||||||
|
case slack.TYPE_CHANNEL, slack.TYPE_IM, slack.TYPE_GROUP:
|
||||||
|
desc = s.Channel
|
||||||
|
}
|
||||||
|
fmt.Printf("Starred %s: %s\n", s.Type, desc)
|
||||||
|
}
|
||||||
|
}
|
17
vendor/github.com/nlopes/slack/examples/users/users.go
generated
vendored
Normal file
17
vendor/github.com/nlopes/slack/examples/users/users.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR_TOKEN_HERE")
|
||||||
|
user, err := api.GetUserInfo("U023BECGF")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("ID: %s, Fullname: %s, Email: %s\n", user.ID, user.Profile.RealName, user.Profile.Email)
|
||||||
|
}
|
58
vendor/github.com/nlopes/slack/examples/websocket/websocket.go
generated
vendored
Normal file
58
vendor/github.com/nlopes/slack/examples/websocket/websocket.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
api := slack.New("YOUR TOKEN HERE")
|
||||||
|
logger := log.New(os.Stdout, "slack-bot: ", log.Lshortfile|log.LstdFlags)
|
||||||
|
slack.SetLogger(logger)
|
||||||
|
api.SetDebug(true)
|
||||||
|
|
||||||
|
rtm := api.NewRTM()
|
||||||
|
go rtm.ManageConnection()
|
||||||
|
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-rtm.IncomingEvents:
|
||||||
|
fmt.Print("Event Received: ")
|
||||||
|
switch ev := msg.Data.(type) {
|
||||||
|
case *slack.HelloEvent:
|
||||||
|
// Ignore hello
|
||||||
|
|
||||||
|
case *slack.ConnectedEvent:
|
||||||
|
fmt.Println("Infos:", ev.Info)
|
||||||
|
fmt.Println("Connection counter:", ev.ConnectionCount)
|
||||||
|
// Replace #general with your Channel ID
|
||||||
|
rtm.SendMessage(rtm.NewOutgoingMessage("Hello world", "#general"))
|
||||||
|
|
||||||
|
case *slack.MessageEvent:
|
||||||
|
fmt.Printf("Message: %v\n", ev)
|
||||||
|
|
||||||
|
case *slack.PresenceChangeEvent:
|
||||||
|
fmt.Printf("Presence Change: %v\n", ev)
|
||||||
|
|
||||||
|
case *slack.LatencyReport:
|
||||||
|
fmt.Printf("Current latency: %v\n", ev.Value)
|
||||||
|
|
||||||
|
case *slack.RTMError:
|
||||||
|
fmt.Printf("Error: %s\n", ev.Error())
|
||||||
|
|
||||||
|
case *slack.InvalidAuthEvent:
|
||||||
|
fmt.Printf("Invalid credentials")
|
||||||
|
break Loop
|
||||||
|
|
||||||
|
default:
|
||||||
|
|
||||||
|
// Ignore other events..
|
||||||
|
// fmt.Printf("Unexpected: %v\n", msg.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
274
vendor/github.com/nlopes/slack/files.go
generated
vendored
Normal file
274
vendor/github.com/nlopes/slack/files.go
generated
vendored
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Add here the defaults in the siten
|
||||||
|
DEFAULT_FILES_USER = ""
|
||||||
|
DEFAULT_FILES_CHANNEL = ""
|
||||||
|
DEFAULT_FILES_TS_FROM = 0
|
||||||
|
DEFAULT_FILES_TS_TO = -1
|
||||||
|
DEFAULT_FILES_TYPES = "all"
|
||||||
|
DEFAULT_FILES_COUNT = 100
|
||||||
|
DEFAULT_FILES_PAGE = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// File contains all the information for a file
|
||||||
|
type File struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Created JSONTime `json:"created"`
|
||||||
|
Timestamp JSONTime `json:"timestamp"`
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Mimetype string `json:"mimetype"`
|
||||||
|
ImageExifRotation int `json:"image_exif_rotation"`
|
||||||
|
Filetype string `json:"filetype"`
|
||||||
|
PrettyType string `json:"pretty_type"`
|
||||||
|
User string `json:"user"`
|
||||||
|
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Editable bool `json:"editable"`
|
||||||
|
IsExternal bool `json:"is_external"`
|
||||||
|
ExternalType string `json:"external_type"`
|
||||||
|
|
||||||
|
Size int `json:"size"`
|
||||||
|
|
||||||
|
URL string `json:"url"` // Deprecated - never set
|
||||||
|
URLDownload string `json:"url_download"` // Deprecated - never set
|
||||||
|
URLPrivate string `json:"url_private"`
|
||||||
|
URLPrivateDownload string `json:"url_private_download"`
|
||||||
|
|
||||||
|
OriginalH int `json:"original_h"`
|
||||||
|
OriginalW int `json:"original_w"`
|
||||||
|
Thumb64 string `json:"thumb_64"`
|
||||||
|
Thumb80 string `json:"thumb_80"`
|
||||||
|
Thumb160 string `json:"thumb_160"`
|
||||||
|
Thumb360 string `json:"thumb_360"`
|
||||||
|
Thumb360Gif string `json:"thumb_360_gif"`
|
||||||
|
Thumb360W int `json:"thumb_360_w"`
|
||||||
|
Thumb360H int `json:"thumb_360_h"`
|
||||||
|
Thumb480 string `json:"thumb_480"`
|
||||||
|
Thumb480W int `json:"thumb_480_w"`
|
||||||
|
Thumb480H int `json:"thumb_480_h"`
|
||||||
|
Thumb720 string `json:"thumb_720"`
|
||||||
|
Thumb720W int `json:"thumb_720_w"`
|
||||||
|
Thumb720H int `json:"thumb_720_h"`
|
||||||
|
Thumb960 string `json:"thumb_960"`
|
||||||
|
Thumb960W int `json:"thumb_960_w"`
|
||||||
|
Thumb960H int `json:"thumb_960_h"`
|
||||||
|
Thumb1024 string `json:"thumb_1024"`
|
||||||
|
Thumb1024W int `json:"thumb_1024_w"`
|
||||||
|
Thumb1024H int `json:"thumb_1024_h"`
|
||||||
|
|
||||||
|
Permalink string `json:"permalink"`
|
||||||
|
PermalinkPublic string `json:"permalink_public"`
|
||||||
|
|
||||||
|
EditLink string `json:"edit_link"`
|
||||||
|
Preview string `json:"preview"`
|
||||||
|
PreviewHighlight string `json:"preview_highlight"`
|
||||||
|
Lines int `json:"lines"`
|
||||||
|
LinesMore int `json:"lines_more"`
|
||||||
|
|
||||||
|
IsPublic bool `json:"is_public"`
|
||||||
|
PublicURLShared bool `json:"public_url_shared"`
|
||||||
|
Channels []string `json:"channels"`
|
||||||
|
Groups []string `json:"groups"`
|
||||||
|
IMs []string `json:"ims"`
|
||||||
|
InitialComment Comment `json:"initial_comment"`
|
||||||
|
CommentsCount int `json:"comments_count"`
|
||||||
|
NumStars int `json:"num_stars"`
|
||||||
|
IsStarred bool `json:"is_starred"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileUploadParameters contains all the parameters necessary (including the optional ones) for an UploadFile() request
|
||||||
|
type FileUploadParameters struct {
|
||||||
|
File string
|
||||||
|
Content string
|
||||||
|
Filetype string
|
||||||
|
Filename string
|
||||||
|
Title string
|
||||||
|
InitialComment string
|
||||||
|
Channels []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFilesParameters contains all the parameters necessary (including the optional ones) for a GetFiles() request
|
||||||
|
type GetFilesParameters struct {
|
||||||
|
User string
|
||||||
|
Channel string
|
||||||
|
TimestampFrom JSONTime
|
||||||
|
TimestampTo JSONTime
|
||||||
|
Types string
|
||||||
|
Count int
|
||||||
|
Page int
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileResponseFull struct {
|
||||||
|
File `json:"file"`
|
||||||
|
Paging `json:"paging"`
|
||||||
|
Comments []Comment `json:"comments"`
|
||||||
|
Files []File `json:"files"`
|
||||||
|
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetFilesParameters provides an instance of GetFilesParameters with all the sane default values set
|
||||||
|
func NewGetFilesParameters() GetFilesParameters {
|
||||||
|
return GetFilesParameters{
|
||||||
|
User: DEFAULT_FILES_USER,
|
||||||
|
Channel: DEFAULT_FILES_CHANNEL,
|
||||||
|
TimestampFrom: DEFAULT_FILES_TS_FROM,
|
||||||
|
TimestampTo: DEFAULT_FILES_TS_TO,
|
||||||
|
Types: DEFAULT_FILES_TYPES,
|
||||||
|
Count: DEFAULT_FILES_COUNT,
|
||||||
|
Page: DEFAULT_FILES_PAGE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileRequest(path string, values url.Values, debug bool) (*fileResponseFull, error) {
|
||||||
|
response := &fileResponseFull{}
|
||||||
|
err := post(path, values, response, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileInfo retrieves a file and related comments
|
||||||
|
func (api *Client) GetFileInfo(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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
return &response.File, response.Comments, &response.Paging, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFiles retrieves all files according to the parameters given
|
||||||
|
func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if params.User != DEFAULT_FILES_USER {
|
||||||
|
values.Add("user", params.User)
|
||||||
|
}
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
if params.TimestampTo != DEFAULT_FILES_TS_TO {
|
||||||
|
values.Add("ts_to", params.TimestampTo.String())
|
||||||
|
}
|
||||||
|
if params.Types != DEFAULT_FILES_TYPES {
|
||||||
|
values.Add("types", params.Types)
|
||||||
|
}
|
||||||
|
if params.Count != DEFAULT_FILES_COUNT {
|
||||||
|
values.Add("count", strconv.Itoa(params.Count))
|
||||||
|
}
|
||||||
|
if params.Page != DEFAULT_FILES_PAGE {
|
||||||
|
values.Add("page", strconv.Itoa(params.Page))
|
||||||
|
}
|
||||||
|
response, err := fileRequest("files.list", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return response.Files, &response.Paging, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadFile uploads a file
|
||||||
|
func (api *Client) UploadFile(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()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response := &fileResponseFull{}
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if params.Filetype != "" {
|
||||||
|
values.Add("filetype", params.Filetype)
|
||||||
|
}
|
||||||
|
if params.Filename != "" {
|
||||||
|
values.Add("filename", params.Filename)
|
||||||
|
}
|
||||||
|
if params.Title != "" {
|
||||||
|
values.Add("title", params.Title)
|
||||||
|
}
|
||||||
|
if params.InitialComment != "" {
|
||||||
|
values.Add("initial_comment", params.InitialComment)
|
||||||
|
}
|
||||||
|
if len(params.Channels) != 0 {
|
||||||
|
values.Add("channels", strings.Join(params.Channels, ","))
|
||||||
|
}
|
||||||
|
if params.Content != "" {
|
||||||
|
values.Add("content", params.Content)
|
||||||
|
err = post("files.upload", values, response, api.debug)
|
||||||
|
} else if params.File != "" {
|
||||||
|
err = postWithMultipartResponse("files.upload", params.File, values, response, api.debug)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return &response.File, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFile deletes a file
|
||||||
|
func (api *Client) DeleteFile(fileID string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"file": {fileID},
|
||||||
|
}
|
||||||
|
_, err := fileRequest("files.delete", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeFilePublicURL disables public/external sharing for a file
|
||||||
|
func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"file": {fileID},
|
||||||
|
}
|
||||||
|
response, err := fileRequest("files.revokePublicURL", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.File, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShareFilePublicURL enabled public/external sharing for a file
|
||||||
|
func (api *Client) ShareFilePublicURL(fileID string) (*File, []Comment, *Paging, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"file": {fileID},
|
||||||
|
}
|
||||||
|
response, err := fileRequest("files.sharedPublicURL", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
return &response.File, response.Comments, &response.Paging, nil
|
||||||
|
}
|
293
vendor/github.com/nlopes/slack/groups.go
generated
vendored
Normal file
293
vendor/github.com/nlopes/slack/groups.go
generated
vendored
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Group contains all the information for a group
|
||||||
|
type Group struct {
|
||||||
|
groupConversation
|
||||||
|
IsGroup bool `json:"is_group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type groupResponseFull struct {
|
||||||
|
Group Group `json:"group"`
|
||||||
|
Groups []Group `json:"groups"`
|
||||||
|
Purpose string `json:"purpose"`
|
||||||
|
Topic string `json:"topic"`
|
||||||
|
NotInGroup bool `json:"not_in_group"`
|
||||||
|
NoOp bool `json:"no_op"`
|
||||||
|
AlreadyClosed bool `json:"already_closed"`
|
||||||
|
AlreadyOpen bool `json:"already_open"`
|
||||||
|
AlreadyInGroup bool `json:"already_in_group"`
|
||||||
|
Channel Channel `json:"channel"`
|
||||||
|
History
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupRequest(path string, values url.Values, debug bool) (*groupResponseFull, error) {
|
||||||
|
response := &groupResponseFull{}
|
||||||
|
err := post(path, values, response, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveGroup archives a private group
|
||||||
|
func (api *Client) ArchiveGroup(group string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
}
|
||||||
|
_, err := groupRequest("groups.archive", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnarchiveGroup unarchives a private group
|
||||||
|
func (api *Client) UnarchiveGroup(group string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
}
|
||||||
|
_, err := groupRequest("groups.unarchive", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateGroup creates a private group
|
||||||
|
func (api *Client) CreateGroup(group string) (*Group, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"name": {group},
|
||||||
|
}
|
||||||
|
response, err := groupRequest("groups.create", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.Group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateChildGroup creates a new private group archiving the old one
|
||||||
|
// This method takes an existing private group and performs the following steps:
|
||||||
|
// 1. Renames the existing group (from "example" to "example-archived").
|
||||||
|
// 2. Archives the existing group.
|
||||||
|
// 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) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
}
|
||||||
|
response, err := groupRequest("groups.createChild", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.Group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseGroup closes a private group
|
||||||
|
func (api *Client) CloseGroup(group string) (bool, bool, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
}
|
||||||
|
response, err := imRequest("groups.close", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return false, false, err
|
||||||
|
}
|
||||||
|
return response.NoOp, response.AlreadyClosed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroupHistory fetches all the history for a private group
|
||||||
|
func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*History, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
}
|
||||||
|
if params.Latest != DEFAULT_HISTORY_LATEST {
|
||||||
|
values.Add("latest", params.Latest)
|
||||||
|
}
|
||||||
|
if params.Oldest != DEFAULT_HISTORY_OLDEST {
|
||||||
|
values.Add("oldest", params.Oldest)
|
||||||
|
}
|
||||||
|
if params.Count != DEFAULT_HISTORY_COUNT {
|
||||||
|
values.Add("count", strconv.Itoa(params.Count))
|
||||||
|
}
|
||||||
|
if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE {
|
||||||
|
if params.Inclusive {
|
||||||
|
values.Add("inclusive", "1")
|
||||||
|
} else {
|
||||||
|
values.Add("inclusive", "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if params.Unreads != DEFAULT_HISTORY_UNREADS {
|
||||||
|
if params.Unreads {
|
||||||
|
values.Add("unreads", "1")
|
||||||
|
} else {
|
||||||
|
values.Add("unreads", "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response, err := groupRequest("groups.history", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.History, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InviteUserToGroup invites a specific user to a private group
|
||||||
|
func (api *Client) InviteUserToGroup(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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return &response.Group, response.AlreadyInGroup, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaveGroup makes authenticated user leave the group
|
||||||
|
func (api *Client) LeaveGroup(group string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
}
|
||||||
|
_, err := groupRequest("groups.leave", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KickUserFromGroup kicks a user from a group
|
||||||
|
func (api *Client) KickUserFromGroup(group, user string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
"user": {user},
|
||||||
|
}
|
||||||
|
_, err := groupRequest("groups.kick", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroups retrieves all groups
|
||||||
|
func (api *Client) GetGroups(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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return response.Groups, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroupInfo retrieves the given group
|
||||||
|
func (api *Client) GetGroupInfo(group string) (*Group, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
}
|
||||||
|
response, err := groupRequest("groups.info", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.Group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGroupReadMark sets the read mark on a private group
|
||||||
|
// Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a
|
||||||
|
// timer before making the call. In this way, any further updates needed during the timeout will not generate extra
|
||||||
|
// calls (just one per channel). This is useful for when reading scroll-back history, or following a busy live
|
||||||
|
// channel. A timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
|
||||||
|
func (api *Client) SetGroupReadMark(group, ts string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
"ts": {ts},
|
||||||
|
}
|
||||||
|
_, err := groupRequest("groups.mark", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenGroup opens a private group
|
||||||
|
func (api *Client) OpenGroup(group string) (bool, bool, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
}
|
||||||
|
response, err := groupRequest("groups.open", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return false, false, err
|
||||||
|
}
|
||||||
|
return response.NoOp, response.AlreadyOpen, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenameGroup renames a group
|
||||||
|
// 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) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
"name": {name},
|
||||||
|
}
|
||||||
|
// XXX: the created entry in this call returns a string instead of a number
|
||||||
|
// so I may have to do some workaround to solve it.
|
||||||
|
response, err := groupRequest("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) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
"purpose": {purpose},
|
||||||
|
}
|
||||||
|
response, err := groupRequest("groups.setPurpose", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return response.Purpose, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGroupTopic sets the group topic
|
||||||
|
func (api *Client) SetGroupTopic(group, topic string) (string, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {group},
|
||||||
|
"topic": {topic},
|
||||||
|
}
|
||||||
|
response, err := groupRequest("groups.setTopic", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return response.Topic, nil
|
||||||
|
}
|
36
vendor/github.com/nlopes/slack/history.go
generated
vendored
Normal file
36
vendor/github.com/nlopes/slack/history.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_HISTORY_LATEST = ""
|
||||||
|
DEFAULT_HISTORY_OLDEST = "0"
|
||||||
|
DEFAULT_HISTORY_COUNT = 100
|
||||||
|
DEFAULT_HISTORY_INCLUSIVE = false
|
||||||
|
DEFAULT_HISTORY_UNREADS = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// HistoryParameters contains all the necessary information to help in the retrieval of history for Channels/Groups/DMs
|
||||||
|
type HistoryParameters struct {
|
||||||
|
Latest string
|
||||||
|
Oldest string
|
||||||
|
Count int
|
||||||
|
Inclusive bool
|
||||||
|
Unreads bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// History contains message history information needed to navigate a Channel / Group / DM history
|
||||||
|
type History struct {
|
||||||
|
Latest string `json:"latest"`
|
||||||
|
Messages []Message `json:"messages"`
|
||||||
|
HasMore bool `json:"has_more"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHistoryParameters provides an instance of HistoryParameters with all the sane default values set
|
||||||
|
func NewHistoryParameters() HistoryParameters {
|
||||||
|
return HistoryParameters{
|
||||||
|
Latest: DEFAULT_HISTORY_LATEST,
|
||||||
|
Oldest: DEFAULT_HISTORY_OLDEST,
|
||||||
|
Count: DEFAULT_HISTORY_COUNT,
|
||||||
|
Inclusive: DEFAULT_HISTORY_INCLUSIVE,
|
||||||
|
Unreads: DEFAULT_HISTORY_UNREADS,
|
||||||
|
}
|
||||||
|
}
|
130
vendor/github.com/nlopes/slack/im.go
generated
vendored
Normal file
130
vendor/github.com/nlopes/slack/im.go
generated
vendored
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type imChannel struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type imResponseFull struct {
|
||||||
|
NoOp bool `json:"no_op"`
|
||||||
|
AlreadyClosed bool `json:"already_closed"`
|
||||||
|
AlreadyOpen bool `json:"already_open"`
|
||||||
|
Channel imChannel `json:"channel"`
|
||||||
|
IMs []IM `json:"ims"`
|
||||||
|
History
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// IM contains information related to the Direct Message channel
|
||||||
|
type IM struct {
|
||||||
|
conversation
|
||||||
|
IsIM bool `json:"is_im"`
|
||||||
|
User string `json:"user"`
|
||||||
|
IsUserDeleted bool `json:"is_user_deleted"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func imRequest(path string, values url.Values, debug bool) (*imResponseFull, error) {
|
||||||
|
response := &imResponseFull{}
|
||||||
|
err := post(path, values, response, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseIMChannel closes the direct message channel
|
||||||
|
func (api *Client) CloseIMChannel(channel string) (bool, bool, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
}
|
||||||
|
response, err := imRequest("im.close", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return false, false, err
|
||||||
|
}
|
||||||
|
return response.NoOp, response.AlreadyClosed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"user": {user},
|
||||||
|
}
|
||||||
|
response, err := imRequest("im.open", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return false, false, "", err
|
||||||
|
}
|
||||||
|
return response.NoOp, response.AlreadyOpen, response.Channel.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkIMChannel sets the read mark of a direct message channel to a specific point
|
||||||
|
func (api *Client) MarkIMChannel(channel, ts string) (err error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
"ts": {ts},
|
||||||
|
}
|
||||||
|
_, err = imRequest("im.mark", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIMHistory retrieves the direct message channel history
|
||||||
|
func (api *Client) GetIMHistory(channel string, params HistoryParameters) (*History, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"channel": {channel},
|
||||||
|
}
|
||||||
|
if params.Latest != DEFAULT_HISTORY_LATEST {
|
||||||
|
values.Add("latest", params.Latest)
|
||||||
|
}
|
||||||
|
if params.Oldest != DEFAULT_HISTORY_OLDEST {
|
||||||
|
values.Add("oldest", params.Oldest)
|
||||||
|
}
|
||||||
|
if params.Count != DEFAULT_HISTORY_COUNT {
|
||||||
|
values.Add("count", strconv.Itoa(params.Count))
|
||||||
|
}
|
||||||
|
if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE {
|
||||||
|
if params.Inclusive {
|
||||||
|
values.Add("inclusive", "1")
|
||||||
|
} else {
|
||||||
|
values.Add("inclusive", "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if params.Unreads != DEFAULT_HISTORY_UNREADS {
|
||||||
|
if params.Unreads {
|
||||||
|
values.Add("unreads", "1")
|
||||||
|
} else {
|
||||||
|
values.Add("unreads", "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response, err := imRequest("im.history", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.History, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIMChannels returns the list of direct message channels
|
||||||
|
func (api *Client) GetIMChannels() ([]IM, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
response, err := imRequest("im.list", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return response.IMs, nil
|
||||||
|
}
|
206
vendor/github.com/nlopes/slack/info.go
generated
vendored
Normal file
206
vendor/github.com/nlopes/slack/info.go
generated
vendored
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserPrefs needs to be implemented
|
||||||
|
type UserPrefs struct {
|
||||||
|
// "highlight_words":"",
|
||||||
|
// "user_colors":"",
|
||||||
|
// "color_names_in_list":true,
|
||||||
|
// "growls_enabled":true,
|
||||||
|
// "tz":"Europe\/London",
|
||||||
|
// "push_dm_alert":true,
|
||||||
|
// "push_mention_alert":true,
|
||||||
|
// "push_everything":true,
|
||||||
|
// "push_idle_wait":2,
|
||||||
|
// "push_sound":"b2.mp3",
|
||||||
|
// "push_loud_channels":"",
|
||||||
|
// "push_mention_channels":"",
|
||||||
|
// "push_loud_channels_set":"",
|
||||||
|
// "email_alerts":"instant",
|
||||||
|
// "email_alerts_sleep_until":0,
|
||||||
|
// "email_misc":false,
|
||||||
|
// "email_weekly":true,
|
||||||
|
// "welcome_message_hidden":false,
|
||||||
|
// "all_channels_loud":true,
|
||||||
|
// "loud_channels":"",
|
||||||
|
// "never_channels":"",
|
||||||
|
// "loud_channels_set":"",
|
||||||
|
// "show_member_presence":true,
|
||||||
|
// "search_sort":"timestamp",
|
||||||
|
// "expand_inline_imgs":true,
|
||||||
|
// "expand_internal_inline_imgs":true,
|
||||||
|
// "expand_snippets":false,
|
||||||
|
// "posts_formatting_guide":true,
|
||||||
|
// "seen_welcome_2":true,
|
||||||
|
// "seen_ssb_prompt":false,
|
||||||
|
// "search_only_my_channels":false,
|
||||||
|
// "emoji_mode":"default",
|
||||||
|
// "has_invited":true,
|
||||||
|
// "has_uploaded":false,
|
||||||
|
// "has_created_channel":true,
|
||||||
|
// "search_exclude_channels":"",
|
||||||
|
// "messages_theme":"default",
|
||||||
|
// "webapp_spellcheck":true,
|
||||||
|
// "no_joined_overlays":false,
|
||||||
|
// "no_created_overlays":true,
|
||||||
|
// "dropbox_enabled":false,
|
||||||
|
// "seen_user_menu_tip_card":true,
|
||||||
|
// "seen_team_menu_tip_card":true,
|
||||||
|
// "seen_channel_menu_tip_card":true,
|
||||||
|
// "seen_message_input_tip_card":true,
|
||||||
|
// "seen_channels_tip_card":true,
|
||||||
|
// "seen_domain_invite_reminder":false,
|
||||||
|
// "seen_member_invite_reminder":false,
|
||||||
|
// "seen_flexpane_tip_card":true,
|
||||||
|
// "seen_search_input_tip_card":true,
|
||||||
|
// "mute_sounds":false,
|
||||||
|
// "arrow_history":false,
|
||||||
|
// "tab_ui_return_selects":true,
|
||||||
|
// "obey_inline_img_limit":true,
|
||||||
|
// "new_msg_snd":"knock_brush.mp3",
|
||||||
|
// "collapsible":false,
|
||||||
|
// "collapsible_by_click":true,
|
||||||
|
// "require_at":false,
|
||||||
|
// "mac_ssb_bounce":"",
|
||||||
|
// "mac_ssb_bullet":true,
|
||||||
|
// "win_ssb_bullet":true,
|
||||||
|
// "expand_non_media_attachments":true,
|
||||||
|
// "show_typing":true,
|
||||||
|
// "pagekeys_handled":true,
|
||||||
|
// "last_snippet_type":"",
|
||||||
|
// "display_real_names_override":0,
|
||||||
|
// "time24":false,
|
||||||
|
// "enter_is_special_in_tbt":false,
|
||||||
|
// "graphic_emoticons":false,
|
||||||
|
// "convert_emoticons":true,
|
||||||
|
// "autoplay_chat_sounds":true,
|
||||||
|
// "ss_emojis":true,
|
||||||
|
// "sidebar_behavior":"",
|
||||||
|
// "mark_msgs_read_immediately":true,
|
||||||
|
// "start_scroll_at_oldest":true,
|
||||||
|
// "snippet_editor_wrap_long_lines":false,
|
||||||
|
// "ls_disabled":false,
|
||||||
|
// "sidebar_theme":"default",
|
||||||
|
// "sidebar_theme_custom_values":"",
|
||||||
|
// "f_key_search":false,
|
||||||
|
// "k_key_omnibox":true,
|
||||||
|
// "speak_growls":false,
|
||||||
|
// "mac_speak_voice":"com.apple.speech.synthesis.voice.Alex",
|
||||||
|
// "mac_speak_speed":250,
|
||||||
|
// "comma_key_prefs":false,
|
||||||
|
// "at_channel_suppressed_channels":"",
|
||||||
|
// "push_at_channel_suppressed_channels":"",
|
||||||
|
// "prompted_for_email_disabling":false,
|
||||||
|
// "full_text_extracts":false,
|
||||||
|
// "no_text_in_notifications":false,
|
||||||
|
// "muted_channels":"",
|
||||||
|
// "no_macssb1_banner":false,
|
||||||
|
// "privacy_policy_seen":true,
|
||||||
|
// "search_exclude_bots":false,
|
||||||
|
// "fuzzy_matching":false
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserDetails contains user details coming in the initial response from StartRTM
|
||||||
|
type UserDetails struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Created JSONTime `json:"created"`
|
||||||
|
ManualPresence string `json:"manual_presence"`
|
||||||
|
Prefs UserPrefs `json:"prefs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONTime exists so that we can have a String method converting the date
|
||||||
|
type JSONTime int64
|
||||||
|
|
||||||
|
// String converts the unix timestamp into a string
|
||||||
|
func (t JSONTime) String() string {
|
||||||
|
tm := t.Time()
|
||||||
|
return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns a `time.Time` representation of this value.
|
||||||
|
func (t JSONTime) Time() time.Time {
|
||||||
|
return time.Unix(int64(t), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Team contains details about a team
|
||||||
|
type Team struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icons XXX: needs further investigation
|
||||||
|
type Icons struct {
|
||||||
|
Image48 string `json:"image_48"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bot contains information about a bot
|
||||||
|
type Bot struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Deleted bool `json:"deleted"`
|
||||||
|
Icons Icons `json:"icons"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info contains various details about Users, Channels, Bots and the authenticated user.
|
||||||
|
// It is returned by StartRTM or included in the "ConnectedEvent" RTM event.
|
||||||
|
type Info struct {
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
User *UserDetails `json:"self,omitempty"`
|
||||||
|
Team *Team `json:"team,omitempty"`
|
||||||
|
Users []User `json:"users,omitempty"`
|
||||||
|
Channels []Channel `json:"channels,omitempty"`
|
||||||
|
Groups []Group `json:"groups,omitempty"`
|
||||||
|
Bots []Bot `json:"bots,omitempty"`
|
||||||
|
IMs []IM `json:"ims,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type infoResponseFull struct {
|
||||||
|
Info
|
||||||
|
WebResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBotByID returns a bot given a bot id
|
||||||
|
func (info Info) GetBotByID(botID string) *Bot {
|
||||||
|
for _, bot := range info.Bots {
|
||||||
|
if bot.ID == botID {
|
||||||
|
return &bot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByID returns a user given a user id
|
||||||
|
func (info Info) GetUserByID(userID string) *User {
|
||||||
|
for _, user := range info.Users {
|
||||||
|
if user.ID == userID {
|
||||||
|
return &user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChannelByID returns a channel given a channel id
|
||||||
|
func (info Info) GetChannelByID(channelID string) *Channel {
|
||||||
|
for _, channel := range info.Channels {
|
||||||
|
if channel.ID == channelID {
|
||||||
|
return &channel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroupByID returns a group given a group id
|
||||||
|
func (info Info) GetGroupByID(groupID string) *Group {
|
||||||
|
for _, group := range info.Groups {
|
||||||
|
if group.ID == groupID {
|
||||||
|
return &group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
75
vendor/github.com/nlopes/slack/item.go
generated
vendored
Normal file
75
vendor/github.com/nlopes/slack/item.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
const (
|
||||||
|
TYPE_MESSAGE = "message"
|
||||||
|
TYPE_FILE = "file"
|
||||||
|
TYPE_FILE_COMMENT = "file_comment"
|
||||||
|
TYPE_CHANNEL = "channel"
|
||||||
|
TYPE_IM = "im"
|
||||||
|
TYPE_GROUP = "group"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Item is any type of slack message - message, file, or file comment.
|
||||||
|
type Item struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Channel string `json:"channel,omitempty"`
|
||||||
|
Message *Message `json:"message,omitempty"`
|
||||||
|
File *File `json:"file,omitempty"`
|
||||||
|
Comment *Comment `json:"comment,omitempty"`
|
||||||
|
Timestamp string `json:"ts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMessageItem turns a message on a channel into a typed message struct.
|
||||||
|
func NewMessageItem(ch string, m *Message) Item {
|
||||||
|
return Item{Type: TYPE_MESSAGE, Channel: ch, Message: m}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileItem turns a file into a typed file struct.
|
||||||
|
func NewFileItem(f *File) Item {
|
||||||
|
return Item{Type: TYPE_FILE, File: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileCommentItem turns a file and comment into a typed file_comment struct.
|
||||||
|
func NewFileCommentItem(f *File, c *Comment) Item {
|
||||||
|
return Item{Type: TYPE_FILE_COMMENT, File: f, Comment: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewChannelItem turns a channel id into a typed channel struct.
|
||||||
|
func NewChannelItem(ch string) Item {
|
||||||
|
return Item{Type: TYPE_CHANNEL, Channel: ch}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIMItem turns a channel id into a typed im struct.
|
||||||
|
func NewIMItem(ch string) Item {
|
||||||
|
return Item{Type: TYPE_IM, Channel: ch}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGroupItem turns a channel id into a typed group struct.
|
||||||
|
func NewGroupItem(ch string) Item {
|
||||||
|
return Item{Type: TYPE_GROUP, Channel: ch}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ItemRef is a reference to a message of any type. One of FileID,
|
||||||
|
// CommentId, or the combination of ChannelId and Timestamp must be
|
||||||
|
// specified.
|
||||||
|
type ItemRef struct {
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
File string `json:"file"`
|
||||||
|
Comment string `json:"file_comment"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRefToMessage initializes a reference to to a message.
|
||||||
|
func NewRefToMessage(channel, timestamp string) ItemRef {
|
||||||
|
return ItemRef{Channel: channel, Timestamp: timestamp}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRefToFile initializes a reference to a file.
|
||||||
|
func NewRefToFile(file string) ItemRef {
|
||||||
|
return ItemRef{File: file}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRefToComment initializes a reference to a file comment.
|
||||||
|
func NewRefToComment(comment string) ItemRef {
|
||||||
|
return ItemRef{Comment: comment}
|
||||||
|
}
|
30
vendor/github.com/nlopes/slack/messageID.go
generated
vendored
Normal file
30
vendor/github.com/nlopes/slack/messageID.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// IDGenerator provides an interface for generating integer ID values.
|
||||||
|
type IDGenerator interface {
|
||||||
|
Next() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSafeID returns a new instance of an IDGenerator which is safe for
|
||||||
|
// concurrent use by multiple goroutines.
|
||||||
|
func NewSafeID(startID int) IDGenerator {
|
||||||
|
return &safeID{
|
||||||
|
nextID: startID,
|
||||||
|
mutex: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type safeID struct {
|
||||||
|
nextID int
|
||||||
|
mutex *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *safeID) Next() int {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
id := s.nextID
|
||||||
|
s.nextID++
|
||||||
|
return id
|
||||||
|
}
|
131
vendor/github.com/nlopes/slack/messages.go
generated
vendored
Normal file
131
vendor/github.com/nlopes/slack/messages.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message is an auxiliary type to allow us to have a message containing sub messages
|
||||||
|
type Message struct {
|
||||||
|
Msg
|
||||||
|
SubMessage *Msg `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
|
||||||
|
// Message Subtypes
|
||||||
|
SubType string `json:"subtype,omitempty"`
|
||||||
|
|
||||||
|
// Hidden Subtypes
|
||||||
|
Hidden bool `json:"hidden,omitempty"` // message_changed, message_deleted, unpinned_item
|
||||||
|
DeletedTimestamp string `json:"deleted_ts,omitempty"` // message_deleted
|
||||||
|
EventTimestamp string `json:"event_ts,omitempty"`
|
||||||
|
|
||||||
|
// bot_message (https://api.slack.com/events/message/bot_message)
|
||||||
|
BotID string `json:"bot_id,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Icons *Icon `json:"icons,omitempty"`
|
||||||
|
|
||||||
|
// channel_join, group_join
|
||||||
|
Inviter string `json:"inviter,omitempty"`
|
||||||
|
|
||||||
|
// channel_topic, group_topic
|
||||||
|
Topic string `json:"topic,omitempty"`
|
||||||
|
|
||||||
|
// channel_purpose, group_purpose
|
||||||
|
Purpose string `json:"purpose,omitempty"`
|
||||||
|
|
||||||
|
// channel_name, group_name
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
OldName string `json:"old_name,omitempty"`
|
||||||
|
|
||||||
|
// channel_archive, group_archive
|
||||||
|
Members []string `json:"members,omitempty"`
|
||||||
|
|
||||||
|
// file_share, file_comment, file_mention
|
||||||
|
File *File `json:"file,omitempty"`
|
||||||
|
|
||||||
|
// file_share
|
||||||
|
Upload bool `json:"upload,omitempty"`
|
||||||
|
|
||||||
|
// file_comment
|
||||||
|
Comment *Comment `json:"comment,omitempty"`
|
||||||
|
|
||||||
|
// pinned_item
|
||||||
|
ItemType string `json:"item_type,omitempty"`
|
||||||
|
|
||||||
|
// https://api.slack.com/rtm
|
||||||
|
ReplyTo int `json:"reply_to,omitempty"`
|
||||||
|
Team string `json:"team,omitempty"`
|
||||||
|
|
||||||
|
// reactions
|
||||||
|
Reactions []ItemReaction `json:"reactions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon is used for bot messages
|
||||||
|
type Icon struct {
|
||||||
|
IconURL string `json:"icon_url,omitempty"`
|
||||||
|
IconEmoji string `json:"icon_emoji,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edited indicates that a message has been edited.
|
||||||
|
type Edited struct {
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Timestamp string `json:"ts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event contains the event type
|
||||||
|
type Event struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping contains information about a Ping Event
|
||||||
|
type Ping struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pong contains information about a Pong Event
|
||||||
|
type Pong struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
ReplyTo int `json:"reply_to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewOutgoingMessage prepares an OutgoingMessage that the user can
|
||||||
|
// use to send a message. Use this function to properly set the
|
||||||
|
// messageID.
|
||||||
|
func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage {
|
||||||
|
id := rtm.idGen.Next()
|
||||||
|
return &OutgoingMessage{
|
||||||
|
ID: id,
|
||||||
|
Type: "message",
|
||||||
|
Channel: channel,
|
||||||
|
Text: text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTypingMessage prepares an OutgoingMessage that the user can
|
||||||
|
// use to send as a typing indicator. Use this function to properly set the
|
||||||
|
// messageID.
|
||||||
|
func (rtm *RTM) NewTypingMessage(channel string) *OutgoingMessage {
|
||||||
|
id := rtm.idGen.Next()
|
||||||
|
return &OutgoingMessage{
|
||||||
|
ID: id,
|
||||||
|
Type: "typing",
|
||||||
|
Channel: channel,
|
||||||
|
}
|
||||||
|
}
|
119
vendor/github.com/nlopes/slack/misc.go
generated
vendored
Normal file
119
vendor/github.com/nlopes/slack/misc.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var HTTPClient = &http.Client{}
|
||||||
|
|
||||||
|
type WebResponse struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Error *WebError `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebError string
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
wr := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
ioWriter, err := wr.CreateFormFile("file", filepath.Base(fullpath))
|
||||||
|
if err != nil {
|
||||||
|
wr.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bytes, err := io.Copy(ioWriter, file)
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", wr.FormDataContentType())
|
||||||
|
req.URL.RawQuery = (values).Encode()
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseResponseBody(body io.ReadCloser, intf *interface{}, debug bool) error {
|
||||||
|
response, err := ioutil.ReadAll(body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: will be api.Debugf
|
||||||
|
if debug {
|
||||||
|
logger.Printf("parseResponseBody: %s\n", string(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(response, &intf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
return parseResponseBody(resp.Body, &intf, debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
func postForm(endpoint string, values url.Values, intf interface{}, debug bool) error {
|
||||||
|
resp, err := HTTPClient.PostForm(endpoint, values)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
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 parseAdminResponse(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)
|
||||||
|
}
|
54
vendor/github.com/nlopes/slack/oauth.go
generated
vendored
Normal file
54
vendor/github.com/nlopes/slack/oauth.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OAuthResponseIncomingWebhook struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
ConfigurationURL string `json:"configuration_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OAuthResponseBot struct {
|
||||||
|
BotUserID string `json:"bot_user_id"`
|
||||||
|
BotAccessToken string `json:"bot_access_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OAuthResponse struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
TeamName string `json:"team_name"`
|
||||||
|
TeamID string `json:"team_id"`
|
||||||
|
IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"`
|
||||||
|
Bot OAuthResponseBot `json:"bot"`
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return response.AccessToken, response.Scope, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOAuthResponse(clientID, clientSecret, code, redirectURI string, debug bool) (resp *OAuthResponse, err error) {
|
||||||
|
values := url.Values{
|
||||||
|
"client_id": {clientID},
|
||||||
|
"client_secret": {clientSecret},
|
||||||
|
"code": {code},
|
||||||
|
"redirect_uri": {redirectURI},
|
||||||
|
}
|
||||||
|
response := &OAuthResponse{}
|
||||||
|
err = post("oauth.access", values, response, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
20
vendor/github.com/nlopes/slack/pagination.go
generated
vendored
Normal file
20
vendor/github.com/nlopes/slack/pagination.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// Paging contains paging information
|
||||||
|
type Paging struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Pages int `json:"pages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination contains pagination information
|
||||||
|
// This is different from Paging in that it contains additional details
|
||||||
|
type Pagination struct {
|
||||||
|
TotalCount int `json:"total_count"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
PageCount int `json:"page_count"`
|
||||||
|
First int `json:"first"`
|
||||||
|
Last int `json:"last"`
|
||||||
|
}
|
79
vendor/github.com/nlopes/slack/pins.go
generated
vendored
Normal file
79
vendor/github.com/nlopes/slack/pins.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type listPinsResponseFull struct {
|
||||||
|
Items []Item
|
||||||
|
Paging `json:"paging"`
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddPin pins an item in a channel
|
||||||
|
func (api *Client) AddPin(channel string, item ItemRef) error {
|
||||||
|
values := url.Values{
|
||||||
|
"channel": {channel},
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if item.Timestamp != "" {
|
||||||
|
values.Set("timestamp", string(item.Timestamp))
|
||||||
|
}
|
||||||
|
if item.File != "" {
|
||||||
|
values.Set("file", string(item.File))
|
||||||
|
}
|
||||||
|
if item.Comment != "" {
|
||||||
|
values.Set("file_comment", string(item.Comment))
|
||||||
|
}
|
||||||
|
response := &SlackResponse{}
|
||||||
|
if err := post("pins.add", values, response, api.debug); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemovePin un-pins an item from a channel
|
||||||
|
func (api *Client) RemovePin(channel string, item ItemRef) error {
|
||||||
|
values := url.Values{
|
||||||
|
"channel": {channel},
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if item.Timestamp != "" {
|
||||||
|
values.Set("timestamp", string(item.Timestamp))
|
||||||
|
}
|
||||||
|
if item.File != "" {
|
||||||
|
values.Set("file", string(item.File))
|
||||||
|
}
|
||||||
|
if item.Comment != "" {
|
||||||
|
values.Set("file_comment", string(item.Comment))
|
||||||
|
}
|
||||||
|
response := &SlackResponse{}
|
||||||
|
if err := post("pins.remove", values, response, api.debug); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPins returns information about the items a user reacted to.
|
||||||
|
func (api *Client) ListPins(channel string) ([]Item, *Paging, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"channel": {channel},
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
response := &listPinsResponseFull{}
|
||||||
|
err := post("pins.list", values, response, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response.Items, &response.Paging, nil
|
||||||
|
}
|
246
vendor/github.com/nlopes/slack/reactions.go
generated
vendored
Normal file
246
vendor/github.com/nlopes/slack/reactions.go
generated
vendored
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ItemReaction is the reactions that have happened on an item.
|
||||||
|
type ItemReaction struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
Users []string `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReactedItem is an item that was reacted to, and the details of the
|
||||||
|
// reactions.
|
||||||
|
type ReactedItem struct {
|
||||||
|
Item
|
||||||
|
Reactions []ItemReaction
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReactionsParameters is the inputs to get reactions to an item.
|
||||||
|
type GetReactionsParameters struct {
|
||||||
|
Full bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetReactionsParameters initializes the inputs to get reactions to an item.
|
||||||
|
func NewGetReactionsParameters() GetReactionsParameters {
|
||||||
|
return GetReactionsParameters{
|
||||||
|
Full: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type getReactionsResponseFull struct {
|
||||||
|
Type string
|
||||||
|
M struct {
|
||||||
|
Reactions []ItemReaction
|
||||||
|
} `json:"message"`
|
||||||
|
F struct {
|
||||||
|
Reactions []ItemReaction
|
||||||
|
} `json:"file"`
|
||||||
|
FC struct {
|
||||||
|
Reactions []ItemReaction
|
||||||
|
} `json:"comment"`
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res getReactionsResponseFull) extractReactions() []ItemReaction {
|
||||||
|
switch res.Type {
|
||||||
|
case "message":
|
||||||
|
return res.M.Reactions
|
||||||
|
case "file":
|
||||||
|
return res.F.Reactions
|
||||||
|
case "file_comment":
|
||||||
|
return res.FC.Reactions
|
||||||
|
}
|
||||||
|
return []ItemReaction{}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_REACTIONS_USER = ""
|
||||||
|
DEFAULT_REACTIONS_COUNT = 100
|
||||||
|
DEFAULT_REACTIONS_PAGE = 1
|
||||||
|
DEFAULT_REACTIONS_FULL = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListReactionsParameters is the inputs to find all reactions by a user.
|
||||||
|
type ListReactionsParameters struct {
|
||||||
|
User string
|
||||||
|
Count int
|
||||||
|
Page int
|
||||||
|
Full bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListReactionsParameters initializes the inputs to find all reactions
|
||||||
|
// performed by a user.
|
||||||
|
func NewListReactionsParameters() ListReactionsParameters {
|
||||||
|
return ListReactionsParameters{
|
||||||
|
User: DEFAULT_REACTIONS_USER,
|
||||||
|
Count: DEFAULT_REACTIONS_COUNT,
|
||||||
|
Page: DEFAULT_REACTIONS_PAGE,
|
||||||
|
Full: DEFAULT_REACTIONS_FULL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type listReactionsResponseFull struct {
|
||||||
|
Items []struct {
|
||||||
|
Type string
|
||||||
|
Channel string
|
||||||
|
M struct {
|
||||||
|
*Message
|
||||||
|
} `json:"message"`
|
||||||
|
F struct {
|
||||||
|
*File
|
||||||
|
Reactions []ItemReaction
|
||||||
|
} `json:"file"`
|
||||||
|
FC struct {
|
||||||
|
*Comment
|
||||||
|
Reactions []ItemReaction
|
||||||
|
} `json:"comment"`
|
||||||
|
}
|
||||||
|
Paging `json:"paging"`
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (res listReactionsResponseFull) extractReactedItems() []ReactedItem {
|
||||||
|
items := make([]ReactedItem, len(res.Items))
|
||||||
|
for i, input := range res.Items {
|
||||||
|
item := ReactedItem{}
|
||||||
|
item.Type = input.Type
|
||||||
|
switch input.Type {
|
||||||
|
case "message":
|
||||||
|
item.Channel = input.Channel
|
||||||
|
item.Message = input.M.Message
|
||||||
|
item.Reactions = input.M.Reactions
|
||||||
|
case "file":
|
||||||
|
item.File = input.F.File
|
||||||
|
item.Reactions = input.F.Reactions
|
||||||
|
case "file_comment":
|
||||||
|
item.File = input.F.File
|
||||||
|
item.Comment = input.FC.Comment
|
||||||
|
item.Reactions = input.FC.Reactions
|
||||||
|
}
|
||||||
|
items[i] = item
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddReaction adds a reaction emoji to a message, file or file comment.
|
||||||
|
func (api *Client) AddReaction(name string, item ItemRef) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if name != "" {
|
||||||
|
values.Set("name", name)
|
||||||
|
}
|
||||||
|
if item.Channel != "" {
|
||||||
|
values.Set("channel", string(item.Channel))
|
||||||
|
}
|
||||||
|
if item.Timestamp != "" {
|
||||||
|
values.Set("timestamp", string(item.Timestamp))
|
||||||
|
}
|
||||||
|
if item.File != "" {
|
||||||
|
values.Set("file", string(item.File))
|
||||||
|
}
|
||||||
|
if item.Comment != "" {
|
||||||
|
values.Set("file_comment", string(item.Comment))
|
||||||
|
}
|
||||||
|
response := &SlackResponse{}
|
||||||
|
if err := post("reactions.add", values, response, api.debug); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveReaction removes a reaction emoji from a message, file or file comment.
|
||||||
|
func (api *Client) RemoveReaction(name string, item ItemRef) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if name != "" {
|
||||||
|
values.Set("name", name)
|
||||||
|
}
|
||||||
|
if item.Channel != "" {
|
||||||
|
values.Set("channel", string(item.Channel))
|
||||||
|
}
|
||||||
|
if item.Timestamp != "" {
|
||||||
|
values.Set("timestamp", string(item.Timestamp))
|
||||||
|
}
|
||||||
|
if item.File != "" {
|
||||||
|
values.Set("file", string(item.File))
|
||||||
|
}
|
||||||
|
if item.Comment != "" {
|
||||||
|
values.Set("file_comment", string(item.Comment))
|
||||||
|
}
|
||||||
|
response := &SlackResponse{}
|
||||||
|
if err := post("reactions.remove", values, response, api.debug); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReactions returns details about the reactions on an item.
|
||||||
|
func (api *Client) GetReactions(item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if item.Channel != "" {
|
||||||
|
values.Set("channel", string(item.Channel))
|
||||||
|
}
|
||||||
|
if item.Timestamp != "" {
|
||||||
|
values.Set("timestamp", string(item.Timestamp))
|
||||||
|
}
|
||||||
|
if item.File != "" {
|
||||||
|
values.Set("file", string(item.File))
|
||||||
|
}
|
||||||
|
if item.Comment != "" {
|
||||||
|
values.Set("file_comment", string(item.Comment))
|
||||||
|
}
|
||||||
|
if params.Full != DEFAULT_REACTIONS_FULL {
|
||||||
|
values.Set("full", strconv.FormatBool(params.Full))
|
||||||
|
}
|
||||||
|
response := &getReactionsResponseFull{}
|
||||||
|
if err := post("reactions.get", values, response, api.debug); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response.extractReactions(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListReactions returns information about the items a user reacted to.
|
||||||
|
func (api *Client) ListReactions(params ListReactionsParameters) ([]ReactedItem, *Paging, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if params.User != DEFAULT_REACTIONS_USER {
|
||||||
|
values.Add("user", params.User)
|
||||||
|
}
|
||||||
|
if params.Count != DEFAULT_REACTIONS_COUNT {
|
||||||
|
values.Add("count", strconv.Itoa(params.Count))
|
||||||
|
}
|
||||||
|
if params.Page != DEFAULT_REACTIONS_PAGE {
|
||||||
|
values.Add("page", strconv.Itoa(params.Page))
|
||||||
|
}
|
||||||
|
if params.Full != DEFAULT_REACTIONS_FULL {
|
||||||
|
values.Add("full", strconv.FormatBool(params.Full))
|
||||||
|
}
|
||||||
|
response := &listReactionsResponseFull{}
|
||||||
|
err := post("reactions.list", values, response, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response.extractReactedItems(), &response.Paging, nil
|
||||||
|
}
|
39
vendor/github.com/nlopes/slack/rtm.go
generated
vendored
Normal file
39
vendor/github.com/nlopes/slack/rtm.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartRTM calls the "rtm.start" endpoint and returns the provided URL and the full Info
|
||||||
|
// block.
|
||||||
|
//
|
||||||
|
// To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()`
|
||||||
|
// on it.
|
||||||
|
func (api *Client) StartRTM() (info *Info, websocketURL string, err error) {
|
||||||
|
response := &infoResponseFull{}
|
||||||
|
err = post("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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRTM returns a RTM, which provides a fully managed connection to
|
||||||
|
// Slack's websocket-based Real-Time Messaging protocol./
|
||||||
|
func (api *Client) NewRTM() *RTM {
|
||||||
|
return newRTM(api)
|
||||||
|
}
|
137
vendor/github.com/nlopes/slack/search.go
generated
vendored
Normal file
137
vendor/github.com/nlopes/slack/search.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_SEARCH_SORT = "score"
|
||||||
|
DEFAULT_SEARCH_SORT_DIR = "desc"
|
||||||
|
DEFAULT_SEARCH_HIGHLIGHT = false
|
||||||
|
DEFAULT_SEARCH_COUNT = 100
|
||||||
|
DEFAULT_SEARCH_PAGE = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type SearchParameters struct {
|
||||||
|
Sort string
|
||||||
|
SortDirection string
|
||||||
|
Highlight bool
|
||||||
|
Count int
|
||||||
|
Page int
|
||||||
|
}
|
||||||
|
|
||||||
|
type CtxChannel struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CtxMessage struct {
|
||||||
|
User string `json:"user"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Timestamp string `json:"ts"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchMessage struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Channel CtxChannel `json:"channel"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Timestamp string `json:"ts"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Permalink string `json:"permalink"`
|
||||||
|
Previous CtxMessage `json:"previous"`
|
||||||
|
Previous2 CtxMessage `json:"previous_2"`
|
||||||
|
Next CtxMessage `json:"next"`
|
||||||
|
Next2 CtxMessage `json:"next_2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchMessages struct {
|
||||||
|
Matches []SearchMessage `json:"matches"`
|
||||||
|
Paging `json:"paging"`
|
||||||
|
Pagination `json:"pagination"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchFiles struct {
|
||||||
|
Matches []File `json:"matches"`
|
||||||
|
Paging `json:"paging"`
|
||||||
|
Pagination `json:"pagination"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type searchResponseFull struct {
|
||||||
|
Query string `json:"query"`
|
||||||
|
SearchMessages `json:"messages"`
|
||||||
|
SearchFiles `json:"files"`
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSearchParameters() SearchParameters {
|
||||||
|
return SearchParameters{
|
||||||
|
Sort: DEFAULT_SEARCH_SORT,
|
||||||
|
SortDirection: DEFAULT_SEARCH_SORT_DIR,
|
||||||
|
Highlight: DEFAULT_SEARCH_HIGHLIGHT,
|
||||||
|
Count: DEFAULT_SEARCH_COUNT,
|
||||||
|
Page: DEFAULT_SEARCH_PAGE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *Client) _search(path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"query": {query},
|
||||||
|
}
|
||||||
|
if params.Sort != DEFAULT_SEARCH_SORT {
|
||||||
|
values.Add("sort", params.Sort)
|
||||||
|
}
|
||||||
|
if params.SortDirection != DEFAULT_SEARCH_SORT_DIR {
|
||||||
|
values.Add("sort_dir", params.SortDirection)
|
||||||
|
}
|
||||||
|
if params.Highlight != DEFAULT_SEARCH_HIGHLIGHT {
|
||||||
|
values.Add("highlight", strconv.Itoa(1))
|
||||||
|
}
|
||||||
|
if params.Count != DEFAULT_SEARCH_COUNT {
|
||||||
|
values.Add("count", strconv.Itoa(params.Count))
|
||||||
|
}
|
||||||
|
if params.Page != DEFAULT_SEARCH_PAGE {
|
||||||
|
values.Add("page", strconv.Itoa(params.Page))
|
||||||
|
}
|
||||||
|
response = &searchResponseFull{}
|
||||||
|
err := post(path, values, response, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *Client) Search(query string, params SearchParameters) (*SearchMessages, *SearchFiles, error) {
|
||||||
|
response, err := api._search("search.all", query, params, true, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return &response.SearchMessages, &response.SearchFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *Client) SearchFiles(query string, params SearchParameters) (*SearchFiles, error) {
|
||||||
|
response, err := api._search("search.files", query, params, true, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.SearchFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *Client) SearchMessages(query string, params SearchParameters) (*SearchMessages, error) {
|
||||||
|
response, err := api._search("search.messages", query, params, false, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.SearchMessages, nil
|
||||||
|
}
|
88
vendor/github.com/nlopes/slack/slack.go
generated
vendored
Normal file
88
vendor/github.com/nlopes/slack/slack.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger *log.Logger // A logger that can be set by consumers
|
||||||
|
/*
|
||||||
|
Added as a var so that we can change this for testing purposes
|
||||||
|
*/
|
||||||
|
var SLACK_API string = "https://slack.com/api/"
|
||||||
|
var SLACK_WEB_API_FORMAT string = "https://%s.slack.com/api/users.admin.%s?t=%s"
|
||||||
|
|
||||||
|
type SlackResponse struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthTestResponse struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Team string `json:"team"`
|
||||||
|
User string `json:"user"`
|
||||||
|
TeamID string `json:"team_id"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type authTestResponseFull struct {
|
||||||
|
SlackResponse
|
||||||
|
AuthTestResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
config struct {
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
info Info
|
||||||
|
debug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger let's library users supply a logger, so that api debugging
|
||||||
|
// can be logged along with the application's debugging info.
|
||||||
|
func SetLogger(l *log.Logger) {
|
||||||
|
logger = l
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(token string) *Client {
|
||||||
|
s := &Client{}
|
||||||
|
s.config.token = token
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthTest tests if the user is able to do authenticated requests or not
|
||||||
|
func (api *Client) AuthTest() (response *AuthTestResponse, error error) {
|
||||||
|
responseFull := &authTestResponseFull{}
|
||||||
|
err := post("auth.test", url.Values{"token": {api.config.token}}, responseFull, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !responseFull.Ok {
|
||||||
|
return nil, errors.New(responseFull.Error)
|
||||||
|
}
|
||||||
|
return &responseFull.AuthTestResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDebug switches the api into debug mode
|
||||||
|
// When in debug mode, it logs various info about what its doing
|
||||||
|
// If you ever use this in production, don't call SetDebug(true)
|
||||||
|
func (api *Client) SetDebug(debug bool) {
|
||||||
|
api.debug = debug
|
||||||
|
if debug && logger == nil {
|
||||||
|
logger = log.New(os.Stdout, "nlopes/slack", log.LstdFlags | log.Lshortfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *Client) Debugf(format string, v ...interface{}) {
|
||||||
|
if api.debug {
|
||||||
|
logger.Printf(format, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *Client) Debugln(v ...interface{}) {
|
||||||
|
if api.debug {
|
||||||
|
logger.Println(v...)
|
||||||
|
}
|
||||||
|
}
|
135
vendor/github.com/nlopes/slack/stars.go
generated
vendored
Normal file
135
vendor/github.com/nlopes/slack/stars.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_STARS_USER = ""
|
||||||
|
DEFAULT_STARS_COUNT = 100
|
||||||
|
DEFAULT_STARS_PAGE = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type StarsParameters struct {
|
||||||
|
User string
|
||||||
|
Count int
|
||||||
|
Page int
|
||||||
|
}
|
||||||
|
|
||||||
|
type StarredItem Item
|
||||||
|
|
||||||
|
type listResponseFull struct {
|
||||||
|
Items []Item `json:"items"`
|
||||||
|
Paging `json:"paging"`
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStarsParameters initialises StarsParameters with default values
|
||||||
|
func NewStarsParameters() StarsParameters {
|
||||||
|
return StarsParameters{
|
||||||
|
User: DEFAULT_STARS_USER,
|
||||||
|
Count: DEFAULT_STARS_COUNT,
|
||||||
|
Page: DEFAULT_STARS_PAGE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStar stars an item in a channel
|
||||||
|
func (api *Client) AddStar(channel string, item ItemRef) error {
|
||||||
|
values := url.Values{
|
||||||
|
"channel": {channel},
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if item.Timestamp != "" {
|
||||||
|
values.Set("timestamp", string(item.Timestamp))
|
||||||
|
}
|
||||||
|
if item.File != "" {
|
||||||
|
values.Set("file", string(item.File))
|
||||||
|
}
|
||||||
|
if item.Comment != "" {
|
||||||
|
values.Set("file_comment", string(item.Comment))
|
||||||
|
}
|
||||||
|
response := &SlackResponse{}
|
||||||
|
if err := post("stars.add", values, response, api.debug); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveStar removes a starred item from a channel
|
||||||
|
func (api *Client) RemoveStar(channel string, item ItemRef) error {
|
||||||
|
values := url.Values{
|
||||||
|
"channel": {channel},
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if item.Timestamp != "" {
|
||||||
|
values.Set("timestamp", string(item.Timestamp))
|
||||||
|
}
|
||||||
|
if item.File != "" {
|
||||||
|
values.Set("file", string(item.File))
|
||||||
|
}
|
||||||
|
if item.Comment != "" {
|
||||||
|
values.Set("file_comment", string(item.Comment))
|
||||||
|
}
|
||||||
|
response := &SlackResponse{}
|
||||||
|
if err := post("stars.remove", values, response, api.debug); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListStars returns information about the stars a user added
|
||||||
|
func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
if params.User != DEFAULT_STARS_USER {
|
||||||
|
values.Add("user", params.User)
|
||||||
|
}
|
||||||
|
if params.Count != DEFAULT_STARS_COUNT {
|
||||||
|
values.Add("count", strconv.Itoa(params.Count))
|
||||||
|
}
|
||||||
|
if params.Page != DEFAULT_STARS_PAGE {
|
||||||
|
values.Add("page", strconv.Itoa(params.Page))
|
||||||
|
}
|
||||||
|
response := &listResponseFull{}
|
||||||
|
err := post("stars.list", values, response, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, nil, errors.New(response.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
|
||||||
|
// be looking at according to what is in the Type.
|
||||||
|
// for _, item := range items {
|
||||||
|
// switch c.Type {
|
||||||
|
// case "file_comment":
|
||||||
|
// log.Println(c.Comment)
|
||||||
|
// case "file":
|
||||||
|
// ...
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
starredItems := make([]StarredItem, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
starredItems[i] = StarredItem(item)
|
||||||
|
}
|
||||||
|
return starredItems, paging, nil
|
||||||
|
}
|
46
vendor/github.com/nlopes/slack/team.go
generated
vendored
Normal file
46
vendor/github.com/nlopes/slack/team.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TeamResponse struct {
|
||||||
|
Team TeamInfo `json:"team"`
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type TeamInfo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
EmailDomain string `json:"email_domain"`
|
||||||
|
Icon map[string]interface{} `json:"icon"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func teamRequest(path string, values url.Values, debug bool) (*TeamResponse, error) {
|
||||||
|
response := &TeamResponse{}
|
||||||
|
err := post(path, values, response, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTeamInfo gets the Team Information of the user
|
||||||
|
func (api *Client) GetTeamInfo() (*TeamInfo, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := teamRequest("team.info", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.Team, nil
|
||||||
|
}
|
140
vendor/github.com/nlopes/slack/users.go
generated
vendored
Normal file
140
vendor/github.com/nlopes/slack/users.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserProfile contains all the information details of a given user
|
||||||
|
type UserProfile struct {
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
RealName string `json:"real_name"`
|
||||||
|
RealNameNormalized string `json:"real_name_normalized"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Skype string `json:"skype"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Image24 string `json:"image_24"`
|
||||||
|
Image32 string `json:"image_32"`
|
||||||
|
Image48 string `json:"image_48"`
|
||||||
|
Image72 string `json:"image_72"`
|
||||||
|
Image192 string `json:"image_192"`
|
||||||
|
ImageOriginal string `json:"image_original"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// User contains all the information of a user
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Deleted bool `json:"deleted"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
RealName string `json:"real_name"`
|
||||||
|
TZ string `json:"tz,omitempty"`
|
||||||
|
TZLabel string `json:"tz_label"`
|
||||||
|
TZOffset int `json:"tz_offset"`
|
||||||
|
Profile UserProfile `json:"profile"`
|
||||||
|
IsBot bool `json:"is_bot"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
IsOwner bool `json:"is_owner"`
|
||||||
|
IsPrimaryOwner bool `json:"is_primary_owner"`
|
||||||
|
IsRestricted bool `json:"is_restricted"`
|
||||||
|
IsUltraRestricted bool `json:"is_ultra_restricted"`
|
||||||
|
Has2FA bool `json:"has_2fa"`
|
||||||
|
HasFiles bool `json:"has_files"`
|
||||||
|
Presence string `json:"presence"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserPresence contains details about a user online status
|
||||||
|
type UserPresence struct {
|
||||||
|
Presence string `json:"presence,omitempty"`
|
||||||
|
Online bool `json:"online,omitempty"`
|
||||||
|
AutoAway bool `json:"auto_away,omitempty"`
|
||||||
|
ManualAway bool `json:"manual_away,omitempty"`
|
||||||
|
ConnectionCount int `json:"connection_count,omitempty"`
|
||||||
|
LastActivity JSONTime `json:"last_activity,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type userResponseFull struct {
|
||||||
|
Members []User `json:"members,omitempty"` // ListUsers
|
||||||
|
User `json:"user,omitempty"` // GetUserInfo
|
||||||
|
UserPresence // GetUserPresence
|
||||||
|
SlackResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func userRequest(path string, values url.Values, debug bool) (*userResponseFull, error) {
|
||||||
|
response := &userResponseFull{}
|
||||||
|
err := post(path, values, response, debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !response.Ok {
|
||||||
|
return nil, errors.New(response.Error)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserPresence will retrieve the current presence status of given user.
|
||||||
|
func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"user": {user},
|
||||||
|
}
|
||||||
|
response, err := userRequest("users.getPresence", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.UserPresence, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo will retrive the complete user information
|
||||||
|
func (api *Client) GetUserInfo(user string) (*User, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"user": {user},
|
||||||
|
}
|
||||||
|
response, err := userRequest("users.info", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response.User, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUsers returns the list of users (with their detailed information)
|
||||||
|
func (api *Client) GetUsers() ([]User, error) {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"presence": {"1"},
|
||||||
|
}
|
||||||
|
response, err := userRequest("users.list", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return response.Members, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserAsActive marks the currently authenticated user as active
|
||||||
|
func (api *Client) SetUserAsActive() error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
}
|
||||||
|
_, err := userRequest("users.setActive", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserPresence changes the currently authenticated user presence
|
||||||
|
func (api *Client) SetUserPresence(presence string) error {
|
||||||
|
values := url.Values{
|
||||||
|
"token": {api.config.token},
|
||||||
|
"presence": {presence},
|
||||||
|
}
|
||||||
|
_, err := userRequest("users.setPresence", values, api.debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
93
vendor/github.com/nlopes/slack/websocket.go
generated
vendored
Normal file
93
vendor/github.com/nlopes/slack/websocket.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxMessageTextLength is the current maximum message length in number of characters as defined here
|
||||||
|
// https://api.slack.com/rtm#limits
|
||||||
|
MaxMessageTextLength = 4000
|
||||||
|
)
|
||||||
|
|
||||||
|
// RTM represents a managed websocket connection. It also supports
|
||||||
|
// all the methods of the `Client` type.
|
||||||
|
//
|
||||||
|
// Create this element with Client's NewRTM().
|
||||||
|
type RTM struct {
|
||||||
|
idGen IDGenerator
|
||||||
|
pings map[int]time.Time
|
||||||
|
|
||||||
|
// Connection life-cycle
|
||||||
|
conn *websocket.Conn
|
||||||
|
IncomingEvents chan RTMEvent
|
||||||
|
outgoingMessages chan OutgoingMessage
|
||||||
|
killChannel chan bool
|
||||||
|
forcePing chan bool
|
||||||
|
rawEvents chan json.RawMessage
|
||||||
|
wasIntentional bool
|
||||||
|
isConnected bool
|
||||||
|
|
||||||
|
// Client is the main API, embedded
|
||||||
|
Client
|
||||||
|
websocketURL string
|
||||||
|
|
||||||
|
// UserDetails upon connection
|
||||||
|
info *Info
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect and wait, blocking until a successful disconnection.
|
||||||
|
func (rtm *RTM) Disconnect() error {
|
||||||
|
if !rtm.isConnected {
|
||||||
|
return errors.New("Invalid call to Disconnect - Slack API is already disconnected")
|
||||||
|
}
|
||||||
|
rtm.killChannel <- true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconnect only makes sense if you've successfully disconnectd with Disconnect().
|
||||||
|
func (rtm *RTM) Reconnect() error {
|
||||||
|
logger.Println("RTM::Reconnect not implemented!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInfo returns the info structure received when calling
|
||||||
|
// "startrtm", holding all channels, groups and other metadata needed
|
||||||
|
// to implement a full chat client. It will be non-nil after a call to
|
||||||
|
// StartRTM().
|
||||||
|
func (rtm *RTM) GetInfo() *Info {
|
||||||
|
return rtm.info
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessage submits a simple message through the websocket. For
|
||||||
|
// more complicated messages, use `rtm.PostMessage` with a complete
|
||||||
|
// struct describing your attachments and all.
|
||||||
|
func (rtm *RTM) SendMessage(msg *OutgoingMessage) {
|
||||||
|
if msg == nil {
|
||||||
|
rtm.Debugln("Error: Attempted to SendMessage(nil)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rtm.outgoingMessages <- *msg
|
||||||
|
}
|
72
vendor/github.com/nlopes/slack/websocket_channels.go
generated
vendored
Normal file
72
vendor/github.com/nlopes/slack/websocket_channels.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// ChannelCreatedEvent represents the Channel created event
|
||||||
|
type ChannelCreatedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Channel ChannelCreatedInfo `json:"channel"`
|
||||||
|
EventTimestamp string `json:"event_ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelCreatedInfo represents the information associated with the Channel created event
|
||||||
|
type ChannelCreatedInfo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
IsChannel bool `json:"is_channel"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Created int `json:"created"`
|
||||||
|
Creator string `json:"creator"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelJoinedEvent represents the Channel joined event
|
||||||
|
type ChannelJoinedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Channel Channel `json:"channel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelInfoEvent represents the Channel info event
|
||||||
|
type ChannelInfoEvent struct {
|
||||||
|
// channel_left
|
||||||
|
// channel_deleted
|
||||||
|
// channel_archive
|
||||||
|
// channel_unarchive
|
||||||
|
Type string `json:"type"`
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Timestamp string `json:"ts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelRenameEvent represents the Channel rename event
|
||||||
|
type ChannelRenameEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Channel ChannelRenameInfo `json:"channel"`
|
||||||
|
Timestamp string `json:"event_ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelRenameInfo represents the information associated with a Channel rename event
|
||||||
|
type ChannelRenameInfo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Created string `json:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelHistoryChangedEvent represents the Channel history changed event
|
||||||
|
type ChannelHistoryChangedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Latest string `json:"latest"`
|
||||||
|
Timestamp string `json:"ts"`
|
||||||
|
EventTimestamp string `json:"event_ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelMarkedEvent represents the Channel marked event
|
||||||
|
type ChannelMarkedEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// ChannelLeftEvent represents the Channel left event
|
||||||
|
type ChannelLeftEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// ChannelDeletedEvent represents the Channel deleted event
|
||||||
|
type ChannelDeletedEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// ChannelArchiveEvent represents the Channel archive event
|
||||||
|
type ChannelArchiveEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// ChannelUnarchiveEvent represents the Channel unarchive event
|
||||||
|
type ChannelUnarchiveEvent ChannelInfoEvent
|
23
vendor/github.com/nlopes/slack/websocket_dm.go
generated
vendored
Normal file
23
vendor/github.com/nlopes/slack/websocket_dm.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// IMCreatedEvent represents the IM created event
|
||||||
|
type IMCreatedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Channel ChannelCreatedInfo `json:"channel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMHistoryChangedEvent represents the IM history changed event
|
||||||
|
type IMHistoryChangedEvent ChannelHistoryChangedEvent
|
||||||
|
|
||||||
|
// IMOpenEvent represents the IM open event
|
||||||
|
type IMOpenEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// IMCloseEvent represents the IM close event
|
||||||
|
type IMCloseEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// IMMarkedEvent represents the IM marked event
|
||||||
|
type IMMarkedEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// IMMarkedHistoryChanged represents the IM marked history changed event
|
||||||
|
type IMMarkedHistoryChanged ChannelInfoEvent
|
8
vendor/github.com/nlopes/slack/websocket_dnd.go
generated
vendored
Normal file
8
vendor/github.com/nlopes/slack/websocket_dnd.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// DNDUpdatedEvent represents the update event for Do Not Disturb
|
||||||
|
type DNDUpdatedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Status DNDStatus `json:"dnd_status"`
|
||||||
|
}
|
49
vendor/github.com/nlopes/slack/websocket_files.go
generated
vendored
Normal file
49
vendor/github.com/nlopes/slack/websocket_files.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// FileActionEvent represents the File action event
|
||||||
|
type fileActionEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
EventTimestamp string `json:"event_ts"`
|
||||||
|
File File `json:"file"`
|
||||||
|
// FileID is used for FileDeletedEvent
|
||||||
|
FileID string `json:"file_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileCreatedEvent represents the File created event
|
||||||
|
type FileCreatedEvent fileActionEvent
|
||||||
|
|
||||||
|
// FileSharedEvent represents the File shared event
|
||||||
|
type FileSharedEvent fileActionEvent
|
||||||
|
|
||||||
|
// FilePublicEvent represents the File public event
|
||||||
|
type FilePublicEvent fileActionEvent
|
||||||
|
|
||||||
|
// FileUnsharedEvent represents the File unshared event
|
||||||
|
type FileUnsharedEvent fileActionEvent
|
||||||
|
|
||||||
|
// FileChangeEvent represents the File change event
|
||||||
|
type FileChangeEvent fileActionEvent
|
||||||
|
|
||||||
|
// FileDeletedEvent represents the File deleted event
|
||||||
|
type FileDeletedEvent fileActionEvent
|
||||||
|
|
||||||
|
// FilePrivateEvent represents the File private event
|
||||||
|
type FilePrivateEvent fileActionEvent
|
||||||
|
|
||||||
|
// FileCommentAddedEvent represents the File comment added event
|
||||||
|
type FileCommentAddedEvent struct {
|
||||||
|
fileActionEvent
|
||||||
|
Comment Comment `json:"comment"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileCommentEditedEvent represents the File comment edited event
|
||||||
|
type FileCommentEditedEvent struct {
|
||||||
|
fileActionEvent
|
||||||
|
Comment Comment `json:"comment"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileCommentDeletedEvent represents the File comment deleted event
|
||||||
|
type FileCommentDeletedEvent struct {
|
||||||
|
fileActionEvent
|
||||||
|
Comment string `json:"comment"`
|
||||||
|
}
|
49
vendor/github.com/nlopes/slack/websocket_groups.go
generated
vendored
Normal file
49
vendor/github.com/nlopes/slack/websocket_groups.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// GroupCreatedEvent represents the Group created event
|
||||||
|
type GroupCreatedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Channel ChannelCreatedInfo `json:"channel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: Should we really do this? event.Group is probably nicer than event.Channel
|
||||||
|
// even though the api returns "channel"
|
||||||
|
|
||||||
|
// GroupMarkedEvent represents the Group marked event
|
||||||
|
type GroupMarkedEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// GroupOpenEvent represents the Group open event
|
||||||
|
type GroupOpenEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// GroupCloseEvent represents the Group close event
|
||||||
|
type GroupCloseEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// GroupArchiveEvent represents the Group archive event
|
||||||
|
type GroupArchiveEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// GroupUnarchiveEvent represents the Group unarchive event
|
||||||
|
type GroupUnarchiveEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// GroupLeftEvent represents the Group left event
|
||||||
|
type GroupLeftEvent ChannelInfoEvent
|
||||||
|
|
||||||
|
// GroupJoinedEvent represents the Group joined event
|
||||||
|
type GroupJoinedEvent ChannelJoinedEvent
|
||||||
|
|
||||||
|
// GroupRenameEvent represents the Group rename event
|
||||||
|
type GroupRenameEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Group GroupRenameInfo `json:"channel"`
|
||||||
|
Timestamp string `json:"ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupRenameInfo represents the group info related to the renamed group
|
||||||
|
type GroupRenameInfo struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Created string `json:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupHistoryChangedEvent represents the Group history changed event
|
||||||
|
type GroupHistoryChangedEvent ChannelHistoryChangedEvent
|
92
vendor/github.com/nlopes/slack/websocket_internals.go
generated
vendored
Normal file
92
vendor/github.com/nlopes/slack/websocket_internals.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal events, created by this lib and not mapped to Slack APIs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ConnectedEvent is used for when we connect to Slack
|
||||||
|
type ConnectedEvent struct {
|
||||||
|
ConnectionCount int // 1 = first time, 2 = second time
|
||||||
|
Info *Info
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectionErrorEvent contains information about a connection error
|
||||||
|
type ConnectionErrorEvent struct {
|
||||||
|
Attempt int
|
||||||
|
ErrorObj error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnectionErrorEvent) Error() string {
|
||||||
|
return c.ErrorObj.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectingEvent contains information about our connection attempt
|
||||||
|
type ConnectingEvent struct {
|
||||||
|
Attempt int // 1 = first attempt, 2 = second attempt
|
||||||
|
ConnectionCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisconnectedEvent contains information about how we disconnected
|
||||||
|
type DisconnectedEvent struct {
|
||||||
|
Intentional bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatencyReport contains information about connection latency
|
||||||
|
type LatencyReport struct {
|
||||||
|
Value time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidAuthEvent is used in case we can't even authenticate with the API
|
||||||
|
type InvalidAuthEvent struct{}
|
||||||
|
|
||||||
|
// UnmarshallingErrorEvent is used when there are issues deconstructing a response
|
||||||
|
type UnmarshallingErrorEvent struct {
|
||||||
|
ErrorObj error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u UnmarshallingErrorEvent) Error() string {
|
||||||
|
return u.ErrorObj.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageTooLongEvent is used when sending a message that is too long
|
||||||
|
type MessageTooLongEvent struct {
|
||||||
|
Message OutgoingMessage
|
||||||
|
MaxLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MessageTooLongEvent) Error() string {
|
||||||
|
return fmt.Sprintf("Message too long (max %d characters)", m.MaxLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutgoingErrorEvent contains information in case there were errors sending messages
|
||||||
|
type OutgoingErrorEvent struct {
|
||||||
|
Message OutgoingMessage
|
||||||
|
ErrorObj error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o OutgoingErrorEvent) Error() string {
|
||||||
|
return o.ErrorObj.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncomingEventError contains information about an unexpected error receiving a websocket event
|
||||||
|
type IncomingEventError struct {
|
||||||
|
ErrorObj error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IncomingEventError) Error() string {
|
||||||
|
return i.ErrorObj.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AckErrorEvent i
|
||||||
|
type AckErrorEvent struct {
|
||||||
|
ErrorObj error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AckErrorEvent) Error() string {
|
||||||
|
return a.ErrorObj.Error()
|
||||||
|
}
|
427
vendor/github.com/nlopes/slack/websocket_managed_conn.go
generated
vendored
Normal file
427
vendor/github.com/nlopes/slack/websocket_managed_conn.go
generated
vendored
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ManageConnection can be called on a Slack RTM instance returned by the
|
||||||
|
// NewRTM method. It will connect to the slack RTM API and handle all incoming
|
||||||
|
// and outgoing events. If a connection fails then it will attempt to reconnect
|
||||||
|
// and will notify any listeners through an error event on the IncomingEvents
|
||||||
|
// channel.
|
||||||
|
//
|
||||||
|
// If the connection ends and the disconnect was unintentional then this will
|
||||||
|
// attempt to reconnect.
|
||||||
|
//
|
||||||
|
// This should only be called once per slack API! Otherwise expect undefined
|
||||||
|
// behavior.
|
||||||
|
//
|
||||||
|
// The defined error events are located in websocket_internals.go.
|
||||||
|
func (rtm *RTM) ManageConnection() {
|
||||||
|
var connectionCount int
|
||||||
|
for {
|
||||||
|
connectionCount++
|
||||||
|
// start trying to connect
|
||||||
|
// the returned err is already passed onto the IncomingEvents channel
|
||||||
|
info, conn, err := rtm.connect(connectionCount)
|
||||||
|
// if err != nil then the connection is sucessful - otherwise it is
|
||||||
|
// fatal
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rtm.info = info
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"connected", &ConnectedEvent{
|
||||||
|
ConnectionCount: connectionCount,
|
||||||
|
Info: info,
|
||||||
|
}}
|
||||||
|
|
||||||
|
rtm.conn = conn
|
||||||
|
rtm.isConnected = true
|
||||||
|
|
||||||
|
keepRunning := make(chan bool)
|
||||||
|
// we're now connected (or have failed fatally) so we can set up
|
||||||
|
// listeners
|
||||||
|
go rtm.handleIncomingEvents(keepRunning)
|
||||||
|
|
||||||
|
// this should be a blocking call until the connection has ended
|
||||||
|
rtm.handleEvents(keepRunning, 30*time.Second)
|
||||||
|
|
||||||
|
// after being disconnected we need to check if it was intentional
|
||||||
|
// if not then we should try to reconnect
|
||||||
|
if rtm.wasIntentional {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// else continue and run the loop again to connect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// used to provide exponential backoff wait time with jitter before trying
|
||||||
|
// to connect to slack again
|
||||||
|
boff := &backoff{
|
||||||
|
Min: 100 * time.Millisecond,
|
||||||
|
Max: 5 * time.Minute,
|
||||||
|
Factor: 2,
|
||||||
|
Jitter: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// send connecting event
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"connecting", &ConnectingEvent{
|
||||||
|
Attempt: boff.attempts + 1,
|
||||||
|
ConnectionCount: connectionCount,
|
||||||
|
}}
|
||||||
|
// attempt to start the connection
|
||||||
|
info, conn, err := rtm.startRTMAndDial()
|
||||||
|
if err == nil {
|
||||||
|
return info, conn, nil
|
||||||
|
}
|
||||||
|
// check for fatal errors - currently only invalid_auth
|
||||||
|
if sErr, ok := err.(*WebError); ok && (sErr.Error() == "invalid_auth" || sErr.Error() == "account_inactive") {
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}}
|
||||||
|
return nil, nil, sErr
|
||||||
|
}
|
||||||
|
// any other errors are treated as recoverable and we try again after
|
||||||
|
// sending the event along the IncomingEvents channel
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"connection_error", &ConnectionErrorEvent{
|
||||||
|
Attempt: boff.attempts,
|
||||||
|
ErrorObj: err,
|
||||||
|
}}
|
||||||
|
// get time we should wait before attempting to connect again
|
||||||
|
dur := boff.Duration()
|
||||||
|
rtm.Debugf("reconnection %d failed: %s", boff.attempts+1, err)
|
||||||
|
rtm.Debugln(" -> reconnecting in", dur)
|
||||||
|
time.Sleep(dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := websocketProxyDial(url, "http://api.slack.com")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return info, conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// killConnection stops the websocket connection and signals to all goroutines
|
||||||
|
// that they should cease listening to the connection for events.
|
||||||
|
//
|
||||||
|
// This should not be called directly! Instead a boolean value (true for
|
||||||
|
// intentional, false otherwise) should be sent to the killChannel on the RTM.
|
||||||
|
func (rtm *RTM) killConnection(keepRunning chan bool, intentional bool) error {
|
||||||
|
rtm.Debugln("killing connection")
|
||||||
|
if rtm.isConnected {
|
||||||
|
close(keepRunning)
|
||||||
|
}
|
||||||
|
rtm.isConnected = false
|
||||||
|
rtm.wasIntentional = intentional
|
||||||
|
err := rtm.conn.Close()
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"disconnected", &DisconnectedEvent{intentional}}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEvents is a blocking function that handles all events. This sends
|
||||||
|
// pings when asked to (on rtm.forcePing) and upon every given elapsed
|
||||||
|
// interval. This also sends outgoing messages that are received from the RTM's
|
||||||
|
// outgoingMessages channel. This also handles incoming raw events from the RTM
|
||||||
|
// rawEvents channel.
|
||||||
|
func (rtm *RTM) handleEvents(keepRunning chan bool, interval time.Duration) {
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
// catch "stop" signal on channel close
|
||||||
|
case intentional := <-rtm.killChannel:
|
||||||
|
_ = rtm.killConnection(keepRunning, intentional)
|
||||||
|
return
|
||||||
|
// send pings on ticker interval
|
||||||
|
case <-ticker.C:
|
||||||
|
err := rtm.ping()
|
||||||
|
if err != nil {
|
||||||
|
_ = rtm.killConnection(keepRunning, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-rtm.forcePing:
|
||||||
|
err := rtm.ping()
|
||||||
|
if err != nil {
|
||||||
|
_ = rtm.killConnection(keepRunning, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// listen for messages that need to be sent
|
||||||
|
case msg := <-rtm.outgoingMessages:
|
||||||
|
rtm.sendOutgoingMessage(msg)
|
||||||
|
// listen for incoming messages that need to be parsed
|
||||||
|
case rawEvent := <-rtm.rawEvents:
|
||||||
|
rtm.handleRawEvent(rawEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleIncomingEvents monitors the RTM's opened websocket for any incoming
|
||||||
|
// events. It pushes the raw events onto the RTM channel rawEvents.
|
||||||
|
//
|
||||||
|
// This will stop executing once the RTM's keepRunning channel has been closed
|
||||||
|
// or has anything sent to it.
|
||||||
|
func (rtm *RTM) handleIncomingEvents(keepRunning <-chan bool) {
|
||||||
|
for {
|
||||||
|
// non-blocking listen to see if channel is closed
|
||||||
|
select {
|
||||||
|
// catch "stop" signal on channel close
|
||||||
|
case <-keepRunning:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
rtm.receiveIncomingEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendOutgoingMessage sends the given OutgoingMessage to the slack websocket.
|
||||||
|
//
|
||||||
|
// It does not currently detect if a outgoing message fails due to a disconnect
|
||||||
|
// and instead lets a future failed 'PING' detect the failed connection.
|
||||||
|
func (rtm *RTM) sendOutgoingMessage(msg OutgoingMessage) {
|
||||||
|
rtm.Debugln("Sending message:", msg)
|
||||||
|
if len(msg.Text) > MaxMessageTextLength {
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"outgoing_error", &MessageTooLongEvent{
|
||||||
|
Message: msg,
|
||||||
|
MaxLength: MaxMessageTextLength,
|
||||||
|
}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := websocket.JSON.Send(rtm.conn, msg)
|
||||||
|
if err != nil {
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"outgoing_error", &OutgoingErrorEvent{
|
||||||
|
Message: msg,
|
||||||
|
ErrorObj: err,
|
||||||
|
}}
|
||||||
|
// TODO force ping?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ping sends a 'PING' message to the RTM's websocket. If the 'PING' message
|
||||||
|
// fails to send then this returns an error signifying that the connection
|
||||||
|
// should be considered disconnected.
|
||||||
|
//
|
||||||
|
// This does not handle incoming 'PONG' responses but does store the time of
|
||||||
|
// each successful 'PING' send so latency can be detected upon a 'PONG'
|
||||||
|
// response.
|
||||||
|
func (rtm *RTM) ping() error {
|
||||||
|
id := rtm.idGen.Next()
|
||||||
|
rtm.Debugln("Sending PING ", id)
|
||||||
|
rtm.pings[id] = time.Now()
|
||||||
|
|
||||||
|
msg := &Ping{ID: id, Type: "ping"}
|
||||||
|
err := websocket.JSON.Send(rtm.conn, msg)
|
||||||
|
if err != nil {
|
||||||
|
rtm.Debugf("RTM Error sending 'PING %d': %s", id, err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// receiveIncomingEvent attempts to receive an event from the RTM's websocket.
|
||||||
|
// This will block until a frame is available from the websocket.
|
||||||
|
func (rtm *RTM) receiveIncomingEvent() {
|
||||||
|
event := json.RawMessage{}
|
||||||
|
err := websocket.JSON.Receive(rtm.conn, &event)
|
||||||
|
if err == io.EOF {
|
||||||
|
// EOF's don't seem to signify a failed connection so instead we ignore
|
||||||
|
// them here and detect a failed connection upon attempting to send a
|
||||||
|
// 'PING' message
|
||||||
|
|
||||||
|
// trigger a 'PING' to detect pontential websocket disconnect
|
||||||
|
rtm.forcePing <- true
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"incoming_error", &IncomingEventError{
|
||||||
|
ErrorObj: err,
|
||||||
|
}}
|
||||||
|
// force a ping here too?
|
||||||
|
return
|
||||||
|
} else if len(event) == 0 {
|
||||||
|
rtm.Debugln("Received empty event")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rtm.Debugln("Incoming Event:", string(event[:]))
|
||||||
|
rtm.rawEvents <- event
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRawEvent takes a raw JSON message received from the slack websocket
|
||||||
|
// and handles the encoded event.
|
||||||
|
func (rtm *RTM) handleRawEvent(rawEvent json.RawMessage) {
|
||||||
|
event := &Event{}
|
||||||
|
err := json.Unmarshal(rawEvent, event)
|
||||||
|
if err != nil {
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch event.Type {
|
||||||
|
case "":
|
||||||
|
rtm.handleAck(rawEvent)
|
||||||
|
case "hello":
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"hello", &HelloEvent{}}
|
||||||
|
case "pong":
|
||||||
|
rtm.handlePong(rawEvent)
|
||||||
|
default:
|
||||||
|
rtm.handleEvent(event.Type, rawEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleAck handles an incoming 'ACK' message.
|
||||||
|
func (rtm *RTM) handleAck(event json.RawMessage) {
|
||||||
|
ack := &AckMessage{}
|
||||||
|
if err := json.Unmarshal(event, ack); err != nil {
|
||||||
|
rtm.Debugln("RTM Error unmarshalling 'ack' event:", err)
|
||||||
|
rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ack.Ok {
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"ack", ack}
|
||||||
|
} else {
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"ack_error", &AckErrorEvent{ack.Error}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePong handles an incoming 'PONG' message which should be in response to
|
||||||
|
// a previously sent 'PING' message. This is then used to compute the
|
||||||
|
// connection's latency.
|
||||||
|
func (rtm *RTM) handlePong(event json.RawMessage) {
|
||||||
|
pong := &Pong{}
|
||||||
|
if err := json.Unmarshal(event, pong); err != nil {
|
||||||
|
rtm.Debugln("RTM Error unmarshalling 'pong' event:", err)
|
||||||
|
rtm.Debugln(" -> Erroneous 'ping' event:", string(event))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pingTime, exists := rtm.pings[pong.ReplyTo]; exists {
|
||||||
|
latency := time.Since(pingTime)
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"latency_report", &LatencyReport{Value: latency}}
|
||||||
|
delete(rtm.pings, pong.ReplyTo)
|
||||||
|
} else {
|
||||||
|
rtm.Debugln("RTM Error - unmatched 'pong' event:", string(event))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleEvent is the "default" response to an event that does not have a
|
||||||
|
// special case. It matches the command's name to a mapping of defined events
|
||||||
|
// and then sends the corresponding event struct to the IncomingEvents channel.
|
||||||
|
// If the event type is not found or the event cannot be unmarshalled into the
|
||||||
|
// correct struct then this sends an UnmarshallingErrorEvent to the
|
||||||
|
// IncomingEvents channel.
|
||||||
|
func (rtm *RTM) handleEvent(typeStr string, event json.RawMessage) {
|
||||||
|
v, exists := eventMapping[typeStr]
|
||||||
|
if !exists {
|
||||||
|
rtm.Debugf("RTM Error, received unmapped event %q: %s\n", typeStr, string(event))
|
||||||
|
err := fmt.Errorf("RTM Error: Received unmapped event %q: %s\n", typeStr, string(event))
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t := reflect.TypeOf(v)
|
||||||
|
recvEvent := reflect.New(t).Interface()
|
||||||
|
err := json.Unmarshal(event, recvEvent)
|
||||||
|
if err != nil {
|
||||||
|
rtm.Debugf("RTM Error, could not unmarshall event %q: %s\n", typeStr, string(event))
|
||||||
|
err := fmt.Errorf("RTM Error: Could not unmarshall event %q: %s\n", typeStr, string(event))
|
||||||
|
rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rtm.IncomingEvents <- RTMEvent{typeStr, recvEvent}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventMapping holds a mapping of event names to their corresponding struct
|
||||||
|
// implementations. The structs should be instances of the unmarshalling
|
||||||
|
// target for the matching event type.
|
||||||
|
var eventMapping = map[string]interface{}{
|
||||||
|
"message": MessageEvent{},
|
||||||
|
"presence_change": PresenceChangeEvent{},
|
||||||
|
"user_typing": UserTypingEvent{},
|
||||||
|
|
||||||
|
"channel_marked": ChannelMarkedEvent{},
|
||||||
|
"channel_created": ChannelCreatedEvent{},
|
||||||
|
"channel_joined": ChannelJoinedEvent{},
|
||||||
|
"channel_left": ChannelLeftEvent{},
|
||||||
|
"channel_deleted": ChannelDeletedEvent{},
|
||||||
|
"channel_rename": ChannelRenameEvent{},
|
||||||
|
"channel_archive": ChannelArchiveEvent{},
|
||||||
|
"channel_unarchive": ChannelUnarchiveEvent{},
|
||||||
|
"channel_history_changed": ChannelHistoryChangedEvent{},
|
||||||
|
|
||||||
|
"dnd_updated": DNDUpdatedEvent{},
|
||||||
|
"dnd_updated_user": DNDUpdatedEvent{},
|
||||||
|
|
||||||
|
"im_created": IMCreatedEvent{},
|
||||||
|
"im_open": IMOpenEvent{},
|
||||||
|
"im_close": IMCloseEvent{},
|
||||||
|
"im_marked": IMMarkedEvent{},
|
||||||
|
"im_history_changed": IMHistoryChangedEvent{},
|
||||||
|
|
||||||
|
"group_marked": GroupMarkedEvent{},
|
||||||
|
"group_open": GroupOpenEvent{},
|
||||||
|
"group_joined": GroupJoinedEvent{},
|
||||||
|
"group_left": GroupLeftEvent{},
|
||||||
|
"group_close": GroupCloseEvent{},
|
||||||
|
"group_rename": GroupRenameEvent{},
|
||||||
|
"group_archive": GroupArchiveEvent{},
|
||||||
|
"group_unarchive": GroupUnarchiveEvent{},
|
||||||
|
"group_history_changed": GroupHistoryChangedEvent{},
|
||||||
|
|
||||||
|
"file_created": FileCreatedEvent{},
|
||||||
|
"file_shared": FileSharedEvent{},
|
||||||
|
"file_unshared": FileUnsharedEvent{},
|
||||||
|
"file_public": FilePublicEvent{},
|
||||||
|
"file_private": FilePrivateEvent{},
|
||||||
|
"file_change": FileChangeEvent{},
|
||||||
|
"file_deleted": FileDeletedEvent{},
|
||||||
|
"file_comment_added": FileCommentAddedEvent{},
|
||||||
|
"file_comment_edited": FileCommentEditedEvent{},
|
||||||
|
"file_comment_deleted": FileCommentDeletedEvent{},
|
||||||
|
|
||||||
|
"pin_added": PinAddedEvent{},
|
||||||
|
"pin_removed": PinRemovedEvent{},
|
||||||
|
|
||||||
|
"star_added": StarAddedEvent{},
|
||||||
|
"star_removed": StarRemovedEvent{},
|
||||||
|
|
||||||
|
"reaction_added": ReactionAddedEvent{},
|
||||||
|
"reaction_removed": ReactionRemovedEvent{},
|
||||||
|
|
||||||
|
"pref_change": PrefChangeEvent{},
|
||||||
|
|
||||||
|
"team_join": TeamJoinEvent{},
|
||||||
|
"team_rename": TeamRenameEvent{},
|
||||||
|
"team_pref_change": TeamPrefChangeEvent{},
|
||||||
|
"team_domain_change": TeamDomainChangeEvent{},
|
||||||
|
"team_migration_started": TeamMigrationStartedEvent{},
|
||||||
|
|
||||||
|
"manual_presence_change": ManualPresenceChangeEvent{},
|
||||||
|
|
||||||
|
"user_change": UserChangeEvent{},
|
||||||
|
|
||||||
|
"emoji_changed": EmojiChangedEvent{},
|
||||||
|
|
||||||
|
"commands_changed": CommandsChangedEvent{},
|
||||||
|
|
||||||
|
"email_domain_changed": EmailDomainChangedEvent{},
|
||||||
|
|
||||||
|
"bot_added": BotAddedEvent{},
|
||||||
|
"bot_changed": BotChangedEvent{},
|
||||||
|
|
||||||
|
"accounts_changed": AccountsChangedEvent{},
|
||||||
|
|
||||||
|
"reconnect_url": ReconnectUrlEvent{},
|
||||||
|
}
|
117
vendor/github.com/nlopes/slack/websocket_misc.go
generated
vendored
Normal file
117
vendor/github.com/nlopes/slack/websocket_misc.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AckMessage is used for messages received in reply to other messages
|
||||||
|
type AckMessage struct {
|
||||||
|
ReplyTo int `json:"reply_to"`
|
||||||
|
Timestamp string `json:"ts"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
RTMResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// RTMResponse encapsulates response details as returned by the Slack API
|
||||||
|
type RTMResponse struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Error *RTMError `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RTMError encapsulates error information as returned by the Slack API
|
||||||
|
type RTMError struct {
|
||||||
|
Code int
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s RTMError) Error() string {
|
||||||
|
return fmt.Sprintf("Code %d - %s", s.Code, s.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageEvent represents a Slack Message (used as the event type for an incoming message)
|
||||||
|
type MessageEvent Message
|
||||||
|
|
||||||
|
// RTMEvent is the main wrapper. You will find all the other messages attached
|
||||||
|
type RTMEvent struct {
|
||||||
|
Type string
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelloEvent represents the hello event
|
||||||
|
type HelloEvent struct{}
|
||||||
|
|
||||||
|
// PresenceChangeEvent represents the presence change event
|
||||||
|
type PresenceChangeEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Presence string `json:"presence"`
|
||||||
|
User string `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserTypingEvent represents the user typing event
|
||||||
|
type UserTypingEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Channel string `json:"channel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrefChangeEvent represents a user preferences change event
|
||||||
|
type PrefChangeEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value json.RawMessage `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManualPresenceChangeEvent represents the manual presence change event
|
||||||
|
type ManualPresenceChangeEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Presence string `json:"presence"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserChangeEvent represents the user change event
|
||||||
|
type UserChangeEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User User `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmojiChangedEvent represents the emoji changed event
|
||||||
|
type EmojiChangedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
EventTimestamp string `json:"event_ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandsChangedEvent represents the commands changed event
|
||||||
|
type CommandsChangedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
EventTimestamp string `json:"event_ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmailDomainChangedEvent represents the email domain changed event
|
||||||
|
type EmailDomainChangedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
EventTimestamp string `json:"event_ts"`
|
||||||
|
EmailDomain string `json:"email_domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BotAddedEvent represents the bot added event
|
||||||
|
type BotAddedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Bot Bot `json:"bot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BotChangedEvent represents the bot changed event
|
||||||
|
type BotChangedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Bot Bot `json:"bot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountsChangedEvent represents the accounts changed event
|
||||||
|
type AccountsChangedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReconnectUrlEvent represents the receiving reconnect url event
|
||||||
|
type ReconnectUrlEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
16
vendor/github.com/nlopes/slack/websocket_pins.go
generated
vendored
Normal file
16
vendor/github.com/nlopes/slack/websocket_pins.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
type pinEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Item Item `json:"item"`
|
||||||
|
Channel string `json:"channel_id"`
|
||||||
|
EventTimestamp string `json:"event_ts"`
|
||||||
|
HasPins bool `json:"has_pins,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PinAddedEvent represents the Pin added event
|
||||||
|
type PinAddedEvent pinEvent
|
||||||
|
|
||||||
|
// PinRemovedEvent represents the Pin removed event
|
||||||
|
type PinRemovedEvent pinEvent
|
83
vendor/github.com/nlopes/slack/websocket_proxy.go
generated
vendored
Normal file
83
vendor/github.com/nlopes/slack/websocket_proxy.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Taken and reworked from: https://gist.github.com/madmo/8548738
|
||||||
|
func websocketHTTPConnect(proxy, urlString string) (net.Conn, error) {
|
||||||
|
p, err := net.Dial("tcp", proxy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
turl, err := url.Parse(urlString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := http.Request{
|
||||||
|
Method: "CONNECT",
|
||||||
|
URL: &url.URL{},
|
||||||
|
Host: turl.Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
cc := httputil.NewProxyClientConn(p, nil)
|
||||||
|
cc.Do(&req)
|
||||||
|
if err != nil && err != httputil.ErrPersistEOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rwc, _ := cc.Hijack()
|
||||||
|
|
||||||
|
return rwc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func websocketProxyDial(urlString, origin string) (ws *websocket.Conn, err error) {
|
||||||
|
if os.Getenv("HTTP_PROXY") == "" {
|
||||||
|
return websocket.Dial(urlString, "", origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
purl, err := url.Parse(os.Getenv("HTTP_PROXY"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := websocket.NewConfig(urlString, origin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := websocketHTTPConnect(purl.Host, urlString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch config.Location.Scheme {
|
||||||
|
case "ws":
|
||||||
|
case "wss":
|
||||||
|
tlsClient := tls.Client(client, &tls.Config{
|
||||||
|
ServerName: strings.Split(config.Location.Host, ":")[0],
|
||||||
|
})
|
||||||
|
err := tlsClient.Handshake()
|
||||||
|
if err != nil {
|
||||||
|
tlsClient.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client = tlsClient
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid websocket schema")
|
||||||
|
}
|
||||||
|
|
||||||
|
return websocket.NewClient(config, client)
|
||||||
|
}
|
25
vendor/github.com/nlopes/slack/websocket_reactions.go
generated
vendored
Normal file
25
vendor/github.com/nlopes/slack/websocket_reactions.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// reactionItem is a lighter-weight item than is returned by the reactions list.
|
||||||
|
type reactionItem struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Channel string `json:"channel,omitempty"`
|
||||||
|
File string `json:"file,omitempty"`
|
||||||
|
FileComment string `json:"file_comment,omitempty"`
|
||||||
|
Timestamp string `json:"ts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type reactionEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User string `json:"user"`
|
||||||
|
ItemUser string `json:"item_user"`
|
||||||
|
Item reactionItem `json:"item"`
|
||||||
|
Reaction string `json:"reaction"`
|
||||||
|
EventTimestamp string `json:"event_ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReactionAddedEvent represents the Reaction added event
|
||||||
|
type ReactionAddedEvent reactionEvent
|
||||||
|
|
||||||
|
// ReactionRemovedEvent represents the Reaction removed event
|
||||||
|
type ReactionRemovedEvent reactionEvent
|
14
vendor/github.com/nlopes/slack/websocket_stars.go
generated
vendored
Normal file
14
vendor/github.com/nlopes/slack/websocket_stars.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
type starEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User string `json:"user"`
|
||||||
|
Item StarredItem `json:"item"`
|
||||||
|
EventTimestamp string `json:"event_ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StarAddedEvent represents the Star added event
|
||||||
|
type StarAddedEvent starEvent
|
||||||
|
|
||||||
|
// StarRemovedEvent represents the Star removed event
|
||||||
|
type StarRemovedEvent starEvent
|
33
vendor/github.com/nlopes/slack/websocket_teams.go
generated
vendored
Normal file
33
vendor/github.com/nlopes/slack/websocket_teams.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
// TeamJoinEvent represents the Team join event
|
||||||
|
type TeamJoinEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User User `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamRenameEvent represents the Team rename event
|
||||||
|
type TeamRenameEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
EventTimestamp string `json:"event_ts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamPrefChangeEvent represents the Team preference change event
|
||||||
|
type TeamPrefChangeEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Value []string `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamDomainChangeEvent represents the Team domain change event
|
||||||
|
type TeamDomainChangeEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TeamMigrationStartedEvent represents the Team migration started event
|
||||||
|
type TeamMigrationStartedEvent struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
20
vendor/github.com/nlopes/slack/websocket_utils.go
generated
vendored
Normal file
20
vendor/github.com/nlopes/slack/websocket_utils.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
var portMapping = map[string]string{"ws": "80", "wss": "443"}
|
||||||
|
|
||||||
|
func websocketizeURLPort(orig string) (string, error) {
|
||||||
|
urlObj, err := url.ParseRequestURI(orig)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
_, _, err = net.SplitHostPort(urlObj.Host)
|
||||||
|
if err != nil {
|
||||||
|
return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
|
||||||
|
}
|
||||||
|
return orig, nil
|
||||||
|
}
|
27
vendor/golang.org/x/net/websocket/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/net/websocket/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
113
vendor/golang.org/x/net/websocket/client.go
generated
vendored
Normal file
113
vendor/golang.org/x/net/websocket/client.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DialError is an error that occurs while dialling a websocket server.
|
||||||
|
type DialError struct {
|
||||||
|
*Config
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DialError) Error() string {
|
||||||
|
return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfig creates a new WebSocket config for client connection.
|
||||||
|
func NewConfig(server, origin string) (config *Config, err error) {
|
||||||
|
config = new(Config)
|
||||||
|
config.Version = ProtocolVersionHybi13
|
||||||
|
config.Location, err = url.ParseRequestURI(server)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.Origin, err = url.ParseRequestURI(origin)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.Header = http.Header(make(map[string][]string))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new WebSocket client connection over rwc.
|
||||||
|
func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) {
|
||||||
|
br := bufio.NewReader(rwc)
|
||||||
|
bw := bufio.NewWriter(rwc)
|
||||||
|
err = hybiClientHandshake(config, br, bw)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := bufio.NewReadWriter(br, bw)
|
||||||
|
ws = newHybiClientConn(config, buf, rwc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial opens a new client connection to a WebSocket.
|
||||||
|
func Dial(url_, protocol, origin string) (ws *Conn, err error) {
|
||||||
|
config, err := NewConfig(url_, origin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if protocol != "" {
|
||||||
|
config.Protocol = []string{protocol}
|
||||||
|
}
|
||||||
|
return DialConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
var portMap = map[string]string{
|
||||||
|
"ws": "80",
|
||||||
|
"wss": "443",
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAuthority(location *url.URL) string {
|
||||||
|
if _, ok := portMap[location.Scheme]; ok {
|
||||||
|
if _, _, err := net.SplitHostPort(location.Host); err != nil {
|
||||||
|
return net.JoinHostPort(location.Host, portMap[location.Scheme])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return location.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialConfig opens a new client connection to a WebSocket with a config.
|
||||||
|
func DialConfig(config *Config) (ws *Conn, err error) {
|
||||||
|
var client net.Conn
|
||||||
|
if config.Location == nil {
|
||||||
|
return nil, &DialError{config, ErrBadWebSocketLocation}
|
||||||
|
}
|
||||||
|
if config.Origin == nil {
|
||||||
|
return nil, &DialError{config, ErrBadWebSocketOrigin}
|
||||||
|
}
|
||||||
|
switch config.Location.Scheme {
|
||||||
|
case "ws":
|
||||||
|
client, err = net.Dial("tcp", parseAuthority(config.Location))
|
||||||
|
|
||||||
|
case "wss":
|
||||||
|
client, err = tls.Dial("tcp", parseAuthority(config.Location), config.TlsConfig)
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = ErrBadScheme
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
goto Error
|
||||||
|
}
|
||||||
|
|
||||||
|
ws, err = NewClient(config, client)
|
||||||
|
if err != nil {
|
||||||
|
client.Close()
|
||||||
|
goto Error
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
Error:
|
||||||
|
return nil, &DialError{config, err}
|
||||||
|
}
|
583
vendor/golang.org/x/net/websocket/hybi.go
generated
vendored
Normal file
583
vendor/golang.org/x/net/websocket/hybi.go
generated
vendored
Normal file
@ -0,0 +1,583 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
// This file implements a protocol of hybi draft.
|
||||||
|
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||||
|
|
||||||
|
closeStatusNormal = 1000
|
||||||
|
closeStatusGoingAway = 1001
|
||||||
|
closeStatusProtocolError = 1002
|
||||||
|
closeStatusUnsupportedData = 1003
|
||||||
|
closeStatusFrameTooLarge = 1004
|
||||||
|
closeStatusNoStatusRcvd = 1005
|
||||||
|
closeStatusAbnormalClosure = 1006
|
||||||
|
closeStatusBadMessageData = 1007
|
||||||
|
closeStatusPolicyViolation = 1008
|
||||||
|
closeStatusTooBigData = 1009
|
||||||
|
closeStatusExtensionMismatch = 1010
|
||||||
|
|
||||||
|
maxControlFramePayloadLength = 125
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadMaskingKey = &ProtocolError{"bad masking key"}
|
||||||
|
ErrBadPongMessage = &ProtocolError{"bad pong message"}
|
||||||
|
ErrBadClosingStatus = &ProtocolError{"bad closing status"}
|
||||||
|
ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"}
|
||||||
|
ErrNotImplemented = &ProtocolError{"not implemented"}
|
||||||
|
|
||||||
|
handshakeHeader = map[string]bool{
|
||||||
|
"Host": true,
|
||||||
|
"Upgrade": true,
|
||||||
|
"Connection": true,
|
||||||
|
"Sec-Websocket-Key": true,
|
||||||
|
"Sec-Websocket-Origin": true,
|
||||||
|
"Sec-Websocket-Version": true,
|
||||||
|
"Sec-Websocket-Protocol": true,
|
||||||
|
"Sec-Websocket-Accept": true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// A hybiFrameHeader is a frame header as defined in hybi draft.
|
||||||
|
type hybiFrameHeader struct {
|
||||||
|
Fin bool
|
||||||
|
Rsv [3]bool
|
||||||
|
OpCode byte
|
||||||
|
Length int64
|
||||||
|
MaskingKey []byte
|
||||||
|
|
||||||
|
data *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// A hybiFrameReader is a reader for hybi frame.
|
||||||
|
type hybiFrameReader struct {
|
||||||
|
reader io.Reader
|
||||||
|
|
||||||
|
header hybiFrameHeader
|
||||||
|
pos int64
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) {
|
||||||
|
n, err = frame.reader.Read(msg)
|
||||||
|
if frame.header.MaskingKey != nil {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4]
|
||||||
|
frame.pos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode }
|
||||||
|
|
||||||
|
func (frame *hybiFrameReader) HeaderReader() io.Reader {
|
||||||
|
if frame.header.data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if frame.header.data.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return frame.header.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil }
|
||||||
|
|
||||||
|
func (frame *hybiFrameReader) Len() (n int) { return frame.length }
|
||||||
|
|
||||||
|
// A hybiFrameReaderFactory creates new frame reader based on its frame type.
|
||||||
|
type hybiFrameReaderFactory struct {
|
||||||
|
*bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
|
||||||
|
// See Section 5.2 Base Framing protocol for detail.
|
||||||
|
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
|
||||||
|
func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) {
|
||||||
|
hybiFrame := new(hybiFrameReader)
|
||||||
|
frame = hybiFrame
|
||||||
|
var header []byte
|
||||||
|
var b byte
|
||||||
|
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
|
||||||
|
b, err = buf.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
header = append(header, b)
|
||||||
|
hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
j := uint(6 - i)
|
||||||
|
hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0
|
||||||
|
}
|
||||||
|
hybiFrame.header.OpCode = header[0] & 0x0f
|
||||||
|
|
||||||
|
// Second byte. Mask/Payload len(7bits)
|
||||||
|
b, err = buf.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
header = append(header, b)
|
||||||
|
mask := (b & 0x80) != 0
|
||||||
|
b &= 0x7f
|
||||||
|
lengthFields := 0
|
||||||
|
switch {
|
||||||
|
case b <= 125: // Payload length 7bits.
|
||||||
|
hybiFrame.header.Length = int64(b)
|
||||||
|
case b == 126: // Payload length 7+16bits
|
||||||
|
lengthFields = 2
|
||||||
|
case b == 127: // Payload length 7+64bits
|
||||||
|
lengthFields = 8
|
||||||
|
}
|
||||||
|
for i := 0; i < lengthFields; i++ {
|
||||||
|
b, err = buf.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits
|
||||||
|
b &= 0x7f
|
||||||
|
}
|
||||||
|
header = append(header, b)
|
||||||
|
hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
|
||||||
|
}
|
||||||
|
if mask {
|
||||||
|
// Masking key. 4 bytes.
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
b, err = buf.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
header = append(header, b)
|
||||||
|
hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length)
|
||||||
|
hybiFrame.header.data = bytes.NewBuffer(header)
|
||||||
|
hybiFrame.length = len(header) + int(hybiFrame.header.Length)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// A HybiFrameWriter is a writer for hybi frame.
|
||||||
|
type hybiFrameWriter struct {
|
||||||
|
writer *bufio.Writer
|
||||||
|
|
||||||
|
header *hybiFrameHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) {
|
||||||
|
var header []byte
|
||||||
|
var b byte
|
||||||
|
if frame.header.Fin {
|
||||||
|
b |= 0x80
|
||||||
|
}
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
if frame.header.Rsv[i] {
|
||||||
|
j := uint(6 - i)
|
||||||
|
b |= 1 << j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b |= frame.header.OpCode
|
||||||
|
header = append(header, b)
|
||||||
|
if frame.header.MaskingKey != nil {
|
||||||
|
b = 0x80
|
||||||
|
} else {
|
||||||
|
b = 0
|
||||||
|
}
|
||||||
|
lengthFields := 0
|
||||||
|
length := len(msg)
|
||||||
|
switch {
|
||||||
|
case length <= 125:
|
||||||
|
b |= byte(length)
|
||||||
|
case length < 65536:
|
||||||
|
b |= 126
|
||||||
|
lengthFields = 2
|
||||||
|
default:
|
||||||
|
b |= 127
|
||||||
|
lengthFields = 8
|
||||||
|
}
|
||||||
|
header = append(header, b)
|
||||||
|
for i := 0; i < lengthFields; i++ {
|
||||||
|
j := uint((lengthFields - i - 1) * 8)
|
||||||
|
b = byte((length >> j) & 0xff)
|
||||||
|
header = append(header, b)
|
||||||
|
}
|
||||||
|
if frame.header.MaskingKey != nil {
|
||||||
|
if len(frame.header.MaskingKey) != 4 {
|
||||||
|
return 0, ErrBadMaskingKey
|
||||||
|
}
|
||||||
|
header = append(header, frame.header.MaskingKey...)
|
||||||
|
frame.writer.Write(header)
|
||||||
|
data := make([]byte, length)
|
||||||
|
for i := range data {
|
||||||
|
data[i] = msg[i] ^ frame.header.MaskingKey[i%4]
|
||||||
|
}
|
||||||
|
frame.writer.Write(data)
|
||||||
|
err = frame.writer.Flush()
|
||||||
|
return length, err
|
||||||
|
}
|
||||||
|
frame.writer.Write(header)
|
||||||
|
frame.writer.Write(msg)
|
||||||
|
err = frame.writer.Flush()
|
||||||
|
return length, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (frame *hybiFrameWriter) Close() error { return nil }
|
||||||
|
|
||||||
|
type hybiFrameWriterFactory struct {
|
||||||
|
*bufio.Writer
|
||||||
|
needMaskingKey bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
|
||||||
|
frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType}
|
||||||
|
if buf.needMaskingKey {
|
||||||
|
frameHeader.MaskingKey, err = generateMaskingKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type hybiFrameHandler struct {
|
||||||
|
conn *Conn
|
||||||
|
payloadType byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) {
|
||||||
|
if handler.conn.IsServerConn() {
|
||||||
|
// The client MUST mask all frames sent to the server.
|
||||||
|
if frame.(*hybiFrameReader).header.MaskingKey == nil {
|
||||||
|
handler.WriteClose(closeStatusProtocolError)
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The server MUST NOT mask all frames.
|
||||||
|
if frame.(*hybiFrameReader).header.MaskingKey != nil {
|
||||||
|
handler.WriteClose(closeStatusProtocolError)
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if header := frame.HeaderReader(); header != nil {
|
||||||
|
io.Copy(ioutil.Discard, header)
|
||||||
|
}
|
||||||
|
switch frame.PayloadType() {
|
||||||
|
case ContinuationFrame:
|
||||||
|
frame.(*hybiFrameReader).header.OpCode = handler.payloadType
|
||||||
|
case TextFrame, BinaryFrame:
|
||||||
|
handler.payloadType = frame.PayloadType()
|
||||||
|
case CloseFrame:
|
||||||
|
return nil, io.EOF
|
||||||
|
case PingFrame, PongFrame:
|
||||||
|
b := make([]byte, maxControlFramePayloadLength)
|
||||||
|
n, err := io.ReadFull(frame, b)
|
||||||
|
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
io.Copy(ioutil.Discard, frame)
|
||||||
|
if frame.PayloadType() == PingFrame {
|
||||||
|
if _, err := handler.WritePong(b[:n]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return frame, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *hybiFrameHandler) WriteClose(status int) (err error) {
|
||||||
|
handler.conn.wio.Lock()
|
||||||
|
defer handler.conn.wio.Unlock()
|
||||||
|
w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(msg, uint16(status))
|
||||||
|
_, err = w.Write(msg)
|
||||||
|
w.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) {
|
||||||
|
handler.conn.wio.Lock()
|
||||||
|
defer handler.conn.wio.Unlock()
|
||||||
|
w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
n, err = w.Write(msg)
|
||||||
|
w.Close()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
|
||||||
|
func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||||
|
if buf == nil {
|
||||||
|
br := bufio.NewReader(rwc)
|
||||||
|
bw := bufio.NewWriter(rwc)
|
||||||
|
buf = bufio.NewReadWriter(br, bw)
|
||||||
|
}
|
||||||
|
ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
|
||||||
|
frameReaderFactory: hybiFrameReaderFactory{buf.Reader},
|
||||||
|
frameWriterFactory: hybiFrameWriterFactory{
|
||||||
|
buf.Writer, request == nil},
|
||||||
|
PayloadType: TextFrame,
|
||||||
|
defaultCloseStatus: closeStatusNormal}
|
||||||
|
ws.frameHandler = &hybiFrameHandler{conn: ws}
|
||||||
|
return ws
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateMaskingKey generates a masking key for a frame.
|
||||||
|
func generateMaskingKey() (maskingKey []byte, err error) {
|
||||||
|
maskingKey = make([]byte, 4)
|
||||||
|
if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateNonce generates a nonce consisting of a randomly selected 16-byte
|
||||||
|
// value that has been base64-encoded.
|
||||||
|
func generateNonce() (nonce []byte) {
|
||||||
|
key := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
nonce = make([]byte, 24)
|
||||||
|
base64.StdEncoding.Encode(nonce, key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeZone removes IPv6 zone identifer from host.
|
||||||
|
// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080"
|
||||||
|
func removeZone(host string) string {
|
||||||
|
if !strings.HasPrefix(host, "[") {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
i := strings.LastIndex(host, "]")
|
||||||
|
if i < 0 {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
j := strings.LastIndex(host[:i], "%")
|
||||||
|
if j < 0 {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
return host[:j] + host[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
|
||||||
|
// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
|
||||||
|
func getNonceAccept(nonce []byte) (expected []byte, err error) {
|
||||||
|
h := sha1.New()
|
||||||
|
if _, err = h.Write(nonce); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = h.Write([]byte(websocketGUID)); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expected = make([]byte, 28)
|
||||||
|
base64.StdEncoding.Encode(expected, h.Sum(nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17
|
||||||
|
func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
|
||||||
|
bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
|
||||||
|
|
||||||
|
// According to RFC 6874, an HTTP client, proxy, or other
|
||||||
|
// intermediary must remove any IPv6 zone identifier attached
|
||||||
|
// to an outgoing URI.
|
||||||
|
bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")
|
||||||
|
bw.WriteString("Upgrade: websocket\r\n")
|
||||||
|
bw.WriteString("Connection: Upgrade\r\n")
|
||||||
|
nonce := generateNonce()
|
||||||
|
if config.handshakeData != nil {
|
||||||
|
nonce = []byte(config.handshakeData["key"])
|
||||||
|
}
|
||||||
|
bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")
|
||||||
|
bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
|
||||||
|
|
||||||
|
if config.Version != ProtocolVersionHybi13 {
|
||||||
|
return ErrBadProtocolVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n")
|
||||||
|
if len(config.Protocol) > 0 {
|
||||||
|
bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n")
|
||||||
|
}
|
||||||
|
// TODO(ukai): send Sec-WebSocket-Extensions.
|
||||||
|
err = config.Header.WriteSubset(bw, handshakeHeader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bw.WriteString("\r\n")
|
||||||
|
if err = bw.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 101 {
|
||||||
|
return ErrBadStatus
|
||||||
|
}
|
||||||
|
if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" ||
|
||||||
|
strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
|
||||||
|
return ErrBadUpgrade
|
||||||
|
}
|
||||||
|
expectedAccept, err := getNonceAccept(nonce)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) {
|
||||||
|
return ErrChallengeResponse
|
||||||
|
}
|
||||||
|
if resp.Header.Get("Sec-WebSocket-Extensions") != "" {
|
||||||
|
return ErrUnsupportedExtensions
|
||||||
|
}
|
||||||
|
offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol")
|
||||||
|
if offeredProtocol != "" {
|
||||||
|
protocolMatched := false
|
||||||
|
for i := 0; i < len(config.Protocol); i++ {
|
||||||
|
if config.Protocol[i] == offeredProtocol {
|
||||||
|
protocolMatched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !protocolMatched {
|
||||||
|
return ErrBadWebSocketProtocol
|
||||||
|
}
|
||||||
|
config.Protocol = []string{offeredProtocol}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHybiClientConn creates a client WebSocket connection after handshake.
|
||||||
|
func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
|
||||||
|
return newHybiConn(config, buf, rwc, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
|
||||||
|
type hybiServerHandshaker struct {
|
||||||
|
*Config
|
||||||
|
accept []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
|
||||||
|
c.Version = ProtocolVersionHybi13
|
||||||
|
if req.Method != "GET" {
|
||||||
|
return http.StatusMethodNotAllowed, ErrBadRequestMethod
|
||||||
|
}
|
||||||
|
// HTTP version can be safely ignored.
|
||||||
|
|
||||||
|
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
|
||||||
|
!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
|
||||||
|
return http.StatusBadRequest, ErrNotWebSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
key := req.Header.Get("Sec-Websocket-Key")
|
||||||
|
if key == "" {
|
||||||
|
return http.StatusBadRequest, ErrChallengeResponse
|
||||||
|
}
|
||||||
|
version := req.Header.Get("Sec-Websocket-Version")
|
||||||
|
switch version {
|
||||||
|
case "13":
|
||||||
|
c.Version = ProtocolVersionHybi13
|
||||||
|
default:
|
||||||
|
return http.StatusBadRequest, ErrBadWebSocketVersion
|
||||||
|
}
|
||||||
|
var scheme string
|
||||||
|
if req.TLS != nil {
|
||||||
|
scheme = "wss"
|
||||||
|
} else {
|
||||||
|
scheme = "ws"
|
||||||
|
}
|
||||||
|
c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusBadRequest, err
|
||||||
|
}
|
||||||
|
protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
|
||||||
|
if protocol != "" {
|
||||||
|
protocols := strings.Split(protocol, ",")
|
||||||
|
for i := 0; i < len(protocols); i++ {
|
||||||
|
c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.accept, err = getNonceAccept([]byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
return http.StatusSwitchingProtocols, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin parses the Origin header in req.
|
||||||
|
// If the Origin header is not set, it returns nil and nil.
|
||||||
|
func Origin(config *Config, req *http.Request) (*url.URL, error) {
|
||||||
|
var origin string
|
||||||
|
switch config.Version {
|
||||||
|
case ProtocolVersionHybi13:
|
||||||
|
origin = req.Header.Get("Origin")
|
||||||
|
}
|
||||||
|
if origin == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return url.ParseRequestURI(origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
|
||||||
|
if len(c.Protocol) > 0 {
|
||||||
|
if len(c.Protocol) != 1 {
|
||||||
|
// You need choose a Protocol in Handshake func in Server.
|
||||||
|
return ErrBadWebSocketProtocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
|
||||||
|
buf.WriteString("Upgrade: websocket\r\n")
|
||||||
|
buf.WriteString("Connection: Upgrade\r\n")
|
||||||
|
buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n")
|
||||||
|
if len(c.Protocol) > 0 {
|
||||||
|
buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
|
||||||
|
}
|
||||||
|
// TODO(ukai): send Sec-WebSocket-Extensions.
|
||||||
|
if c.Header != nil {
|
||||||
|
err := c.Header.WriteSubset(buf, handshakeHeader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteString("\r\n")
|
||||||
|
return buf.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||||
|
return newHybiServerConn(c.Config, buf, rwc, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
|
||||||
|
func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
||||||
|
return newHybiConn(config, buf, rwc, request)
|
||||||
|
}
|
113
vendor/golang.org/x/net/websocket/server.go
generated
vendored
Normal file
113
vendor/golang.org/x/net/websocket/server.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) {
|
||||||
|
var hs serverHandshaker = &hybiServerHandshaker{Config: config}
|
||||||
|
code, err := hs.ReadHandshake(buf.Reader, req)
|
||||||
|
if err == ErrBadWebSocketVersion {
|
||||||
|
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||||
|
fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion)
|
||||||
|
buf.WriteString("\r\n")
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
buf.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||||
|
buf.WriteString("\r\n")
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
buf.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if handshake != nil {
|
||||||
|
err = handshake(config, req)
|
||||||
|
if err != nil {
|
||||||
|
code = http.StatusForbidden
|
||||||
|
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||||
|
buf.WriteString("\r\n")
|
||||||
|
buf.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = hs.AcceptHandshake(buf.Writer)
|
||||||
|
if err != nil {
|
||||||
|
code = http.StatusBadRequest
|
||||||
|
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code))
|
||||||
|
buf.WriteString("\r\n")
|
||||||
|
buf.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn = hs.NewServerConn(buf, rwc, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server represents a server of a WebSocket.
|
||||||
|
type Server struct {
|
||||||
|
// Config is a WebSocket configuration for new WebSocket connection.
|
||||||
|
Config
|
||||||
|
|
||||||
|
// Handshake is an optional function in WebSocket handshake.
|
||||||
|
// For example, you can check, or don't check Origin header.
|
||||||
|
// Another example, you can select config.Protocol.
|
||||||
|
Handshake func(*Config, *http.Request) error
|
||||||
|
|
||||||
|
// Handler handles a WebSocket connection.
|
||||||
|
Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements the http.Handler interface for a WebSocket
|
||||||
|
func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
s.serveWebSocket(w, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) {
|
||||||
|
rwc, buf, err := w.(http.Hijacker).Hijack()
|
||||||
|
if err != nil {
|
||||||
|
panic("Hijack failed: " + err.Error())
|
||||||
|
}
|
||||||
|
// The server should abort the WebSocket connection if it finds
|
||||||
|
// the client did not send a handshake that matches with protocol
|
||||||
|
// specification.
|
||||||
|
defer rwc.Close()
|
||||||
|
conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if conn == nil {
|
||||||
|
panic("unexpected nil conn")
|
||||||
|
}
|
||||||
|
s.Handler(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler is a simple interface to a WebSocket browser client.
|
||||||
|
// It checks if Origin header is valid URL by default.
|
||||||
|
// You might want to verify websocket.Conn.Config().Origin in the func.
|
||||||
|
// If you use Server instead of Handler, you could call websocket.Origin and
|
||||||
|
// check the origin in your Handshake func. So, if you want to accept
|
||||||
|
// non-browser clients, which do not send an Origin header, set a
|
||||||
|
// Server.Handshake that does not check the origin.
|
||||||
|
type Handler func(*Conn)
|
||||||
|
|
||||||
|
func checkOrigin(config *Config, req *http.Request) (err error) {
|
||||||
|
config.Origin, err = Origin(config, req)
|
||||||
|
if err == nil && config.Origin == nil {
|
||||||
|
return fmt.Errorf("null origin")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements the http.Handler interface for a WebSocket
|
||||||
|
func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
s := Server{Handler: h, Handshake: checkOrigin}
|
||||||
|
s.serveWebSocket(w, req)
|
||||||
|
}
|
411
vendor/golang.org/x/net/websocket/websocket.go
generated
vendored
Normal file
411
vendor/golang.org/x/net/websocket/websocket.go
generated
vendored
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package websocket implements a client and server for the WebSocket protocol
|
||||||
|
// as specified in RFC 6455.
|
||||||
|
package websocket // import "golang.org/x/net/websocket"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProtocolVersionHybi13 = 13
|
||||||
|
ProtocolVersionHybi = ProtocolVersionHybi13
|
||||||
|
SupportedProtocolVersion = "13"
|
||||||
|
|
||||||
|
ContinuationFrame = 0
|
||||||
|
TextFrame = 1
|
||||||
|
BinaryFrame = 2
|
||||||
|
CloseFrame = 8
|
||||||
|
PingFrame = 9
|
||||||
|
PongFrame = 10
|
||||||
|
UnknownFrame = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProtocolError represents WebSocket protocol errors.
|
||||||
|
type ProtocolError struct {
|
||||||
|
ErrorString string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *ProtocolError) Error() string { return err.ErrorString }
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadProtocolVersion = &ProtocolError{"bad protocol version"}
|
||||||
|
ErrBadScheme = &ProtocolError{"bad scheme"}
|
||||||
|
ErrBadStatus = &ProtocolError{"bad status"}
|
||||||
|
ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"}
|
||||||
|
ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"}
|
||||||
|
ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"}
|
||||||
|
ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"}
|
||||||
|
ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"}
|
||||||
|
ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"}
|
||||||
|
ErrBadFrame = &ProtocolError{"bad frame"}
|
||||||
|
ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"}
|
||||||
|
ErrNotWebSocket = &ProtocolError{"not websocket protocol"}
|
||||||
|
ErrBadRequestMethod = &ProtocolError{"bad method"}
|
||||||
|
ErrNotSupported = &ProtocolError{"not supported"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Addr is an implementation of net.Addr for WebSocket.
|
||||||
|
type Addr struct {
|
||||||
|
*url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network returns the network type for a WebSocket, "websocket".
|
||||||
|
func (addr *Addr) Network() string { return "websocket" }
|
||||||
|
|
||||||
|
// Config is a WebSocket configuration
|
||||||
|
type Config struct {
|
||||||
|
// A WebSocket server address.
|
||||||
|
Location *url.URL
|
||||||
|
|
||||||
|
// A Websocket client origin.
|
||||||
|
Origin *url.URL
|
||||||
|
|
||||||
|
// WebSocket subprotocols.
|
||||||
|
Protocol []string
|
||||||
|
|
||||||
|
// WebSocket protocol version.
|
||||||
|
Version int
|
||||||
|
|
||||||
|
// TLS config for secure WebSocket (wss).
|
||||||
|
TlsConfig *tls.Config
|
||||||
|
|
||||||
|
// Additional header fields to be sent in WebSocket opening handshake.
|
||||||
|
Header http.Header
|
||||||
|
|
||||||
|
handshakeData map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverHandshaker is an interface to handle WebSocket server side handshake.
|
||||||
|
type serverHandshaker interface {
|
||||||
|
// ReadHandshake reads handshake request message from client.
|
||||||
|
// Returns http response code and error if any.
|
||||||
|
ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error)
|
||||||
|
|
||||||
|
// AcceptHandshake accepts the client handshake request and sends
|
||||||
|
// handshake response back to client.
|
||||||
|
AcceptHandshake(buf *bufio.Writer) (err error)
|
||||||
|
|
||||||
|
// NewServerConn creates a new WebSocket connection.
|
||||||
|
NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// frameReader is an interface to read a WebSocket frame.
|
||||||
|
type frameReader interface {
|
||||||
|
// Reader is to read payload of the frame.
|
||||||
|
io.Reader
|
||||||
|
|
||||||
|
// PayloadType returns payload type.
|
||||||
|
PayloadType() byte
|
||||||
|
|
||||||
|
// HeaderReader returns a reader to read header of the frame.
|
||||||
|
HeaderReader() io.Reader
|
||||||
|
|
||||||
|
// TrailerReader returns a reader to read trailer of the frame.
|
||||||
|
// If it returns nil, there is no trailer in the frame.
|
||||||
|
TrailerReader() io.Reader
|
||||||
|
|
||||||
|
// Len returns total length of the frame, including header and trailer.
|
||||||
|
Len() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// frameReaderFactory is an interface to creates new frame reader.
|
||||||
|
type frameReaderFactory interface {
|
||||||
|
NewFrameReader() (r frameReader, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// frameWriter is an interface to write a WebSocket frame.
|
||||||
|
type frameWriter interface {
|
||||||
|
// Writer is to write payload of the frame.
|
||||||
|
io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// frameWriterFactory is an interface to create new frame writer.
|
||||||
|
type frameWriterFactory interface {
|
||||||
|
NewFrameWriter(payloadType byte) (w frameWriter, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type frameHandler interface {
|
||||||
|
HandleFrame(frame frameReader) (r frameReader, err error)
|
||||||
|
WriteClose(status int) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn represents a WebSocket connection.
|
||||||
|
//
|
||||||
|
// Multiple goroutines may invoke methods on a Conn simultaneously.
|
||||||
|
type Conn struct {
|
||||||
|
config *Config
|
||||||
|
request *http.Request
|
||||||
|
|
||||||
|
buf *bufio.ReadWriter
|
||||||
|
rwc io.ReadWriteCloser
|
||||||
|
|
||||||
|
rio sync.Mutex
|
||||||
|
frameReaderFactory
|
||||||
|
frameReader
|
||||||
|
|
||||||
|
wio sync.Mutex
|
||||||
|
frameWriterFactory
|
||||||
|
|
||||||
|
frameHandler
|
||||||
|
PayloadType byte
|
||||||
|
defaultCloseStatus int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements the io.Reader interface:
|
||||||
|
// it reads data of a frame from the WebSocket connection.
|
||||||
|
// if msg is not large enough for the frame data, it fills the msg and next Read
|
||||||
|
// will read the rest of the frame data.
|
||||||
|
// it reads Text frame or Binary frame.
|
||||||
|
func (ws *Conn) Read(msg []byte) (n int, err error) {
|
||||||
|
ws.rio.Lock()
|
||||||
|
defer ws.rio.Unlock()
|
||||||
|
again:
|
||||||
|
if ws.frameReader == nil {
|
||||||
|
frame, err := ws.frameReaderFactory.NewFrameReader()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
ws.frameReader, err = ws.frameHandler.HandleFrame(frame)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if ws.frameReader == nil {
|
||||||
|
goto again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err = ws.frameReader.Read(msg)
|
||||||
|
if err == io.EOF {
|
||||||
|
if trailer := ws.frameReader.TrailerReader(); trailer != nil {
|
||||||
|
io.Copy(ioutil.Discard, trailer)
|
||||||
|
}
|
||||||
|
ws.frameReader = nil
|
||||||
|
goto again
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements the io.Writer interface:
|
||||||
|
// it writes data as a frame to the WebSocket connection.
|
||||||
|
func (ws *Conn) Write(msg []byte) (n int, err error) {
|
||||||
|
ws.wio.Lock()
|
||||||
|
defer ws.wio.Unlock()
|
||||||
|
w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
n, err = w.Write(msg)
|
||||||
|
w.Close()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements the io.Closer interface.
|
||||||
|
func (ws *Conn) Close() error {
|
||||||
|
err := ws.frameHandler.WriteClose(ws.defaultCloseStatus)
|
||||||
|
err1 := ws.rwc.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *Conn) IsClientConn() bool { return ws.request == nil }
|
||||||
|
func (ws *Conn) IsServerConn() bool { return ws.request != nil }
|
||||||
|
|
||||||
|
// LocalAddr returns the WebSocket Origin for the connection for client, or
|
||||||
|
// the WebSocket location for server.
|
||||||
|
func (ws *Conn) LocalAddr() net.Addr {
|
||||||
|
if ws.IsClientConn() {
|
||||||
|
return &Addr{ws.config.Origin}
|
||||||
|
}
|
||||||
|
return &Addr{ws.config.Location}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr returns the WebSocket location for the connection for client, or
|
||||||
|
// the Websocket Origin for server.
|
||||||
|
func (ws *Conn) RemoteAddr() net.Addr {
|
||||||
|
if ws.IsClientConn() {
|
||||||
|
return &Addr{ws.config.Location}
|
||||||
|
}
|
||||||
|
return &Addr{ws.config.Origin}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn")
|
||||||
|
|
||||||
|
// SetDeadline sets the connection's network read & write deadlines.
|
||||||
|
func (ws *Conn) SetDeadline(t time.Time) error {
|
||||||
|
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||||
|
return conn.SetDeadline(t)
|
||||||
|
}
|
||||||
|
return errSetDeadline
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline sets the connection's network read deadline.
|
||||||
|
func (ws *Conn) SetReadDeadline(t time.Time) error {
|
||||||
|
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||||
|
return conn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
return errSetDeadline
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline sets the connection's network write deadline.
|
||||||
|
func (ws *Conn) SetWriteDeadline(t time.Time) error {
|
||||||
|
if conn, ok := ws.rwc.(net.Conn); ok {
|
||||||
|
return conn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
return errSetDeadline
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config returns the WebSocket config.
|
||||||
|
func (ws *Conn) Config() *Config { return ws.config }
|
||||||
|
|
||||||
|
// Request returns the http request upgraded to the WebSocket.
|
||||||
|
// It is nil for client side.
|
||||||
|
func (ws *Conn) Request() *http.Request { return ws.request }
|
||||||
|
|
||||||
|
// Codec represents a symmetric pair of functions that implement a codec.
|
||||||
|
type Codec struct {
|
||||||
|
Marshal func(v interface{}) (data []byte, payloadType byte, err error)
|
||||||
|
Unmarshal func(data []byte, payloadType byte, v interface{}) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends v marshaled by cd.Marshal as single frame to ws.
|
||||||
|
func (cd Codec) Send(ws *Conn, v interface{}) (err error) {
|
||||||
|
data, payloadType, err := cd.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ws.wio.Lock()
|
||||||
|
defer ws.wio.Unlock()
|
||||||
|
w, err := ws.frameWriterFactory.NewFrameWriter(payloadType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(data)
|
||||||
|
w.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores in v.
|
||||||
|
func (cd Codec) Receive(ws *Conn, v interface{}) (err error) {
|
||||||
|
ws.rio.Lock()
|
||||||
|
defer ws.rio.Unlock()
|
||||||
|
if ws.frameReader != nil {
|
||||||
|
_, err = io.Copy(ioutil.Discard, ws.frameReader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ws.frameReader = nil
|
||||||
|
}
|
||||||
|
again:
|
||||||
|
frame, err := ws.frameReaderFactory.NewFrameReader()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
frame, err = ws.frameHandler.HandleFrame(frame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if frame == nil {
|
||||||
|
goto again
|
||||||
|
}
|
||||||
|
payloadType := frame.PayloadType()
|
||||||
|
data, err := ioutil.ReadAll(frame)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return cd.Unmarshal(data, payloadType, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshal(v interface{}) (msg []byte, payloadType byte, err error) {
|
||||||
|
switch data := v.(type) {
|
||||||
|
case string:
|
||||||
|
return []byte(data), TextFrame, nil
|
||||||
|
case []byte:
|
||||||
|
return data, BinaryFrame, nil
|
||||||
|
}
|
||||||
|
return nil, UnknownFrame, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
|
||||||
|
switch data := v.(type) {
|
||||||
|
case *string:
|
||||||
|
*data = string(msg)
|
||||||
|
return nil
|
||||||
|
case *[]byte:
|
||||||
|
*data = msg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Message is a codec to send/receive text/binary data in a frame on WebSocket connection.
|
||||||
|
To send/receive text frame, use string type.
|
||||||
|
To send/receive binary frame, use []byte type.
|
||||||
|
|
||||||
|
Trivial usage:
|
||||||
|
|
||||||
|
import "websocket"
|
||||||
|
|
||||||
|
// receive text frame
|
||||||
|
var message string
|
||||||
|
websocket.Message.Receive(ws, &message)
|
||||||
|
|
||||||
|
// send text frame
|
||||||
|
message = "hello"
|
||||||
|
websocket.Message.Send(ws, message)
|
||||||
|
|
||||||
|
// receive binary frame
|
||||||
|
var data []byte
|
||||||
|
websocket.Message.Receive(ws, &data)
|
||||||
|
|
||||||
|
// send binary frame
|
||||||
|
data = []byte{0, 1, 2}
|
||||||
|
websocket.Message.Send(ws, data)
|
||||||
|
|
||||||
|
*/
|
||||||
|
var Message = Codec{marshal, unmarshal}
|
||||||
|
|
||||||
|
func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) {
|
||||||
|
msg, err = json.Marshal(v)
|
||||||
|
return msg, TextFrame, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) {
|
||||||
|
return json.Unmarshal(msg, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
JSON is a codec to send/receive JSON data in a frame from a WebSocket connection.
|
||||||
|
|
||||||
|
Trivial usage:
|
||||||
|
|
||||||
|
import "websocket"
|
||||||
|
|
||||||
|
type T struct {
|
||||||
|
Msg string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// receive JSON type T
|
||||||
|
var data T
|
||||||
|
websocket.JSON.Receive(ws, &data)
|
||||||
|
|
||||||
|
// send JSON type T
|
||||||
|
websocket.JSON.Send(ws, data)
|
||||||
|
*/
|
||||||
|
var JSON = Codec{jsonMarshal, jsonUnmarshal}
|
17
vendor/manifest
vendored
17
vendor/manifest
vendored
@ -110,6 +110,14 @@
|
|||||||
"path": "/i18n",
|
"path": "/i18n",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"importpath": "github.com/nlopes/slack",
|
||||||
|
"repository": "https://github.com/nlopes/slack",
|
||||||
|
"vcs": "git",
|
||||||
|
"revision": "4feee83bb2b31d790977ce727a028c6a542c72c7",
|
||||||
|
"branch": "HEAD",
|
||||||
|
"notests": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"importpath": "github.com/pborman/uuid",
|
"importpath": "github.com/pborman/uuid",
|
||||||
"repository": "https://github.com/pborman/uuid",
|
"repository": "https://github.com/pborman/uuid",
|
||||||
@ -186,6 +194,15 @@
|
|||||||
"path": "/lex/httplex",
|
"path": "/lex/httplex",
|
||||||
"notests": true
|
"notests": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"importpath": "golang.org/x/net/websocket",
|
||||||
|
"repository": "https://go.googlesource.com/net",
|
||||||
|
"vcs": "git",
|
||||||
|
"revision": "1358eff22f0dd0c54fc521042cc607f6ff4b531a",
|
||||||
|
"branch": "master",
|
||||||
|
"path": "/websocket",
|
||||||
|
"notests": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"importpath": "gopkg.in/gcfg.v1",
|
"importpath": "gopkg.in/gcfg.v1",
|
||||||
"repository": "https://gopkg.in/gcfg.v1",
|
"repository": "https://gopkg.in/gcfg.v1",
|
||||||
|
Loading…
Reference in New Issue
Block a user