Compare commits
	
		
			32 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 6e410b096e | ||
|   | f9e5994348 | ||
|   | ee77272cfd | ||
|   | 16ed2aca6a | ||
|   | 0f530e7902 | ||
|   | 4ed66ce20e | ||
|   | b30e85836e | ||
|   | e449a97bd0 | ||
|   | 39043f3fa4 | ||
|   | 12389d602e | ||
|   | 44144587a0 | ||
|   | d0a30e354b | ||
|   | c261dc89d5 | ||
|   | c2c135bca2 | ||
|   | eb20cb237d | ||
|   | 106404d32f | ||
|   | e06efbad9f | ||
|   | 3311c7f923 | ||
|   | 3a6c655dfb | ||
|   | e11d786775 | ||
|   | 889b6debc4 | ||
|   | 9cb3413d9c | ||
|   | 131826e1d1 | ||
|   | 96e21dd051 | ||
|   | 32e5f396e7 | ||
|   | 6c6000dbbd | ||
|   | 24defcb970 | ||
|   | a1a11a88b3 | ||
|   | a997ae29ad | ||
|   | ff94796700 | ||
|   | 1f72ca4c4e | ||
|   | 46faad8b57 | 
| @@ -2,10 +2,10 @@ FROM alpine:edge | |||||||
| ENTRYPOINT ["/bin/matterbridge"] | ENTRYPOINT ["/bin/matterbridge"] | ||||||
|  |  | ||||||
| COPY . /go/src/github.com/42wim/matterbridge | COPY . /go/src/github.com/42wim/matterbridge | ||||||
| RUN apk update && apk add go git gcc musl-dev \ | RUN apk update && apk add go git gcc musl-dev ca-certificates \ | ||||||
|         && cd /go/src/github.com/42wim/matterbridge \ |         && cd /go/src/github.com/42wim/matterbridge \ | ||||||
|         && export GOPATH=/go \ |         && export GOPATH=/go \ | ||||||
|         && go get \ |         && go get \ | ||||||
|         && go build -o /bin/matterbridge \ |         && go build -o /bin/matterbridge \ | ||||||
|         && rm -rf /go \ |         && rm -rf /go \ | ||||||
|         && apk del --purge git go |         && apk del --purge git go gcc musl-dev | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,25 +1,40 @@ | |||||||
| # matterbridge | # matterbridge | ||||||
|  |  | ||||||
| Simple bridge between mattermost and IRC.  | Simple bridge between mattermost, IRC, XMPP and Gitter | ||||||
|  |  | ||||||
| * Relays public channel messages between mattermost and IRC. | * Relays public channel messages between mattermost, IRC, XMPP and Gitter. Pick and mix. | ||||||
| * Supports multiple mattermost and irc channels. | * Supports multiple channels. | ||||||
| * Matterbridge -plus also works with private groups on your mattermost. | * Matterbridge -plus also works with private groups on your mattermost. | ||||||
|  |  | ||||||
| This project has now [matterbridge-plus](https://github.com/42wim/matterbridge-plus/) merged in.  | Look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for documentation and an example. | ||||||
| Breaking changes for matterbridge can be found in [migration](https://github.com/42wim/matterbridge/blob/master/migration.md) |  | ||||||
|  | ## Changelog | ||||||
|  | Since v0.6.1 support for XMPP, Gitter and Slack is added. More details in [changelog.md] (https://github.com/42wim/matterbridge/blob/master/changelog.md) | ||||||
|  |  | ||||||
| ## Requirements: | ## Requirements: | ||||||
| * [Mattermost] (https://github.com/mattermost/platform/) 3.x (stable, not a dev build) | Accounts to one of the supported bridges | ||||||
|  | * [Mattermost] (https://github.com/mattermost/platform/) | ||||||
| ### Webhooks version | * [IRC] (http://www.mirc.com/servers.html) | ||||||
| * Configured incoming/outgoing [webhooks](https://www.mattermost.org/webhooks/) on your mattermost instance. | * [XMPP] (https://jabber.org) | ||||||
|  | * [Gitter] (https://gitter.im) | ||||||
| ### Plus (API) version |  | ||||||
| * A dedicated user(bot) on your mattermost instance. |  | ||||||
|  |  | ||||||
| ## binaries | ## binaries | ||||||
| Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/tag/v0.5.0) | Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/) | ||||||
|  | * For use with mattermost 3.3.0+ [v0.6.1](https://github.com/42wim/matterircd/releases/tag/v0.6.1) | ||||||
|  | * For use with mattermost 3.0.0-3.2.0 [v0.5.0](https://github.com/42wim/matterircd/releases/tag/v0.5.0) | ||||||
|  |  | ||||||
|  | ## Compatibility | ||||||
|  | ### Mattermost  | ||||||
|  | * Matterbridge v0.6.1 works with mattermost 3.3.0 and higher [3.3.0 release](https://github.com/mattermost/platform/releases/tag/v3.3.0) | ||||||
|  | * Matterbridge v0.5.0 works with mattermost 3.0.0 - 3.2.0 [3.2.0 release](https://github.com/mattermost/platform/releases/tag/v3.2.0) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #### Webhooks version | ||||||
|  | * Configured incoming/outgoing [webhooks](https://www.mattermost.org/webhooks/) on your mattermost instance. | ||||||
|  |  | ||||||
|  | #### Plus (API) version | ||||||
|  | * A dedicated user(bot) on your mattermost instance. | ||||||
|  |  | ||||||
|  |  | ||||||
| ## building | ## building | ||||||
| Go 1.6+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH] (https://golang.org/doc/code.html#GOPATH) | Go 1.6+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH] (https://golang.org/doc/code.html#GOPATH) | ||||||
| @@ -48,7 +63,7 @@ Usage of ./matterbridge: | |||||||
|   -debug |   -debug | ||||||
|         enable debug |         enable debug | ||||||
|   -plus |   -plus | ||||||
|         running using API instead of webhooks |         running using API instead of webhooks (deprecated, set Plus flag in [general] config) | ||||||
|   -version |   -version | ||||||
|         show version |         show version | ||||||
| ``` | ``` | ||||||
|   | |||||||
							
								
								
									
										478
									
								
								bridge/bridge.go
									
									
									
									
									
								
							
							
						
						
									
										478
									
								
								bridge/bridge.go
									
									
									
									
									
								
							| @@ -1,407 +1,151 @@ | |||||||
| package bridge | package bridge | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"crypto/tls" | 	//"fmt" | ||||||
| 	"github.com/42wim/matterbridge/matterclient" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	"github.com/42wim/matterbridge/matterhook" | 	"github.com/42wim/matterbridge/bridge/gitter" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/irc" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/mattermost" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/slack" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/xmpp" | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
| 	"github.com/peterhellberg/giphy" |  | ||||||
| 	ircm "github.com/sorcix/irc" |  | ||||||
| 	"github.com/thoj/go-ircevent" |  | ||||||
| 	"regexp" |  | ||||||
| 	"sort" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| //type Bridge struct { |  | ||||||
| type MMhook struct { |  | ||||||
| 	mh *matterhook.Client |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type MMapi struct { |  | ||||||
| 	mc            *matterclient.MMClient |  | ||||||
| 	mmMap         map[string]string |  | ||||||
| 	mmIgnoreNicks []string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type MMirc struct { |  | ||||||
| 	i              *irc.Connection |  | ||||||
| 	ircNick        string |  | ||||||
| 	ircMap         map[string]string |  | ||||||
| 	names          map[string][]string |  | ||||||
| 	ircIgnoreNicks []string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type MMMessage struct { |  | ||||||
| 	Text     string |  | ||||||
| 	Channel  string |  | ||||||
| 	Username string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type Bridge struct { | type Bridge struct { | ||||||
| 	MMhook | 	*config.Config | ||||||
| 	MMapi | 	Source      string | ||||||
| 	MMirc | 	Bridges     []Bridger | ||||||
| 	*Config | 	Channels    []map[string]string | ||||||
| 	kind string | 	ignoreNicks map[string][]string | ||||||
| } | } | ||||||
|  |  | ||||||
| type FancyLog struct { | type Bridger interface { | ||||||
| 	irc *log.Entry | 	Send(msg config.Message) error | ||||||
| 	mm  *log.Entry | 	Name() string | ||||||
|  | 	Connect() error | ||||||
|  | 	//Command(cmd string) string | ||||||
| } | } | ||||||
|  |  | ||||||
| var flog FancyLog | func NewBridge(cfg *config.Config) error { | ||||||
|  | 	c := make(chan config.Message) | ||||||
| const Legacy = "legacy" |  | ||||||
|  |  | ||||||
| func initFLog() { |  | ||||||
| 	flog.irc = log.WithFields(log.Fields{"module": "irc"}) |  | ||||||
| 	flog.mm = log.WithFields(log.Fields{"module": "mattermost"}) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewBridge(name string, config *Config, kind string) *Bridge { |  | ||||||
| 	initFLog() |  | ||||||
| 	b := &Bridge{} | 	b := &Bridge{} | ||||||
| 	b.Config = config | 	b.Config = cfg | ||||||
| 	b.kind = kind | 	if cfg.IRC.Enable { | ||||||
| 	b.ircNick = b.Config.IRC.Nick | 		b.Bridges = append(b.Bridges, birc.New(cfg, c)) | ||||||
| 	b.ircMap = make(map[string]string) |  | ||||||
| 	b.mmMap = make(map[string]string) |  | ||||||
| 	b.MMirc.names = make(map[string][]string) |  | ||||||
| 	b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks) |  | ||||||
| 	b.mmIgnoreNicks = strings.Fields(b.Config.Mattermost.IgnoreNicks) |  | ||||||
| 	for _, val := range b.Config.Channel { |  | ||||||
| 		b.ircMap[val.IRC] = val.Mattermost |  | ||||||
| 		b.mmMap[val.Mattermost] = val.IRC |  | ||||||
| 	} | 	} | ||||||
| 	if kind == Legacy { | 	if cfg.Mattermost.Enable { | ||||||
| 		b.mh = matterhook.New(b.Config.Mattermost.URL, | 		b.Bridges = append(b.Bridges, bmattermost.New(cfg, c)) | ||||||
| 			matterhook.Config{InsecureSkipVerify: b.Config.Mattermost.SkipTLSVerify, |  | ||||||
| 				BindAddress: b.Config.Mattermost.BindAddress}) |  | ||||||
| 	} else { |  | ||||||
| 		b.mc = matterclient.New(b.Config.Mattermost.Login, b.Config.Mattermost.Password, |  | ||||||
| 			b.Config.Mattermost.Team, b.Config.Mattermost.Server) |  | ||||||
| 		b.mc.SkipTLSVerify = b.Config.Mattermost.SkipTLSVerify |  | ||||||
| 		b.mc.NoTLS = b.Config.Mattermost.NoTLS |  | ||||||
| 		flog.mm.Infof("Trying login %s (team: %s) on %s", b.Config.Mattermost.Login, b.Config.Mattermost.Team, b.Config.Mattermost.Server) |  | ||||||
| 		err := b.mc.Login() |  | ||||||
| 		if err != nil { |  | ||||||
| 			flog.mm.Fatal("Can not connect", err) |  | ||||||
| 		} |  | ||||||
| 		flog.mm.Info("Login ok") |  | ||||||
| 		b.mc.JoinChannel(b.Config.Mattermost.Channel) |  | ||||||
| 		for _, val := range b.Config.Channel { |  | ||||||
| 			b.mc.JoinChannel(val.Mattermost) |  | ||||||
| 		} |  | ||||||
| 		go b.mc.WsReceiver() |  | ||||||
| 	} | 	} | ||||||
| 	flog.irc.Info("Trying IRC connection") | 	if cfg.Xmpp.Enable { | ||||||
| 	b.i = b.createIRC(name) | 		b.Bridges = append(b.Bridges, bxmpp.New(cfg, c)) | ||||||
| 	flog.irc.Info("Connection succeeded") |  | ||||||
| 	go b.handleMatter() |  | ||||||
| 	return b |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) createIRC(name string) *irc.Connection { |  | ||||||
| 	i := irc.IRC(b.Config.IRC.Nick, b.Config.IRC.Nick) |  | ||||||
| 	i.UseTLS = b.Config.IRC.UseTLS |  | ||||||
| 	i.UseSASL = b.Config.IRC.UseSASL |  | ||||||
| 	i.SASLLogin = b.Config.IRC.NickServNick |  | ||||||
| 	i.SASLPassword = b.Config.IRC.NickServPassword |  | ||||||
| 	i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.IRC.SkipTLSVerify} |  | ||||||
| 	if b.Config.IRC.Password != "" { |  | ||||||
| 		i.Password = b.Config.IRC.Password |  | ||||||
| 	} | 	} | ||||||
| 	i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection) | 	if cfg.Gitter.Enable { | ||||||
| 	err := i.Connect(b.Config.IRC.Server) | 		b.Bridges = append(b.Bridges, bgitter.New(cfg, c)) | ||||||
| 	if err != nil { |  | ||||||
| 		flog.irc.Fatal(err) |  | ||||||
| 	} | 	} | ||||||
| 	return i | 	if cfg.Slack.Enable { | ||||||
| } | 		b.Bridges = append(b.Bridges, bslack.New(cfg, c)) | ||||||
|  |  | ||||||
| func (b *Bridge) handleNewConnection(event *irc.Event) { |  | ||||||
| 	flog.irc.Info("Registering callbacks") |  | ||||||
| 	i := b.i |  | ||||||
| 	b.ircNick = event.Arguments[0] |  | ||||||
| 	i.AddCallback("PRIVMSG", b.handlePrivMsg) |  | ||||||
| 	i.AddCallback("CTCP_ACTION", b.handlePrivMsg) |  | ||||||
| 	i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames) |  | ||||||
| 	i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames) |  | ||||||
| 	i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime) |  | ||||||
| 	i.AddCallback(ircm.NOTICE, b.handleNotice) |  | ||||||
| 	i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.irc.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) }) |  | ||||||
| 	i.AddCallback("PING", func(e *irc.Event) { |  | ||||||
| 		i.SendRaw("PONG :" + e.Message()) |  | ||||||
| 		flog.irc.Debugf("PING/PONG") |  | ||||||
| 	}) |  | ||||||
| 	if b.Config.Mattermost.ShowJoinPart { |  | ||||||
| 		i.AddCallback("JOIN", b.handleJoinPart) |  | ||||||
| 		i.AddCallback("PART", b.handleJoinPart) |  | ||||||
| 	} | 	} | ||||||
| 	i.AddCallback("*", b.handleOther) | 	if len(b.Bridges) < 2 { | ||||||
| 	b.setupChannels() | 		log.Fatalf("only %d sections enabled. Need at least 2 sections enabled (eg [IRC] and [mattermost]", len(b.Bridges)) | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) setupChannels() { |  | ||||||
| 	i := b.i |  | ||||||
| 	for _, val := range b.Config.Channel { |  | ||||||
| 		flog.irc.Infof("Joining %s as %s", val.IRC, b.ircNick) |  | ||||||
| 		i.Join(val.IRC) |  | ||||||
| 	} | 	} | ||||||
| } | 	for _, br := range b.Bridges { | ||||||
|  | 		br.Connect() | ||||||
| func (b *Bridge) handleIrcBotCommand(event *irc.Event) bool { |  | ||||||
| 	parts := strings.Fields(event.Message()) |  | ||||||
| 	exp, _ := regexp.Compile("[:,]+$") |  | ||||||
| 	channel := event.Arguments[0] |  | ||||||
| 	command := "" |  | ||||||
| 	if len(parts) == 2 { |  | ||||||
| 		command = parts[1] |  | ||||||
| 	} | 	} | ||||||
| 	if exp.ReplaceAllString(parts[0], "") == b.ircNick { | 	b.mapChannels() | ||||||
| 		switch command { | 	b.mapIgnores() | ||||||
| 		case "users": | 	b.handleReceive(c) | ||||||
| 			usernames := b.mc.UsernamesInChannel(b.getMMChannel(channel)) |  | ||||||
| 			sort.Strings(usernames) |  | ||||||
| 			b.i.Privmsg(channel, "Users on Mattermost: "+strings.Join(usernames, ", ")) |  | ||||||
| 		default: |  | ||||||
| 			b.i.Privmsg(channel, "Valid commands are: [users, help]") |  | ||||||
| 		} |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) ircNickFormat(nick string) string { |  | ||||||
| 	if nick == b.ircNick { |  | ||||||
| 		return nick |  | ||||||
| 	} |  | ||||||
| 	if b.Config.Mattermost.RemoteNickFormat == nil { |  | ||||||
| 		return "irc-" + nick |  | ||||||
| 	} |  | ||||||
| 	return strings.Replace(*b.Config.Mattermost.RemoteNickFormat, "{NICK}", nick, -1) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handlePrivMsg(event *irc.Event) { |  | ||||||
| 	flog.irc.Debugf("handlePrivMsg() %s %s", event.Nick, event.Message()) |  | ||||||
| 	if b.ignoreMessage(event.Nick, event.Message(), "irc") { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if b.handleIrcBotCommand(event) { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	msg := "" |  | ||||||
| 	if event.Code == "CTCP_ACTION" { |  | ||||||
| 		msg = event.Nick + " " |  | ||||||
| 	} |  | ||||||
| 	msg += event.Message() |  | ||||||
| 	b.Send(b.ircNickFormat(event.Nick), msg, b.getMMChannel(event.Arguments[0])) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleJoinPart(event *irc.Event) { |  | ||||||
| 	b.Send(b.ircNick, b.ircNickFormat(event.Nick)+" "+strings.ToLower(event.Code)+"s "+event.Message(), b.getMMChannel(event.Arguments[0])) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleNotice(event *irc.Event) { |  | ||||||
| 	if strings.Contains(event.Message(), "This nickname is registered") { |  | ||||||
| 		b.i.Privmsg(b.Config.IRC.NickServNick, "IDENTIFY "+b.Config.IRC.NickServPassword) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) nicksPerRow() int { |  | ||||||
| 	if b.Config.Mattermost.NicksPerRow < 1 { |  | ||||||
| 		return 4 |  | ||||||
| 	} |  | ||||||
| 	return b.Config.Mattermost.NicksPerRow |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) formatnicks(nicks []string, continued bool) string { |  | ||||||
| 	switch b.Config.Mattermost.NickFormatter { |  | ||||||
| 	case "table": |  | ||||||
| 		return tableformatter(nicks, b.nicksPerRow(), continued) |  | ||||||
| 	default: |  | ||||||
| 		return plainformatter(nicks, b.nicksPerRow()) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) storeNames(event *irc.Event) { |  | ||||||
| 	channel := event.Arguments[2] |  | ||||||
| 	b.MMirc.names[channel] = append( |  | ||||||
| 		b.MMirc.names[channel], |  | ||||||
| 		strings.Split(strings.TrimSpace(event.Message()), " ")...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) endNames(event *irc.Event) { |  | ||||||
| 	channel := event.Arguments[1] |  | ||||||
| 	sort.Strings(b.MMirc.names[channel]) |  | ||||||
| 	maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow() |  | ||||||
| 	continued := false |  | ||||||
| 	for len(b.MMirc.names[channel]) > maxNamesPerPost { |  | ||||||
| 		b.Send( |  | ||||||
| 			b.ircNick, |  | ||||||
| 			b.formatnicks(b.MMirc.names[channel][0:maxNamesPerPost], continued), |  | ||||||
| 			b.getMMChannel(channel)) |  | ||||||
| 		b.MMirc.names[channel] = b.MMirc.names[channel][maxNamesPerPost:] |  | ||||||
| 		continued = true |  | ||||||
| 	} |  | ||||||
| 	b.Send(b.ircNick, b.formatnicks(b.MMirc.names[channel], continued), b.getMMChannel(channel)) |  | ||||||
| 	b.MMirc.names[channel] = nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleTopicWhoTime(event *irc.Event) { |  | ||||||
| 	parts := strings.Split(event.Arguments[2], "!") |  | ||||||
| 	t, err := strconv.ParseInt(event.Arguments[3], 10, 64) |  | ||||||
| 	if err != nil { |  | ||||||
| 		flog.irc.Errorf("Invalid time stamp: %s", event.Arguments[3]) |  | ||||||
| 	} |  | ||||||
| 	user := parts[0] |  | ||||||
| 	if len(parts) > 1 { |  | ||||||
| 		user += " [" + parts[1] + "]" |  | ||||||
| 	} |  | ||||||
| 	flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t, 0)) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleOther(event *irc.Event) { |  | ||||||
| 	flog.irc.Debugf("%#v", event) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) Send(nick string, message string, channel string) error { |  | ||||||
| 	return b.SendType(nick, message, channel, "") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) SendType(nick string, message string, channel string, mtype string) error { |  | ||||||
| 	if b.Config.Mattermost.PrefixMessagesWithNick { |  | ||||||
| 		if IsMarkup(message) { |  | ||||||
| 			message = nick + "\n\n" + message |  | ||||||
| 		} else { |  | ||||||
| 			message = nick + " " + message |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if b.kind == Legacy { |  | ||||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL} |  | ||||||
| 		matterMessage.Channel = channel |  | ||||||
| 		matterMessage.UserName = nick |  | ||||||
| 		matterMessage.Type = mtype |  | ||||||
| 		matterMessage.Text = message |  | ||||||
| 		err := b.mh.Send(matterMessage) |  | ||||||
| 		if err != nil { |  | ||||||
| 			flog.mm.Info(err) |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		flog.mm.Debug("->mattermost channel: ", channel, " ", message) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	flog.mm.Debug("->mattermost channel: ", channel, " ", message) |  | ||||||
| 	b.mc.PostMessage(channel, message) |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bridge) handleMatterHook(mchan chan *MMMessage) { | func (b *Bridge) handleReceive(c chan config.Message) { | ||||||
| 	for { | 	for { | ||||||
| 		message := b.mh.Receive() | 		select { | ||||||
| 		flog.mm.Debugf("receiving from matterhook %#v", message) | 		case msg := <-c: | ||||||
| 		m := &MMMessage{} | 			for _, br := range b.Bridges { | ||||||
| 		m.Username = message.UserName | 				b.handleMessage(msg, br) | ||||||
| 		m.Text = message.Text | 			} | ||||||
| 		m.Channel = message.ChannelName |  | ||||||
| 		mchan <- m |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) handleMatterClient(mchan chan *MMMessage) { |  | ||||||
| 	for message := range b.mc.MessageChan { |  | ||||||
| 		// do not post our own messages back to irc |  | ||||||
| 		if message.Raw.Action == "posted" && b.mc.User.Username != message.Username { |  | ||||||
| 			flog.mm.Debugf("receiving from matterclient %#v", message) |  | ||||||
| 			m := &MMMessage{} |  | ||||||
| 			m.Username = message.Username |  | ||||||
| 			m.Channel = message.Channel |  | ||||||
| 			m.Text = message.Text |  | ||||||
| 			mchan <- m |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bridge) handleMatter() { | func (b *Bridge) mapChannels() error { | ||||||
| 	flog.mm.Infof("Choosing Mattermost connection type %s", b.kind) | 	for _, val := range b.Config.Channel { | ||||||
| 	mchan := make(chan *MMMessage) | 		m := make(map[string]string) | ||||||
| 	if b.kind == Legacy { | 		m["irc"] = val.IRC | ||||||
| 		go b.handleMatterHook(mchan) | 		m["mattermost"] = val.Mattermost | ||||||
| 	} else { | 		m["xmpp"] = val.Xmpp | ||||||
| 		go b.handleMatterClient(mchan) | 		m["gitter"] = val.Gitter | ||||||
|  | 		m["slack"] = val.Slack | ||||||
|  | 		b.Channels = append(b.Channels, m) | ||||||
| 	} | 	} | ||||||
| 	flog.mm.Info("Start listening for Mattermost messages") | 	return nil | ||||||
| 	for message := range mchan { | } | ||||||
| 		var username string |  | ||||||
| 		if b.ignoreMessage(message.Username, message.Text, "mattermost") { | func (b *Bridge) mapIgnores() { | ||||||
| 			continue | 	m := make(map[string][]string) | ||||||
|  | 	m["irc"] = strings.Fields(b.Config.IRC.IgnoreNicks) | ||||||
|  | 	m["mattermost"] = strings.Fields(b.Config.Mattermost.IgnoreNicks) | ||||||
|  | 	m["xmpp"] = strings.Fields(b.Config.Xmpp.IgnoreNicks) | ||||||
|  | 	m["gitter"] = strings.Fields(b.Config.Gitter.IgnoreNicks) | ||||||
|  | 	m["slack"] = strings.Fields(b.Config.Slack.IgnoreNicks) | ||||||
|  | 	b.ignoreNicks = m | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bridge) getDestChannel(msg *config.Message, dest string) string { | ||||||
|  | 	for _, v := range b.Channels { | ||||||
|  | 		if v[msg.Origin] == msg.Channel { | ||||||
|  | 			return v[dest] | ||||||
| 		} | 		} | ||||||
| 		username = message.Username + ": " | 	} | ||||||
| 		if b.Config.IRC.RemoteNickFormat != "" { | 	return "" | ||||||
| 			username = strings.Replace(b.Config.IRC.RemoteNickFormat, "{NICK}", message.Username, -1) | } | ||||||
| 		} |  | ||||||
| 		cmds := strings.Fields(message.Text) | func (b *Bridge) handleMessage(msg config.Message, dest Bridger) { | ||||||
| 		// empty message | 	if b.ignoreMessage(&msg) { | ||||||
| 		if len(cmds) == 0 { | 		return | ||||||
| 			continue | 	} | ||||||
| 		} | 	if dest.Name() != msg.Origin { | ||||||
| 		cmd := cmds[0] | 		msg.Channel = b.getDestChannel(&msg, dest.Name()) | ||||||
| 		switch cmd { | 		if msg.Channel == "" { | ||||||
| 		case "!users": | 			return | ||||||
| 			flog.mm.Info("Received !users from ", message.Username) |  | ||||||
| 			b.i.SendRaw("NAMES " + b.getIRCChannel(message.Channel)) |  | ||||||
| 			continue |  | ||||||
| 		case "!gif": |  | ||||||
| 			message.Text = b.giphyRandom(strings.Fields(strings.Replace(message.Text, "!gif ", "", 1))) |  | ||||||
| 			b.Send(b.ircNick, message.Text, b.getIRCChannel(message.Channel)) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		texts := strings.Split(message.Text, "\n") |  | ||||||
| 		for _, text := range texts { |  | ||||||
| 			flog.mm.Debug("Sending message from " + message.Username + " to " + message.Channel) |  | ||||||
| 			b.i.Privmsg(b.getIRCChannel(message.Channel), username+text) |  | ||||||
| 		} | 		} | ||||||
|  | 		b.modifyMessage(&msg, dest.Name()) | ||||||
|  | 		log.Debugf("sending %#v from %s to %s", msg, msg.Origin, dest.Name()) | ||||||
|  | 		dest.Send(msg) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *Bridge) giphyRandom(query []string) string { | func (b *Bridge) ignoreMessage(msg *config.Message) bool { | ||||||
| 	g := giphy.DefaultClient |  | ||||||
| 	if b.Config.General.GiphyAPIKey != "" { |  | ||||||
| 		g.APIKey = b.Config.General.GiphyAPIKey |  | ||||||
| 	} |  | ||||||
| 	res, err := g.Random(query) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "error" |  | ||||||
| 	} |  | ||||||
| 	return res.Data.FixedHeightDownsampledURL |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) getMMChannel(ircChannel string) string { |  | ||||||
| 	mmChannel := b.ircMap[ircChannel] |  | ||||||
| 	if b.kind == Legacy { |  | ||||||
| 		return mmChannel |  | ||||||
| 	} |  | ||||||
| 	return b.mc.GetChannelId(mmChannel, "") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) getIRCChannel(mmChannel string) string { |  | ||||||
| 	return b.mmMap[mmChannel] |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *Bridge) ignoreMessage(nick string, message string, protocol string) bool { |  | ||||||
| 	var ignoreNicks = b.mmIgnoreNicks |  | ||||||
| 	if protocol == "irc" { |  | ||||||
| 		ignoreNicks = b.ircIgnoreNicks |  | ||||||
| 	} |  | ||||||
| 	// should we discard messages ? | 	// should we discard messages ? | ||||||
| 	for _, entry := range ignoreNicks { | 	for _, entry := range b.ignoreNicks[msg.Origin] { | ||||||
| 		if nick == entry { | 		if msg.Username == entry { | ||||||
| 			return true | 			return true | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func setNickFormat(msg *config.Message, format string) { | ||||||
|  | 	if format == "" { | ||||||
|  | 		msg.Username = msg.Origin + "-" + msg.Username + ": " | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	msg.Username = strings.Replace(format, "{NICK}", msg.Username, -1) | ||||||
|  | 	msg.Username = strings.Replace(msg.Username, "{BRIDGE}", msg.Origin, -1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bridge) modifyMessage(msg *config.Message, dest string) { | ||||||
|  | 	switch dest { | ||||||
|  | 	case "irc": | ||||||
|  | 		setNickFormat(msg, b.Config.IRC.RemoteNickFormat) | ||||||
|  | 	case "gitter": | ||||||
|  | 		setNickFormat(msg, b.Config.Gitter.RemoteNickFormat) | ||||||
|  | 	case "xmpp": | ||||||
|  | 		setNickFormat(msg, b.Config.Xmpp.RemoteNickFormat) | ||||||
|  | 	case "mattermost": | ||||||
|  | 		setNickFormat(msg, b.Config.Mattermost.RemoteNickFormat) | ||||||
|  | 	case "slack": | ||||||
|  | 		setNickFormat(msg, b.Config.Slack.RemoteNickFormat) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| package bridge | package config | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"gopkg.in/gcfg.v1" | 	"gopkg.in/gcfg.v1" | ||||||
| @@ -6,6 +6,13 @@ import ( | |||||||
| 	"log" | 	"log" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | type Message struct { | ||||||
|  | 	Text     string | ||||||
|  | 	Channel  string | ||||||
|  | 	Username string | ||||||
|  | 	Origin   string | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type Config struct { | type Config struct { | ||||||
| 	IRC struct { | 	IRC struct { | ||||||
| 		UseTLS           bool | 		UseTLS           bool | ||||||
| @@ -19,6 +26,14 @@ type Config struct { | |||||||
| 		NickServPassword string | 		NickServPassword string | ||||||
| 		RemoteNickFormat string | 		RemoteNickFormat string | ||||||
| 		IgnoreNicks      string | 		IgnoreNicks      string | ||||||
|  | 		Enable           bool | ||||||
|  | 	} | ||||||
|  | 	Gitter struct { | ||||||
|  | 		Enable           bool | ||||||
|  | 		IgnoreNicks      string | ||||||
|  | 		Nick             string | ||||||
|  | 		RemoteNickFormat string | ||||||
|  | 		Token            string | ||||||
| 	} | 	} | ||||||
| 	Mattermost struct { | 	Mattermost struct { | ||||||
| 		URL                    string | 		URL                    string | ||||||
| @@ -34,16 +49,47 @@ type Config struct { | |||||||
| 		Team                   string | 		Team                   string | ||||||
| 		Login                  string | 		Login                  string | ||||||
| 		Password               string | 		Password               string | ||||||
| 		RemoteNickFormat       *string | 		RemoteNickFormat       string | ||||||
| 		IgnoreNicks            string | 		IgnoreNicks            string | ||||||
| 		NoTLS                  bool | 		NoTLS                  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 { | ||||||
|  | 		IgnoreNicks      string | ||||||
|  | 		Jid              string | ||||||
|  | 		Password         string | ||||||
|  | 		Server           string | ||||||
|  | 		Muc              string | ||||||
|  | 		Nick             string | ||||||
|  | 		RemoteNickFormat string | ||||||
|  | 		Enable           bool | ||||||
| 	} | 	} | ||||||
| 	Channel map[string]*struct { | 	Channel map[string]*struct { | ||||||
| 		IRC        string | 		IRC        string | ||||||
| 		Mattermost string | 		Mattermost string | ||||||
|  | 		Xmpp       string | ||||||
|  | 		Gitter     string | ||||||
|  | 		Slack      string | ||||||
| 	} | 	} | ||||||
| 	General struct { | 	General struct { | ||||||
| 		GiphyAPIKey string | 		GiphyAPIKey string | ||||||
|  | 		Xmpp        bool | ||||||
|  | 		Irc         bool | ||||||
|  | 		Mattermost  bool | ||||||
|  | 		Plus        bool | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										110
									
								
								bridge/gitter/gitter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								bridge/gitter/gitter.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | |||||||
|  | package bgitter | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
|  | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	"github.com/sromku/go-gitter" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Bgitter struct { | ||||||
|  | 	c *gitter.Gitter | ||||||
|  | 	*config.Config | ||||||
|  | 	Remote chan config.Message | ||||||
|  | 	Rooms  []gitter.Room | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Message struct { | ||||||
|  | 	Text     string | ||||||
|  | 	Channel  string | ||||||
|  | 	Username string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var flog *log.Entry | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	flog = log.WithFields(log.Fields{"module": "gitter"}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New(config *config.Config, c chan config.Message) *Bgitter { | ||||||
|  | 	b := &Bgitter{} | ||||||
|  | 	b.Config = config | ||||||
|  | 	b.Remote = c | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bgitter) Connect() error { | ||||||
|  | 	var err error | ||||||
|  | 	flog.Info("Trying Gitter connection") | ||||||
|  | 	b.c = gitter.New(b.Config.Gitter.Token) | ||||||
|  | 	_, err = b.c.GetUser() | ||||||
|  | 	if err != nil { | ||||||
|  | 		flog.Debugf("%#v", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	flog.Info("Connection succeeded") | ||||||
|  | 	b.setupChannels() | ||||||
|  | 	go b.handleGitter() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bgitter) Name() string { | ||||||
|  | 	return "gitter" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bgitter) Send(msg config.Message) error { | ||||||
|  | 	roomID := b.getRoomID(msg.Channel) | ||||||
|  | 	if roomID == "" { | ||||||
|  | 		flog.Errorf("Could not find roomID for %v", msg.Channel) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	// add ZWSP because gitter echoes our own messages | ||||||
|  | 	return b.c.SendMessage(roomID, msg.Username+msg.Text+" ") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bgitter) getRoomID(channel string) string { | ||||||
|  | 	for _, v := range b.Rooms { | ||||||
|  | 		if v.URI == channel { | ||||||
|  | 			return v.ID | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bgitter) handleGitter() { | ||||||
|  | 	for _, val := range b.Config.Channel { | ||||||
|  | 		room := val.Gitter | ||||||
|  | 		roomID := b.getRoomID(room) | ||||||
|  | 		if roomID == "" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		stream := b.c.Stream(roomID) | ||||||
|  | 		go b.c.Listen(stream) | ||||||
|  |  | ||||||
|  | 		go func(stream *gitter.Stream, room string) { | ||||||
|  | 			for { | ||||||
|  | 				event := <-stream.Event | ||||||
|  | 				switch ev := event.Data.(type) { | ||||||
|  | 				case *gitter.MessageReceived: | ||||||
|  | 					// check for ZWSP to see if it's not an echo | ||||||
|  | 					if !strings.HasSuffix(ev.Message.Text, "") { | ||||||
|  | 						b.Remote <- config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room, Origin: "gitter"} | ||||||
|  | 					} | ||||||
|  | 				case *gitter.GitterConnectionClosed: | ||||||
|  | 					flog.Errorf("connection with gitter closed for room %s", room) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}(stream, room) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bgitter) setupChannels() { | ||||||
|  | 	b.Rooms, _ = b.c.GetRooms() | ||||||
|  | 	for _, val := range b.Config.Channel { | ||||||
|  | 		flog.Infof("Joining %s as %s", val.Gitter, b.Gitter.Nick) | ||||||
|  | 		_, err := b.c.JoinRoom(val.Gitter) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Errorf("Joining %s failed", val.Gitter) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package bridge | package birc | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"strings" | 	"strings" | ||||||
							
								
								
									
										194
									
								
								bridge/irc/irc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								bridge/irc/irc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,194 @@ | |||||||
|  | package birc | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
|  | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	ircm "github.com/sorcix/irc" | ||||||
|  | 	"github.com/thoj/go-ircevent" | ||||||
|  | 	"sort" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | //type Bridge struct { | ||||||
|  | type Birc struct { | ||||||
|  | 	i              *irc.Connection | ||||||
|  | 	ircNick        string | ||||||
|  | 	ircMap         map[string]string | ||||||
|  | 	names          map[string][]string | ||||||
|  | 	ircIgnoreNicks []string | ||||||
|  | 	*config.Config | ||||||
|  | 	Remote chan config.Message | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type FancyLog struct { | ||||||
|  | 	irc *log.Entry | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var flog FancyLog | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	flog.irc = log.WithFields(log.Fields{"module": "irc"}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New(config *config.Config, c chan config.Message) *Birc { | ||||||
|  | 	b := &Birc{} | ||||||
|  | 	b.Config = config | ||||||
|  | 	b.Remote = c | ||||||
|  | 	b.ircNick = b.Config.IRC.Nick | ||||||
|  | 	b.ircMap = make(map[string]string) | ||||||
|  | 	b.names = make(map[string][]string) | ||||||
|  | 	b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks) | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) Command(msg *config.Message) string { | ||||||
|  | 	switch msg.Text { | ||||||
|  | 	case "!users": | ||||||
|  | 		b.i.SendRaw("NAMES " + msg.Channel) | ||||||
|  | 	} | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) Connect() error { | ||||||
|  | 	flog.irc.Info("Trying IRC connection") | ||||||
|  | 	i := irc.IRC(b.Config.IRC.Nick, b.Config.IRC.Nick) | ||||||
|  | 	i.UseTLS = b.Config.IRC.UseTLS | ||||||
|  | 	i.UseSASL = b.Config.IRC.UseSASL | ||||||
|  | 	i.SASLLogin = b.Config.IRC.NickServNick | ||||||
|  | 	i.SASLPassword = b.Config.IRC.NickServPassword | ||||||
|  | 	i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.IRC.SkipTLSVerify} | ||||||
|  | 	if b.Config.IRC.Password != "" { | ||||||
|  | 		i.Password = b.Config.IRC.Password | ||||||
|  | 	} | ||||||
|  | 	i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection) | ||||||
|  | 	err := i.Connect(b.Config.IRC.Server) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	flog.irc.Info("Connection succeeded") | ||||||
|  | 	b.i = i | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) Name() string { | ||||||
|  | 	return "irc" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) Send(msg config.Message) error { | ||||||
|  | 	if msg.Origin == "irc" { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	if strings.HasPrefix(msg.Text, "!") { | ||||||
|  | 		b.Command(&msg) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	b.i.Privmsg(msg.Channel, msg.Username+msg.Text) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) endNames(event *irc.Event) { | ||||||
|  | 	channel := event.Arguments[1] | ||||||
|  | 	sort.Strings(b.names[channel]) | ||||||
|  | 	maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow() | ||||||
|  | 	continued := false | ||||||
|  | 	for len(b.names[channel]) > maxNamesPerPost { | ||||||
|  | 		b.Remote <- config.Message{Username: b.ircNick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost], continued), Channel: channel, Origin: "irc"} | ||||||
|  | 		b.names[channel] = b.names[channel][maxNamesPerPost:] | ||||||
|  | 		continued = true | ||||||
|  | 	} | ||||||
|  | 	b.Remote <- config.Message{Username: b.ircNick, Text: b.formatnicks(b.names[channel], continued), Channel: channel, Origin: "irc"} | ||||||
|  | 	b.names[channel] = nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) handleNewConnection(event *irc.Event) { | ||||||
|  | 	flog.irc.Info("Registering callbacks") | ||||||
|  | 	i := b.i | ||||||
|  | 	b.ircNick = event.Arguments[0] | ||||||
|  | 	i.AddCallback("PRIVMSG", b.handlePrivMsg) | ||||||
|  | 	i.AddCallback("CTCP_ACTION", b.handlePrivMsg) | ||||||
|  | 	i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime) | ||||||
|  | 	i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames) | ||||||
|  | 	i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames) | ||||||
|  | 	i.AddCallback(ircm.NOTICE, b.handleNotice) | ||||||
|  | 	i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.irc.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) }) | ||||||
|  | 	i.AddCallback("PING", func(e *irc.Event) { | ||||||
|  | 		i.SendRaw("PONG :" + e.Message()) | ||||||
|  | 		flog.irc.Debugf("PING/PONG") | ||||||
|  | 	}) | ||||||
|  | 	if b.Config.Mattermost.ShowJoinPart { | ||||||
|  | 		i.AddCallback("JOIN", b.handleJoinPart) | ||||||
|  | 		i.AddCallback("PART", b.handleJoinPart) | ||||||
|  | 	} | ||||||
|  | 	i.AddCallback("*", b.handleOther) | ||||||
|  | 	b.setupChannels() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) handleJoinPart(event *irc.Event) { | ||||||
|  | 	//b.Send(b.ircNick, b.ircNickFormat(event.Nick)+" "+strings.ToLower(event.Code)+"s "+event.Message(), b.getMMChannel(event.Arguments[0])) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) handleNotice(event *irc.Event) { | ||||||
|  | 	if strings.Contains(event.Message(), "This nickname is registered") { | ||||||
|  | 		b.i.Privmsg(b.Config.IRC.NickServNick, "IDENTIFY "+b.Config.IRC.NickServPassword) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) handleOther(event *irc.Event) { | ||||||
|  | 	flog.irc.Debugf("%#v", event) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) handlePrivMsg(event *irc.Event) { | ||||||
|  | 	flog.irc.Debugf("handlePrivMsg() %s %s", event.Nick, event.Message()) | ||||||
|  | 	msg := "" | ||||||
|  | 	if event.Code == "CTCP_ACTION" { | ||||||
|  | 		msg = event.Nick + " " | ||||||
|  | 	} | ||||||
|  | 	msg += event.Message() | ||||||
|  | 	b.Remote <- config.Message{Username: event.Nick, Text: msg, Channel: event.Arguments[0], Origin: "irc"} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) handleTopicWhoTime(event *irc.Event) { | ||||||
|  | 	parts := strings.Split(event.Arguments[2], "!") | ||||||
|  | 	t, err := strconv.ParseInt(event.Arguments[3], 10, 64) | ||||||
|  | 	if err != nil { | ||||||
|  | 		flog.irc.Errorf("Invalid time stamp: %s", event.Arguments[3]) | ||||||
|  | 	} | ||||||
|  | 	user := parts[0] | ||||||
|  | 	if len(parts) > 1 { | ||||||
|  | 		user += " [" + parts[1] + "]" | ||||||
|  | 	} | ||||||
|  | 	flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t, 0)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) nicksPerRow() int { | ||||||
|  | 	if b.Config.Mattermost.NicksPerRow < 1 { | ||||||
|  | 		return 4 | ||||||
|  | 	} | ||||||
|  | 	return b.Config.Mattermost.NicksPerRow | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) setupChannels() { | ||||||
|  | 	for _, val := range b.Config.Channel { | ||||||
|  | 		flog.irc.Infof("Joining %s as %s", val.IRC, b.ircNick) | ||||||
|  | 		b.i.Join(val.IRC) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) storeNames(event *irc.Event) { | ||||||
|  | 	channel := event.Arguments[2] | ||||||
|  | 	b.names[channel] = append( | ||||||
|  | 		b.names[channel], | ||||||
|  | 		strings.Split(strings.TrimSpace(event.Message()), " ")...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Birc) formatnicks(nicks []string, continued bool) string { | ||||||
|  | 	switch b.Config.Mattermost.NickFormatter { | ||||||
|  | 	case "table": | ||||||
|  | 		return tableformatter(nicks, b.nicksPerRow(), continued) | ||||||
|  | 	default: | ||||||
|  | 		return plainformatter(nicks, b.nicksPerRow()) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										174
									
								
								bridge/mattermost/mattermost.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								bridge/mattermost/mattermost.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | |||||||
|  | package bmattermost | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
|  | 	"github.com/42wim/matterbridge/matterclient" | ||||||
|  | 	"github.com/42wim/matterbridge/matterhook" | ||||||
|  | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | //type Bridge struct { | ||||||
|  | type MMhook struct { | ||||||
|  | 	mh *matterhook.Client | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type MMapi struct { | ||||||
|  | 	mc            *matterclient.MMClient | ||||||
|  | 	mmMap         map[string]string | ||||||
|  | 	mmIgnoreNicks []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type MMMessage struct { | ||||||
|  | 	Text     string | ||||||
|  | 	Channel  string | ||||||
|  | 	Username string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Bmattermost struct { | ||||||
|  | 	MMhook | ||||||
|  | 	MMapi | ||||||
|  | 	*config.Config | ||||||
|  | 	Plus   bool | ||||||
|  | 	Remote chan config.Message | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type FancyLog struct { | ||||||
|  | 	irc  *log.Entry | ||||||
|  | 	mm   *log.Entry | ||||||
|  | 	xmpp *log.Entry | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var flog FancyLog | ||||||
|  |  | ||||||
|  | const Legacy = "legacy" | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	flog.irc = log.WithFields(log.Fields{"module": "irc"}) | ||||||
|  | 	flog.mm = log.WithFields(log.Fields{"module": "mattermost"}) | ||||||
|  | 	flog.xmpp = log.WithFields(log.Fields{"module": "xmpp"}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New(cfg *config.Config, c chan config.Message) *Bmattermost { | ||||||
|  | 	b := &Bmattermost{} | ||||||
|  | 	b.Config = cfg | ||||||
|  | 	b.Remote = c | ||||||
|  | 	b.Plus = cfg.General.Plus | ||||||
|  | 	b.mmMap = make(map[string]string) | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmattermost) Command(cmd string) string { | ||||||
|  | 	return "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmattermost) Connect() error { | ||||||
|  | 	if !b.Plus { | ||||||
|  | 		b.mh = matterhook.New(b.Config.Mattermost.URL, | ||||||
|  | 			matterhook.Config{InsecureSkipVerify: b.Config.Mattermost.SkipTLSVerify, | ||||||
|  | 				BindAddress: b.Config.Mattermost.BindAddress}) | ||||||
|  | 	} else { | ||||||
|  | 		b.mc = matterclient.New(b.Config.Mattermost.Login, b.Config.Mattermost.Password, | ||||||
|  | 			b.Config.Mattermost.Team, b.Config.Mattermost.Server) | ||||||
|  | 		b.mc.SkipTLSVerify = b.Config.Mattermost.SkipTLSVerify | ||||||
|  | 		b.mc.NoTLS = b.Config.Mattermost.NoTLS | ||||||
|  | 		flog.mm.Infof("Trying login %s (team: %s) on %s", b.Config.Mattermost.Login, b.Config.Mattermost.Team, b.Config.Mattermost.Server) | ||||||
|  | 		err := b.mc.Login() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		flog.mm.Info("Login ok") | ||||||
|  | 		b.mc.JoinChannel(b.Config.Mattermost.Channel) | ||||||
|  | 		for _, val := range b.Config.Channel { | ||||||
|  | 			b.mc.JoinChannel(val.Mattermost) | ||||||
|  | 		} | ||||||
|  | 		go b.mc.WsReceiver() | ||||||
|  | 	} | ||||||
|  | 	go b.handleMatter() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmattermost) Name() string { | ||||||
|  | 	return "mattermost" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmattermost) Send(msg config.Message) error { | ||||||
|  | 	flog.mm.Infof("mattermost send %#v", msg) | ||||||
|  | 	if msg.Origin != "mattermost" { | ||||||
|  | 		return b.SendType(msg.Username, msg.Text, msg.Channel, "") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmattermost) SendType(nick string, message string, channel string, mtype string) error { | ||||||
|  | 	if b.Config.Mattermost.PrefixMessagesWithNick { | ||||||
|  | 		/*if IsMarkup(message) { | ||||||
|  | 			message = nick + "\n\n" + message | ||||||
|  | 		} else { | ||||||
|  | 		*/ | ||||||
|  | 		message = nick + " " + message | ||||||
|  | 		//} | ||||||
|  | 	} | ||||||
|  | 	if !b.Plus { | ||||||
|  | 		matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL} | ||||||
|  | 		matterMessage.Channel = channel | ||||||
|  | 		matterMessage.UserName = nick | ||||||
|  | 		matterMessage.Type = mtype | ||||||
|  | 		matterMessage.Text = message | ||||||
|  | 		err := b.mh.Send(matterMessage) | ||||||
|  | 		if err != nil { | ||||||
|  | 			flog.mm.Info(err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		flog.mm.Debug("->mattermost channel: ", channel, " ", message) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	flog.mm.Debug("->mattermost channel plus: ", channel, " ", message) | ||||||
|  | 	b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmattermost) handleMatter() { | ||||||
|  | 	flog.mm.Infof("Choosing API based Mattermost connection: %t", b.Plus) | ||||||
|  | 	mchan := make(chan *MMMessage) | ||||||
|  | 	if b.Plus { | ||||||
|  | 		go b.handleMatterClient(mchan) | ||||||
|  | 	} else { | ||||||
|  | 		go b.handleMatterHook(mchan) | ||||||
|  | 	} | ||||||
|  | 	flog.mm.Info("Start listening for Mattermost messages") | ||||||
|  | 	for message := range mchan { | ||||||
|  | 		texts := strings.Split(message.Text, "\n") | ||||||
|  | 		for _, text := range texts { | ||||||
|  | 			flog.mm.Debug("Sending message from " + message.Username + " to " + message.Channel) | ||||||
|  | 			b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: "mattermost"} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) { | ||||||
|  | 	for message := range b.mc.MessageChan { | ||||||
|  | 		// do not post our own messages back to irc | ||||||
|  | 		if message.Raw.Event == "posted" && b.mc.User.Username != message.Username { | ||||||
|  | 			flog.mm.Debugf("receiving from matterclient %#v", message) | ||||||
|  | 			flog.mm.Debugf("receiving from matterclient %#v", message.Raw) | ||||||
|  | 			m := &MMMessage{} | ||||||
|  | 			m.Username = message.Username | ||||||
|  | 			m.Channel = message.Channel | ||||||
|  | 			m.Text = message.Text | ||||||
|  | 			mchan <- m | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) { | ||||||
|  | 	for { | ||||||
|  | 		message := b.mh.Receive() | ||||||
|  | 		flog.mm.Debugf("receiving from matterhook %#v", message) | ||||||
|  | 		m := &MMMessage{} | ||||||
|  | 		m.Username = message.UserName | ||||||
|  | 		m.Text = message.Text | ||||||
|  | 		m.Channel = message.ChannelName | ||||||
|  | 		mchan <- m | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										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 | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										133
									
								
								bridge/xmpp/xmpp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								bridge/xmpp/xmpp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | |||||||
|  | package bxmpp | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
|  | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	"github.com/mattn/go-xmpp" | ||||||
|  |  | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Bxmpp struct { | ||||||
|  | 	xc      *xmpp.Client | ||||||
|  | 	xmppMap map[string]string | ||||||
|  | 	*config.Config | ||||||
|  | 	Remote chan config.Message | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type FancyLog struct { | ||||||
|  | 	xmpp *log.Entry | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Message struct { | ||||||
|  | 	Text     string | ||||||
|  | 	Channel  string | ||||||
|  | 	Username string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var flog FancyLog | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	flog.xmpp = log.WithFields(log.Fields{"module": "xmpp"}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func New(config *config.Config, c chan config.Message) *Bxmpp { | ||||||
|  | 	b := &Bxmpp{} | ||||||
|  | 	b.xmppMap = make(map[string]string) | ||||||
|  | 	b.Config = config | ||||||
|  | 	b.Remote = c | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bxmpp) Connect() error { | ||||||
|  | 	var err error | ||||||
|  | 	flog.xmpp.Info("Trying XMPP connection") | ||||||
|  | 	b.xc, err = b.createXMPP() | ||||||
|  | 	if err != nil { | ||||||
|  | 		flog.xmpp.Debugf("%#v", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	flog.xmpp.Info("Connection succeeded") | ||||||
|  | 	b.setupChannels() | ||||||
|  | 	go b.handleXmpp() | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bxmpp) Name() string { | ||||||
|  | 	return "xmpp" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bxmpp) Send(msg config.Message) error { | ||||||
|  | 	b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Xmpp.Muc, Text: msg.Username + msg.Text}) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { | ||||||
|  | 	options := xmpp.Options{ | ||||||
|  | 		Host:     b.Config.Xmpp.Server, | ||||||
|  | 		User:     b.Config.Xmpp.Jid, | ||||||
|  | 		Password: b.Config.Xmpp.Password, | ||||||
|  | 		NoTLS:    true, | ||||||
|  | 		StartTLS: true, | ||||||
|  | 		//StartTLS:      false, | ||||||
|  | 		Debug:                        true, | ||||||
|  | 		Session:                      true, | ||||||
|  | 		Status:                       "", | ||||||
|  | 		StatusMessage:                "", | ||||||
|  | 		Resource:                     "", | ||||||
|  | 		InsecureAllowUnencryptedAuth: false, | ||||||
|  | 		//InsecureAllowUnencryptedAuth: true, | ||||||
|  | 	} | ||||||
|  | 	var err error | ||||||
|  | 	b.xc, err = options.NewClient() | ||||||
|  | 	return b.xc, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bxmpp) setupChannels() { | ||||||
|  | 	for _, val := range b.Config.Channel { | ||||||
|  | 		flog.xmpp.Infof("Joining %s as %s", val.Xmpp, b.Xmpp.Nick) | ||||||
|  | 		b.xc.JoinMUCNoHistory(val.Xmpp+"@"+b.Xmpp.Muc, b.Xmpp.Nick) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bxmpp) xmppKeepAlive() { | ||||||
|  | 	go func() { | ||||||
|  | 		ticker := time.NewTicker(90 * time.Second) | ||||||
|  | 		for { | ||||||
|  | 			select { | ||||||
|  | 			case <-ticker.C: | ||||||
|  | 				b.xc.Send(xmpp.Chat{}) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *Bxmpp) handleXmpp() error { | ||||||
|  | 	for { | ||||||
|  | 		m, err := b.xc.Recv() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		switch v := m.(type) { | ||||||
|  | 		case xmpp.Chat: | ||||||
|  | 			var channel, nick string | ||||||
|  | 			if v.Type == "groupchat" { | ||||||
|  | 				s := strings.Split(v.Remote, "@") | ||||||
|  | 				if len(s) == 2 { | ||||||
|  | 					channel = s[0] | ||||||
|  | 				} | ||||||
|  | 				s = strings.Split(s[1], "/") | ||||||
|  | 				if len(s) == 2 { | ||||||
|  | 					nick = s[1] | ||||||
|  | 				} | ||||||
|  | 				if nick != b.Xmpp.Nick { | ||||||
|  | 					flog.xmpp.Infof("sending message to remote %s %s %s", nick, v.Text, channel) | ||||||
|  | 					b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Origin: "xmpp"} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		case xmpp.Presence: | ||||||
|  | 			// do nothing | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								changelog.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								changelog.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | # v0.6.1 | ||||||
|  | ## New features | ||||||
|  | * Slack support added.  See matterbridge.conf.sample for more information | ||||||
|  | ## Bugfix | ||||||
|  | * Fix 100% CPU bug on incorrect closed connections | ||||||
|  |  | ||||||
|  | # v0.6.0-beta2 | ||||||
|  | ## New features | ||||||
|  | * Gitter support added.  See matterbridge.conf.sample for more information | ||||||
|  |  | ||||||
|  | # v0.6.0-beta1 | ||||||
|  | ## Breaking changes from 0.5 to 0.6 | ||||||
|  | ### commandline | ||||||
|  | * -plus switch deprecated. Use ```Plus=true``` or ```Plus``` in ```[general]``` section | ||||||
|  |  | ||||||
|  | ### IRC section | ||||||
|  | * ```Enabled``` added (default false)   | ||||||
|  | Add ```Enabled=true``` or ```Enabled``` to the ```[IRC]``` section if you want to enable the IRC bridge | ||||||
|  |  | ||||||
|  | ### Mattermost section | ||||||
|  | * ```Enabled``` added (default false)   | ||||||
|  | Add ```Enabled=true``` or ```Enabled``` to the ```[mattermost]``` section if you want to enable the mattermost bridge | ||||||
|  |  | ||||||
|  | ### General section | ||||||
|  | * Use ```Plus=true``` or ```Plus``` in ```[general]``` section to enable the API version of matterbridge | ||||||
|  |  | ||||||
|  | ## New features | ||||||
|  | * Matterbridge now bridges between any specified protocol (not only mattermost anymore)  | ||||||
|  | * XMPP support added.  See matterbridge.conf.sample for more information | ||||||
|  | * RemoteNickFormat {BRIDGE} variable added   | ||||||
|  | You can now add the originating bridge to ```RemoteNickFormat```   | ||||||
|  | eg ```RemoteNickFormat="[{BRIDGE}] <{NICK}> "``` | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # v0.5.0 | ||||||
|  | ## Breaking changes from 0.4 to 0.5 for matterbridge (webhooks version) | ||||||
|  | ### IRC section | ||||||
|  | #### Server | ||||||
|  | Port removed, added to server | ||||||
|  | ``` | ||||||
|  | server="irc.freenode.net" | ||||||
|  | port=6667 | ||||||
|  | ``` | ||||||
|  | changed to | ||||||
|  | ``` | ||||||
|  | server="irc.freenode.net:6667" | ||||||
|  | ``` | ||||||
|  | #### Channel | ||||||
|  | Removed see Channels section below | ||||||
|  |  | ||||||
|  | #### UseSlackCircumfix=true | ||||||
|  | Removed, can be done by using ```RemoteNickFormat="<{NICK}> "``` | ||||||
|  |  | ||||||
|  | ### Mattermost section | ||||||
|  | #### BindAddress | ||||||
|  | Port removed, added to BindAddress | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | BindAddress="0.0.0.0" | ||||||
|  | port=9999 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | changed to | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | BindAddress="0.0.0.0:9999" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### Token | ||||||
|  | Removed | ||||||
|  |  | ||||||
|  | ### Channels section | ||||||
|  | ``` | ||||||
|  | [Token "outgoingwebhooktoken1"]  | ||||||
|  | IRCChannel="#off-topic" | ||||||
|  | MMChannel="off-topic" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | changed to | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | [Channel "channelnameofchoice"]  | ||||||
|  | IRC="#off-topic" | ||||||
|  | Mattermost="off-topic" | ||||||
|  | ``` | ||||||
| @@ -3,6 +3,9 @@ | |||||||
| #IRC section | #IRC section | ||||||
| ################################################################### | ################################################################### | ||||||
| [IRC] | [IRC] | ||||||
|  | #Enable enables this bridge | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | Enable=true | ||||||
| #irc server to connect to.  | #irc server to connect to.  | ||||||
| #REQUIRED | #REQUIRED | ||||||
| Server="irc.freenode.net:6667" | Server="irc.freenode.net:6667" | ||||||
| @@ -13,7 +16,7 @@ UseTLS=false | |||||||
|  |  | ||||||
| #Enable SASL (PLAIN) authentication. (freenode requires this from eg AWS hosts) | #Enable SASL (PLAIN) authentication. (freenode requires this from eg AWS hosts) | ||||||
| #It uses NickServNick and NickServPassword as login and password | #It uses NickServNick and NickServPassword as login and password | ||||||
| #OPTIONAL (deefault false) | #OPTIONAL (default false) | ||||||
| UseSASL=false | UseSASL=false | ||||||
|  |  | ||||||
| #Enable to not verify the certificate on your irc server. i | #Enable to not verify the certificate on your irc server. i | ||||||
| @@ -31,21 +34,55 @@ Nick="matterbot" | |||||||
| NickServNick="nickserv" | NickServNick="nickserv" | ||||||
| NickServPassword="secret" | NickServPassword="secret" | ||||||
|  |  | ||||||
| #RemoteNickFormat defines how Mattermost users appear on irc | #RemoteNickFormat defines how remote users appear on this bridge  | ||||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||||
| #OPTIONAL (default NICK:) | #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||||
| RemoteNickFormat="{NICK}: " | #OPTIONAL (default {BRIDGE}-{NICK}) | ||||||
|  | RemoteNickFormat="[{BRIDGE}] <{NICK}>  | ||||||
|  |  | ||||||
| #Nicks you want to ignore.  | #Nicks you want to ignore.  | ||||||
| #Messages from those users will not be sent to mattermost. | #Messages from those users will not be sent to other bridges. | ||||||
| #OPTIONAL | #OPTIONAL | ||||||
| IgnoreNicks="ircspammer1 ircspammer2" | IgnoreNicks="ircspammer1 ircspammer2" | ||||||
|  |  | ||||||
|  | ################################################################### | ||||||
|  | #XMPP section | ||||||
|  | ################################################################### | ||||||
|  | [XMPP] | ||||||
|  | #Enable enables this bridge | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | Enable=true | ||||||
|  |  | ||||||
|  | #xmpp server to connect to.  | ||||||
|  | #REQUIRED | ||||||
|  | Server="jabber.example.com:5222" | ||||||
|  |  | ||||||
|  | #Jid | ||||||
|  | #REQUIRED | ||||||
|  | Jid="user@example.com" | ||||||
|  |  | ||||||
|  | #Password | ||||||
|  | #REQUIRED | ||||||
|  | Password="yourpass" | ||||||
|  |  | ||||||
|  | #MUC | ||||||
|  | #REQUIRED | ||||||
|  | Muc="conference.jabber.example.com" | ||||||
|  |  | ||||||
|  | #Your nick in the rooms | ||||||
|  | #REQUIRED | ||||||
|  | Nick="xmppbot" | ||||||
|  |  | ||||||
|  |  | ||||||
| ################################################################### | ################################################################### | ||||||
| #mattermost section | #mattermost section | ||||||
| ################################################################### | ################################################################### | ||||||
|  |  | ||||||
| [mattermost] | [mattermost] | ||||||
|  | #Enable enables this bridge | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | Enable=true | ||||||
|  |  | ||||||
| #### Settings for webhook matterbridge. | #### Settings for webhook matterbridge. | ||||||
| #### These settings will not be used when using -plus switch which doesn't use  | #### These settings will not be used when using -plus switch which doesn't use  | ||||||
| #### webhooks. | #### webhooks. | ||||||
| @@ -83,7 +120,7 @@ Team="yourteam" | |||||||
| Login="yourlogin" | Login="yourlogin" | ||||||
| Password="yourpass" | Password="yourpass" | ||||||
|  |  | ||||||
| #Disable to make a http connection to your mattermost.  | #Enable this to make a http connection (instead of https) to your mattermost.  | ||||||
| #OPTIONAL (default false) | #OPTIONAL (default false) | ||||||
| NoTLS=false | NoTLS=false | ||||||
|  |  | ||||||
| @@ -98,18 +135,19 @@ SkipTLSVerify=true | |||||||
| #OPTIONAL (default false) | #OPTIONAL (default false) | ||||||
| ShowJoinPart=false | ShowJoinPart=false | ||||||
|  |  | ||||||
| #Whether to prefix messages from IRC to mattermost with the sender's nick.  | #Whether to prefix messages from other bridges to mattermost with the sender's nick.  | ||||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  | #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||||
| #mattermost server. If you set PrefixMessagesWithNick to true, each message  | #mattermost server. If you set PrefixMessagesWithNick to true, each message  | ||||||
| #from IRC to Mattermost will by default be prefixed by "irc-" + nick. You can,  | #from bridge to Mattermost will by default be prefixed by "bridge-" + nick. You can,  | ||||||
| #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  | #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  | ||||||
| #OPTIONAL (default false) | #OPTIONAL (default false) | ||||||
| PrefixMessagesWithNick=false | PrefixMessagesWithNick=false | ||||||
|  |  | ||||||
| #RemoteNickFormat defines how IRC users appear on Mattermost.  | #RemoteNickFormat defines how remote users appear on this bridge  | ||||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||||
| #OPTIONAL (default irc-NICK) | #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||||
| RemoteNickFormat="irc-{NICK}" | #OPTIONAL (default {BRIDGE}-{NICK}) | ||||||
|  | RemoteNickFormat="[{BRIDGE}] <{NICK}>  | ||||||
|  |  | ||||||
| #how to format the list of IRC nicks when displayed in mattermost.  | #how to format the list of IRC nicks when displayed in mattermost.  | ||||||
| #Possible options are "table" and "plain" | #Possible options are "table" and "plain" | ||||||
| @@ -119,7 +157,95 @@ NickFormatter=plain | |||||||
| #OPTIONAL (default 4) | #OPTIONAL (default 4) | ||||||
| NicksPerRow=4 | NicksPerRow=4 | ||||||
|  |  | ||||||
| #Nicks you want to ignore. Messages from those users will not be sent to IRC.  | #Nicks you want to ignore. Messages from those users will not be bridged. | ||||||
|  | #OPTIONAL  | ||||||
|  | IgnoreNicks="mmbot spammer2" | ||||||
|  |  | ||||||
|  | ################################################################### | ||||||
|  | #Gitter section | ||||||
|  | #Best to make a dedicated gitter account for the bot. | ||||||
|  | ################################################################### | ||||||
|  | [Gitter] | ||||||
|  | #Enable enables this bridge | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | Enable=true | ||||||
|  |  | ||||||
|  | #Token to connect with Gitter API | ||||||
|  | #You can get your token by going to https://developer.gitter.im/docs/welcome and SIGN IN | ||||||
|  | #REQUIRED | ||||||
|  | Token="Yourtokenhere" | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore. Messages of those users will not be bridged. | ||||||
|  | #OPTIONAL  | ||||||
|  | IgnoreNicks="spammer1 spammer2" | ||||||
|  |  | ||||||
|  | #RemoteNickFormat defines how remote users appear on this bridge  | ||||||
|  | #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||||
|  | #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||||
|  | #OPTIONAL (default {BRIDGE}-{NICK}) | ||||||
|  | RemoteNickFormat="[{BRIDGE}] <{NICK}>  | ||||||
|  |  | ||||||
|  | ################################################################### | ||||||
|  | #slack section | ||||||
|  | ################################################################### | ||||||
|  |  | ||||||
|  | [slack] | ||||||
|  | #Enable enables this bridge | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | Enable=true | ||||||
|  |  | ||||||
|  | #### Settings for webhook matterbridge. | ||||||
|  | #### These settings will not be used when useAPI is enabled | ||||||
|  |  | ||||||
|  | #Url is your incoming webhook url as specified in slack | ||||||
|  | #See account settings - integrations - incoming webhooks on slack | ||||||
|  | #REQUIRED (unless useAPI=true) | ||||||
|  | URL="https://hooks.slack.com/services/yourhook" | ||||||
|  |  | ||||||
|  | #Address to listen on for outgoing webhook requests from slack | ||||||
|  | #See account settings - integrations - outgoing webhooks on slack | ||||||
|  | #This setting will not be used when useAPI is eanbled | ||||||
|  | #webhooks | ||||||
|  | #REQUIRED (unless useAPI=true) | ||||||
|  | BindAddress="0.0.0.0:9999" | ||||||
|  |  | ||||||
|  | #Icon that will be showed in slack | ||||||
|  | #OPTIONAL | ||||||
|  | IconURL="http://youricon.png" | ||||||
|  |  | ||||||
|  | #### Settings for using slack API | ||||||
|  | #OPTIONAL | ||||||
|  | useAPI=false | ||||||
|  |  | ||||||
|  | #Token to connect with the Slack API | ||||||
|  | #REQUIRED (when useAPI=true) | ||||||
|  | Token="yourslacktoken" | ||||||
|  |  | ||||||
|  | #### Shared settings for webhooks and API | ||||||
|  |  | ||||||
|  | #Whether to prefix messages from other bridges to mattermost with the sender's nick.  | ||||||
|  | #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||||
|  | #slack server. If you set PrefixMessagesWithNick to true, each message  | ||||||
|  | #from bridge to Slack will by default be prefixed by "bridge-" + nick. You can,  | ||||||
|  | #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  | ||||||
|  | #OPTIONAL (default false) | ||||||
|  | PrefixMessagesWithNick=false | ||||||
|  |  | ||||||
|  | #RemoteNickFormat defines how remote users appear on this bridge  | ||||||
|  | #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||||
|  | #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||||
|  | #OPTIONAL (default {BRIDGE}-{NICK}) | ||||||
|  | RemoteNickFormat="[{BRIDGE}] <{NICK}>  | ||||||
|  |  | ||||||
|  | #how to format the list of IRC nicks when displayed in slack | ||||||
|  | #Possible options are "table" and "plain" | ||||||
|  | #OPTIONAL (default plain) | ||||||
|  | NickFormatter=plain | ||||||
|  | #How many nicks to list per row for formatters that support this.  | ||||||
|  | #OPTIONAL (default 4) | ||||||
|  | NicksPerRow=4 | ||||||
|  |  | ||||||
|  | #Nicks you want to ignore. Messages from those users will not be bridged. | ||||||
| #OPTIONAL  | #OPTIONAL  | ||||||
| IgnoreNicks="mmbot spammer2" | IgnoreNicks="mmbot spammer2" | ||||||
|  |  | ||||||
| @@ -130,14 +256,24 @@ IgnoreNicks="mmbot spammer2" | |||||||
| #The name is just an identifier for you. | #The name is just an identifier for you. | ||||||
| #REQUIRED (at least 1 channel) | #REQUIRED (at least 1 channel) | ||||||
| [Channel "channel1"]  | [Channel "channel1"]  | ||||||
| #Choose the IRC channel to send mattermost messages to. | #Choose the IRC channel to send messages to. | ||||||
| IRC="#off-topic" | IRC="#off-topic" | ||||||
| #Choose the mattermost channel to send IRC messages to. | #Choose the mattermost channel to messages to. | ||||||
| mattermost="off-topic" | mattermost="off-topic" | ||||||
|  | #Choose the xmpp channel to send messages to. | ||||||
|  | xmpp="off-topic" | ||||||
|  | #Choose the Gitter channel to send messages to. | ||||||
|  | #Gitter channels are named "user/repo" | ||||||
|  | gitter="42wim/matterbridge" | ||||||
|  | #Choose the slack channel to send messages to. | ||||||
|  | slack="general" | ||||||
|  |  | ||||||
| [Channel "testchannel"] | [Channel "testchannel"] | ||||||
| IRC="#testing" | IRC="#testing" | ||||||
| mattermost="testing" | mattermost="testing" | ||||||
|  | xmpp="testing" | ||||||
|  | gitter="user/repo" | ||||||
|  | slack="testing" | ||||||
|  |  | ||||||
| ################################################################### | ################################################################### | ||||||
| #general | #general | ||||||
| @@ -146,3 +282,6 @@ mattermost="testing" | |||||||
| #request your API key on https://github.com/giphy/GiphyAPI. This is a public beta key.  | #request your API key on https://github.com/giphy/GiphyAPI. This is a public beta key.  | ||||||
| #OPTIONAL | #OPTIONAL | ||||||
| GiphyApiKey="dc6zaTOxFJmzC" | GiphyApiKey="dc6zaTOxFJmzC" | ||||||
|  |  | ||||||
|  | #Enabling plus means you'll use the API version instead of the webhooks one | ||||||
|  | Plus=false | ||||||
|   | |||||||
| @@ -4,10 +4,11 @@ import ( | |||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/42wim/matterbridge/bridge" | 	"github.com/42wim/matterbridge/bridge" | ||||||
|  | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	log "github.com/Sirupsen/logrus" | 	log "github.com/Sirupsen/logrus" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var version = "0.5.0-beta2" | var version = "0.6.1" | ||||||
|  |  | ||||||
| func init() { | func init() { | ||||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||||
| @@ -17,7 +18,7 @@ func main() { | |||||||
| 	flagConfig := flag.String("conf", "matterbridge.conf", "config file") | 	flagConfig := flag.String("conf", "matterbridge.conf", "config file") | ||||||
| 	flagDebug := flag.Bool("debug", false, "enable debug") | 	flagDebug := flag.Bool("debug", false, "enable debug") | ||||||
| 	flagVersion := flag.Bool("version", false, "show version") | 	flagVersion := flag.Bool("version", false, "show version") | ||||||
| 	flagPlus := flag.Bool("plus", false, "running using API instead of webhooks") | 	flagPlus := flag.Bool("plus", false, "running using API instead of webhooks (deprecated, set Plus flag in [general] config)") | ||||||
| 	flag.Parse() | 	flag.Parse() | ||||||
| 	if *flagVersion { | 	if *flagVersion { | ||||||
| 		fmt.Println("version:", version) | 		fmt.Println("version:", version) | ||||||
| @@ -29,10 +30,12 @@ func main() { | |||||||
| 		log.SetLevel(log.DebugLevel) | 		log.SetLevel(log.DebugLevel) | ||||||
| 	} | 	} | ||||||
| 	fmt.Println("running version", version) | 	fmt.Println("running version", version) | ||||||
|  | 	cfg := config.NewConfig(*flagConfig) | ||||||
| 	if *flagPlus { | 	if *flagPlus { | ||||||
| 		bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "") | 		cfg.General.Plus = true | ||||||
| 	} else { | 	} | ||||||
| 		bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "legacy") | 	err := bridge.NewBridge(cfg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Debugf("starting bridge failed %#v", err) | ||||||
| 	} | 	} | ||||||
| 	select {} |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package matterclient | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
|  | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/cookiejar" | 	"net/http/cookiejar" | ||||||
| @@ -27,7 +28,7 @@ type Credentials struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| type Message struct { | type Message struct { | ||||||
| 	Raw      *model.Message | 	Raw      *model.WebSocketEvent | ||||||
| 	Post     *model.Post | 	Post     *model.Post | ||||||
| 	Team     string | 	Team     string | ||||||
| 	Channel  string | 	Channel  string | ||||||
| @@ -49,14 +50,16 @@ type MMClient struct { | |||||||
| 	Team        *Team | 	Team        *Team | ||||||
| 	OtherTeams  []*Team | 	OtherTeams  []*Team | ||||||
| 	Client      *model.Client | 	Client      *model.Client | ||||||
| 	WsClient    *websocket.Conn |  | ||||||
| 	WsQuit      bool |  | ||||||
| 	WsAway      bool |  | ||||||
| 	WsConnected bool |  | ||||||
| 	User        *model.User | 	User        *model.User | ||||||
| 	Users       map[string]*model.User | 	Users       map[string]*model.User | ||||||
| 	MessageChan chan *Message | 	MessageChan chan *Message | ||||||
| 	log         *log.Entry | 	log         *log.Entry | ||||||
|  | 	WsClient    *websocket.Conn | ||||||
|  | 	WsQuit      bool | ||||||
|  | 	WsAway      bool | ||||||
|  | 	WsConnected bool | ||||||
|  | 	WsSequence  int64 | ||||||
|  | 	WsPingChan  chan *model.WebSocketResponse | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(login, pass, team, server string) *MMClient { | func New(login, pass, team, server string) *MMClient { | ||||||
| @@ -151,7 +154,7 @@ func (m *MMClient) Login() error { | |||||||
| 	m.Client.SetTeamId(m.Team.Id) | 	m.Client.SetTeamId(m.Team.Id) | ||||||
|  |  | ||||||
| 	// setup websocket connection | 	// setup websocket connection | ||||||
| 	wsurl := wsScheme + m.Credentials.Server + "/api/v3/users/websocket" | 	wsurl := wsScheme + m.Credentials.Server + model.API_URL_SUFFIX + "/users/websocket" | ||||||
| 	header := http.Header{} | 	header := http.Header{} | ||||||
| 	header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) | 	header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) | ||||||
|  |  | ||||||
| @@ -169,6 +172,8 @@ func (m *MMClient) Login() error { | |||||||
| 	} | 	} | ||||||
| 	b.Reset() | 	b.Reset() | ||||||
|  |  | ||||||
|  | 	m.WsSequence = 1 | ||||||
|  | 	m.WsPingChan = make(chan *model.WebSocketResponse) | ||||||
| 	// only start to parse WS messages when login is completely done | 	// only start to parse WS messages when login is completely done | ||||||
| 	m.WsConnected = true | 	m.WsConnected = true | ||||||
|  |  | ||||||
| @@ -180,7 +185,6 @@ func (m *MMClient) Logout() error { | |||||||
| 	m.WsQuit = true | 	m.WsQuit = true | ||||||
| 	m.WsClient.Close() | 	m.WsClient.Close() | ||||||
| 	m.WsClient.UnderlyingConn().Close() | 	m.WsClient.UnderlyingConn().Close() | ||||||
| 	m.WsClient = nil |  | ||||||
| 	_, err := m.Client.Logout() | 	_, err := m.Client.Logout() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -190,42 +194,46 @@ func (m *MMClient) Logout() error { | |||||||
|  |  | ||||||
| func (m *MMClient) WsReceiver() { | func (m *MMClient) WsReceiver() { | ||||||
| 	for { | 	for { | ||||||
| 		var rmsg model.Message | 		var rawMsg json.RawMessage | ||||||
|  | 		var err error | ||||||
|  |  | ||||||
| 		if m.WsQuit { | 		if m.WsQuit { | ||||||
| 			m.log.Debug("exiting WsReceiver") | 			m.log.Debug("exiting WsReceiver") | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 		if err := m.WsClient.ReadJSON(&rmsg); err != nil { |  | ||||||
|  | 		if !m.WsConnected { | ||||||
|  | 			time.Sleep(time.Millisecond * 100) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if _, rawMsg, err = m.WsClient.ReadMessage(); err != nil { | ||||||
| 			m.log.Error("error:", err) | 			m.log.Error("error:", err) | ||||||
| 			// reconnect | 			// reconnect | ||||||
| 			m.Login() | 			m.Login() | ||||||
| 		} | 		} | ||||||
| 		// we're not fully logged in yet. |  | ||||||
| 		if !m.WsConnected { | 		var event model.WebSocketEvent | ||||||
|  | 		if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { | ||||||
|  | 			m.log.Debugf("WsReceiver: %#v", event) | ||||||
|  | 			msg := &Message{Raw: &event, Team: m.Credentials.Team} | ||||||
|  | 			m.parseMessage(msg) | ||||||
|  | 			m.MessageChan <- msg | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if rmsg.Action == "ping" { |  | ||||||
| 			m.handleWsPing() | 		var response model.WebSocketResponse | ||||||
|  | 		if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { | ||||||
|  | 			m.log.Debugf("WsReceiver: %#v", response) | ||||||
|  | 			m.parseResponse(response) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		msg := &Message{Raw: &rmsg, Team: m.Credentials.Team} |  | ||||||
| 		m.parseMessage(msg) |  | ||||||
| 		m.MessageChan <- msg |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MMClient) handleWsPing() { |  | ||||||
| 	m.log.Debug("Ws PING") |  | ||||||
| 	if !m.WsQuit && !m.WsAway { |  | ||||||
| 		m.log.Debug("Ws PONG") |  | ||||||
| 		m.WsClient.WriteMessage(websocket.PongMessage, []byte{}) |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) parseMessage(rmsg *Message) { | func (m *MMClient) parseMessage(rmsg *Message) { | ||||||
| 	switch rmsg.Raw.Action { | 	switch rmsg.Raw.Event { | ||||||
| 	case model.ACTION_POSTED: | 	case model.WEBSOCKET_EVENT_POSTED: | ||||||
| 		m.parseActionPost(rmsg) | 		m.parseActionPost(rmsg) | ||||||
| 		/* | 		/* | ||||||
| 			case model.ACTION_USER_REMOVED: | 			case model.ACTION_USER_REMOVED: | ||||||
| @@ -236,8 +244,17 @@ func (m *MMClient) parseMessage(rmsg *Message) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (m *MMClient) parseResponse(rmsg model.WebSocketResponse) { | ||||||
|  | 	if rmsg.Data != nil { | ||||||
|  | 		// ping reply | ||||||
|  | 		if rmsg.Data["text"].(string) == "pong" { | ||||||
|  | 			m.WsPingChan <- &rmsg | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (m *MMClient) parseActionPost(rmsg *Message) { | func (m *MMClient) parseActionPost(rmsg *Message) { | ||||||
| 	data := model.PostFromJson(strings.NewReader(rmsg.Raw.Props["post"])) | 	data := model.PostFromJson(strings.NewReader(rmsg.Raw.Data["post"].(string))) | ||||||
| 	// we don't have the user, refresh the userlist | 	// we don't have the user, refresh the userlist | ||||||
| 	if m.GetUser(data.UserId) == nil { | 	if m.GetUser(data.UserId) == nil { | ||||||
| 		m.UpdateUsers() | 		m.UpdateUsers() | ||||||
| @@ -246,7 +263,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) { | |||||||
| 	rmsg.Channel = m.GetChannelName(data.ChannelId) | 	rmsg.Channel = m.GetChannelName(data.ChannelId) | ||||||
| 	rmsg.Team = m.GetTeamName(rmsg.Raw.TeamId) | 	rmsg.Team = m.GetTeamName(rmsg.Raw.TeamId) | ||||||
| 	// direct message | 	// direct message | ||||||
| 	if data.Type == "D" { | 	if rmsg.Raw.Data["channel_type"] == "D" { | ||||||
| 		rmsg.Channel = m.GetUser(data.UserId).Username | 		rmsg.Channel = m.GetUser(data.UserId).Username | ||||||
| 	} | 	} | ||||||
| 	rmsg.Text = data.Message | 	rmsg.Text = data.Message | ||||||
| @@ -255,7 +272,10 @@ func (m *MMClient) parseActionPost(rmsg *Message) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) UpdateUsers() error { | func (m *MMClient) UpdateUsers() error { | ||||||
| 	mmusers, _ := m.Client.GetProfilesForDirectMessageList(m.Team.Id) | 	mmusers, err := m.Client.GetProfilesForDirectMessageList(m.Team.Id) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.New(err.DetailedError) | ||||||
|  | 	} | ||||||
| 	m.Lock() | 	m.Lock() | ||||||
| 	m.Users = mmusers.Data.(map[string]*model.User) | 	m.Users = mmusers.Data.(map[string]*model.User) | ||||||
| 	m.Unlock() | 	m.Unlock() | ||||||
| @@ -263,8 +283,14 @@ func (m *MMClient) UpdateUsers() error { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (m *MMClient) UpdateChannels() error { | func (m *MMClient) UpdateChannels() error { | ||||||
| 	mmchannels, _ := m.Client.GetChannels("") | 	mmchannels, err := m.Client.GetChannels("") | ||||||
| 	mmchannels2, _ := m.Client.GetMoreChannels("") | 	if err != nil { | ||||||
|  | 		return errors.New(err.DetailedError) | ||||||
|  | 	} | ||||||
|  | 	mmchannels2, err := m.Client.GetMoreChannels("") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return errors.New(err.DetailedError) | ||||||
|  | 	} | ||||||
| 	m.Lock() | 	m.Lock() | ||||||
| 	m.Team.Channels = mmchannels.Data.(*model.ChannelList) | 	m.Team.Channels = mmchannels.Data.(*model.ChannelList) | ||||||
| 	m.Team.MoreChannels = mmchannels2.Data.(*model.ChannelList) | 	m.Team.MoreChannels = mmchannels2.Data.(*model.ChannelList) | ||||||
| @@ -396,7 +422,7 @@ func (m *MMClient) UpdateChannelHeader(channelId string, header string) { | |||||||
|  |  | ||||||
| func (m *MMClient) UpdateLastViewed(channelId string) { | func (m *MMClient) UpdateLastViewed(channelId string) { | ||||||
| 	m.log.Debugf("posting lastview %#v", channelId) | 	m.log.Debugf("posting lastview %#v", channelId) | ||||||
| 	_, err := m.Client.UpdateLastViewedAt(channelId) | 	_, err := m.Client.UpdateLastViewedAt(channelId, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		m.log.Error(err) | 		m.log.Error(err) | ||||||
| 	} | 	} | ||||||
| @@ -498,6 +524,7 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string { | |||||||
| 	var channels []*model.Channel | 	var channels []*model.Channel | ||||||
| 	for _, t := range m.OtherTeams { | 	for _, t := range m.OtherTeams { | ||||||
| 		channels = append(channels, t.Channels.Channels...) | 		channels = append(channels, t.Channels.Channels...) | ||||||
|  | 		channels = append(channels, t.MoreChannels.Channels...) | ||||||
| 		for _, c := range channels { | 		for _, c := range channels { | ||||||
| 			if c.Id == channelId { | 			if c.Id == channelId { | ||||||
| 				return t.Id | 				return t.Id | ||||||
| @@ -534,6 +561,42 @@ func (m *MMClient) GetUser(userId string) *model.User { | |||||||
| 	return m.Users[userId] | 	return m.Users[userId] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (m *MMClient) GetStatus(userId string) string { | ||||||
|  | 	res, err := m.Client.GetStatuses() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	status := res.Data.(map[string]string) | ||||||
|  | 	if status[userId] == model.STATUS_AWAY { | ||||||
|  | 		return "away" | ||||||
|  | 	} | ||||||
|  | 	if status[userId] == model.STATUS_ONLINE { | ||||||
|  | 		return "online" | ||||||
|  | 	} | ||||||
|  | 	return "offline" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *MMClient) StatusLoop() { | ||||||
|  | 	for { | ||||||
|  | 		if m.WsQuit { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if m.WsConnected { | ||||||
|  | 			m.log.Debug("WS PING") | ||||||
|  | 			m.sendWSRequest("ping", nil) | ||||||
|  | 			select { | ||||||
|  | 			case <-m.WsPingChan: | ||||||
|  | 				m.log.Debug("WS PONG received") | ||||||
|  | 			case <-time.After(time.Second * 5): | ||||||
|  | 				m.Logout() | ||||||
|  | 				m.WsQuit = false | ||||||
|  | 				m.Login() | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		time.Sleep(time.Second * 60) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| // initialize user and teams | // initialize user and teams | ||||||
| func (m *MMClient) initUser() error { | func (m *MMClient) initUser() error { | ||||||
| 	m.Lock() | 	m.Lock() | ||||||
| @@ -568,3 +631,14 @@ func (m *MMClient) initUser() error { | |||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (m *MMClient) sendWSRequest(action string, data map[string]interface{}) error { | ||||||
|  | 	req := &model.WebSocketRequest{} | ||||||
|  | 	req.Seq = m.WsSequence | ||||||
|  | 	req.Action = action | ||||||
|  | 	req.Data = data | ||||||
|  | 	m.WsSequence++ | ||||||
|  | 	m.log.Debugf("sendWsRequest %#v", req) | ||||||
|  | 	m.WsClient.WriteJSON(req) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -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"` | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								vendor/github.com/mattermost/platform/einterfaces/account_migration.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/mattermost/platform/einterfaces/account_migration.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||||
|  | // See License.txt for license information. | ||||||
|  |  | ||||||
|  | package einterfaces | ||||||
|  |  | ||||||
|  | import "github.com/mattermost/platform/model" | ||||||
|  |  | ||||||
|  | type AccountMigrationInterface interface { | ||||||
|  | 	MigrateToLdap(fromAuthService string, forignUserFieldNameToMatch string) *model.AppError | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var theAccountMigrationInterface AccountMigrationInterface | ||||||
|  |  | ||||||
|  | func RegisterAccountMigrationInterface(newInterface AccountMigrationInterface) { | ||||||
|  | 	theAccountMigrationInterface = newInterface | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetAccountMigrationInterface() AccountMigrationInterface { | ||||||
|  | 	return theAccountMigrationInterface | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								vendor/github.com/mattermost/platform/einterfaces/cluster.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								vendor/github.com/mattermost/platform/einterfaces/cluster.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||||
|  | // See License.txt for license information. | ||||||
|  |  | ||||||
|  | package einterfaces | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/mattermost/platform/model" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ClusterInterface interface { | ||||||
|  | 	StartInterNodeCommunication() | ||||||
|  | 	StopInterNodeCommunication() | ||||||
|  | 	GetClusterInfos() []*model.ClusterInfo | ||||||
|  | 	RemoveAllSessionsForUserId(userId string) | ||||||
|  | 	InvalidateCacheForUser(userId string) | ||||||
|  | 	InvalidateCacheForChannel(channelId string) | ||||||
|  | 	Publish(event *model.WebSocketEvent) | ||||||
|  | 	UpdateStatus(status *model.Status) | ||||||
|  | 	GetLogs() ([]string, *model.AppError) | ||||||
|  | 	GetClusterId() string | ||||||
|  | 	ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var theClusterInterface ClusterInterface | ||||||
|  |  | ||||||
|  | func RegisterClusterInterface(newInterface ClusterInterface) { | ||||||
|  | 	theClusterInterface = newInterface | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetClusterInterface() ClusterInterface { | ||||||
|  | 	return theClusterInterface | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/einterfaces/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/einterfaces/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -15,6 +15,9 @@ type LdapInterface interface { | |||||||
| 	ValidateFilter(filter string) *model.AppError | 	ValidateFilter(filter string) *model.AppError | ||||||
| 	Syncronize() *model.AppError | 	Syncronize() *model.AppError | ||||||
| 	StartLdapSyncJob() | 	StartLdapSyncJob() | ||||||
|  | 	SyncNow() | ||||||
|  | 	RunTest() *model.AppError | ||||||
|  | 	GetAllLdapUsers() ([]*model.User, *model.AppError) | ||||||
| } | } | ||||||
|  |  | ||||||
| var theLdapInterface LdapInterface | var theLdapInterface LdapInterface | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								vendor/github.com/mattermost/platform/model/access.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/mattermost/platform/model/access.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -15,10 +15,12 @@ const ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type AccessData struct { | type AccessData struct { | ||||||
| 	AuthCode     string `json:"auth_code"` | 	ClientId     string `json:"client_id"` | ||||||
|  | 	UserId       string `json:"user_id"` | ||||||
| 	Token        string `json:"token"` | 	Token        string `json:"token"` | ||||||
| 	RefreshToken string `json:"refresh_token"` | 	RefreshToken string `json:"refresh_token"` | ||||||
| 	RedirectUri  string `json:"redirect_uri"` | 	RedirectUri  string `json:"redirect_uri"` | ||||||
|  | 	ExpiresAt    int64  `json:"expires_at"` | ||||||
| } | } | ||||||
|  |  | ||||||
| type AccessResponse struct { | type AccessResponse struct { | ||||||
| @@ -33,8 +35,12 @@ type AccessResponse struct { | |||||||
| // correctly. | // correctly. | ||||||
| func (ad *AccessData) IsValid() *AppError { | func (ad *AccessData) IsValid() *AppError { | ||||||
|  |  | ||||||
| 	if len(ad.AuthCode) == 0 || len(ad.AuthCode) > 128 { | 	if len(ad.ClientId) == 0 || len(ad.ClientId) > 26 { | ||||||
| 		return NewLocAppError("AccessData.IsValid", "model.access.is_valid.auth_code.app_error", nil, "") | 		return NewLocAppError("AccessData.IsValid", "model.access.is_valid.client_id.app_error", nil, "") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(ad.UserId) == 0 || len(ad.UserId) > 26 { | ||||||
|  | 		return NewLocAppError("AccessData.IsValid", "model.access.is_valid.user_id.app_error", nil, "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(ad.Token) != 26 { | 	if len(ad.Token) != 26 { | ||||||
| @@ -52,6 +58,19 @@ func (ad *AccessData) IsValid() *AppError { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (me *AccessData) IsExpired() bool { | ||||||
|  |  | ||||||
|  | 	if me.ExpiresAt <= 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if GetMillis() > me.ExpiresAt { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
| func (ad *AccessData) ToJson() string { | func (ad *AccessData) ToJson() string { | ||||||
| 	b, err := json.Marshal(ad) | 	b, err := json.Marshal(ad) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								vendor/github.com/mattermost/platform/model/authorize.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/mattermost/platform/model/authorize.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -11,6 +11,7 @@ import ( | |||||||
| const ( | const ( | ||||||
| 	AUTHCODE_EXPIRE_TIME   = 60 * 10 // 10 minutes | 	AUTHCODE_EXPIRE_TIME   = 60 * 10 // 10 minutes | ||||||
| 	AUTHCODE_RESPONSE_TYPE = "code" | 	AUTHCODE_RESPONSE_TYPE = "code" | ||||||
|  | 	DEFAULT_SCOPE          = "user" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AuthData struct { | type AuthData struct { | ||||||
| @@ -71,6 +72,10 @@ func (ad *AuthData) PreSave() { | |||||||
| 	if ad.CreateAt == 0 { | 	if ad.CreateAt == 0 { | ||||||
| 		ad.CreateAt = GetMillis() | 		ad.CreateAt = GetMillis() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if len(ad.Scope) == 0 { | ||||||
|  | 		ad.Scope = DEFAULT_SCOPE | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (ad *AuthData) ToJson() string { | func (ad *AuthData) ToJson() string { | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/model/channel.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/channel.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -124,9 +124,6 @@ func (o *Channel) ExtraUpdated() { | |||||||
| 	o.ExtraUpdateAt = GetMillis() | 	o.ExtraUpdateAt = GetMillis() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (o *Channel) PreExport() { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func GetDMNameFromIds(userId1, userId2 string) string { | func GetDMNameFromIds(userId1, userId2 string) string { | ||||||
| 	if userId1 > userId2 { | 	if userId1 > userId2 { | ||||||
| 		return userId2 + "__" + userId1 | 		return userId2 + "__" + userId1 | ||||||
|   | |||||||
							
								
								
									
										260
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										260
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -20,6 +20,7 @@ import ( | |||||||
| const ( | const ( | ||||||
| 	HEADER_REQUEST_ID         = "X-Request-ID" | 	HEADER_REQUEST_ID         = "X-Request-ID" | ||||||
| 	HEADER_VERSION_ID         = "X-Version-ID" | 	HEADER_VERSION_ID         = "X-Version-ID" | ||||||
|  | 	HEADER_CLUSTER_ID         = "X-Cluster-ID" | ||||||
| 	HEADER_ETAG_SERVER        = "ETag" | 	HEADER_ETAG_SERVER        = "ETag" | ||||||
| 	HEADER_ETAG_CLIENT        = "If-None-Match" | 	HEADER_ETAG_CLIENT        = "If-None-Match" | ||||||
| 	HEADER_FORWARDED          = "X-Forwarded-For" | 	HEADER_FORWARDED          = "X-Forwarded-For" | ||||||
| @@ -32,6 +33,9 @@ const ( | |||||||
| 	HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" | 	HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" | ||||||
| 	STATUS                    = "status" | 	STATUS                    = "status" | ||||||
| 	STATUS_OK                 = "OK" | 	STATUS_OK                 = "OK" | ||||||
|  | 	STATUS_FAIL               = "FAIL" | ||||||
|  |  | ||||||
|  | 	CLIENT_DIR = "webapp/dist" | ||||||
|  |  | ||||||
| 	API_URL_SUFFIX_V1 = "/api/v1" | 	API_URL_SUFFIX_V1 = "/api/v1" | ||||||
| 	API_URL_SUFFIX_V3 = "/api/v3" | 	API_URL_SUFFIX_V3 = "/api/v3" | ||||||
| @@ -276,6 +280,9 @@ func (c *Client) GetPing() (map[string]string, *AppError) { | |||||||
|  |  | ||||||
| // Team Routes Section | // Team Routes Section | ||||||
|  |  | ||||||
|  | // SignupTeam sends an email with a team sign-up link to the provided address if email | ||||||
|  | // verification is enabled, otherwise it returns a map with a "follow_link" entry | ||||||
|  | // containing the team sign-up link. | ||||||
| func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) { | func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) { | ||||||
| 	m := make(map[string]string) | 	m := make(map[string]string) | ||||||
| 	m["email"] = email | 	m["email"] = email | ||||||
| @@ -289,6 +296,8 @@ func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppErro | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // CreateTeamFromSignup creates a team based on the provided TeamSignup struct. On success | ||||||
|  | // it returns the TeamSignup struct. | ||||||
| func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) { | func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil { | 	if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -299,6 +308,8 @@ func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppErro | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // CreateTeam creates a team based on the provided Team struct. On success it returns | ||||||
|  | // the Team struct with the Id, CreateAt and other server-decided fields populated. | ||||||
| func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { | func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil { | 	if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -309,6 +320,7 @@ func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetAllTeams returns a map of all teams using team ids as the key. | ||||||
| func (c *Client) GetAllTeams() (*Result, *AppError) { | func (c *Client) GetAllTeams() (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/teams/all", "", ""); err != nil { | 	if r, err := c.DoApiGet("/teams/all", "", ""); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -319,6 +331,8 @@ func (c *Client) GetAllTeams() (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetAllTeamListings returns a map of all teams that are available to join | ||||||
|  | // using team ids as the key. Must be authenticated. | ||||||
| func (c *Client) GetAllTeamListings() (*Result, *AppError) { | func (c *Client) GetAllTeamListings() (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/teams/all_team_listings", "", ""); err != nil { | 	if r, err := c.DoApiGet("/teams/all_team_listings", "", ""); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -329,6 +343,8 @@ func (c *Client) GetAllTeamListings() (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // FindTeamByName returns the strings "true" or "false" depending on if a team | ||||||
|  | // with the provided name was found. | ||||||
| func (c *Client) FindTeamByName(name string) (*Result, *AppError) { | func (c *Client) FindTeamByName(name string) (*Result, *AppError) { | ||||||
| 	m := make(map[string]string) | 	m := make(map[string]string) | ||||||
| 	m["name"] = name | 	m["name"] = name | ||||||
| @@ -365,6 +381,8 @@ func (c *Client) AddUserToTeam(teamId string, userId string) (*Result, *AppError | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // AddUserToTeamFromInvite adds a user to a team based off data provided in an invite link. | ||||||
|  | // Either hash and dataToHash are required or inviteId is required. | ||||||
| func (c *Client) AddUserToTeamFromInvite(hash, dataToHash, inviteId string) (*Result, *AppError) { | func (c *Client) AddUserToTeamFromInvite(hash, dataToHash, inviteId string) (*Result, *AppError) { | ||||||
| 	data := make(map[string]string) | 	data := make(map[string]string) | ||||||
| 	data["hash"] = hash | 	data["hash"] = hash | ||||||
| @@ -409,6 +427,9 @@ func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // UpdateTeam updates a team based on the changes in the provided team struct. On success | ||||||
|  | // it returns a sanitized version of the updated team. Must be authenticated as a team admin | ||||||
|  | // for that team or a system admin. | ||||||
| func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { | func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/update", team.ToJson()); err != nil { | 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/update", team.ToJson()); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -419,6 +440,9 @@ func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // User Routes Section | ||||||
|  |  | ||||||
|  | // CreateUser creates a user in the system based on the provided user struct. | ||||||
| func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { | func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil { | 	if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -429,6 +453,8 @@ func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // CreateUserWithInvite creates a user based on the provided user struct. Either the hash and | ||||||
|  | // data strings or the inviteId is required from the invite. | ||||||
| func (c *Client) CreateUserWithInvite(user *User, hash string, data string, inviteId string) (*Result, *AppError) { | func (c *Client) CreateUserWithInvite(user *User, hash string, data string, inviteId string) (*Result, *AppError) { | ||||||
|  |  | ||||||
| 	url := "/users/create?d=" + url.QueryEscape(data) + "&h=" + url.QueryEscape(hash) + "&iid=" + url.QueryEscape(inviteId) | 	url := "/users/create?d=" + url.QueryEscape(data) + "&h=" + url.QueryEscape(hash) + "&iid=" + url.QueryEscape(inviteId) | ||||||
| @@ -452,6 +478,7 @@ func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Re | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetUser returns a user based on a provided user id string. Must be authenticated. | ||||||
| func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { | func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/users/"+id+"/get", "", etag); err != nil { | 	if r, err := c.DoApiGet("/users/"+id+"/get", "", etag); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -462,6 +489,7 @@ func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetMe returns the current user. | ||||||
| func (c *Client) GetMe(etag string) (*Result, *AppError) { | func (c *Client) GetMe(etag string) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/users/me", "", etag); err != nil { | 	if r, err := c.DoApiGet("/users/me", "", etag); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -472,6 +500,8 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetProfilesForDirectMessageList returns a map of users for a team that can be direct | ||||||
|  | // messaged, using user id as the key. Must be authenticated. | ||||||
| func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppError) { | func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil { | 	if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -482,6 +512,8 @@ func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppEr | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetProfiles returns a map of users for a team using user id as the key. Must | ||||||
|  | // be authenticated. | ||||||
| func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { | func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil { | 	if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -492,6 +524,8 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetDirectProfiles gets a map of users that are currently shown in the sidebar, | ||||||
|  | // using user id as the key. Must be authenticated. | ||||||
| func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { | func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil { | 	if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -502,6 +536,7 @@ func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // LoginById authenticates a user by user id and password. | ||||||
| func (c *Client) LoginById(id string, password string) (*Result, *AppError) { | func (c *Client) LoginById(id string, password string) (*Result, *AppError) { | ||||||
| 	m := make(map[string]string) | 	m := make(map[string]string) | ||||||
| 	m["id"] = id | 	m["id"] = id | ||||||
| @@ -509,6 +544,8 @@ func (c *Client) LoginById(id string, password string) (*Result, *AppError) { | |||||||
| 	return c.login(m) | 	return c.login(m) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Login authenticates a user by login id, which can be username, email or some sort | ||||||
|  | // of SSO identifier based on configuration, and a password. | ||||||
| func (c *Client) Login(loginId string, password string) (*Result, *AppError) { | func (c *Client) Login(loginId string, password string) (*Result, *AppError) { | ||||||
| 	m := make(map[string]string) | 	m := make(map[string]string) | ||||||
| 	m["login_id"] = loginId | 	m["login_id"] = loginId | ||||||
| @@ -516,6 +553,7 @@ func (c *Client) Login(loginId string, password string) (*Result, *AppError) { | |||||||
| 	return c.login(m) | 	return c.login(m) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // LoginByLdap authenticates a user by LDAP id and password. | ||||||
| func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppError) { | func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppError) { | ||||||
| 	m := make(map[string]string) | 	m := make(map[string]string) | ||||||
| 	m["login_id"] = loginId | 	m["login_id"] = loginId | ||||||
| @@ -524,6 +562,9 @@ func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppErro | |||||||
| 	return c.login(m) | 	return c.login(m) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // LoginWithDevice authenticates a user by login id (username, email or some sort | ||||||
|  | // of SSO identifier based on configuration), password and attaches a device id to | ||||||
|  | // the session. | ||||||
| func (c *Client) LoginWithDevice(loginId string, password string, deviceId string) (*Result, *AppError) { | func (c *Client) LoginWithDevice(loginId string, password string, deviceId string) (*Result, *AppError) { | ||||||
| 	m := make(map[string]string) | 	m := make(map[string]string) | ||||||
| 	m["login_id"] = loginId | 	m["login_id"] = loginId | ||||||
| @@ -550,6 +591,7 @@ func (c *Client) login(m map[string]string) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Logout terminates the current user's session. | ||||||
| func (c *Client) Logout() (*Result, *AppError) { | func (c *Client) Logout() (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiPost("/users/logout", ""); err != nil { | 	if r, err := c.DoApiPost("/users/logout", ""); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -564,6 +606,9 @@ func (c *Client) Logout() (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // CheckMfa returns a map with key "mfa_required" with the string value "true" or "false", | ||||||
|  | // indicating whether MFA is required to log the user in, based on a provided login id | ||||||
|  | // (username, email or some sort of SSO identifier based on configuration). | ||||||
| func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { | func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { | ||||||
| 	m := make(map[string]string) | 	m := make(map[string]string) | ||||||
| 	m["login_id"] = loginId | 	m["login_id"] = loginId | ||||||
| @@ -577,6 +622,8 @@ func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GenerateMfaQrCode returns a QR code imagem containing the secret, to be scanned | ||||||
|  | // by a multi-factor authentication mobile application. Must be authenticated. | ||||||
| func (c *Client) GenerateMfaQrCode() (*Result, *AppError) { | func (c *Client) GenerateMfaQrCode() (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/users/generate_mfa_qr", "", ""); err != nil { | 	if r, err := c.DoApiGet("/users/generate_mfa_qr", "", ""); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -587,6 +634,9 @@ func (c *Client) GenerateMfaQrCode() (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // UpdateMfa activates multi-factor authenticates for the current user if activate | ||||||
|  | // is true and a valid token is provided. If activate is false, then token is not | ||||||
|  | // required and multi-factor authentication is disabled for the current user. | ||||||
| func (c *Client) UpdateMfa(activate bool, token string) (*Result, *AppError) { | func (c *Client) UpdateMfa(activate bool, token string) (*Result, *AppError) { | ||||||
| 	m := make(map[string]interface{}) | 	m := make(map[string]interface{}) | ||||||
| 	m["activate"] = activate | 	m["activate"] = activate | ||||||
| @@ -761,6 +811,26 @@ func (c *Client) GetLogs() (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *Client) GetClusterStatus() ([]*ClusterInfo, *AppError) { | ||||||
|  | 	if r, err := c.DoApiGet("/admin/cluster_status", "", ""); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return ClusterInfosFromJson(r.Body), nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetRecentlyActiveUsers returns a map of users including lastActivityAt using user id as the key | ||||||
|  | func (c *Client) GetRecentlyActiveUsers(teamId string) (*Result, *AppError) { | ||||||
|  | 	if r, err := c.DoApiGet("/admin/recently_active_users/"+teamId, "", ""); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||||
|  | 			r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *Client) GetAllAudits() (*Result, *AppError) { | func (c *Client) GetAllAudits() (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil { | 	if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -828,6 +898,19 @@ func (c *Client) TestEmail(config *Config) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TestLdap will run a connection test on the current LDAP settings. | ||||||
|  | // It will return the standard OK response if settings work. Otherwise | ||||||
|  | // it will return an appropriate error. | ||||||
|  | func (c *Client) TestLdap(config *Config) (*Result, *AppError) { | ||||||
|  | 	if r, err := c.DoApiPost("/admin/ldap_test", config.ToJson()); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||||
|  | 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *Client) GetComplianceReports() (*Result, *AppError) { | func (c *Client) GetComplianceReports() (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/admin/compliance_reports", "", ""); err != nil { | 	if r, err := c.DoApiGet("/admin/compliance_reports", "", ""); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -1068,8 +1151,13 @@ func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) { | // UpdateLastViewedAt will mark a channel as read. | ||||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/update_last_viewed_at", ""); err != nil { | // The channelId indicates the channel to mark as read. If active is true, push notifications | ||||||
|  | // will be cleared if there are unread messages. The default for active is true. | ||||||
|  | func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *AppError) { | ||||||
|  | 	data := make(map[string]interface{}) | ||||||
|  | 	data["active"] = active | ||||||
|  | 	if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/update_last_viewed_at", StringInterfaceToJson(data)); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} else { | 	} else { | ||||||
| 		defer closeBody(r) | 		defer closeBody(r) | ||||||
| @@ -1181,6 +1269,18 @@ func (c *Client) SearchPosts(terms string, isOrSearch bool) (*Result, *AppError) | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetFlaggedPosts will return a post list of posts that have been flagged by the user. | ||||||
|  | // The page is set by the integer parameters offset and limit. | ||||||
|  | func (c *Client) GetFlaggedPosts(offset int, limit int) (*Result, *AppError) { | ||||||
|  | 	if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/flagged/%v/%v", offset, limit), "", ""); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||||
|  | 			r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) { | func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) { | ||||||
| 	return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType) | 	return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType) | ||||||
| } | } | ||||||
| @@ -1368,8 +1468,24 @@ func (c *Client) AdminResetPassword(userId, newPassword string) (*Result, *AppEr | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *Client) GetStatuses(data []string) (*Result, *AppError) { | // GetStatuses returns a map of string statuses using user id as the key | ||||||
| 	if r, err := c.DoApiPost("/users/status", ArrayToJson(data)); err != nil { | func (c *Client) GetStatuses() (*Result, *AppError) { | ||||||
|  | 	if r, err := c.DoApiGet("/users/status", "", ""); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||||
|  | 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SetActiveChannel sets the the channel id the user is currently viewing. | ||||||
|  | // The channelId key is required but the value can be blank. Returns standard | ||||||
|  | // response. | ||||||
|  | func (c *Client) SetActiveChannel(channelId string) (*Result, *AppError) { | ||||||
|  | 	data := map[string]string{} | ||||||
|  | 	data["channel_id"] = channelId | ||||||
|  | 	if r, err := c.DoApiPost("/users/status/set_active_channel", MapToJson(data)); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} else { | 	} else { | ||||||
| 		defer closeBody(r) | 		defer closeBody(r) | ||||||
| @@ -1398,6 +1514,8 @@ func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // RegisterApp creates a new OAuth2 app to be used with the OAuth2 Provider. On success | ||||||
|  | // it returns the created app. Must be authenticated as a user. | ||||||
| func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { | func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil { | 	if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -1408,6 +1526,9 @@ func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // AllowOAuth allows a new session by an OAuth2 App. On success | ||||||
|  | // it returns the url to be redirected back to the app which initiated the oauth2 flow. | ||||||
|  | // Must be authenticated as a user. | ||||||
| func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (*Result, *AppError) { | func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiGet("/oauth/allow?response_type="+rspType+"&client_id="+clientId+"&redirect_uri="+url.QueryEscape(redirect)+"&scope="+scope+"&state="+url.QueryEscape(state), "", ""); err != nil { | 	if r, err := c.DoApiGet("/oauth/allow?response_type="+rspType+"&client_id="+clientId+"&redirect_uri="+url.QueryEscape(redirect)+"&scope="+scope+"&state="+url.QueryEscape(state), "", ""); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -1418,8 +1539,83 @@ func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (* | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetOAuthAppsByUser returns the OAuth2 Apps registered by the user. On success | ||||||
|  | // it returns a list of OAuth2 Apps from the same user or all the registered apps if the user | ||||||
|  | // is a System Administrator. Must be authenticated as a user. | ||||||
|  | func (c *Client) GetOAuthAppsByUser() (*Result, *AppError) { | ||||||
|  | 	if r, err := c.DoApiGet("/oauth/list", "", ""); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||||
|  | 			r.Header.Get(HEADER_ETAG_SERVER), OAuthAppListFromJson(r.Body)}, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetOAuthAppInfo lookup an OAuth2 App using the client_id. On success | ||||||
|  | // it returns a Sanitized OAuth2 App. Must be authenticated as a user. | ||||||
|  | func (c *Client) GetOAuthAppInfo(clientId string) (*Result, *AppError) { | ||||||
|  | 	if r, err := c.DoApiGet("/oauth/app/"+clientId, "", ""); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||||
|  | 			r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DeleteOAuthApp deletes an OAuth2 app, the app must be deleted by the same user who created it or | ||||||
|  | // a System Administrator. On success returs Status OK. Must be authenticated as a user. | ||||||
|  | func (c *Client) DeleteOAuthApp(id string) (*Result, *AppError) { | ||||||
|  | 	data := make(map[string]string) | ||||||
|  | 	data["id"] = id | ||||||
|  | 	if r, err := c.DoApiPost("/oauth/delete", MapToJson(data)); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||||
|  | 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetOAuthAuthorizedApps returns the OAuth2 Apps authorized by the user. On success | ||||||
|  | // it returns a list of sanitized OAuth2 Authorized Apps by the user. | ||||||
|  | func (c *Client) GetOAuthAuthorizedApps() (*Result, *AppError) { | ||||||
|  | 	if r, err := c.DoApiGet("/oauth/authorized", "", ""); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||||
|  | 			r.Header.Get(HEADER_ETAG_SERVER), OAuthAppListFromJson(r.Body)}, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // OAuthDeauthorizeApp deauthorize a user an OAuth 2.0 app. On success | ||||||
|  | // it returns status OK or an AppError on fail. | ||||||
|  | func (c *Client) OAuthDeauthorizeApp(clientId string) *AppError { | ||||||
|  | 	if r, err := c.DoApiPost("/oauth/"+clientId+"/deauthorize", ""); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RegenerateOAuthAppSecret generates a new OAuth App Client Secret. On success | ||||||
|  | // it returns an OAuth2 App. Must be authenticated as a user and the same user who | ||||||
|  | // registered the app or a System Admin. | ||||||
|  | func (c *Client) RegenerateOAuthAppSecret(clientId string) (*Result, *AppError) { | ||||||
|  | 	if r, err := c.DoApiPost("/oauth/"+clientId+"/regen_secret", ""); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||||
|  | 			r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) { | func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiPost("/oauth/access_token", data.Encode()); err != nil { | 	if r, err := c.DoPost("/oauth/access_token", data.Encode(), "application/x-www-form-urlencoded"); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} else { | 	} else { | ||||||
| 		defer closeBody(r) | 		defer closeBody(r) | ||||||
| @@ -1509,6 +1705,16 @@ func (c *Client) GetPreferenceCategory(category string) (*Result, *AppError) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // DeletePreferences deletes a list of preferences owned by the current user. If successful, | ||||||
|  | // it will return status=ok. Otherwise, an error will be returned. | ||||||
|  | func (c *Client) DeletePreferences(preferences *Preferences) (bool, *AppError) { | ||||||
|  | 	if r, err := c.DoApiPost("/preferences/delete", preferences.ToJson()); err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} else { | ||||||
|  | 		return c.CheckStatusOK(r), nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) { | func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) { | ||||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil { | 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -1648,3 +1854,47 @@ func (c *Client) DeleteEmoji(id string) (bool, *AppError) { | |||||||
| func (c *Client) GetCustomEmojiImageUrl(id string) string { | func (c *Client) GetCustomEmojiImageUrl(id string) string { | ||||||
| 	return c.GetEmojiRoute() + "/" + id | 	return c.GetEmojiRoute() + "/" + id | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Uploads a x509 base64 Certificate or Private Key file to be used with SAML. | ||||||
|  | // data byte array is required and needs to be a Multi-Part with 'certificate' as the field name | ||||||
|  | // contentType is also required. Returns nil if succesful, otherwise returns an AppError | ||||||
|  | func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppError { | ||||||
|  | 	url := c.ApiUrl + "/admin/add_certificate" | ||||||
|  | 	rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) | ||||||
|  | 	rq.Header.Set("Content-Type", contentType) | ||||||
|  |  | ||||||
|  | 	if len(c.AuthToken) > 0 { | ||||||
|  | 		rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if rp, err := c.HttpClient.Do(rq); err != nil { | ||||||
|  | 		return NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) | ||||||
|  | 	} else if rp.StatusCode >= 300 { | ||||||
|  | 		return AppErrorFromJson(rp.Body) | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(rp) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Removes a x509 base64 Certificate or Private Key file used with SAML. | ||||||
|  | // filename is required. Returns nil if successful, otherwise returns an AppError | ||||||
|  | func (c *Client) RemoveCertificateFile(filename string) *AppError { | ||||||
|  | 	if r, err := c.DoApiPost("/admin/remove_certificate", MapToJson(map[string]string{"filename": filename})); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Checks if the x509 base64 Certificates and Private Key files used with SAML exists on the file system. | ||||||
|  | // Returns a map[string]interface{} if successful, otherwise returns an AppError. Must be System Admin authenticated. | ||||||
|  | func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{}, *AppError) { | ||||||
|  | 	if r, err := c.DoApiGet("/admin/remove_certificate", "", ""); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} else { | ||||||
|  | 		defer closeBody(r) | ||||||
|  | 		return StringInterfaceFromJson(r.Body), nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										66
									
								
								vendor/github.com/mattermost/platform/model/cluster_info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								vendor/github.com/mattermost/platform/model/cluster_info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||||
|  | // See License.txt for license information. | ||||||
|  |  | ||||||
|  | package model | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ClusterInfo struct { | ||||||
|  | 	Id                 string `json:"id"` | ||||||
|  | 	Version            string `json:"version"` | ||||||
|  | 	ConfigHash         string `json:"config_hash"` | ||||||
|  | 	InterNodeUrl       string `json:"internode_url"` | ||||||
|  | 	Hostname           string `json:"hostname"` | ||||||
|  | 	LastSuccessfulPing int64  `json:"last_ping"` | ||||||
|  | 	IsAlive            bool   `json:"is_alive"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (me *ClusterInfo) ToJson() string { | ||||||
|  | 	b, err := json.Marshal(me) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} else { | ||||||
|  | 		return string(b) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ClusterInfoFromJson(data io.Reader) *ClusterInfo { | ||||||
|  | 	decoder := json.NewDecoder(data) | ||||||
|  | 	var me ClusterInfo | ||||||
|  | 	err := decoder.Decode(&me) | ||||||
|  | 	if err == nil { | ||||||
|  | 		return &me | ||||||
|  | 	} else { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (me *ClusterInfo) HaveEstablishedInitialContact() bool { | ||||||
|  | 	if me.Id != "" { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ClusterInfosToJson(objmap []*ClusterInfo) string { | ||||||
|  | 	if b, err := json.Marshal(objmap); err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} else { | ||||||
|  | 		return string(b) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ClusterInfosFromJson(data io.Reader) []*ClusterInfo { | ||||||
|  | 	decoder := json.NewDecoder(data) | ||||||
|  |  | ||||||
|  | 	var objmap []*ClusterInfo | ||||||
|  | 	if err := decoder.Decode(&objmap); err != nil { | ||||||
|  | 		return make([]*ClusterInfo, 0) | ||||||
|  | 	} else { | ||||||
|  | 		return objmap | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										187
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										187
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,10 +6,12 @@ package model | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"net/url" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	CONN_SECURITY_NONE     = "" | 	CONN_SECURITY_NONE     = "" | ||||||
|  | 	CONN_SECURITY_PLAIN    = "PLAIN" | ||||||
| 	CONN_SECURITY_TLS      = "TLS" | 	CONN_SECURITY_TLS      = "TLS" | ||||||
| 	CONN_SECURITY_STARTTLS = "STARTTLS" | 	CONN_SECURITY_STARTTLS = "STARTTLS" | ||||||
|  |  | ||||||
| @@ -22,8 +24,9 @@ const ( | |||||||
| 	PASSWORD_MAXIMUM_LENGTH = 64 | 	PASSWORD_MAXIMUM_LENGTH = 64 | ||||||
| 	PASSWORD_MINIMUM_LENGTH = 5 | 	PASSWORD_MINIMUM_LENGTH = 5 | ||||||
|  |  | ||||||
| 	SERVICE_GITLAB = "gitlab" | 	SERVICE_GITLAB    = "gitlab" | ||||||
| 	SERVICE_GOOGLE = "google" | 	SERVICE_GOOGLE    = "google" | ||||||
|  | 	SERVICE_OFFICE365 = "office365" | ||||||
|  |  | ||||||
| 	WEBSERVER_MODE_REGULAR  = "regular" | 	WEBSERVER_MODE_REGULAR  = "regular" | ||||||
| 	WEBSERVER_MODE_GZIP     = "gzip" | 	WEBSERVER_MODE_GZIP     = "gzip" | ||||||
| @@ -44,9 +47,15 @@ const ( | |||||||
| 	RESTRICT_EMOJI_CREATION_ALL          = "all" | 	RESTRICT_EMOJI_CREATION_ALL          = "all" | ||||||
| 	RESTRICT_EMOJI_CREATION_ADMIN        = "admin" | 	RESTRICT_EMOJI_CREATION_ADMIN        = "admin" | ||||||
| 	RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN = "system_admin" | 	RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN = "system_admin" | ||||||
|  |  | ||||||
|  | 	EMAIL_BATCHING_BUFFER_SIZE = 256 | ||||||
|  | 	EMAIL_BATCHING_INTERVAL    = 30 | ||||||
|  |  | ||||||
|  | 	SITENAME_MAX_LENGTH = 30 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type ServiceSettings struct { | type ServiceSettings struct { | ||||||
|  | 	SiteURL                           *string | ||||||
| 	ListenAddress                     string | 	ListenAddress                     string | ||||||
| 	MaximumLoginAttempts              int | 	MaximumLoginAttempts              int | ||||||
| 	SegmentDeveloperKey               string | 	SegmentDeveloperKey               string | ||||||
| @@ -75,6 +84,12 @@ type ServiceSettings struct { | |||||||
| 	RestrictCustomEmojiCreation       *string | 	RestrictCustomEmojiCreation       *string | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type ClusterSettings struct { | ||||||
|  | 	Enable                 *bool | ||||||
|  | 	InterNodeListenAddress *string | ||||||
|  | 	InterNodeUrls          []string | ||||||
|  | } | ||||||
|  |  | ||||||
| type SSOSettings struct { | type SSOSettings struct { | ||||||
| 	Enable          bool | 	Enable          bool | ||||||
| 	Secret          string | 	Secret          string | ||||||
| @@ -103,6 +118,7 @@ type LogSettings struct { | |||||||
| 	FileFormat             string | 	FileFormat             string | ||||||
| 	FileLocation           string | 	FileLocation           string | ||||||
| 	EnableWebhookDebugging bool | 	EnableWebhookDebugging bool | ||||||
|  | 	EnableDiagnostics      *bool | ||||||
| } | } | ||||||
|  |  | ||||||
| type PasswordSettings struct { | type PasswordSettings struct { | ||||||
| @@ -118,7 +134,7 @@ type FileSettings struct { | |||||||
| 	DriverName                 string | 	DriverName                 string | ||||||
| 	Directory                  string | 	Directory                  string | ||||||
| 	EnablePublicLink           bool | 	EnablePublicLink           bool | ||||||
| 	PublicLinkSalt             string | 	PublicLinkSalt             *string | ||||||
| 	ThumbnailWidth             int | 	ThumbnailWidth             int | ||||||
| 	ThumbnailHeight            int | 	ThumbnailHeight            int | ||||||
| 	PreviewWidth               int | 	PreviewWidth               int | ||||||
| @@ -155,6 +171,9 @@ type EmailSettings struct { | |||||||
| 	SendPushNotifications    *bool | 	SendPushNotifications    *bool | ||||||
| 	PushNotificationServer   *string | 	PushNotificationServer   *string | ||||||
| 	PushNotificationContents *string | 	PushNotificationContents *string | ||||||
|  | 	EnableEmailBatching      *bool | ||||||
|  | 	EmailBatchingBufferSize  *int | ||||||
|  | 	EmailBatchingInterval    *int | ||||||
| } | } | ||||||
|  |  | ||||||
| type RateLimitSettings struct { | type RateLimitSettings struct { | ||||||
| @@ -189,10 +208,12 @@ type TeamSettings struct { | |||||||
| 	RestrictTeamNames                *bool | 	RestrictTeamNames                *bool | ||||||
| 	EnableCustomBrand                *bool | 	EnableCustomBrand                *bool | ||||||
| 	CustomBrandText                  *string | 	CustomBrandText                  *string | ||||||
|  | 	CustomDescriptionText            *string | ||||||
| 	RestrictDirectMessage            *string | 	RestrictDirectMessage            *string | ||||||
| 	RestrictTeamInvite               *string | 	RestrictTeamInvite               *string | ||||||
| 	RestrictPublicChannelManagement  *string | 	RestrictPublicChannelManagement  *string | ||||||
| 	RestrictPrivateChannelManagement *string | 	RestrictPrivateChannelManagement *string | ||||||
|  | 	UserStatusAwayTimeout            *int64 | ||||||
| } | } | ||||||
|  |  | ||||||
| type LdapSettings struct { | type LdapSettings struct { | ||||||
| @@ -265,6 +286,12 @@ type SamlSettings struct { | |||||||
| 	LoginButtonText *string | 	LoginButtonText *string | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type NativeAppSettings struct { | ||||||
|  | 	AppDownloadLink        *string | ||||||
|  | 	AndroidAppDownloadLink *string | ||||||
|  | 	IosAppDownloadLink     *string | ||||||
|  | } | ||||||
|  |  | ||||||
| type Config struct { | type Config struct { | ||||||
| 	ServiceSettings      ServiceSettings | 	ServiceSettings      ServiceSettings | ||||||
| 	TeamSettings         TeamSettings | 	TeamSettings         TeamSettings | ||||||
| @@ -278,10 +305,13 @@ type Config struct { | |||||||
| 	SupportSettings      SupportSettings | 	SupportSettings      SupportSettings | ||||||
| 	GitLabSettings       SSOSettings | 	GitLabSettings       SSOSettings | ||||||
| 	GoogleSettings       SSOSettings | 	GoogleSettings       SSOSettings | ||||||
|  | 	Office365Settings    SSOSettings | ||||||
| 	LdapSettings         LdapSettings | 	LdapSettings         LdapSettings | ||||||
| 	ComplianceSettings   ComplianceSettings | 	ComplianceSettings   ComplianceSettings | ||||||
| 	LocalizationSettings LocalizationSettings | 	LocalizationSettings LocalizationSettings | ||||||
| 	SamlSettings         SamlSettings | 	SamlSettings         SamlSettings | ||||||
|  | 	NativeAppSettings    NativeAppSettings | ||||||
|  | 	ClusterSettings      ClusterSettings | ||||||
| } | } | ||||||
|  |  | ||||||
| func (o *Config) ToJson() string { | func (o *Config) ToJson() string { | ||||||
| @@ -299,6 +329,8 @@ func (o *Config) GetSSOService(service string) *SSOSettings { | |||||||
| 		return &o.GitLabSettings | 		return &o.GitLabSettings | ||||||
| 	case SERVICE_GOOGLE: | 	case SERVICE_GOOGLE: | ||||||
| 		return &o.GoogleSettings | 		return &o.GoogleSettings | ||||||
|  | 	case SERVICE_OFFICE365: | ||||||
|  | 		return &o.Office365Settings | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| @@ -326,8 +358,9 @@ func (o *Config) SetDefaults() { | |||||||
| 		*o.FileSettings.MaxFileSize = 52428800 // 50 MB | 		*o.FileSettings.MaxFileSize = 52428800 // 50 MB | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(o.FileSettings.PublicLinkSalt) == 0 { | 	if len(*o.FileSettings.PublicLinkSalt) == 0 { | ||||||
| 		o.FileSettings.PublicLinkSalt = NewRandomString(32) | 		o.FileSettings.PublicLinkSalt = new(string) | ||||||
|  | 		*o.FileSettings.PublicLinkSalt = NewRandomString(32) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if o.FileSettings.AmazonS3LocationConstraint == nil { | 	if o.FileSettings.AmazonS3LocationConstraint == nil { | ||||||
| @@ -348,6 +381,11 @@ func (o *Config) SetDefaults() { | |||||||
| 		o.EmailSettings.PasswordResetSalt = NewRandomString(32) | 		o.EmailSettings.PasswordResetSalt = NewRandomString(32) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if o.ServiceSettings.SiteURL == nil { | ||||||
|  | 		o.ServiceSettings.SiteURL = new(string) | ||||||
|  | 		*o.ServiceSettings.SiteURL = "" | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if o.ServiceSettings.EnableDeveloper == nil { | 	if o.ServiceSettings.EnableDeveloper == nil { | ||||||
| 		o.ServiceSettings.EnableDeveloper = new(bool) | 		o.ServiceSettings.EnableDeveloper = new(bool) | ||||||
| 		*o.ServiceSettings.EnableDeveloper = false | 		*o.ServiceSettings.EnableDeveloper = false | ||||||
| @@ -408,6 +446,11 @@ func (o *Config) SetDefaults() { | |||||||
| 		*o.TeamSettings.CustomBrandText = "" | 		*o.TeamSettings.CustomBrandText = "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if o.TeamSettings.CustomDescriptionText == nil { | ||||||
|  | 		o.TeamSettings.CustomDescriptionText = new(string) | ||||||
|  | 		*o.TeamSettings.CustomDescriptionText = "" | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if o.TeamSettings.EnableOpenServer == nil { | 	if o.TeamSettings.EnableOpenServer == nil { | ||||||
| 		o.TeamSettings.EnableOpenServer = new(bool) | 		o.TeamSettings.EnableOpenServer = new(bool) | ||||||
| 		*o.TeamSettings.EnableOpenServer = false | 		*o.TeamSettings.EnableOpenServer = false | ||||||
| @@ -433,6 +476,11 @@ func (o *Config) SetDefaults() { | |||||||
| 		*o.TeamSettings.RestrictPrivateChannelManagement = PERMISSIONS_ALL | 		*o.TeamSettings.RestrictPrivateChannelManagement = PERMISSIONS_ALL | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if o.TeamSettings.UserStatusAwayTimeout == nil { | ||||||
|  | 		o.TeamSettings.UserStatusAwayTimeout = new(int64) | ||||||
|  | 		*o.TeamSettings.UserStatusAwayTimeout = 300 | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if o.EmailSettings.EnableSignInWithEmail == nil { | 	if o.EmailSettings.EnableSignInWithEmail == nil { | ||||||
| 		o.EmailSettings.EnableSignInWithEmail = new(bool) | 		o.EmailSettings.EnableSignInWithEmail = new(bool) | ||||||
|  |  | ||||||
| @@ -468,13 +516,28 @@ func (o *Config) SetDefaults() { | |||||||
| 		*o.EmailSettings.FeedbackOrganization = "" | 		*o.EmailSettings.FeedbackOrganization = "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if o.EmailSettings.EnableEmailBatching == nil { | ||||||
|  | 		o.EmailSettings.EnableEmailBatching = new(bool) | ||||||
|  | 		*o.EmailSettings.EnableEmailBatching = false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if o.EmailSettings.EmailBatchingBufferSize == nil { | ||||||
|  | 		o.EmailSettings.EmailBatchingBufferSize = new(int) | ||||||
|  | 		*o.EmailSettings.EmailBatchingBufferSize = EMAIL_BATCHING_BUFFER_SIZE | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if o.EmailSettings.EmailBatchingInterval == nil { | ||||||
|  | 		o.EmailSettings.EmailBatchingInterval = new(int) | ||||||
|  | 		*o.EmailSettings.EmailBatchingInterval = EMAIL_BATCHING_INTERVAL | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if !IsSafeLink(o.SupportSettings.TermsOfServiceLink) { | 	if !IsSafeLink(o.SupportSettings.TermsOfServiceLink) { | ||||||
| 		o.SupportSettings.TermsOfServiceLink = nil | 		o.SupportSettings.TermsOfServiceLink = nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if o.SupportSettings.TermsOfServiceLink == nil { | 	if o.SupportSettings.TermsOfServiceLink == nil { | ||||||
| 		o.SupportSettings.TermsOfServiceLink = new(string) | 		o.SupportSettings.TermsOfServiceLink = new(string) | ||||||
| 		*o.SupportSettings.TermsOfServiceLink = "/static/help/terms.html" | 		*o.SupportSettings.TermsOfServiceLink = "https://about.mattermost.com/default-terms/" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !IsSafeLink(o.SupportSettings.PrivacyPolicyLink) { | 	if !IsSafeLink(o.SupportSettings.PrivacyPolicyLink) { | ||||||
| @@ -483,7 +546,7 @@ func (o *Config) SetDefaults() { | |||||||
|  |  | ||||||
| 	if o.SupportSettings.PrivacyPolicyLink == nil { | 	if o.SupportSettings.PrivacyPolicyLink == nil { | ||||||
| 		o.SupportSettings.PrivacyPolicyLink = new(string) | 		o.SupportSettings.PrivacyPolicyLink = new(string) | ||||||
| 		*o.SupportSettings.PrivacyPolicyLink = "/static/help/privacy.html" | 		*o.SupportSettings.PrivacyPolicyLink = "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !IsSafeLink(o.SupportSettings.AboutLink) { | 	if !IsSafeLink(o.SupportSettings.AboutLink) { | ||||||
| @@ -492,7 +555,7 @@ func (o *Config) SetDefaults() { | |||||||
|  |  | ||||||
| 	if o.SupportSettings.AboutLink == nil { | 	if o.SupportSettings.AboutLink == nil { | ||||||
| 		o.SupportSettings.AboutLink = new(string) | 		o.SupportSettings.AboutLink = new(string) | ||||||
| 		*o.SupportSettings.AboutLink = "/static/help/about.html" | 		*o.SupportSettings.AboutLink = "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !IsSafeLink(o.SupportSettings.HelpLink) { | 	if !IsSafeLink(o.SupportSettings.HelpLink) { | ||||||
| @@ -501,7 +564,7 @@ func (o *Config) SetDefaults() { | |||||||
|  |  | ||||||
| 	if o.SupportSettings.HelpLink == nil { | 	if o.SupportSettings.HelpLink == nil { | ||||||
| 		o.SupportSettings.HelpLink = new(string) | 		o.SupportSettings.HelpLink = new(string) | ||||||
| 		*o.SupportSettings.HelpLink = "/static/help/help.html" | 		*o.SupportSettings.HelpLink = "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !IsSafeLink(o.SupportSettings.ReportAProblemLink) { | 	if !IsSafeLink(o.SupportSettings.ReportAProblemLink) { | ||||||
| @@ -510,7 +573,7 @@ func (o *Config) SetDefaults() { | |||||||
|  |  | ||||||
| 	if o.SupportSettings.ReportAProblemLink == nil { | 	if o.SupportSettings.ReportAProblemLink == nil { | ||||||
| 		o.SupportSettings.ReportAProblemLink = new(string) | 		o.SupportSettings.ReportAProblemLink = new(string) | ||||||
| 		*o.SupportSettings.ReportAProblemLink = "/static/help/report_problem.html" | 		*o.SupportSettings.ReportAProblemLink = "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if o.SupportSettings.SupportEmail == nil { | 	if o.SupportSettings.SupportEmail == nil { | ||||||
| @@ -675,6 +738,20 @@ func (o *Config) SetDefaults() { | |||||||
| 		*o.ServiceSettings.RestrictCustomEmojiCreation = RESTRICT_EMOJI_CREATION_ALL | 		*o.ServiceSettings.RestrictCustomEmojiCreation = RESTRICT_EMOJI_CREATION_ALL | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if o.ClusterSettings.InterNodeListenAddress == nil { | ||||||
|  | 		o.ClusterSettings.InterNodeListenAddress = new(string) | ||||||
|  | 		*o.ClusterSettings.InterNodeListenAddress = ":8075" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if o.ClusterSettings.Enable == nil { | ||||||
|  | 		o.ClusterSettings.Enable = new(bool) | ||||||
|  | 		*o.ClusterSettings.Enable = false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if o.ClusterSettings.InterNodeUrls == nil { | ||||||
|  | 		o.ClusterSettings.InterNodeUrls = []string{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if o.ComplianceSettings.Enable == nil { | 	if o.ComplianceSettings.Enable == nil { | ||||||
| 		o.ComplianceSettings.Enable = new(bool) | 		o.ComplianceSettings.Enable = new(bool) | ||||||
| 		*o.ComplianceSettings.Enable = false | 		*o.ComplianceSettings.Enable = false | ||||||
| @@ -705,6 +782,11 @@ func (o *Config) SetDefaults() { | |||||||
| 		*o.LocalizationSettings.AvailableLocales = "" | 		*o.LocalizationSettings.AvailableLocales = "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if o.LogSettings.EnableDiagnostics == nil { | ||||||
|  | 		o.LogSettings.EnableDiagnostics = new(bool) | ||||||
|  | 		*o.LogSettings.EnableDiagnostics = true | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if o.SamlSettings.Enable == nil { | 	if o.SamlSettings.Enable == nil { | ||||||
| 		o.SamlSettings.Enable = new(bool) | 		o.SamlSettings.Enable = new(bool) | ||||||
| 		*o.SamlSettings.Enable = false | 		*o.SamlSettings.Enable = false | ||||||
| @@ -784,6 +866,21 @@ func (o *Config) SetDefaults() { | |||||||
| 		o.SamlSettings.LocaleAttribute = new(string) | 		o.SamlSettings.LocaleAttribute = new(string) | ||||||
| 		*o.SamlSettings.LocaleAttribute = "" | 		*o.SamlSettings.LocaleAttribute = "" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if o.NativeAppSettings.AppDownloadLink == nil { | ||||||
|  | 		o.NativeAppSettings.AppDownloadLink = new(string) | ||||||
|  | 		*o.NativeAppSettings.AppDownloadLink = "https://about.mattermost.com/downloads/" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if o.NativeAppSettings.AndroidAppDownloadLink == nil { | ||||||
|  | 		o.NativeAppSettings.AndroidAppDownloadLink = new(string) | ||||||
|  | 		*o.NativeAppSettings.AndroidAppDownloadLink = "https://about.mattermost.com/mattermost-android-app/" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if o.NativeAppSettings.IosAppDownloadLink == nil { | ||||||
|  | 		o.NativeAppSettings.IosAppDownloadLink = new(string) | ||||||
|  | 		*o.NativeAppSettings.IosAppDownloadLink = "https://about.mattermost.com/mattermost-ios-app/" | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (o *Config) IsValid() *AppError { | func (o *Config) IsValid() *AppError { | ||||||
| @@ -792,10 +889,24 @@ func (o *Config) IsValid() *AppError { | |||||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.login_attempts.app_error", nil, "") | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.login_attempts.app_error", nil, "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if len(*o.ServiceSettings.SiteURL) != 0 { | ||||||
|  | 		if _, err := url.ParseRequestURI(*o.ServiceSettings.SiteURL); err != nil { | ||||||
|  | 			return NewLocAppError("Config.IsValid", "model.config.is_valid.site_url.app_error", nil, "") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if len(o.ServiceSettings.ListenAddress) == 0 { | 	if len(o.ServiceSettings.ListenAddress) == 0 { | ||||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "") | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if *o.ClusterSettings.Enable && *o.EmailSettings.EnableEmailBatching { | ||||||
|  | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.cluster_email_batching.app_error", nil, "") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(*o.ServiceSettings.SiteURL) == 0 && *o.EmailSettings.EnableEmailBatching { | ||||||
|  | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.site_url_email_batching.app_error", nil, "") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if o.TeamSettings.MaxUsersPerTeam <= 0 { | 	if o.TeamSettings.MaxUsersPerTeam <= 0 { | ||||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "") | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "") | ||||||
| 	} | 	} | ||||||
| @@ -856,11 +967,11 @@ func (o *Config) IsValid() *AppError { | |||||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_width.app_error", nil, "") | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_width.app_error", nil, "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(o.FileSettings.PublicLinkSalt) < 32 { | 	if len(*o.FileSettings.PublicLinkSalt) < 32 { | ||||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "") | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !(o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_STARTTLS) { | 	if !(o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_STARTTLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_PLAIN) { | ||||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.email_security.app_error", nil, "") | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.email_security.app_error", nil, "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -872,6 +983,14 @@ func (o *Config) IsValid() *AppError { | |||||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.email_reset_salt.app_error", nil, "") | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.email_reset_salt.app_error", nil, "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if *o.EmailSettings.EmailBatchingBufferSize <= 0 { | ||||||
|  | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.email_batching_buffer_size.app_error", nil, "") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if *o.EmailSettings.EmailBatchingInterval < 30 { | ||||||
|  | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.email_batching_interval.app_error", nil, "") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if o.RateLimitSettings.MemoryStoreSize <= 0 { | 	if o.RateLimitSettings.MemoryStoreSize <= 0 { | ||||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "") | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "") | ||||||
| 	} | 	} | ||||||
| @@ -893,21 +1012,29 @@ func (o *Config) IsValid() *AppError { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if *o.LdapSettings.Enable { | 	if *o.LdapSettings.Enable { | ||||||
| 		if *o.LdapSettings.LdapServer == "" || | 		if *o.LdapSettings.LdapServer == "" { | ||||||
| 			*o.LdapSettings.BaseDN == "" || | 			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_server", nil, "") | ||||||
| 			*o.LdapSettings.BindUsername == "" || | 		} | ||||||
| 			*o.LdapSettings.BindPassword == "" || |  | ||||||
| 			*o.LdapSettings.FirstNameAttribute == "" || | 		if *o.LdapSettings.BaseDN == "" { | ||||||
| 			*o.LdapSettings.LastNameAttribute == "" || | 			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_basedn", nil, "") | ||||||
| 			*o.LdapSettings.EmailAttribute == "" || | 		} | ||||||
| 			*o.LdapSettings.UsernameAttribute == "" || |  | ||||||
| 			*o.LdapSettings.IdAttribute == "" { | 		if *o.LdapSettings.EmailAttribute == "" { | ||||||
| 			return NewLocAppError("Config.IsValid", "Required LDAP field missing", nil, "") | 			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_email", nil, "") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if *o.LdapSettings.UsernameAttribute == "" { | ||||||
|  | 			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_username", nil, "") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if *o.LdapSettings.IdAttribute == "" { | ||||||
|  | 			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_id", nil, "") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if *o.SamlSettings.Enable { | 	if *o.SamlSettings.Enable { | ||||||
| 		if len(*o.SamlSettings.IdpUrl) == 0 { | 		if len(*o.SamlSettings.IdpUrl) == 0 || !IsValidHttpUrl(*o.SamlSettings.IdpUrl) { | ||||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_idp_url.app_error", nil, "") | 			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_idp_url.app_error", nil, "") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -927,14 +1054,6 @@ func (o *Config) IsValid() *AppError { | |||||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_username_attribute.app_error", nil, "") | 			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_username_attribute.app_error", nil, "") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if len(*o.SamlSettings.FirstNameAttribute) == 0 { |  | ||||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_first_name_attribute.app_error", nil, "") |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if len(*o.SamlSettings.LastNameAttribute) == 0 { |  | ||||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_last_name_attribute.app_error", nil, "") |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if *o.SamlSettings.Verify { | 		if *o.SamlSettings.Verify { | ||||||
| 			if len(*o.SamlSettings.AssertionConsumerServiceURL) == 0 || !IsValidHttpUrl(*o.SamlSettings.AssertionConsumerServiceURL) { | 			if len(*o.SamlSettings.AssertionConsumerServiceURL) == 0 || !IsValidHttpUrl(*o.SamlSettings.AssertionConsumerServiceURL) { | ||||||
| 				return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_assertion_consumer_service_url.app_error", nil, "") | 				return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_assertion_consumer_service_url.app_error", nil, "") | ||||||
| @@ -960,6 +1079,10 @@ func (o *Config) IsValid() *AppError { | |||||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.password_length.app_error", map[string]interface{}{"MinLength": PASSWORD_MINIMUM_LENGTH, "MaxLength": PASSWORD_MAXIMUM_LENGTH}, "") | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.password_length.app_error", map[string]interface{}{"MinLength": PASSWORD_MINIMUM_LENGTH, "MaxLength": PASSWORD_MAXIMUM_LENGTH}, "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if len(o.TeamSettings.SiteName) > SITENAME_MAX_LENGTH { | ||||||
|  | 		return NewLocAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SITENAME_MAX_LENGTH}, "") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -976,7 +1099,7 @@ func (o *Config) Sanitize() { | |||||||
| 		*o.LdapSettings.BindPassword = FAKE_SETTING | 		*o.LdapSettings.BindPassword = FAKE_SETTING | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	o.FileSettings.PublicLinkSalt = FAKE_SETTING | 	*o.FileSettings.PublicLinkSalt = FAKE_SETTING | ||||||
| 	if len(o.FileSettings.AmazonS3SecretAccessKey) > 0 { | 	if len(o.FileSettings.AmazonS3SecretAccessKey) > 0 { | ||||||
| 		o.FileSettings.AmazonS3SecretAccessKey = FAKE_SETTING | 		o.FileSettings.AmazonS3SecretAccessKey = FAKE_SETTING | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								vendor/github.com/mattermost/platform/model/job.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/mattermost/platform/model/job.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -84,6 +84,12 @@ func (task *ScheduledTask) Cancel() { | |||||||
| 	removeTaskByName(task.Name) | 	removeTaskByName(task.Name) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Executes the task immediatly. A recurring task will be run regularally after interval. | ||||||
|  | func (task *ScheduledTask) Execute() { | ||||||
|  | 	task.function() | ||||||
|  | 	task.timer.Reset(task.Interval) | ||||||
|  | } | ||||||
|  |  | ||||||
| func (task *ScheduledTask) String() string { | func (task *ScheduledTask) String() string { | ||||||
| 	return fmt.Sprintf( | 	return fmt.Sprintf( | ||||||
| 		"%s\nInterval: %s\nRecurring: %t\n", | 		"%s\nInterval: %s\nRecurring: %t\n", | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										36
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -35,8 +35,10 @@ type Features struct { | |||||||
| 	Users                *int  `json:"users"` | 	Users                *int  `json:"users"` | ||||||
| 	LDAP                 *bool `json:"ldap"` | 	LDAP                 *bool `json:"ldap"` | ||||||
| 	MFA                  *bool `json:"mfa"` | 	MFA                  *bool `json:"mfa"` | ||||||
| 	GoogleSSO            *bool `json:"google_sso"` | 	GoogleOAuth          *bool `json:"google_oauth"` | ||||||
|  | 	Office365OAuth       *bool `json:"office365_oauth"` | ||||||
| 	Compliance           *bool `json:"compliance"` | 	Compliance           *bool `json:"compliance"` | ||||||
|  | 	Cluster              *bool `json:"cluster"` | ||||||
| 	CustomBrand          *bool `json:"custom_brand"` | 	CustomBrand          *bool `json:"custom_brand"` | ||||||
| 	MHPNS                *bool `json:"mhpns"` | 	MHPNS                *bool `json:"mhpns"` | ||||||
| 	SAML                 *bool `json:"saml"` | 	SAML                 *bool `json:"saml"` | ||||||
| @@ -44,6 +46,22 @@ type Features struct { | |||||||
| 	FutureFeatures       *bool `json:"future_features"` | 	FutureFeatures       *bool `json:"future_features"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (f *Features) ToMap() map[string]interface{} { | ||||||
|  | 	return map[string]interface{}{ | ||||||
|  | 		"ldap":         *f.LDAP, | ||||||
|  | 		"mfa":          *f.MFA, | ||||||
|  | 		"google":       *f.GoogleOAuth, | ||||||
|  | 		"office365":    *f.Office365OAuth, | ||||||
|  | 		"compliance":   *f.Compliance, | ||||||
|  | 		"cluster":      *f.Cluster, | ||||||
|  | 		"custom_brand": *f.CustomBrand, | ||||||
|  | 		"mhpns":        *f.MHPNS, | ||||||
|  | 		"saml":         *f.SAML, | ||||||
|  | 		"password":     *f.PasswordRequirements, | ||||||
|  | 		"future":       *f.FutureFeatures, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func (f *Features) SetDefaults() { | func (f *Features) SetDefaults() { | ||||||
| 	if f.FutureFeatures == nil { | 	if f.FutureFeatures == nil { | ||||||
| 		f.FutureFeatures = new(bool) | 		f.FutureFeatures = new(bool) | ||||||
| @@ -65,9 +83,14 @@ func (f *Features) SetDefaults() { | |||||||
| 		*f.MFA = *f.FutureFeatures | 		*f.MFA = *f.FutureFeatures | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if f.GoogleSSO == nil { | 	if f.GoogleOAuth == nil { | ||||||
| 		f.GoogleSSO = new(bool) | 		f.GoogleOAuth = new(bool) | ||||||
| 		*f.GoogleSSO = *f.FutureFeatures | 		*f.GoogleOAuth = *f.FutureFeatures | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if f.Office365OAuth == nil { | ||||||
|  | 		f.Office365OAuth = new(bool) | ||||||
|  | 		*f.Office365OAuth = *f.FutureFeatures | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if f.Compliance == nil { | 	if f.Compliance == nil { | ||||||
| @@ -75,6 +98,11 @@ func (f *Features) SetDefaults() { | |||||||
| 		*f.Compliance = *f.FutureFeatures | 		*f.Compliance = *f.FutureFeatures | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if f.Cluster == nil { | ||||||
|  | 		f.Cluster = new(bool) | ||||||
|  | 		*f.Cluster = *f.FutureFeatures | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if f.CustomBrand == nil { | 	if f.CustomBrand == nil { | ||||||
| 		f.CustomBrand = new(bool) | 		f.CustomBrand = new(bool) | ||||||
| 		*f.CustomBrand = *f.FutureFeatures | 		*f.CustomBrand = *f.FutureFeatures | ||||||
|   | |||||||
							
								
								
									
										61
									
								
								vendor/github.com/mattermost/platform/model/message.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										61
									
								
								vendor/github.com/mattermost/platform/model/message.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,61 +0,0 @@ | |||||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. |  | ||||||
| // See License.txt for license information. |  | ||||||
|  |  | ||||||
| package model |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"io" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	ACTION_TYPING             = "typing" |  | ||||||
| 	ACTION_POSTED             = "posted" |  | ||||||
| 	ACTION_POST_EDITED        = "post_edited" |  | ||||||
| 	ACTION_POST_DELETED       = "post_deleted" |  | ||||||
| 	ACTION_CHANNEL_DELETED    = "channel_deleted" |  | ||||||
| 	ACTION_CHANNEL_VIEWED     = "channel_viewed" |  | ||||||
| 	ACTION_DIRECT_ADDED       = "direct_added" |  | ||||||
| 	ACTION_NEW_USER           = "new_user" |  | ||||||
| 	ACTION_LEAVE_TEAM         = "leave_team" |  | ||||||
| 	ACTION_USER_ADDED         = "user_added" |  | ||||||
| 	ACTION_USER_REMOVED       = "user_removed" |  | ||||||
| 	ACTION_PREFERENCE_CHANGED = "preference_changed" |  | ||||||
| 	ACTION_EPHEMERAL_MESSAGE  = "ephemeral_message" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type Message struct { |  | ||||||
| 	TeamId    string            `json:"team_id"` |  | ||||||
| 	ChannelId string            `json:"channel_id"` |  | ||||||
| 	UserId    string            `json:"user_id"` |  | ||||||
| 	Action    string            `json:"action"` |  | ||||||
| 	Props     map[string]string `json:"props"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *Message) Add(key string, value string) { |  | ||||||
| 	m.Props[key] = value |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewMessage(teamId string, channelId string, userId string, action string) *Message { |  | ||||||
| 	return &Message{TeamId: teamId, ChannelId: channelId, UserId: userId, Action: action, Props: make(map[string]string)} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (o *Message) ToJson() string { |  | ||||||
| 	b, err := json.Marshal(o) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "" |  | ||||||
| 	} else { |  | ||||||
| 		return string(b) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func MessageFromJson(data io.Reader) *Message { |  | ||||||
| 	decoder := json.NewDecoder(data) |  | ||||||
| 	var o Message |  | ||||||
| 	err := decoder.Decode(&o) |  | ||||||
| 	if err == nil { |  | ||||||
| 		return &o |  | ||||||
| 	} else { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										40
									
								
								vendor/github.com/mattermost/platform/model/oauth.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/mattermost/platform/model/oauth.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -25,8 +25,10 @@ type OAuthApp struct { | |||||||
| 	ClientSecret string      `json:"client_secret"` | 	ClientSecret string      `json:"client_secret"` | ||||||
| 	Name         string      `json:"name"` | 	Name         string      `json:"name"` | ||||||
| 	Description  string      `json:"description"` | 	Description  string      `json:"description"` | ||||||
|  | 	IconURL      string      `json:"icon_url"` | ||||||
| 	CallbackUrls StringArray `json:"callback_urls"` | 	CallbackUrls StringArray `json:"callback_urls"` | ||||||
| 	Homepage     string      `json:"homepage"` | 	Homepage     string      `json:"homepage"` | ||||||
|  | 	IsTrusted    bool        `json:"is_trusted"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // IsValid validates the app and returns an error if it isn't configured | // IsValid validates the app and returns an error if it isn't configured | ||||||
| @@ -61,7 +63,13 @@ func (a *OAuthApp) IsValid() *AppError { | |||||||
| 		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id) | 		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(a.Homepage) == 0 || len(a.Homepage) > 256 { | 	for _, callback := range a.CallbackUrls { | ||||||
|  | 		if !IsValidHttpUrl(callback) { | ||||||
|  | 			return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) { | ||||||
| 		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id) | 		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -69,6 +77,12 @@ func (a *OAuthApp) IsValid() *AppError { | |||||||
| 		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id) | 		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if len(a.IconURL) > 0 { | ||||||
|  | 		if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) { | ||||||
|  | 			return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -85,10 +99,6 @@ func (a *OAuthApp) PreSave() { | |||||||
|  |  | ||||||
| 	a.CreateAt = GetMillis() | 	a.CreateAt = GetMillis() | ||||||
| 	a.UpdateAt = a.CreateAt | 	a.UpdateAt = a.CreateAt | ||||||
|  |  | ||||||
| 	if len(a.ClientSecret) > 0 { |  | ||||||
| 		a.ClientSecret = HashPassword(a.ClientSecret) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // PreUpdate should be run before updating the app in the db. | // PreUpdate should be run before updating the app in the db. | ||||||
| @@ -157,3 +167,23 @@ func OAuthAppMapFromJson(data io.Reader) map[string]*OAuthApp { | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func OAuthAppListToJson(l []*OAuthApp) string { | ||||||
|  | 	b, err := json.Marshal(l) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} else { | ||||||
|  | 		return string(b) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func OAuthAppListFromJson(data io.Reader) []*OAuthApp { | ||||||
|  | 	decoder := json.NewDecoder(data) | ||||||
|  | 	var o []*OAuthApp | ||||||
|  | 	err := decoder.Decode(&o) | ||||||
|  | 	if err == nil { | ||||||
|  | 		return o | ||||||
|  | 	} else { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								vendor/github.com/mattermost/platform/model/outgoing_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/mattermost/platform/model/outgoing_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -9,6 +9,7 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type OutgoingWebhook struct { | type OutgoingWebhook struct { | ||||||
| @@ -21,6 +22,7 @@ type OutgoingWebhook struct { | |||||||
| 	ChannelId    string      `json:"channel_id"` | 	ChannelId    string      `json:"channel_id"` | ||||||
| 	TeamId       string      `json:"team_id"` | 	TeamId       string      `json:"team_id"` | ||||||
| 	TriggerWords StringArray `json:"trigger_words"` | 	TriggerWords StringArray `json:"trigger_words"` | ||||||
|  | 	TriggerWhen  int         `json:"trigger_when"` | ||||||
| 	CallbackURLs StringArray `json:"callback_urls"` | 	CallbackURLs StringArray `json:"callback_urls"` | ||||||
| 	DisplayName  string      `json:"display_name"` | 	DisplayName  string      `json:"display_name"` | ||||||
| 	Description  string      `json:"description"` | 	Description  string      `json:"description"` | ||||||
| @@ -171,6 +173,10 @@ func (o *OutgoingWebhook) IsValid() *AppError { | |||||||
| 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") | 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if o.TriggerWhen > 1 { | ||||||
|  | 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -204,3 +210,17 @@ func (o *OutgoingWebhook) HasTriggerWord(word string) bool { | |||||||
|  |  | ||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool { | ||||||
|  | 	if len(o.TriggerWords) == 0 || len(word) == 0 { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, trigger := range o.TriggerWords { | ||||||
|  | 		if strings.HasPrefix(word, trigger) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								vendor/github.com/mattermost/platform/model/post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/mattermost/platform/model/post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -15,6 +15,7 @@ const ( | |||||||
| 	POST_SLACK_ATTACHMENT      = "slack_attachment" | 	POST_SLACK_ATTACHMENT      = "slack_attachment" | ||||||
| 	POST_SYSTEM_GENERIC        = "system_generic" | 	POST_SYSTEM_GENERIC        = "system_generic" | ||||||
| 	POST_JOIN_LEAVE            = "system_join_leave" | 	POST_JOIN_LEAVE            = "system_join_leave" | ||||||
|  | 	POST_ADD_REMOVE            = "system_add_remove" | ||||||
| 	POST_HEADER_CHANGE         = "system_header_change" | 	POST_HEADER_CHANGE         = "system_header_change" | ||||||
| 	POST_CHANNEL_DELETED       = "system_channel_deleted" | 	POST_CHANNEL_DELETED       = "system_channel_deleted" | ||||||
| 	POST_EPHEMERAL             = "system_ephemeral" | 	POST_EPHEMERAL             = "system_ephemeral" | ||||||
| @@ -109,7 +110,7 @@ func (o *Post) IsValid() *AppError { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// should be removed once more message types are supported | 	// should be removed once more message types are supported | ||||||
| 	if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE) { | 	if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_ADD_REMOVE || o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE) { | ||||||
| 		return NewLocAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type) | 		return NewLocAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -162,9 +163,6 @@ func (o *Post) AddProp(key string, value interface{}) { | |||||||
| 	o.Props[key] = value | 	o.Props[key] = value | ||||||
| } | } | ||||||
|  |  | ||||||
| func (o *Post) PreExport() { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (o *Post) IsSystemMessage() bool { | func (o *Post) IsSystemMessage() bool { | ||||||
| 	return len(o.Type) >= len(POST_SYSTEM_MESSAGE_PREFIX) && o.Type[:len(POST_SYSTEM_MESSAGE_PREFIX)] == POST_SYSTEM_MESSAGE_PREFIX | 	return len(o.Type) >= len(POST_SYSTEM_MESSAGE_PREFIX) && o.Type[:len(POST_SYSTEM_MESSAGE_PREFIX)] == POST_SYSTEM_MESSAGE_PREFIX | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										61
									
								
								vendor/github.com/mattermost/platform/model/preference.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										61
									
								
								vendor/github.com/mattermost/platform/model/preference.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,6 +6,8 @@ package model | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strings" | ||||||
| 	"unicode/utf8" | 	"unicode/utf8" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -13,12 +15,28 @@ const ( | |||||||
| 	PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show" | 	PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show" | ||||||
| 	PREFERENCE_CATEGORY_TUTORIAL_STEPS      = "tutorial_step" | 	PREFERENCE_CATEGORY_TUTORIAL_STEPS      = "tutorial_step" | ||||||
| 	PREFERENCE_CATEGORY_ADVANCED_SETTINGS   = "advanced_settings" | 	PREFERENCE_CATEGORY_ADVANCED_SETTINGS   = "advanced_settings" | ||||||
|  | 	PREFERENCE_CATEGORY_FLAGGED_POST        = "flagged_post" | ||||||
|  |  | ||||||
| 	PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings" | 	PREFERENCE_CATEGORY_DISPLAY_SETTINGS   = "display_settings" | ||||||
| 	PREFERENCE_NAME_COLLAPSE_SETTING     = "collapse_previews" | 	PREFERENCE_NAME_COLLAPSE_SETTING       = "collapse_previews" | ||||||
|  | 	PREFERENCE_NAME_DISPLAY_NAME_FORMAT    = "name_format" | ||||||
|  | 	PREFERENCE_VALUE_DISPLAY_NAME_NICKNAME = "nickname_full_name" | ||||||
|  | 	PREFERENCE_VALUE_DISPLAY_NAME_FULL     = "full_name" | ||||||
|  | 	PREFERENCE_VALUE_DISPLAY_NAME_USERNAME = "username" | ||||||
|  | 	PREFERENCE_DEFAULT_DISPLAY_NAME_FORMAT = PREFERENCE_VALUE_DISPLAY_NAME_USERNAME | ||||||
|  |  | ||||||
|  | 	PREFERENCE_CATEGORY_THEME = "theme" | ||||||
|  | 	// the name for theme props is the team id | ||||||
|  |  | ||||||
|  | 	PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP = "oauth_app" | ||||||
|  | 	// the name for oauth_app is the client_id and value is the current scope | ||||||
|  |  | ||||||
| 	PREFERENCE_CATEGORY_LAST     = "last" | 	PREFERENCE_CATEGORY_LAST     = "last" | ||||||
| 	PREFERENCE_NAME_LAST_CHANNEL = "channel" | 	PREFERENCE_NAME_LAST_CHANNEL = "channel" | ||||||
|  |  | ||||||
|  | 	PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications" | ||||||
|  | 	PREFERENCE_NAME_EMAIL_INTERVAL    = "email_interval" | ||||||
|  | 	PREFERENCE_DEFAULT_EMAIL_INTERVAL = "30" // default to match the interval of the "immediate" setting (ie 30 seconds) | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type Preference struct { | type Preference struct { | ||||||
| @@ -57,13 +75,48 @@ func (o *Preference) IsValid() *AppError { | |||||||
| 		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category) | 		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(o.Name) == 0 || len(o.Name) > 32 { | 	if len(o.Name) > 32 { | ||||||
| 		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name) | 		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if utf8.RuneCountInString(o.Value) > 128 { | 	if utf8.RuneCountInString(o.Value) > 2000 { | ||||||
| 		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value) | 		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if o.Category == PREFERENCE_CATEGORY_THEME { | ||||||
|  | 		var unused map[string]string | ||||||
|  | 		if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&unused); err != nil { | ||||||
|  | 			return NewLocAppError("Preference.IsValid", "model.preference.is_valid.theme.app_error", nil, "value="+o.Value) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (o *Preference) PreUpdate() { | ||||||
|  | 	if o.Category == PREFERENCE_CATEGORY_THEME { | ||||||
|  | 		// decode the value of theme (a map of strings to string) and eliminate any invalid values | ||||||
|  | 		var props map[string]string | ||||||
|  | 		if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&props); err != nil { | ||||||
|  | 			// just continue, the invalid preference value should get caught by IsValid before saving | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`) | ||||||
|  |  | ||||||
|  | 		// blank out any invalid theme values | ||||||
|  | 		for name, value := range props { | ||||||
|  | 			if name == "image" || name == "type" || name == "codeTheme" { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if !colorPattern.MatchString(value) { | ||||||
|  | 				props[name] = "#ffffff" | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if b, err := json.Marshal(props); err == nil { | ||||||
|  | 			o.Value = string(b) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								vendor/github.com/mattermost/platform/model/push_notification.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/mattermost/platform/model/push_notification.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,12 +6,16 @@ package model | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	PUSH_NOTIFY_APPLE   = "apple" | 	PUSH_NOTIFY_APPLE   = "apple" | ||||||
| 	PUSH_NOTIFY_ANDROID = "android" | 	PUSH_NOTIFY_ANDROID = "android" | ||||||
|  |  | ||||||
|  | 	PUSH_TYPE_MESSAGE = "message" | ||||||
|  | 	PUSH_TYPE_CLEAR   = "clear" | ||||||
|  |  | ||||||
| 	CATEGORY_DM = "DIRECT_MESSAGE" | 	CATEGORY_DM = "DIRECT_MESSAGE" | ||||||
|  |  | ||||||
| 	MHPNS = "https://push.mattermost.com" | 	MHPNS = "https://push.mattermost.com" | ||||||
| @@ -28,6 +32,7 @@ type PushNotification struct { | |||||||
| 	ContentAvailable int    `json:"cont_ava"` | 	ContentAvailable int    `json:"cont_ava"` | ||||||
| 	ChannelId        string `json:"channel_id"` | 	ChannelId        string `json:"channel_id"` | ||||||
| 	ChannelName      string `json:"channel_name"` | 	ChannelName      string `json:"channel_name"` | ||||||
|  | 	Type             string `json:"type"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func (me *PushNotification) ToJson() string { | func (me *PushNotification) ToJson() string { | ||||||
| @@ -39,6 +44,16 @@ func (me *PushNotification) ToJson() string { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (me *PushNotification) SetDeviceIdAndPlatform(deviceId string) { | ||||||
|  | 	if strings.HasPrefix(deviceId, PUSH_NOTIFY_APPLE+":") { | ||||||
|  | 		me.Platform = PUSH_NOTIFY_APPLE | ||||||
|  | 		me.DeviceId = strings.TrimPrefix(deviceId, PUSH_NOTIFY_APPLE+":") | ||||||
|  | 	} else if strings.HasPrefix(deviceId, PUSH_NOTIFY_ANDROID+":") { | ||||||
|  | 		me.Platform = PUSH_NOTIFY_ANDROID | ||||||
|  | 		me.DeviceId = strings.TrimPrefix(deviceId, PUSH_NOTIFY_ANDROID+":") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func PushNotificationFromJson(data io.Reader) *PushNotification { | func PushNotificationFromJson(data io.Reader) *PushNotification { | ||||||
| 	decoder := json.NewDecoder(data) | 	decoder := json.NewDecoder(data) | ||||||
| 	var me PushNotification | 	var me PushNotification | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								vendor/github.com/mattermost/platform/model/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/mattermost/platform/model/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,6 +6,7 @@ package model | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @@ -83,7 +84,11 @@ func (me *Session) IsExpired() bool { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (me *Session) SetExpireInDays(days int) { | func (me *Session) SetExpireInDays(days int) { | ||||||
| 	me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days)) | 	if me.CreateAt == 0 { | ||||||
|  | 		me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days)) | ||||||
|  | 	} else { | ||||||
|  | 		me.ExpiresAt = me.CreateAt + (1000 * 60 * 60 * 24 * int64(days)) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (me *Session) AddProp(key string, value string) { | func (me *Session) AddProp(key string, value string) { | ||||||
| @@ -105,6 +110,11 @@ func (me *Session) GetTeamByTeamId(teamId string) *TeamMember { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (me *Session) IsMobileApp() bool { | ||||||
|  | 	return len(me.DeviceId) > 0 && | ||||||
|  | 		(strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_ANDROID+":")) | ||||||
|  | } | ||||||
|  |  | ||||||
| func SessionsToJson(o []*Session) string { | func SessionsToJson(o []*Session) string { | ||||||
| 	if b, err := json.Marshal(o); err != nil { | 	if b, err := json.Marshal(o); err != nil { | ||||||
| 		return "[]" | 		return "[]" | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								vendor/github.com/mattermost/platform/model/status.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								vendor/github.com/mattermost/platform/model/status.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||||
|  | // See License.txt for license information. | ||||||
|  |  | ||||||
|  | package model | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	STATUS_OFFLINE         = "offline" | ||||||
|  | 	STATUS_AWAY            = "away" | ||||||
|  | 	STATUS_ONLINE          = "online" | ||||||
|  | 	STATUS_CACHE_SIZE      = 10000 | ||||||
|  | 	STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Status struct { | ||||||
|  | 	UserId         string `json:"user_id"` | ||||||
|  | 	Status         string `json:"status"` | ||||||
|  | 	Manual         bool   `json:"manual"` | ||||||
|  | 	LastActivityAt int64  `json:"last_activity_at"` | ||||||
|  | 	ActiveChannel  string `json:"active_channel"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *Status) ToJson() string { | ||||||
|  | 	b, err := json.Marshal(o) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} else { | ||||||
|  | 		return string(b) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func StatusFromJson(data io.Reader) *Status { | ||||||
|  | 	decoder := json.NewDecoder(data) | ||||||
|  | 	var o Status | ||||||
|  | 	err := decoder.Decode(&o) | ||||||
|  | 	if err == nil { | ||||||
|  | 		return &o | ||||||
|  | 	} else { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/model/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -224,9 +224,6 @@ func CleanTeamName(s string) string { | |||||||
| 	return s | 	return s | ||||||
| } | } | ||||||
|  |  | ||||||
| func (o *Team) PreExport() { |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (o *Team) Sanitize() { | func (o *Team) Sanitize() { | ||||||
| 	o.Email = "" | 	o.Email = "" | ||||||
| 	o.AllowedDomains = "" | 	o.AllowedDomains = "" | ||||||
|   | |||||||
							
								
								
									
										75
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										75
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -16,11 +16,6 @@ import ( | |||||||
|  |  | ||||||
| const ( | const ( | ||||||
| 	ROLE_SYSTEM_ADMIN          = "system_admin" | 	ROLE_SYSTEM_ADMIN          = "system_admin" | ||||||
| 	USER_AWAY_TIMEOUT          = 5 * 60 * 1000 // 5 minutes |  | ||||||
| 	USER_OFFLINE_TIMEOUT       = 1 * 60 * 1000 // 1 minute |  | ||||||
| 	USER_OFFLINE               = "offline" |  | ||||||
| 	USER_AWAY                  = "away" |  | ||||||
| 	USER_ONLINE                = "online" |  | ||||||
| 	USER_NOTIFY_ALL            = "all" | 	USER_NOTIFY_ALL            = "all" | ||||||
| 	USER_NOTIFY_MENTION        = "mention" | 	USER_NOTIFY_MENTION        = "mention" | ||||||
| 	USER_NOTIFY_NONE           = "none" | 	USER_NOTIFY_NONE           = "none" | ||||||
| @@ -44,18 +39,16 @@ type User struct { | |||||||
| 	FirstName          string    `json:"first_name"` | 	FirstName          string    `json:"first_name"` | ||||||
| 	LastName           string    `json:"last_name"` | 	LastName           string    `json:"last_name"` | ||||||
| 	Roles              string    `json:"roles"` | 	Roles              string    `json:"roles"` | ||||||
| 	LastActivityAt     int64     `json:"last_activity_at,omitempty"` |  | ||||||
| 	LastPingAt         int64     `json:"last_ping_at,omitempty"` |  | ||||||
| 	AllowMarketing     bool      `json:"allow_marketing,omitempty"` | 	AllowMarketing     bool      `json:"allow_marketing,omitempty"` | ||||||
| 	Props              StringMap `json:"props,omitempty"` | 	Props              StringMap `json:"props,omitempty"` | ||||||
| 	NotifyProps        StringMap `json:"notify_props,omitempty"` | 	NotifyProps        StringMap `json:"notify_props,omitempty"` | ||||||
| 	ThemeProps         StringMap `json:"theme_props,omitempty"` |  | ||||||
| 	LastPasswordUpdate int64     `json:"last_password_update,omitempty"` | 	LastPasswordUpdate int64     `json:"last_password_update,omitempty"` | ||||||
| 	LastPictureUpdate  int64     `json:"last_picture_update,omitempty"` | 	LastPictureUpdate  int64     `json:"last_picture_update,omitempty"` | ||||||
| 	FailedAttempts     int       `json:"failed_attempts,omitempty"` | 	FailedAttempts     int       `json:"failed_attempts,omitempty"` | ||||||
| 	Locale             string    `json:"locale"` | 	Locale             string    `json:"locale"` | ||||||
| 	MfaActive          bool      `json:"mfa_active,omitempty"` | 	MfaActive          bool      `json:"mfa_active,omitempty"` | ||||||
| 	MfaSecret          string    `json:"mfa_secret,omitempty"` | 	MfaSecret          string    `json:"mfa_secret,omitempty"` | ||||||
|  | 	LastActivityAt     int64     `db:"-" json:"last_activity_at,omitempty"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // IsValid validates the user and returns an error if it isn't configured | // IsValid validates the user and returns an error if it isn't configured | ||||||
| @@ -106,10 +99,6 @@ func (u *User) IsValid() *AppError { | |||||||
| 		return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id) | 		return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(u.ThemeProps) > 2000 { |  | ||||||
| 		return NewLocAppError("User.IsValid", "model.user.is_valid.theme.app_error", nil, "user_id="+u.Id) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -179,21 +168,6 @@ func (u *User) PreUpdate() { | |||||||
| 		} | 		} | ||||||
| 		u.NotifyProps["mention_keys"] = strings.Join(goodKeys, ",") | 		u.NotifyProps["mention_keys"] = strings.Join(goodKeys, ",") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if u.ThemeProps != nil { |  | ||||||
| 		colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`) |  | ||||||
|  |  | ||||||
| 		// blank out any invalid theme values |  | ||||||
| 		for name, value := range u.ThemeProps { |  | ||||||
| 			if name == "image" || name == "type" || name == "codeTheme" { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if !colorPattern.MatchString(value) { |  | ||||||
| 				u.ThemeProps[name] = "#ffffff" |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *User) SetDefaultNotifications() { | func (u *User) SetDefaultNotifications() { | ||||||
| @@ -242,14 +216,6 @@ func (u *User) Etag(showFullName, showEmail bool) string { | |||||||
| 	return Etag(u.Id, u.UpdateAt, showFullName, showEmail) | 	return Etag(u.Id, u.UpdateAt, showFullName, showEmail) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *User) IsOffline() bool { |  | ||||||
| 	return (GetMillis()-u.LastPingAt) > USER_OFFLINE_TIMEOUT && (GetMillis()-u.LastActivityAt) > USER_OFFLINE_TIMEOUT |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (u *User) IsAway() bool { |  | ||||||
| 	return (GetMillis() - u.LastActivityAt) > USER_AWAY_TIMEOUT |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Remove any private data from the user object | // Remove any private data from the user object | ||||||
| func (u *User) Sanitize(options map[string]bool) { | func (u *User) Sanitize(options map[string]bool) { | ||||||
| 	u.Password = "" | 	u.Password = "" | ||||||
| @@ -270,7 +236,6 @@ func (u *User) Sanitize(options map[string]bool) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (u *User) ClearNonProfileFields() { | func (u *User) ClearNonProfileFields() { | ||||||
| 	u.UpdateAt = 0 |  | ||||||
| 	u.Password = "" | 	u.Password = "" | ||||||
| 	u.AuthData = new(string) | 	u.AuthData = new(string) | ||||||
| 	*u.AuthData = "" | 	*u.AuthData = "" | ||||||
| @@ -278,16 +243,20 @@ func (u *User) ClearNonProfileFields() { | |||||||
| 	u.MfaActive = false | 	u.MfaActive = false | ||||||
| 	u.MfaSecret = "" | 	u.MfaSecret = "" | ||||||
| 	u.EmailVerified = false | 	u.EmailVerified = false | ||||||
| 	u.LastPingAt = 0 |  | ||||||
| 	u.AllowMarketing = false | 	u.AllowMarketing = false | ||||||
| 	u.Props = StringMap{} | 	u.Props = StringMap{} | ||||||
| 	u.NotifyProps = StringMap{} | 	u.NotifyProps = StringMap{} | ||||||
| 	u.ThemeProps = StringMap{} |  | ||||||
| 	u.LastPasswordUpdate = 0 | 	u.LastPasswordUpdate = 0 | ||||||
| 	u.LastPictureUpdate = 0 | 	u.LastPictureUpdate = 0 | ||||||
| 	u.FailedAttempts = 0 | 	u.FailedAttempts = 0 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (u *User) SanitizeProfile(options map[string]bool) { | ||||||
|  | 	u.ClearNonProfileFields() | ||||||
|  |  | ||||||
|  | 	u.Sanitize(options) | ||||||
|  | } | ||||||
|  |  | ||||||
| func (u *User) MakeNonNil() { | func (u *User) MakeNonNil() { | ||||||
| 	if u.Props == nil { | 	if u.Props == nil { | ||||||
| 		u.Props = make(map[string]string) | 		u.Props = make(map[string]string) | ||||||
| @@ -332,6 +301,24 @@ func (u *User) GetDisplayName() string { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (u *User) GetDisplayNameForPreference(nameFormat string) string { | ||||||
|  | 	displayName := u.Username | ||||||
|  |  | ||||||
|  | 	if nameFormat == PREFERENCE_VALUE_DISPLAY_NAME_NICKNAME { | ||||||
|  | 		if u.Nickname != "" { | ||||||
|  | 			displayName = u.Nickname | ||||||
|  | 		} else if fullName := u.GetFullName(); fullName != "" { | ||||||
|  | 			displayName = fullName | ||||||
|  | 		} | ||||||
|  | 	} else if nameFormat == PREFERENCE_VALUE_DISPLAY_NAME_FULL { | ||||||
|  | 		if fullName := u.GetFullName(); fullName != "" { | ||||||
|  | 			displayName = fullName | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return displayName | ||||||
|  | } | ||||||
|  |  | ||||||
| func IsValidUserRoles(userRoles string) bool { | func IsValidUserRoles(userRoles string) bool { | ||||||
|  |  | ||||||
| 	roles := strings.Split(userRoles, " ") | 	roles := strings.Split(userRoles, " ") | ||||||
| @@ -392,17 +379,6 @@ func (u *User) IsLDAPUser() bool { | |||||||
| 	return false | 	return false | ||||||
| } | } | ||||||
|  |  | ||||||
| func (u *User) PreExport() { |  | ||||||
| 	u.Password = "" |  | ||||||
| 	u.AuthData = new(string) |  | ||||||
| 	*u.AuthData = "" |  | ||||||
| 	u.LastActivityAt = 0 |  | ||||||
| 	u.LastPingAt = 0 |  | ||||||
| 	u.LastPasswordUpdate = 0 |  | ||||||
| 	u.LastPictureUpdate = 0 |  | ||||||
| 	u.FailedAttempts = 0 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // UserFromJson will decode the input and return a User | // UserFromJson will decode the input and return a User | ||||||
| func UserFromJson(data io.Reader) *User { | func UserFromJson(data io.Reader) *User { | ||||||
| 	decoder := json.NewDecoder(data) | 	decoder := json.NewDecoder(data) | ||||||
| @@ -461,6 +437,7 @@ var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`) | |||||||
| var restrictedUsernames = []string{ | var restrictedUsernames = []string{ | ||||||
| 	"all", | 	"all", | ||||||
| 	"channel", | 	"channel", | ||||||
|  | 	"matterbot", | ||||||
| } | } | ||||||
|  |  | ||||||
| func IsValidUsername(s string) bool { | func IsValidUsername(s string) bool { | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -34,12 +34,12 @@ type EncryptStringMap map[string]string | |||||||
|  |  | ||||||
| type AppError struct { | type AppError struct { | ||||||
| 	Id            string                 `json:"id"` | 	Id            string                 `json:"id"` | ||||||
| 	Message       string                 `json:"message"`        // Message to be display to the end user without debugging information | 	Message       string                 `json:"message"`               // Message to be display to the end user without debugging information | ||||||
| 	DetailedError string                 `json:"detailed_error"` // Internal error string to help the developer | 	DetailedError string                 `json:"detailed_error"`        // Internal error string to help the developer | ||||||
| 	RequestId     string                 `json:"request_id"`     // The RequestId that's also set in the header | 	RequestId     string                 `json:"request_id,omitempty"`  // The RequestId that's also set in the header | ||||||
| 	StatusCode    int                    `json:"status_code"`    // The http status code | 	StatusCode    int                    `json:"status_code,omitempty"` // The http status code | ||||||
| 	Where         string                 `json:"-"`              // The function where it happened in the form of Struct.Func | 	Where         string                 `json:"-"`                     // The function where it happened in the form of Struct.Func | ||||||
| 	IsOAuth       bool                   `json:"is_oauth"`       // Whether the error is OAuth specific | 	IsOAuth       bool                   `json:"is_oauth,omitempty"`    // Whether the error is OAuth specific | ||||||
| 	params        map[string]interface{} `json:"-"` | 	params        map[string]interface{} `json:"-"` | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -13,6 +13,8 @@ import ( | |||||||
| // It should be maitained in chronological order with most current | // It should be maitained in chronological order with most current | ||||||
| // release at the front of the list. | // release at the front of the list. | ||||||
| var versions = []string{ | var versions = []string{ | ||||||
|  | 	"3.4.0", | ||||||
|  | 	"3.3.0", | ||||||
| 	"3.2.0", | 	"3.2.0", | ||||||
| 	"3.1.0", | 	"3.1.0", | ||||||
| 	"3.0.0", | 	"3.0.0", | ||||||
|   | |||||||
							
								
								
									
										109
									
								
								vendor/github.com/mattermost/platform/model/websocket_client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								vendor/github.com/mattermost/platform/model/websocket_client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||||
|  | // See License.txt for license information. | ||||||
|  |  | ||||||
|  | package model | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"github.com/gorilla/websocket" | ||||||
|  | 	"net/http" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type WebSocketClient struct { | ||||||
|  | 	Url             string          // The location of the server like "ws://localhost:8065" | ||||||
|  | 	ApiUrl          string          // The api location of the server like "ws://localhost:8065/api/v3" | ||||||
|  | 	Conn            *websocket.Conn // The WebSocket connection | ||||||
|  | 	AuthToken       string          // The token used to open the WebSocket | ||||||
|  | 	Sequence        int64           // The ever-incrementing sequence attached to each WebSocket action | ||||||
|  | 	EventChannel    chan *WebSocketEvent | ||||||
|  | 	ResponseChannel chan *WebSocketResponse | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewWebSocketClient constructs a new WebSocket client with convienence | ||||||
|  | // methods for talking to the server. | ||||||
|  | func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) { | ||||||
|  | 	header := http.Header{} | ||||||
|  | 	header.Set(HEADER_AUTH, "BEARER "+authToken) | ||||||
|  | 	conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", header) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &WebSocketClient{ | ||||||
|  | 		url, | ||||||
|  | 		url + API_URL_SUFFIX, | ||||||
|  | 		conn, | ||||||
|  | 		authToken, | ||||||
|  | 		1, | ||||||
|  | 		make(chan *WebSocketEvent, 100), | ||||||
|  | 		make(chan *WebSocketResponse, 100), | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (wsc *WebSocketClient) Connect() *AppError { | ||||||
|  | 	header := http.Header{} | ||||||
|  | 	header.Set(HEADER_AUTH, "BEARER "+wsc.AuthToken) | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  | 	wsc.Conn, _, err = websocket.DefaultDialer.Dial(wsc.ApiUrl+"/users/websocket", header) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (wsc *WebSocketClient) Close() { | ||||||
|  | 	wsc.Conn.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (wsc *WebSocketClient) Listen() { | ||||||
|  | 	go func() { | ||||||
|  | 		for { | ||||||
|  | 			var rawMsg json.RawMessage | ||||||
|  | 			var err error | ||||||
|  | 			if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			var event WebSocketEvent | ||||||
|  | 			if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { | ||||||
|  | 				wsc.EventChannel <- &event | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			var response WebSocketResponse | ||||||
|  | 			if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { | ||||||
|  | 				wsc.ResponseChannel <- &response | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (wsc *WebSocketClient) SendMessage(action string, data map[string]interface{}) { | ||||||
|  | 	req := &WebSocketRequest{} | ||||||
|  | 	req.Seq = wsc.Sequence | ||||||
|  | 	req.Action = action | ||||||
|  | 	req.Data = data | ||||||
|  |  | ||||||
|  | 	wsc.Sequence++ | ||||||
|  |  | ||||||
|  | 	wsc.Conn.WriteJSON(req) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UserTyping will push a user_typing event out to all connected users | ||||||
|  | // who are in the specified channel | ||||||
|  | func (wsc *WebSocketClient) UserTyping(channelId, parentId string) { | ||||||
|  | 	data := map[string]interface{}{ | ||||||
|  | 		"channel_id": channelId, | ||||||
|  | 		"parent_id":  parentId, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wsc.SendMessage("user_typing", data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetStatuses will return a map of string statuses using user id as the key | ||||||
|  | func (wsc *WebSocketClient) GetStatuses() { | ||||||
|  | 	wsc.SendMessage("get_statuses", nil) | ||||||
|  | } | ||||||
							
								
								
									
										116
									
								
								vendor/github.com/mattermost/platform/model/websocket_message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								vendor/github.com/mattermost/platform/model/websocket_message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | |||||||
|  | // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||||
|  | // See License.txt for license information. | ||||||
|  |  | ||||||
|  | package model | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	WEBSOCKET_EVENT_TYPING             = "typing" | ||||||
|  | 	WEBSOCKET_EVENT_POSTED             = "posted" | ||||||
|  | 	WEBSOCKET_EVENT_POST_EDITED        = "post_edited" | ||||||
|  | 	WEBSOCKET_EVENT_POST_DELETED       = "post_deleted" | ||||||
|  | 	WEBSOCKET_EVENT_CHANNEL_DELETED    = "channel_deleted" | ||||||
|  | 	WEBSOCKET_EVENT_CHANNEL_VIEWED     = "channel_viewed" | ||||||
|  | 	WEBSOCKET_EVENT_DIRECT_ADDED       = "direct_added" | ||||||
|  | 	WEBSOCKET_EVENT_NEW_USER           = "new_user" | ||||||
|  | 	WEBSOCKET_EVENT_LEAVE_TEAM         = "leave_team" | ||||||
|  | 	WEBSOCKET_EVENT_USER_ADDED         = "user_added" | ||||||
|  | 	WEBSOCKET_EVENT_USER_UPDATED       = "user_updated" | ||||||
|  | 	WEBSOCKET_EVENT_USER_REMOVED       = "user_removed" | ||||||
|  | 	WEBSOCKET_EVENT_PREFERENCE_CHANGED = "preference_changed" | ||||||
|  | 	WEBSOCKET_EVENT_EPHEMERAL_MESSAGE  = "ephemeral_message" | ||||||
|  | 	WEBSOCKET_EVENT_STATUS_CHANGE      = "status_change" | ||||||
|  | 	WEBSOCKET_EVENT_HELLO              = "hello" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type WebSocketMessage interface { | ||||||
|  | 	ToJson() string | ||||||
|  | 	IsValid() bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type WebSocketEvent struct { | ||||||
|  | 	TeamId    string                 `json:"team_id"` | ||||||
|  | 	ChannelId string                 `json:"channel_id"` | ||||||
|  | 	UserId    string                 `json:"user_id"` | ||||||
|  | 	Event     string                 `json:"event"` | ||||||
|  | 	Data      map[string]interface{} `json:"data"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *WebSocketEvent) Add(key string, value interface{}) { | ||||||
|  | 	m.Data[key] = value | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewWebSocketEvent(teamId string, channelId string, userId string, event string) *WebSocketEvent { | ||||||
|  | 	return &WebSocketEvent{TeamId: teamId, ChannelId: channelId, UserId: userId, Event: event, Data: make(map[string]interface{})} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *WebSocketEvent) IsValid() bool { | ||||||
|  | 	return o.Event != "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *WebSocketEvent) ToJson() string { | ||||||
|  | 	b, err := json.Marshal(o) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} else { | ||||||
|  | 		return string(b) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func WebSocketEventFromJson(data io.Reader) *WebSocketEvent { | ||||||
|  | 	decoder := json.NewDecoder(data) | ||||||
|  | 	var o WebSocketEvent | ||||||
|  | 	err := decoder.Decode(&o) | ||||||
|  | 	if err == nil { | ||||||
|  | 		return &o | ||||||
|  | 	} else { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type WebSocketResponse struct { | ||||||
|  | 	Status   string                 `json:"status"` | ||||||
|  | 	SeqReply int64                  `json:"seq_reply,omitempty"` | ||||||
|  | 	Data     map[string]interface{} `json:"data,omitempty"` | ||||||
|  | 	Error    *AppError              `json:"error,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *WebSocketResponse) Add(key string, value interface{}) { | ||||||
|  | 	m.Data[key] = value | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewWebSocketResponse(status string, seqReply int64, data map[string]interface{}) *WebSocketResponse { | ||||||
|  | 	return &WebSocketResponse{Status: status, SeqReply: seqReply, Data: data} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewWebSocketError(seqReply int64, err *AppError) *WebSocketResponse { | ||||||
|  | 	return &WebSocketResponse{Status: STATUS_FAIL, SeqReply: seqReply, Error: err} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *WebSocketResponse) IsValid() bool { | ||||||
|  | 	return o.Status != "" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *WebSocketResponse) ToJson() string { | ||||||
|  | 	b, err := json.Marshal(o) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} else { | ||||||
|  | 		return string(b) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse { | ||||||
|  | 	decoder := json.NewDecoder(data) | ||||||
|  | 	var o WebSocketResponse | ||||||
|  | 	err := decoder.Decode(&o) | ||||||
|  | 	if err == nil { | ||||||
|  | 		return &o | ||||||
|  | 	} else { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								vendor/github.com/mattermost/platform/model/websocket_request.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								vendor/github.com/mattermost/platform/model/websocket_request.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||||
|  | // See License.txt for license information. | ||||||
|  |  | ||||||
|  | package model | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  |  | ||||||
|  | 	goi18n "github.com/nicksnyder/go-i18n/i18n" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type WebSocketRequest struct { | ||||||
|  | 	// Client-provided fields | ||||||
|  | 	Seq    int64                  `json:"seq"` | ||||||
|  | 	Action string                 `json:"action"` | ||||||
|  | 	Data   map[string]interface{} `json:"data"` | ||||||
|  |  | ||||||
|  | 	// Server-provided fields | ||||||
|  | 	Session Session              `json:"-"` | ||||||
|  | 	T       goi18n.TranslateFunc `json:"-"` | ||||||
|  | 	Locale  string               `json:"-"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *WebSocketRequest) ToJson() string { | ||||||
|  | 	b, err := json.Marshal(o) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} else { | ||||||
|  | 		return string(b) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func WebSocketRequestFromJson(data io.Reader) *WebSocketRequest { | ||||||
|  | 	decoder := json.NewDecoder(data) | ||||||
|  | 	var o WebSocketRequest | ||||||
|  | 	err := decoder.Decode(&o) | ||||||
|  | 	if err == nil { | ||||||
|  | 		return &o | ||||||
|  | 	} else { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/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. | ||||||
							
								
								
									
										881
									
								
								vendor/github.com/mattn/go-xmpp/xmpp.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										881
									
								
								vendor/github.com/mattn/go-xmpp/xmpp.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,881 @@ | |||||||
|  | // 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. | ||||||
|  |  | ||||||
|  | // TODO(rsc): | ||||||
|  | //	More precise error handling. | ||||||
|  | //	Presence functionality. | ||||||
|  | // TODO(mattn): | ||||||
|  | //  Add proxy authentication. | ||||||
|  |  | ||||||
|  | // Package xmpp implements a simple Google Talk client | ||||||
|  | // using the XMPP protocol described in RFC 3920 and RFC 3921. | ||||||
|  | package xmpp | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"bytes" | ||||||
|  | 	"crypto/md5" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"encoding/xml" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"math/big" | ||||||
|  | 	"net" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	nsStream  = "http://etherx.jabber.org/streams" | ||||||
|  | 	nsTLS     = "urn:ietf:params:xml:ns:xmpp-tls" | ||||||
|  | 	nsSASL    = "urn:ietf:params:xml:ns:xmpp-sasl" | ||||||
|  | 	nsBind    = "urn:ietf:params:xml:ns:xmpp-bind" | ||||||
|  | 	nsClient  = "jabber:client" | ||||||
|  | 	nsSession = "urn:ietf:params:xml:ns:xmpp-session" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Default TLS configuration options | ||||||
|  | var DefaultConfig tls.Config | ||||||
|  |  | ||||||
|  | // Cookie is a unique XMPP session identifier | ||||||
|  | type Cookie uint64 | ||||||
|  |  | ||||||
|  | func getCookie() Cookie { | ||||||
|  | 	var buf [8]byte | ||||||
|  | 	if _, err := rand.Reader.Read(buf[:]); err != nil { | ||||||
|  | 		panic("Failed to read random bytes: " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	return Cookie(binary.LittleEndian.Uint64(buf[:])) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Client holds XMPP connection opitons | ||||||
|  | type Client struct { | ||||||
|  | 	conn   net.Conn // connection to server | ||||||
|  | 	jid    string   // Jabber ID for our connection | ||||||
|  | 	domain string | ||||||
|  | 	p      *xml.Decoder | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) JID() string { | ||||||
|  | 	return c.jid | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func connect(host, user, passwd string) (net.Conn, error) { | ||||||
|  | 	addr := host | ||||||
|  |  | ||||||
|  | 	if strings.TrimSpace(host) == "" { | ||||||
|  | 		a := strings.SplitN(user, "@", 2) | ||||||
|  | 		if len(a) == 2 { | ||||||
|  | 			addr = a[1] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	a := strings.SplitN(host, ":", 2) | ||||||
|  | 	if len(a) == 1 { | ||||||
|  | 		addr += ":5222" | ||||||
|  | 	} | ||||||
|  | 	proxy := os.Getenv("HTTP_PROXY") | ||||||
|  | 	if proxy == "" { | ||||||
|  | 		proxy = os.Getenv("http_proxy") | ||||||
|  | 	} | ||||||
|  | 	if proxy != "" { | ||||||
|  | 		url, err := url.Parse(proxy) | ||||||
|  | 		if err == nil { | ||||||
|  | 			addr = url.Host | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	c, err := net.Dial("tcp", addr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if proxy != "" { | ||||||
|  | 		fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\n", host) | ||||||
|  | 		fmt.Fprintf(c, "Host: %s\r\n", host) | ||||||
|  | 		fmt.Fprintf(c, "\r\n") | ||||||
|  | 		br := bufio.NewReader(c) | ||||||
|  | 		req, _ := http.NewRequest("CONNECT", host, nil) | ||||||
|  | 		resp, err := http.ReadResponse(br, req) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if resp.StatusCode != 200 { | ||||||
|  | 			f := strings.SplitN(resp.Status, " ", 2) | ||||||
|  | 			return nil, errors.New(f[1]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return c, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Options are used to specify additional options for new clients, such as a Resource. | ||||||
|  | type Options struct { | ||||||
|  | 	// Host specifies what host to connect to, as either "hostname" or "hostname:port" | ||||||
|  | 	// If host is not specified, the  DNS SRV should be used to find the host from the domainpart of the JID. | ||||||
|  | 	// Default the port to 5222. | ||||||
|  | 	Host string | ||||||
|  |  | ||||||
|  | 	// User specifies what user to authenticate to the remote server. | ||||||
|  | 	User string | ||||||
|  |  | ||||||
|  | 	// Password supplies the password to use for authentication with the remote server. | ||||||
|  | 	Password string | ||||||
|  |  | ||||||
|  | 	// Resource specifies an XMPP client resource, like "bot", instead of accepting one | ||||||
|  | 	// from the server.  Use "" to let the server generate one for your client. | ||||||
|  | 	Resource string | ||||||
|  |  | ||||||
|  | 	// OAuthScope provides go-xmpp the required scope for OAuth2 authentication. | ||||||
|  | 	OAuthScope string | ||||||
|  |  | ||||||
|  | 	// OAuthToken provides go-xmpp with the required OAuth2 token used to authenticate | ||||||
|  | 	OAuthToken string | ||||||
|  |  | ||||||
|  | 	// OAuthXmlNs provides go-xmpp with the required namespaced used for OAuth2 authentication.  This is | ||||||
|  | 	// provided to the server as the xmlns:auth attribute of the OAuth2 authentication request. | ||||||
|  | 	OAuthXmlNs string | ||||||
|  |  | ||||||
|  | 	// TLS Config | ||||||
|  | 	TLSConfig *tls.Config | ||||||
|  |  | ||||||
|  | 	// InsecureAllowUnencryptedAuth permits authentication over a TCP connection that has not been promoted to | ||||||
|  | 	// TLS by STARTTLS; this could leak authentication information over the network, or permit man in the middle | ||||||
|  | 	// attacks. | ||||||
|  | 	InsecureAllowUnencryptedAuth bool | ||||||
|  |  | ||||||
|  | 	// NoTLS directs go-xmpp to not use TLS initially to contact the server; instead, a plain old unencrypted | ||||||
|  | 	// TCP connection should be used. (Can be combined with StartTLS to support STARTTLS-based servers.) | ||||||
|  | 	NoTLS bool | ||||||
|  |  | ||||||
|  | 	// StartTLS directs go-xmpp to STARTTLS if the server supports it; go-xmpp will automatically STARTTLS | ||||||
|  | 	// if the server requires it regardless of this option. | ||||||
|  | 	StartTLS bool | ||||||
|  |  | ||||||
|  | 	// Debug output | ||||||
|  | 	Debug bool | ||||||
|  |  | ||||||
|  | 	// Use server sessions | ||||||
|  | 	Session bool | ||||||
|  |  | ||||||
|  | 	// Presence Status | ||||||
|  | 	Status string | ||||||
|  |  | ||||||
|  | 	// Status message | ||||||
|  | 	StatusMessage string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewClient establishes a new Client connection based on a set of Options. | ||||||
|  | func (o Options) NewClient() (*Client, error) { | ||||||
|  | 	host := o.Host | ||||||
|  | 	c, err := connect(host, o.User, o.Password) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if strings.LastIndex(o.Host, ":") > 0 { | ||||||
|  | 		host = host[:strings.LastIndex(o.Host, ":")] | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	client := new(Client) | ||||||
|  | 	if o.NoTLS { | ||||||
|  | 		client.conn = c | ||||||
|  | 	} else { | ||||||
|  | 		var tlsconn *tls.Conn | ||||||
|  | 		if o.TLSConfig != nil { | ||||||
|  | 			tlsconn = tls.Client(c, o.TLSConfig) | ||||||
|  | 		} else { | ||||||
|  | 			DefaultConfig.ServerName = host | ||||||
|  | 			tlsconn = tls.Client(c, &DefaultConfig) | ||||||
|  | 		} | ||||||
|  | 		if err = tlsconn.Handshake(); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		insecureSkipVerify := DefaultConfig.InsecureSkipVerify | ||||||
|  | 		if o.TLSConfig != nil { | ||||||
|  | 			insecureSkipVerify = o.TLSConfig.InsecureSkipVerify | ||||||
|  | 		} | ||||||
|  | 		if !insecureSkipVerify { | ||||||
|  | 			if err = tlsconn.VerifyHostname(host); err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		client.conn = tlsconn | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := client.init(&o); err != nil { | ||||||
|  | 		client.Close() | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return client, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewClient creates a new connection to a host given as "hostname" or "hostname:port". | ||||||
|  | // If host is not specified, the  DNS SRV should be used to find the host from the domainpart of the JID. | ||||||
|  | // Default the port to 5222. | ||||||
|  | func NewClient(host, user, passwd string, debug bool) (*Client, error) { | ||||||
|  | 	opts := Options{ | ||||||
|  | 		Host:     host, | ||||||
|  | 		User:     user, | ||||||
|  | 		Password: passwd, | ||||||
|  | 		Debug:    debug, | ||||||
|  | 		Session:  false, | ||||||
|  | 	} | ||||||
|  | 	return opts.NewClient() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewClientNoTLS creates a new client without TLS | ||||||
|  | func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) { | ||||||
|  | 	opts := Options{ | ||||||
|  | 		Host:     host, | ||||||
|  | 		User:     user, | ||||||
|  | 		Password: passwd, | ||||||
|  | 		NoTLS:    true, | ||||||
|  | 		Debug:    debug, | ||||||
|  | 		Session:  false, | ||||||
|  | 	} | ||||||
|  | 	return opts.NewClient() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close closes the XMPP connection | ||||||
|  | func (c *Client) Close() error { | ||||||
|  | 	if c.conn != (*tls.Conn)(nil) { | ||||||
|  | 		return c.conn.Close() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string { | ||||||
|  | 	h := func(text string) []byte { | ||||||
|  | 		h := md5.New() | ||||||
|  | 		h.Write([]byte(text)) | ||||||
|  | 		return h.Sum(nil) | ||||||
|  | 	} | ||||||
|  | 	hex := func(bytes []byte) string { | ||||||
|  | 		return fmt.Sprintf("%x", bytes) | ||||||
|  | 	} | ||||||
|  | 	kd := func(secret, data string) []byte { | ||||||
|  | 		return h(secret + ":" + data) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr | ||||||
|  | 	a2 := authenticate + ":" + digestURI | ||||||
|  | 	response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2)))) | ||||||
|  | 	return response | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func cnonce() string { | ||||||
|  | 	randSize := big.NewInt(0) | ||||||
|  | 	randSize.Lsh(big.NewInt(1), 64) | ||||||
|  | 	cn, err := rand.Int(rand.Reader, randSize) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  | 	return fmt.Sprintf("%016x", cn) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) init(o *Options) error { | ||||||
|  |  | ||||||
|  | 	var domain string | ||||||
|  | 	var user string | ||||||
|  | 	a := strings.SplitN(o.User, "@", 2) | ||||||
|  | 	if len(o.User) > 0 { | ||||||
|  | 		if len(a) != 2 { | ||||||
|  | 			return errors.New("xmpp: invalid username (want user@domain): " + o.User) | ||||||
|  | 		} | ||||||
|  | 		user = a[0] | ||||||
|  | 		domain = a[1] | ||||||
|  | 	} // Otherwise, we'll be attempting ANONYMOUS | ||||||
|  |  | ||||||
|  | 	// Declare intent to be a jabber client and gather stream features. | ||||||
|  | 	f, err := c.startStream(o, domain) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If the server requires we STARTTLS, attempt to do so. | ||||||
|  | 	if f, err = c.startTLSIfRequired(f, o, domain); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if o.User == "" && o.Password == "" { | ||||||
|  | 		foundAnonymous := false | ||||||
|  | 		for _, m := range f.Mechanisms.Mechanism { | ||||||
|  | 			if m == "ANONYMOUS" { | ||||||
|  | 				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='ANONYMOUS' />\n", nsSASL) | ||||||
|  | 				foundAnonymous = true | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if !foundAnonymous { | ||||||
|  | 			return fmt.Errorf("ANONYMOUS authentication is not an option and username and password were not specified") | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		// Even digest forms of authentication are unsafe if we do not know that the host | ||||||
|  | 		// we are talking to is the actual server, and not a man in the middle playing | ||||||
|  | 		// proxy. | ||||||
|  | 		if !c.IsEncrypted() && !o.InsecureAllowUnencryptedAuth { | ||||||
|  | 			return errors.New("refusing to authenticate over unencrypted TCP connection") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		mechanism := "" | ||||||
|  | 		for _, m := range f.Mechanisms.Mechanism { | ||||||
|  | 			if m == "X-OAUTH2" && o.OAuthToken != "" && o.OAuthScope != "" { | ||||||
|  | 				mechanism = m | ||||||
|  | 				// Oauth authentication: send base64-encoded \x00 user \x00 token. | ||||||
|  | 				raw := "\x00" + user + "\x00" + o.OAuthToken | ||||||
|  | 				enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw))) | ||||||
|  | 				base64.StdEncoding.Encode(enc, []byte(raw)) | ||||||
|  | 				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='X-OAUTH2' auth:service='oauth2' "+ | ||||||
|  | 					"xmlns:auth='%s'>%s</auth>\n", nsSASL, o.OAuthXmlNs, enc) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			if m == "PLAIN" { | ||||||
|  | 				mechanism = m | ||||||
|  | 				// Plain authentication: send base64-encoded \x00 user \x00 password. | ||||||
|  | 				raw := "\x00" + user + "\x00" + o.Password | ||||||
|  | 				enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw))) | ||||||
|  | 				base64.StdEncoding.Encode(enc, []byte(raw)) | ||||||
|  | 				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n", nsSASL, enc) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			if m == "DIGEST-MD5" { | ||||||
|  | 				mechanism = m | ||||||
|  | 				// Digest-MD5 authentication | ||||||
|  | 				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", nsSASL) | ||||||
|  | 				var ch saslChallenge | ||||||
|  | 				if err = c.p.DecodeElement(&ch, nil); err != nil { | ||||||
|  | 					return errors.New("unmarshal <challenge>: " + err.Error()) | ||||||
|  | 				} | ||||||
|  | 				b, err := base64.StdEncoding.DecodeString(string(ch)) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				tokens := map[string]string{} | ||||||
|  | 				for _, token := range strings.Split(string(b), ",") { | ||||||
|  | 					kv := strings.SplitN(strings.TrimSpace(token), "=", 2) | ||||||
|  | 					if len(kv) == 2 { | ||||||
|  | 						if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' { | ||||||
|  | 							kv[1] = kv[1][1 : len(kv[1])-1] | ||||||
|  | 						} | ||||||
|  | 						tokens[kv[0]] = kv[1] | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				realm, _ := tokens["realm"] | ||||||
|  | 				nonce, _ := tokens["nonce"] | ||||||
|  | 				qop, _ := tokens["qop"] | ||||||
|  | 				charset, _ := tokens["charset"] | ||||||
|  | 				cnonceStr := cnonce() | ||||||
|  | 				digestURI := "xmpp/" + domain | ||||||
|  | 				nonceCount := fmt.Sprintf("%08x", 1) | ||||||
|  | 				digest := saslDigestResponse(user, realm, o.Password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount) | ||||||
|  | 				message := "username=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr + | ||||||
|  | 					"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset | ||||||
|  |  | ||||||
|  | 				fmt.Fprintf(c.conn, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message))) | ||||||
|  |  | ||||||
|  | 				var rspauth saslRspAuth | ||||||
|  | 				if err = c.p.DecodeElement(&rspauth, nil); err != nil { | ||||||
|  | 					return errors.New("unmarshal <challenge>: " + err.Error()) | ||||||
|  | 				} | ||||||
|  | 				b, err = base64.StdEncoding.DecodeString(string(rspauth)) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				fmt.Fprintf(c.conn, "<response xmlns='%s'/>\n", nsSASL) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		if mechanism == "" { | ||||||
|  | 			return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// Next message should be either success or failure. | ||||||
|  | 	name, val, err := next(c.p) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	switch v := val.(type) { | ||||||
|  | 	case *saslSuccess: | ||||||
|  | 	case *saslFailure: | ||||||
|  | 		// v.Any is type of sub-element in failure, | ||||||
|  | 		// which gives a description of what failed. | ||||||
|  | 		return errors.New("auth failure: " + v.Any.Local) | ||||||
|  | 	default: | ||||||
|  | 		return errors.New("expected <success> or <failure>, got <" + name.Local + "> in " + name.Space) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Now that we're authenticated, we're supposed to start the stream over again. | ||||||
|  | 	// Declare intent to be a jabber client. | ||||||
|  | 	if f, err = c.startStream(o, domain); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Generate a unique cookie | ||||||
|  | 	cookie := getCookie() | ||||||
|  |  | ||||||
|  | 	// Send IQ message asking to bind to the local user name. | ||||||
|  | 	if o.Resource == "" { | ||||||
|  | 		fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'></bind></iq>\n", cookie, nsBind) | ||||||
|  | 	} else { | ||||||
|  | 		fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'><resource>%s</resource></bind></iq>\n", cookie, nsBind, o.Resource) | ||||||
|  | 	} | ||||||
|  | 	var iq clientIQ | ||||||
|  | 	if err = c.p.DecodeElement(&iq, nil); err != nil { | ||||||
|  | 		return errors.New("unmarshal <iq>: " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	if &iq.Bind == nil { | ||||||
|  | 		return errors.New("<iq> result missing <bind>") | ||||||
|  | 	} | ||||||
|  | 	c.jid = iq.Bind.Jid // our local id | ||||||
|  | 	c.domain = domain | ||||||
|  |  | ||||||
|  | 	if o.Session { | ||||||
|  | 		//if server support session, open it | ||||||
|  | 		fmt.Fprintf(c.conn, "<iq to='%s' type='set' id='%x'><session xmlns='%s'/></iq>", xmlEscape(domain), cookie, nsSession) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// We're connected and can now receive and send messages. | ||||||
|  | 	fmt.Fprintf(c.conn, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", o.Status, o.StatusMessage) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // startTlsIfRequired examines the server's stream features and, if STARTTLS is required or supported, performs the TLS handshake. | ||||||
|  | // f will be updated if the handshake completes, as the new stream's features are typically different from the original. | ||||||
|  | func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string) (*streamFeatures, error) { | ||||||
|  | 	// whether we start tls is a matter of opinion: the server's and the user's. | ||||||
|  | 	switch { | ||||||
|  | 	case f.StartTLS == nil: | ||||||
|  | 		// the server does not support STARTTLS | ||||||
|  | 		return f, nil | ||||||
|  | 	case f.StartTLS.Required != nil: | ||||||
|  | 		// the server requires STARTTLS. | ||||||
|  | 	case !o.StartTLS: | ||||||
|  | 		// the user wants STARTTLS and the server supports it. | ||||||
|  | 	} | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	fmt.Fprintf(c.conn, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n") | ||||||
|  | 	var k tlsProceed | ||||||
|  | 	if err = c.p.DecodeElement(&k, nil); err != nil { | ||||||
|  | 		return f, errors.New("unmarshal <proceed>: " + err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tc := o.TLSConfig | ||||||
|  | 	if tc == nil { | ||||||
|  | 		tc = new(tls.Config) | ||||||
|  | 		*tc = DefaultConfig | ||||||
|  | 		//TODO(scott): we should consider using the server's address or reverse lookup | ||||||
|  | 		tc.ServerName = domain | ||||||
|  | 	} | ||||||
|  | 	t := tls.Client(c.conn, tc) | ||||||
|  |  | ||||||
|  | 	if err = t.Handshake(); err != nil { | ||||||
|  | 		return f, errors.New("starttls handshake: " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	c.conn = t | ||||||
|  |  | ||||||
|  | 	// restart our declaration of XMPP stream intentions. | ||||||
|  | 	tf, err := c.startStream(o, domain) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return f, err | ||||||
|  | 	} | ||||||
|  | 	return tf, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // startStream will start a new XML decoder for the connection, signal the start of a stream to the server and verify that the server has | ||||||
|  | // also started the stream; if o.Debug is true, startStream will tee decoded XML data to stderr.  The features advertised by the server | ||||||
|  | // will be returned. | ||||||
|  | func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) { | ||||||
|  | 	if o.Debug { | ||||||
|  | 		c.p = xml.NewDecoder(tee{c.conn, os.Stderr}) | ||||||
|  | 	} else { | ||||||
|  | 		c.p = xml.NewDecoder(c.conn) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := fmt.Fprintf(c.conn, "<?xml version='1.0'?>\n"+ | ||||||
|  | 		"<stream:stream to='%s' xmlns='%s'\n"+ | ||||||
|  | 		" xmlns:stream='%s' version='1.0'>\n", | ||||||
|  | 		xmlEscape(domain), nsClient, nsStream) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// We expect the server to start a <stream>. | ||||||
|  | 	se, err := nextStart(c.p) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if se.Name.Space != nsStream || se.Name.Local != "stream" { | ||||||
|  | 		return nil, fmt.Errorf("expected <stream> but got <%v> in %v", se.Name.Local, se.Name.Space) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Now we're in the stream and can use Unmarshal. | ||||||
|  | 	// Next message should be <features> to tell us authentication options. | ||||||
|  | 	// See section 4.6 in RFC 3920. | ||||||
|  | 	f := new(streamFeatures) | ||||||
|  | 	if err = c.p.DecodeElement(f, nil); err != nil { | ||||||
|  | 		return f, errors.New("unmarshal <features>: " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	return f, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IsEncrypted will return true if the client is connected using a TLS transport, either because it used. | ||||||
|  | // TLS to connect from the outset, or because it successfully used STARTTLS to promote a TCP connection to TLS. | ||||||
|  | func (c *Client) IsEncrypted() bool { | ||||||
|  | 	_, ok := c.conn.(*tls.Conn) | ||||||
|  | 	return ok | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Chat is an incoming or outgoing XMPP chat message. | ||||||
|  | type Chat struct { | ||||||
|  | 	Remote string | ||||||
|  | 	Type   string | ||||||
|  | 	Text   string | ||||||
|  | 	Roster Roster | ||||||
|  | 	Other  []string | ||||||
|  | 	Stamp  time.Time | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Roster []Contact | ||||||
|  |  | ||||||
|  | type Contact struct { | ||||||
|  | 	Remote string | ||||||
|  | 	Name   string | ||||||
|  | 	Group  []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Presence is an XMPP presence notification. | ||||||
|  | type Presence struct { | ||||||
|  | 	From   string | ||||||
|  | 	To     string | ||||||
|  | 	Type   string | ||||||
|  | 	Show   string | ||||||
|  | 	Status string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type IQ struct { | ||||||
|  | 	ID    string | ||||||
|  | 	From  string | ||||||
|  | 	To    string | ||||||
|  | 	Type  string | ||||||
|  | 	Query []byte | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Recv waits to receive the next XMPP stanza. | ||||||
|  | // Return type is either a presence notification or a chat message. | ||||||
|  | func (c *Client) Recv() (stanza interface{}, err error) { | ||||||
|  | 	for { | ||||||
|  | 		_, val, err := next(c.p) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return Chat{}, err | ||||||
|  | 		} | ||||||
|  | 		switch v := val.(type) { | ||||||
|  | 		case *clientMessage: | ||||||
|  | 			stamp, _ := time.Parse( | ||||||
|  | 				"2006-01-02T15:04:05Z", | ||||||
|  | 				v.Delay.Stamp, | ||||||
|  | 			) | ||||||
|  | 			chat := Chat{ | ||||||
|  | 				Remote: v.From, | ||||||
|  | 				Type:   v.Type, | ||||||
|  | 				Text:   v.Body, | ||||||
|  | 				Other:  v.Other, | ||||||
|  | 				Stamp:  stamp, | ||||||
|  | 			} | ||||||
|  | 			return chat, nil | ||||||
|  | 		case *clientQuery: | ||||||
|  | 			var r Roster | ||||||
|  | 			for _, item := range v.Item { | ||||||
|  | 				r = append(r, Contact{item.Jid, item.Name, item.Group}) | ||||||
|  | 			} | ||||||
|  | 			return Chat{Type: "roster", Roster: r}, nil | ||||||
|  | 		case *clientPresence: | ||||||
|  | 			return Presence{v.From, v.To, v.Type, v.Show, v.Status}, nil | ||||||
|  | 		case *clientIQ: | ||||||
|  | 			return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Send sends the message wrapped inside an XMPP message stanza body. | ||||||
|  | func (c *Client) Send(chat Chat) (n int, err error) { | ||||||
|  | 	return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<body>%s</body></message>", | ||||||
|  | 		xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SendOrg sends the original text without being wrapped in an XMPP message stanza. | ||||||
|  | func (c *Client) SendOrg(org string) (n int, err error) { | ||||||
|  | 	return fmt.Fprint(c.conn, org) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) SendPresence(presence Presence) (n int, err error) { | ||||||
|  | 	return fmt.Fprintf(c.conn, "<presence from='%s' to='%s'/>", xmlEscape(presence.From), xmlEscape(presence.To)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SendHtml sends the message as HTML as defined by XEP-0071 | ||||||
|  | func (c *Client) SendHtml(chat Chat) (n int, err error) { | ||||||
|  | 	return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+ | ||||||
|  | 		"<body>%s</body>"+ | ||||||
|  | 		"<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html></message>", | ||||||
|  | 		xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text), chat.Text) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Roster asks for the chat roster. | ||||||
|  | func (c *Client) Roster() error { | ||||||
|  | 	fmt.Fprintf(c.conn, "<iq from='%s' type='get' id='roster1'><query xmlns='jabber:iq:roster'/></iq>\n", xmlEscape(c.jid)) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RFC 3920  C.1  Streams name space | ||||||
|  | type streamFeatures struct { | ||||||
|  | 	XMLName    xml.Name `xml:"http://etherx.jabber.org/streams features"` | ||||||
|  | 	StartTLS   *tlsStartTLS | ||||||
|  | 	Mechanisms saslMechanisms | ||||||
|  | 	Bind       bindBind | ||||||
|  | 	Session    bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type streamError struct { | ||||||
|  | 	XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"` | ||||||
|  | 	Any     xml.Name | ||||||
|  | 	Text    string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RFC 3920  C.3  TLS name space | ||||||
|  | type tlsStartTLS struct { | ||||||
|  | 	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"` | ||||||
|  | 	Required *string  `xml:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type tlsProceed struct { | ||||||
|  | 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type tlsFailure struct { | ||||||
|  | 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls failure"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RFC 3920  C.4  SASL name space | ||||||
|  | type saslMechanisms struct { | ||||||
|  | 	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"` | ||||||
|  | 	Mechanism []string `xml:"mechanism"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type saslAuth struct { | ||||||
|  | 	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"` | ||||||
|  | 	Mechanism string   `xml:",attr"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type saslChallenge string | ||||||
|  |  | ||||||
|  | type saslRspAuth string | ||||||
|  |  | ||||||
|  | type saslResponse string | ||||||
|  |  | ||||||
|  | type saslAbort struct { | ||||||
|  | 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl abort"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type saslSuccess struct { | ||||||
|  | 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type saslFailure struct { | ||||||
|  | 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"` | ||||||
|  | 	Any     xml.Name `xml:",any"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RFC 3920  C.5  Resource binding name space | ||||||
|  | type bindBind struct { | ||||||
|  | 	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` | ||||||
|  | 	Resource string | ||||||
|  | 	Jid      string `xml:"jid"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RFC 3921  B.1  jabber:client | ||||||
|  | type clientMessage struct { | ||||||
|  | 	XMLName xml.Name `xml:"jabber:client message"` | ||||||
|  | 	From    string   `xml:"from,attr"` | ||||||
|  | 	ID      string   `xml:"id,attr"` | ||||||
|  | 	To      string   `xml:"to,attr"` | ||||||
|  | 	Type    string   `xml:"type,attr"` // chat, error, groupchat, headline, or normal | ||||||
|  |  | ||||||
|  | 	// These should technically be []clientText, but string is much more convenient. | ||||||
|  | 	Subject string `xml:"subject"` | ||||||
|  | 	Body    string `xml:"body"` | ||||||
|  | 	Thread  string `xml:"thread"` | ||||||
|  |  | ||||||
|  | 	// Any hasn't matched element | ||||||
|  | 	Other []string `xml:",any"` | ||||||
|  |  | ||||||
|  | 	Delay Delay `xml:"delay"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Delay struct { | ||||||
|  | 	Stamp string `xml:"stamp,attr"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type clientText struct { | ||||||
|  | 	Lang string `xml:",attr"` | ||||||
|  | 	Body string `xml:"chardata"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type clientPresence struct { | ||||||
|  | 	XMLName xml.Name `xml:"jabber:client presence"` | ||||||
|  | 	From    string   `xml:"from,attr"` | ||||||
|  | 	ID      string   `xml:"id,attr"` | ||||||
|  | 	To      string   `xml:"to,attr"` | ||||||
|  | 	Type    string   `xml:"type,attr"` // error, probe, subscribe, subscribed, unavailable, unsubscribe, unsubscribed | ||||||
|  | 	Lang    string   `xml:"lang,attr"` | ||||||
|  |  | ||||||
|  | 	Show     string `xml:"show"`   // away, chat, dnd, xa | ||||||
|  | 	Status   string `xml:"status"` // sb []clientText | ||||||
|  | 	Priority string `xml:"priority,attr"` | ||||||
|  | 	Error    *clientError | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type clientIQ struct { // info/query | ||||||
|  | 	XMLName xml.Name `xml:"jabber:client iq"` | ||||||
|  | 	From    string   `xml:"from,attr"` | ||||||
|  | 	ID      string   `xml:"id,attr"` | ||||||
|  | 	To      string   `xml:"to,attr"` | ||||||
|  | 	Type    string   `xml:"type,attr"` // error, get, result, set | ||||||
|  | 	Query   []byte   `xml:",innerxml"` | ||||||
|  | 	Error   clientError | ||||||
|  | 	Bind    bindBind | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type clientError struct { | ||||||
|  | 	XMLName xml.Name `xml:"jabber:client error"` | ||||||
|  | 	Code    string   `xml:",attr"` | ||||||
|  | 	Type    string   `xml:",attr"` | ||||||
|  | 	Any     xml.Name | ||||||
|  | 	Text    string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type clientQuery struct { | ||||||
|  | 	Item []rosterItem | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type rosterItem struct { | ||||||
|  | 	XMLName      xml.Name `xml:"jabber:iq:roster item"` | ||||||
|  | 	Jid          string   `xml:",attr"` | ||||||
|  | 	Name         string   `xml:",attr"` | ||||||
|  | 	Subscription string   `xml:",attr"` | ||||||
|  | 	Group        []string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Scan XML token stream to find next StartElement. | ||||||
|  | func nextStart(p *xml.Decoder) (xml.StartElement, error) { | ||||||
|  | 	for { | ||||||
|  | 		t, err := p.Token() | ||||||
|  | 		if err != nil && err != io.EOF || t == nil { | ||||||
|  | 			return xml.StartElement{}, err | ||||||
|  | 		} | ||||||
|  | 		switch t := t.(type) { | ||||||
|  | 		case xml.StartElement: | ||||||
|  | 			return t, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Scan XML token stream for next element and save into val. | ||||||
|  | // If val == nil, allocate new element based on proto map. | ||||||
|  | // Either way, return val. | ||||||
|  | func next(p *xml.Decoder) (xml.Name, interface{}, error) { | ||||||
|  | 	// Read start element to find out what type we want. | ||||||
|  | 	se, err := nextStart(p) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return xml.Name{}, nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Put it in an interface and allocate one. | ||||||
|  | 	var nv interface{} | ||||||
|  | 	switch se.Name.Space + " " + se.Name.Local { | ||||||
|  | 	case nsStream + " features": | ||||||
|  | 		nv = &streamFeatures{} | ||||||
|  | 	case nsStream + " error": | ||||||
|  | 		nv = &streamError{} | ||||||
|  | 	case nsTLS + " starttls": | ||||||
|  | 		nv = &tlsStartTLS{} | ||||||
|  | 	case nsTLS + " proceed": | ||||||
|  | 		nv = &tlsProceed{} | ||||||
|  | 	case nsTLS + " failure": | ||||||
|  | 		nv = &tlsFailure{} | ||||||
|  | 	case nsSASL + " mechanisms": | ||||||
|  | 		nv = &saslMechanisms{} | ||||||
|  | 	case nsSASL + " challenge": | ||||||
|  | 		nv = "" | ||||||
|  | 	case nsSASL + " response": | ||||||
|  | 		nv = "" | ||||||
|  | 	case nsSASL + " abort": | ||||||
|  | 		nv = &saslAbort{} | ||||||
|  | 	case nsSASL + " success": | ||||||
|  | 		nv = &saslSuccess{} | ||||||
|  | 	case nsSASL + " failure": | ||||||
|  | 		nv = &saslFailure{} | ||||||
|  | 	case nsBind + " bind": | ||||||
|  | 		nv = &bindBind{} | ||||||
|  | 	case nsClient + " message": | ||||||
|  | 		nv = &clientMessage{} | ||||||
|  | 	case nsClient + " presence": | ||||||
|  | 		nv = &clientPresence{} | ||||||
|  | 	case nsClient + " iq": | ||||||
|  | 		nv = &clientIQ{} | ||||||
|  | 	case nsClient + " error": | ||||||
|  | 		nv = &clientError{} | ||||||
|  | 	default: | ||||||
|  | 		return xml.Name{}, nil, errors.New("unexpected XMPP message " + | ||||||
|  | 			se.Name.Space + " <" + se.Name.Local + "/>") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Unmarshal into that storage. | ||||||
|  | 	if err = p.DecodeElement(nv, &se); err != nil { | ||||||
|  | 		return xml.Name{}, nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return se.Name, nv, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var xmlSpecial = map[byte]string{ | ||||||
|  | 	'<':  "<", | ||||||
|  | 	'>':  ">", | ||||||
|  | 	'"':  """, | ||||||
|  | 	'\'': "'", | ||||||
|  | 	'&':  "&", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func xmlEscape(s string) string { | ||||||
|  | 	var b bytes.Buffer | ||||||
|  | 	for i := 0; i < len(s); i++ { | ||||||
|  | 		c := s[i] | ||||||
|  | 		if s, ok := xmlSpecial[c]; ok { | ||||||
|  | 			b.WriteString(s) | ||||||
|  | 		} else { | ||||||
|  | 			b.WriteByte(c) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return b.String() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type tee struct { | ||||||
|  | 	r io.Reader | ||||||
|  | 	w io.Writer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t tee) Read(p []byte) (n int, err error) { | ||||||
|  | 	n, err = t.r.Read(p) | ||||||
|  | 	if n > 0 { | ||||||
|  | 		t.w.Write(p[0:n]) | ||||||
|  | 		t.w.Write([]byte("\n")) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_information_query.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_information_query.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | package xmpp | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const IQTypeGet = "get" | ||||||
|  | const IQTypeSet = "set" | ||||||
|  | const IQTypeResult = "result" | ||||||
|  |  | ||||||
|  | func (c *Client) Discovery() (string, error) { | ||||||
|  | 	const namespace = "http://jabber.org/protocol/disco#items" | ||||||
|  | 	// use getCookie for a pseudo random id. | ||||||
|  | 	reqID := strconv.FormatUint(uint64(getCookie()), 10) | ||||||
|  | 	return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, namespace, "") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RawInformationQuery sends an information query request to the server. | ||||||
|  | func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) { | ||||||
|  | 	const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>" | ||||||
|  | 	_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body) | ||||||
|  | 	return id, err | ||||||
|  | } | ||||||
							
								
								
									
										134
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_muc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_muc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | |||||||
|  | // Copyright 2013 Flo Lauber <dev@qatfy.at>.  All rights reserved. | ||||||
|  | // Use of this source code is governed by a BSD-style | ||||||
|  | // license that can be found in the LICENSE file. | ||||||
|  |  | ||||||
|  | // TODO(flo): | ||||||
|  | //   - support password protected MUC rooms | ||||||
|  | //   - cleanup signatures of join/leave functions | ||||||
|  | package xmpp | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"time" | ||||||
|  | 	"errors" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	nsMUC     = "http://jabber.org/protocol/muc" | ||||||
|  | 	nsMUCUser = "http://jabber.org/protocol/muc#user" | ||||||
|  | 	NoHistory = 0 | ||||||
|  | 	CharHistory = 1 | ||||||
|  | 	StanzaHistory = 2 | ||||||
|  | 	SecondsHistory = 3 | ||||||
|  | 	SinceHistory = 4 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Send sends room topic wrapped inside an XMPP message stanza body. | ||||||
|  | func (c *Client) SendTopic(chat Chat) (n int, err error) { | ||||||
|  | 	return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>", | ||||||
|  | 		xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) { | ||||||
|  | 	if nick == "" { | ||||||
|  | 		nick = c.jid | ||||||
|  | 	} | ||||||
|  | 	return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+ | ||||||
|  | 		"<x xmlns='%s'>"+ | ||||||
|  | 		"<history maxchars='0'/></x>\n"+ | ||||||
|  | 		"</presence>", | ||||||
|  | 		xmlEscape(jid), xmlEscape(nick), nsMUC) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // xep-0045 7.2 | ||||||
|  | func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_date *time.Time) (n int, err error) { | ||||||
|  | 	if nick == "" { | ||||||
|  | 		nick = c.jid | ||||||
|  | 	} | ||||||
|  | 	switch history_type { | ||||||
|  | 	case NoHistory: | ||||||
|  | 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||||
|  | 			"<x xmlns='%s' />\n" + | ||||||
|  | 			"</presence>", | ||||||
|  | 				xmlEscape(jid), xmlEscape(nick), nsMUC) | ||||||
|  | 	case CharHistory: | ||||||
|  | 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||||
|  | 			"<x xmlns='%s'>\n" + | ||||||
|  | 			"<history maxchars='%d'/></x>\n"+ | ||||||
|  | 			"</presence>", | ||||||
|  | 				xmlEscape(jid), xmlEscape(nick), nsMUC, history) | ||||||
|  | 	case StanzaHistory: | ||||||
|  | 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||||
|  | 			"<x xmlns='%s'>\n" + | ||||||
|  | 			"<history maxstanzas='%d'/></x>\n"+ | ||||||
|  | 			"</presence>", | ||||||
|  | 				xmlEscape(jid), xmlEscape(nick), nsMUC, history) | ||||||
|  | 	case SecondsHistory: | ||||||
|  | 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||||
|  | 			"<x xmlns='%s'>\n" + | ||||||
|  | 			"<history seconds='%d'/></x>\n"+ | ||||||
|  | 			"</presence>", | ||||||
|  | 				xmlEscape(jid), xmlEscape(nick), nsMUC, history) | ||||||
|  | 	case SinceHistory: | ||||||
|  | 		if history_date != nil { | ||||||
|  | 			return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||||
|  | 				"<x xmlns='%s'>\n" + | ||||||
|  | 				"<history since='%s'/></x>\n" + | ||||||
|  | 				"</presence>", | ||||||
|  | 					xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return 0, errors.New("Unknown history option") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // xep-0045 7.2.6 | ||||||
|  | func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_type, history int, history_date *time.Time) (n int, err error) { | ||||||
|  | 	if nick == "" { | ||||||
|  | 		nick = c.jid | ||||||
|  | 	} | ||||||
|  | 	switch history_type { | ||||||
|  | 	case NoHistory: | ||||||
|  | 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||||
|  | 			"<x xmlns='%s'>\n" + | ||||||
|  | 			"<password>%s</password>\n"+ | ||||||
|  | 			"</presence>", | ||||||
|  | 				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password)) | ||||||
|  | 	case CharHistory: | ||||||
|  | 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||||
|  | 			"<x xmlns='%s'>\n" + | ||||||
|  | 			"<password>%s</password>\n"+ | ||||||
|  | 			"<history maxchars='%d'/></x>\n"+ | ||||||
|  | 			"</presence>", | ||||||
|  | 				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) | ||||||
|  | 	case StanzaHistory: | ||||||
|  | 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||||
|  | 			"<x xmlns='%s'>\n" + | ||||||
|  | 			"<password>%s</password>\n"+ | ||||||
|  | 			"<history maxstanzas='%d'/></x>\n"+ | ||||||
|  | 			"</presence>", | ||||||
|  | 				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) | ||||||
|  | 	case SecondsHistory: | ||||||
|  | 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||||
|  | 			"<x xmlns='%s'>\n" + | ||||||
|  | 			"<password>%s</password>\n"+ | ||||||
|  | 			"<history seconds='%d'/></x>\n"+ | ||||||
|  | 			"</presence>", | ||||||
|  | 				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) | ||||||
|  | 	case SinceHistory: | ||||||
|  | 		if history_date != nil { | ||||||
|  | 			return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||||
|  | 				"<x xmlns='%s'>\n" + | ||||||
|  | 				"<password>%s</password>\n"+ | ||||||
|  | 				"<history since='%s'/></x>\n" + | ||||||
|  | 				"</presence>", | ||||||
|  | 					xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return 0, errors.New("Unknown history option") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // xep-0045 7.14 | ||||||
|  | func (c *Client) LeaveMUC(jid string) (n int, err error) { | ||||||
|  | 	return fmt.Fprintf(c.conn, "<presence from='%s' to='%s' type='unavailable' />", | ||||||
|  | 		c.jid, xmlEscape(jid)) | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_ping.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_ping.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | package xmpp | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (c *Client) PingC2S(jid, server string) error { | ||||||
|  | 	if jid == "" { | ||||||
|  | 		jid = c.jid | ||||||
|  | 	} | ||||||
|  | 	if server == "" { | ||||||
|  | 		server = c.domain | ||||||
|  | 	} | ||||||
|  | 	_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+ | ||||||
|  | 		"<ping xmlns='urn:xmpp:ping'/>\n"+ | ||||||
|  | 		"</iq>", | ||||||
|  | 		xmlEscape(jid), xmlEscape(server)) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) PingS2S(fromServer, toServer string) error { | ||||||
|  | 	_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+ | ||||||
|  | 		"<ping xmlns='urn:xmpp:ping'/>\n"+ | ||||||
|  | 		"</iq>", | ||||||
|  | 		xmlEscape(fromServer), xmlEscape(toServer)) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_subscription.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_subscription.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | package xmpp | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (c *Client) ApproveSubscription(jid string) { | ||||||
|  | 	fmt.Fprintf(c.conn, "<presence to='%s' type='subscribed'/>", | ||||||
|  | 		xmlEscape(jid)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) RevokeSubscription(jid string) { | ||||||
|  | 	fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribed'/>", | ||||||
|  | 		xmlEscape(jid)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *Client) RequestSubscription(jid string) { | ||||||
|  | 	fmt.Fprintf(c.conn, "<presence to='%s' type='subscribe'/>", | ||||||
|  | 		xmlEscape(jid)) | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								vendor/github.com/mreiferson/go-httpclient/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/mreiferson/go-httpclient/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | The MIT License (MIT) | ||||||
|  |  | ||||||
|  | Copyright (c) 2012 Matt Reiferson | ||||||
|  |  | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  |  | ||||||
|  | The above copyright notice and this permission notice shall be included in | ||||||
|  | all copies or substantial portions of the Software. | ||||||
|  |  | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  | THE SOFTWARE. | ||||||
							
								
								
									
										237
									
								
								vendor/github.com/mreiferson/go-httpclient/httpclient.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								vendor/github.com/mreiferson/go-httpclient/httpclient.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,237 @@ | |||||||
|  | /* | ||||||
|  | Provides an HTTP Transport that implements the `RoundTripper` interface and | ||||||
|  | can be used as a built in replacement for the standard library's, providing: | ||||||
|  |  | ||||||
|  | 	* connection timeouts | ||||||
|  | 	* request timeouts | ||||||
|  |  | ||||||
|  | This is a thin wrapper around `http.Transport` that sets dial timeouts and uses | ||||||
|  | Go's internal timer scheduler to call the Go 1.1+ `CancelRequest()` API. | ||||||
|  | */ | ||||||
|  | package httpclient | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"errors" | ||||||
|  | 	"io" | ||||||
|  | 	"net" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // returns the current version of the package | ||||||
|  | func Version() string { | ||||||
|  | 	return "0.4.1" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Transport implements the RoundTripper interface and can be used as a replacement | ||||||
|  | // for Go's built in http.Transport implementing end-to-end request timeouts. | ||||||
|  | // | ||||||
|  | // 	transport := &httpclient.Transport{ | ||||||
|  | // 	    ConnectTimeout: 1*time.Second, | ||||||
|  | // 	    ResponseHeaderTimeout: 5*time.Second, | ||||||
|  | // 	    RequestTimeout: 10*time.Second, | ||||||
|  | // 	} | ||||||
|  | // 	defer transport.Close() | ||||||
|  | // | ||||||
|  | // 	client := &http.Client{Transport: transport} | ||||||
|  | // 	req, _ := http.NewRequest("GET", "http://127.0.0.1/test", nil) | ||||||
|  | // 	resp, err := client.Do(req) | ||||||
|  | // 	if err != nil { | ||||||
|  | // 	    return err | ||||||
|  | // 	} | ||||||
|  | // 	defer resp.Body.Close() | ||||||
|  | // | ||||||
|  | type Transport struct { | ||||||
|  | 	// Proxy specifies a function to return a proxy for a given | ||||||
|  | 	// *http.Request. If the function returns a non-nil error, the | ||||||
|  | 	// request is aborted with the provided error. | ||||||
|  | 	// If Proxy is nil or returns a nil *url.URL, no proxy is used. | ||||||
|  | 	Proxy func(*http.Request) (*url.URL, error) | ||||||
|  |  | ||||||
|  | 	// Dial specifies the dial function for creating TCP | ||||||
|  | 	// connections. This will override the Transport's ConnectTimeout and | ||||||
|  | 	// ReadWriteTimeout settings. | ||||||
|  | 	// If Dial is nil, a dialer is generated on demand matching the Transport's | ||||||
|  | 	// options. | ||||||
|  | 	Dial func(network, addr string) (net.Conn, error) | ||||||
|  |  | ||||||
|  | 	// TLSClientConfig specifies the TLS configuration to use with | ||||||
|  | 	// tls.Client. If nil, the default configuration is used. | ||||||
|  | 	TLSClientConfig *tls.Config | ||||||
|  |  | ||||||
|  | 	// DisableKeepAlives, if true, prevents re-use of TCP connections | ||||||
|  | 	// between different HTTP requests. | ||||||
|  | 	DisableKeepAlives bool | ||||||
|  |  | ||||||
|  | 	// DisableCompression, if true, prevents the Transport from | ||||||
|  | 	// requesting compression with an "Accept-Encoding: gzip" | ||||||
|  | 	// request header when the Request contains no existing | ||||||
|  | 	// Accept-Encoding value. If the Transport requests gzip on | ||||||
|  | 	// its own and gets a gzipped response, it's transparently | ||||||
|  | 	// decoded in the Response.Body. However, if the user | ||||||
|  | 	// explicitly requested gzip it is not automatically | ||||||
|  | 	// uncompressed. | ||||||
|  | 	DisableCompression bool | ||||||
|  |  | ||||||
|  | 	// MaxIdleConnsPerHost, if non-zero, controls the maximum idle | ||||||
|  | 	// (keep-alive) to keep per-host.  If zero, | ||||||
|  | 	// http.DefaultMaxIdleConnsPerHost is used. | ||||||
|  | 	MaxIdleConnsPerHost int | ||||||
|  |  | ||||||
|  | 	// ConnectTimeout, if non-zero, is the maximum amount of time a dial will wait for | ||||||
|  | 	// a connect to complete. | ||||||
|  | 	ConnectTimeout time.Duration | ||||||
|  |  | ||||||
|  | 	// ResponseHeaderTimeout, if non-zero, specifies the amount of | ||||||
|  | 	// time to wait for a server's response headers after fully | ||||||
|  | 	// writing the request (including its body, if any). This | ||||||
|  | 	// time does not include the time to read the response body. | ||||||
|  | 	ResponseHeaderTimeout time.Duration | ||||||
|  |  | ||||||
|  | 	// RequestTimeout, if non-zero, specifies the amount of time for the entire | ||||||
|  | 	// request to complete (including all of the above timeouts + entire response body). | ||||||
|  | 	// This should never be less than the sum total of the above two timeouts. | ||||||
|  | 	RequestTimeout time.Duration | ||||||
|  |  | ||||||
|  | 	// ReadWriteTimeout, if non-zero, will set a deadline for every Read and | ||||||
|  | 	// Write operation on the request connection. | ||||||
|  | 	ReadWriteTimeout time.Duration | ||||||
|  |  | ||||||
|  | 	// TCPWriteBufferSize, the size of the operating system's write | ||||||
|  | 	// buffer associated with the connection. | ||||||
|  | 	TCPWriteBufferSize int | ||||||
|  |  | ||||||
|  | 	// TCPReadBuffserSize, the size of the operating system's read | ||||||
|  | 	// buffer associated with the connection. | ||||||
|  | 	TCPReadBufferSize int | ||||||
|  |  | ||||||
|  | 	starter   sync.Once | ||||||
|  | 	transport *http.Transport | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close cleans up the Transport, currently a no-op | ||||||
|  | func (t *Transport) Close() error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Transport) lazyStart() { | ||||||
|  | 	if t.Dial == nil { | ||||||
|  | 		t.Dial = func(netw, addr string) (net.Conn, error) { | ||||||
|  | 			c, err := net.DialTimeout(netw, addr, t.ConnectTimeout) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if t.TCPReadBufferSize != 0 || t.TCPWriteBufferSize != 0 { | ||||||
|  | 				if tcpCon, ok := c.(*net.TCPConn); ok { | ||||||
|  | 					if t.TCPWriteBufferSize != 0 { | ||||||
|  | 						if err = tcpCon.SetWriteBuffer(t.TCPWriteBufferSize); err != nil { | ||||||
|  | 							return nil, err | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					if t.TCPReadBufferSize != 0 { | ||||||
|  | 						if err = tcpCon.SetReadBuffer(t.TCPReadBufferSize); err != nil { | ||||||
|  | 							return nil, err | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					err = errors.New("Not Tcp Connection") | ||||||
|  | 					return nil, err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if t.ReadWriteTimeout > 0 { | ||||||
|  | 				timeoutConn := &rwTimeoutConn{ | ||||||
|  | 					TCPConn:   c.(*net.TCPConn), | ||||||
|  | 					rwTimeout: t.ReadWriteTimeout, | ||||||
|  | 				} | ||||||
|  | 				return timeoutConn, nil | ||||||
|  | 			} | ||||||
|  | 			return c, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	t.transport = &http.Transport{ | ||||||
|  | 		Dial:                  t.Dial, | ||||||
|  | 		Proxy:                 t.Proxy, | ||||||
|  | 		TLSClientConfig:       t.TLSClientConfig, | ||||||
|  | 		DisableKeepAlives:     t.DisableKeepAlives, | ||||||
|  | 		DisableCompression:    t.DisableCompression, | ||||||
|  | 		MaxIdleConnsPerHost:   t.MaxIdleConnsPerHost, | ||||||
|  | 		ResponseHeaderTimeout: t.ResponseHeaderTimeout, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Transport) CancelRequest(req *http.Request) { | ||||||
|  | 	t.starter.Do(t.lazyStart) | ||||||
|  |  | ||||||
|  | 	t.transport.CancelRequest(req) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Transport) CloseIdleConnections() { | ||||||
|  | 	t.starter.Do(t.lazyStart) | ||||||
|  |  | ||||||
|  | 	t.transport.CloseIdleConnections() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Transport) RegisterProtocol(scheme string, rt http.RoundTripper) { | ||||||
|  | 	t.starter.Do(t.lazyStart) | ||||||
|  |  | ||||||
|  | 	t.transport.RegisterProtocol(scheme, rt) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { | ||||||
|  | 	t.starter.Do(t.lazyStart) | ||||||
|  |  | ||||||
|  | 	if t.RequestTimeout > 0 { | ||||||
|  | 		timer := time.AfterFunc(t.RequestTimeout, func() { | ||||||
|  | 			t.transport.CancelRequest(req) | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		resp, err = t.transport.RoundTrip(req) | ||||||
|  | 		if err != nil { | ||||||
|  | 			timer.Stop() | ||||||
|  | 		} else { | ||||||
|  | 			resp.Body = &bodyCloseInterceptor{ReadCloser: resp.Body, timer: timer} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		resp, err = t.transport.RoundTrip(req) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type bodyCloseInterceptor struct { | ||||||
|  | 	io.ReadCloser | ||||||
|  | 	timer *time.Timer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (bci *bodyCloseInterceptor) Close() error { | ||||||
|  | 	bci.timer.Stop() | ||||||
|  | 	return bci.ReadCloser.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A net.Conn that sets a deadline for every Read or Write operation | ||||||
|  | type rwTimeoutConn struct { | ||||||
|  | 	*net.TCPConn | ||||||
|  | 	rwTimeout time.Duration | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *rwTimeoutConn) Read(b []byte) (int, error) { | ||||||
|  | 	err := c.TCPConn.SetDeadline(time.Now().Add(c.rwTimeout)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	return c.TCPConn.Read(b) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *rwTimeoutConn) Write(b []byte) (int, error) { | ||||||
|  | 	err := c.TCPConn.SetDeadline(time.Now().Add(c.rwTimeout)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	return c.TCPConn.Write(b) | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								vendor/github.com/mrexodia/wray/examples/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/mrexodia/wray/examples/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import "github.com/pythonandchips/wray" | ||||||
|  | import "fmt" | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	wray.RegisterTransports([]wray.Transport{&wray.HttpTransport{}}) | ||||||
|  | 	client := wray.NewFayeClient("http://localhost:5000/faye") | ||||||
|  |  | ||||||
|  | 	fmt.Println("subscribing") | ||||||
|  | 	client.Subscribe("/foo", false, func(message wray.Message) { | ||||||
|  | 		fmt.Println("-------------------------------------------") | ||||||
|  | 		fmt.Println(message.Data) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	client.Listen() | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								vendor/github.com/mrexodia/wray/examples/publish.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/mrexodia/wray/examples/publish.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import "github.com/pythonandchips/wray" | ||||||
|  | import "fmt" | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  |   wray.RegisterTransports([]wray.Transport{ &gofaye.HttpTransport{} }) | ||||||
|  |   client := wray.NewFayeClient("http://localhost:5000/faye") | ||||||
|  |  | ||||||
|  |   params := map[string]interface{}{"hello": "from golang"} | ||||||
|  |   fmt.Println("sending") | ||||||
|  |   client.Publish("/foo", params) | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										140
									
								
								vendor/github.com/mrexodia/wray/go_faye.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								vendor/github.com/mrexodia/wray/go_faye.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | |||||||
|  | package wray | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	UNCONNECTED  = 1 | ||||||
|  | 	CONNECTING   = 2 | ||||||
|  | 	CONNECTED    = 3 | ||||||
|  | 	DISCONNECTED = 4 | ||||||
|  |  | ||||||
|  | 	HANDSHAKE = "handshake" | ||||||
|  | 	RETRY     = "retry" | ||||||
|  | 	NONE      = "none" | ||||||
|  |  | ||||||
|  | 	CONNECTION_TIMEOUT = 60.0 | ||||||
|  | 	DEFAULT_RETRY      = 5.0 | ||||||
|  | 	MAX_REQUEST_SIZE   = 2048 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	MANDATORY_CONNECTION_TYPES = []string{"long-polling"} | ||||||
|  | 	registeredTransports       = []Transport{} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type FayeClient struct { | ||||||
|  | 	state         int | ||||||
|  | 	url           string | ||||||
|  | 	subscriptions []Subscription | ||||||
|  | 	transport     Transport | ||||||
|  | 	clientId      string | ||||||
|  | 	schedular     Schedular | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Subscription struct { | ||||||
|  | 	channel  string | ||||||
|  | 	callback func(Message) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SubscriptionPromise struct { | ||||||
|  | 	subscription Subscription | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewFayeClient(url string) *FayeClient { | ||||||
|  | 	schedular := ChannelSchedular{} | ||||||
|  | 	client := &FayeClient{url: url, state: UNCONNECTED, schedular: schedular} | ||||||
|  | 	return client | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self *FayeClient) handshake() { | ||||||
|  | 	t, err := SelectTransport(self, MANDATORY_CONNECTION_TYPES, []string{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("No usable transports available") | ||||||
|  | 	} | ||||||
|  | 	self.transport = t | ||||||
|  | 	self.transport.setUrl(self.url) | ||||||
|  | 	self.state = CONNECTING | ||||||
|  | 	handshakeParams := map[string]interface{}{"channel": "/meta/handshake", | ||||||
|  | 		"version":                  "1.0", | ||||||
|  | 		"supportedConnectionTypes": []string{"long-polling"}} | ||||||
|  | 	response, err := self.transport.send(handshakeParams) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("Handshake failed. Retry in 10 seconds") | ||||||
|  | 		self.state = UNCONNECTED | ||||||
|  | 		self.schedular.wait(10*time.Second, func() { | ||||||
|  | 			fmt.Println("retying handshake") | ||||||
|  | 			self.handshake() | ||||||
|  | 		}) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	self.clientId = response.clientId | ||||||
|  | 	self.state = CONNECTED | ||||||
|  | 	self.transport, err = SelectTransport(self, response.supportedConnectionTypes, []string{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic("Server does not support any available transports. Supported transports: " + strings.Join(response.supportedConnectionTypes, ",")) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self *FayeClient) Subscribe(channel string, force bool, callback func(Message)) SubscriptionPromise { | ||||||
|  | 	if self.state == UNCONNECTED { | ||||||
|  | 		self.handshake() | ||||||
|  | 	} | ||||||
|  | 	subscriptionParams := map[string]interface{}{"channel": "/meta/subscribe", "clientId": self.clientId, "subscription": channel, "id": "1"} | ||||||
|  | 	subscription := Subscription{channel: channel, callback: callback} | ||||||
|  | 	//TODO: deal with subscription failures | ||||||
|  | 	self.transport.send(subscriptionParams) | ||||||
|  | 	self.subscriptions = append(self.subscriptions, subscription) | ||||||
|  | 	return SubscriptionPromise{subscription} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self *FayeClient) handleResponse(response Response) { | ||||||
|  | 	for _, message := range response.messages { | ||||||
|  | 		for _, subscription := range self.subscriptions { | ||||||
|  | 			matched, _ := filepath.Match(subscription.channel, message.Channel) | ||||||
|  | 			if matched { | ||||||
|  | 				go subscription.callback(message) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self *FayeClient) connect() { | ||||||
|  | 	connectParams := map[string]interface{}{"channel": "/meta/connect", "clientId": self.clientId, "connectionType": self.transport.connectionType()} | ||||||
|  | 	responseChannel := make(chan Response) | ||||||
|  | 	go func() { | ||||||
|  | 		response, _ := self.transport.send(connectParams) | ||||||
|  | 		responseChannel <- response | ||||||
|  | 	}() | ||||||
|  | 	self.listen(responseChannel) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self *FayeClient) listen(responseChannel chan Response) { | ||||||
|  | 	response := <-responseChannel | ||||||
|  | 	if response.successful == true { | ||||||
|  | 		go self.handleResponse(response) | ||||||
|  | 	} else { | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self *FayeClient) Listen() { | ||||||
|  | 	for { | ||||||
|  | 		self.connect() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self *FayeClient) Publish(channel string, data map[string]interface{}) { | ||||||
|  | 	if self.state != CONNECTED { | ||||||
|  | 		self.handshake() | ||||||
|  | 	} | ||||||
|  | 	publishParams := map[string]interface{}{"channel": channel, "data": data, "clientId": self.clientId} | ||||||
|  | 	self.transport.send(publishParams) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func RegisterTransports(transports []Transport) { | ||||||
|  | 	registeredTransports = transports | ||||||
|  | } | ||||||
							
								
								
									
										55
									
								
								vendor/github.com/mrexodia/wray/http_transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								vendor/github.com/mrexodia/wray/http_transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | package wray | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type HttpTransport struct { | ||||||
|  | 	url      string | ||||||
|  | 	SendHook func(data map[string]interface{}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self HttpTransport) isUsable(clientUrl string) bool { | ||||||
|  | 	parsedUrl, err := url.Parse(clientUrl) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	if parsedUrl.Scheme == "http" || parsedUrl.Scheme == "https" { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self HttpTransport) connectionType() string { | ||||||
|  | 	return "long-polling" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self HttpTransport) send(data map[string]interface{}) (Response, error) { | ||||||
|  | 	if self.SendHook != nil { | ||||||
|  | 		self.SendHook(data) | ||||||
|  | 	} | ||||||
|  | 	dataBytes, _ := json.Marshal(data) | ||||||
|  | 	buffer := bytes.NewBuffer(dataBytes) | ||||||
|  | 	responseData, err := http.Post(self.url, "application/json", buffer) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return Response{}, err | ||||||
|  | 	} | ||||||
|  | 	if responseData.StatusCode != 200 { | ||||||
|  | 		return Response{}, errors.New(responseData.Status) | ||||||
|  | 	} | ||||||
|  | 	readData, _ := ioutil.ReadAll(responseData.Body) | ||||||
|  | 	responseData.Body.Close() | ||||||
|  | 	var jsonData []interface{} | ||||||
|  | 	json.Unmarshal(readData, &jsonData) | ||||||
|  | 	response := newResponse(jsonData) | ||||||
|  | 	return response, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self *HttpTransport) setUrl(url string) { | ||||||
|  | 	self.url = url | ||||||
|  | } | ||||||
							
								
								
									
										61
									
								
								vendor/github.com/mrexodia/wray/response.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								vendor/github.com/mrexodia/wray/response.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | package wray | ||||||
|  |  | ||||||
|  | type Response struct { | ||||||
|  |   id string | ||||||
|  |   channel string | ||||||
|  |   successful bool | ||||||
|  |   clientId string | ||||||
|  |   supportedConnectionTypes []string | ||||||
|  |   messages []Message | ||||||
|  |   error error | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Message struct { | ||||||
|  |   Channel string | ||||||
|  |   Id string | ||||||
|  |   Data map[string]interface{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newResponse(data []interface{}) Response { | ||||||
|  |   headerData := data[0].(map[string]interface{}) | ||||||
|  |   messagesData := data[1.:] | ||||||
|  |   messages := parseMessages(messagesData) | ||||||
|  |   var id string | ||||||
|  |   if headerData["id"] != nil { | ||||||
|  |     id = headerData["id"].(string) | ||||||
|  |   } | ||||||
|  |   supportedConnectionTypes := []string{} | ||||||
|  |   if headerData["supportedConnectionTypes"] != nil { | ||||||
|  |     d := headerData["supportedConnectionTypes"].([]interface{}) | ||||||
|  |     for _, sct := range(d) { | ||||||
|  |       supportedConnectionTypes = append(supportedConnectionTypes, sct.(string)) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   var clientId string | ||||||
|  |   if headerData["clientId"] != nil { | ||||||
|  |     clientId = headerData["clientId"].(string) | ||||||
|  |   } | ||||||
|  |   return Response{id: id, | ||||||
|  |                   clientId: clientId, | ||||||
|  |                   channel: headerData["channel"].(string), | ||||||
|  |                   successful: headerData["successful"].(bool), | ||||||
|  |                   messages: messages, | ||||||
|  |                   supportedConnectionTypes: supportedConnectionTypes} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func parseMessages(data []interface{}) []Message { | ||||||
|  |   messages := []Message{} | ||||||
|  |   for _, messageData := range(data) { | ||||||
|  |     m := messageData.(map[string]interface{}) | ||||||
|  |     var id string | ||||||
|  |     if m["id"] != nil { | ||||||
|  |       id = m["id"].(string) | ||||||
|  |     } | ||||||
|  |     message := Message{Channel: m["channel"].(string), | ||||||
|  |                        Id: id, | ||||||
|  |                        Data: m["data"].(map[string]interface{})} | ||||||
|  |     messages = append(messages, message) | ||||||
|  |   } | ||||||
|  |   return messages | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								vendor/github.com/mrexodia/wray/schedular.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/mrexodia/wray/schedular.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | package wray | ||||||
|  |  | ||||||
|  | import "time" | ||||||
|  |  | ||||||
|  | type Schedular interface { | ||||||
|  | 	wait(time.Duration, func()) | ||||||
|  | 	delay() time.Duration | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ChannelSchedular struct { | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self ChannelSchedular) wait(delay time.Duration, callback func()) { | ||||||
|  | 	go func() { | ||||||
|  | 		time.Sleep(delay) | ||||||
|  | 		callback() | ||||||
|  | 	}() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (self ChannelSchedular) delay() time.Duration { | ||||||
|  | 	return (1 * time.Minute) | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								vendor/github.com/mrexodia/wray/transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/mrexodia/wray/transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | package wray | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type Transport interface { | ||||||
|  | 	isUsable(string) bool | ||||||
|  | 	connectionType() string | ||||||
|  | 	send(map[string]interface{}) (Response, error) | ||||||
|  | 	setUrl(string) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func SelectTransport(client *FayeClient, transportTypes []string, disabled []string) (Transport, error) { | ||||||
|  | 	for _, transport := range registeredTransports { | ||||||
|  | 		if contains(transport.connectionType(), transportTypes) && transport.isUsable(client.url) { | ||||||
|  | 			return transport, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil, errors.New("No usable transports available") | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								vendor/github.com/mrexodia/wray/utils.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/mrexodia/wray/utils.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | package wray | ||||||
|  |  | ||||||
|  | func contains(target string, slice []string) bool { | ||||||
|  |   for _, t := range(slice) { | ||||||
|  |     if t == target { | ||||||
|  |       return true | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return false | ||||||
|  | } | ||||||
							
								
								
									
										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"` | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user