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"] | ||||
|  | ||||
| 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 \ | ||||
|         && export GOPATH=/go \ | ||||
|         && go get \ | ||||
|         && go build -o /bin/matterbridge \ | ||||
|         && 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 | ||||
|  | ||||
| Simple bridge between mattermost and IRC.  | ||||
| Simple bridge between mattermost, IRC, XMPP and Gitter | ||||
|  | ||||
| * Relays public channel messages between mattermost and IRC. | ||||
| * Supports multiple mattermost and irc channels. | ||||
| * Relays public channel messages between mattermost, IRC, XMPP and Gitter. Pick and mix. | ||||
| * Supports multiple channels. | ||||
| * Matterbridge -plus also works with private groups on your mattermost. | ||||
|  | ||||
| This project has now [matterbridge-plus](https://github.com/42wim/matterbridge-plus/) merged in.  | ||||
| Breaking changes for matterbridge can be found in [migration](https://github.com/42wim/matterbridge/blob/master/migration.md) | ||||
| Look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for documentation and an example. | ||||
|  | ||||
| ## 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: | ||||
| * [Mattermost] (https://github.com/mattermost/platform/) 3.x (stable, not a dev build) | ||||
|  | ||||
| ### 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. | ||||
| Accounts to one of the supported bridges | ||||
| * [Mattermost] (https://github.com/mattermost/platform/) | ||||
| * [IRC] (http://www.mirc.com/servers.html) | ||||
| * [XMPP] (https://jabber.org) | ||||
| * [Gitter] (https://gitter.im) | ||||
|  | ||||
| ## 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 | ||||
| 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 | ||||
|         enable debug | ||||
|   -plus | ||||
|         running using API instead of webhooks | ||||
|         running using API instead of webhooks (deprecated, set Plus flag in [general] config) | ||||
|   -version | ||||
|         show version | ||||
| ``` | ||||
|   | ||||
							
								
								
									
										478
									
								
								bridge/bridge.go
									
									
									
									
									
								
							
							
						
						
									
										478
									
								
								bridge/bridge.go
									
									
									
									
									
								
							| @@ -1,407 +1,151 @@ | ||||
| package bridge | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"github.com/42wim/matterbridge/matterclient" | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	//"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"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" | ||||
| 	"github.com/peterhellberg/giphy" | ||||
| 	ircm "github.com/sorcix/irc" | ||||
| 	"github.com/thoj/go-ircevent" | ||||
| 	"regexp" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"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 { | ||||
| 	MMhook | ||||
| 	MMapi | ||||
| 	MMirc | ||||
| 	*Config | ||||
| 	kind string | ||||
| 	*config.Config | ||||
| 	Source      string | ||||
| 	Bridges     []Bridger | ||||
| 	Channels    []map[string]string | ||||
| 	ignoreNicks map[string][]string | ||||
| } | ||||
|  | ||||
| type FancyLog struct { | ||||
| 	irc *log.Entry | ||||
| 	mm  *log.Entry | ||||
| type Bridger interface { | ||||
| 	Send(msg config.Message) error | ||||
| 	Name() string | ||||
| 	Connect() error | ||||
| 	//Command(cmd string) string | ||||
| } | ||||
|  | ||||
| var flog FancyLog | ||||
|  | ||||
| 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() | ||||
| func NewBridge(cfg *config.Config) error { | ||||
| 	c := make(chan config.Message) | ||||
| 	b := &Bridge{} | ||||
| 	b.Config = config | ||||
| 	b.kind = kind | ||||
| 	b.ircNick = b.Config.IRC.Nick | ||||
| 	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 | ||||
| 	b.Config = cfg | ||||
| 	if cfg.IRC.Enable { | ||||
| 		b.Bridges = append(b.Bridges, birc.New(cfg, c)) | ||||
| 	} | ||||
| 	if kind == Legacy { | ||||
| 		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 { | ||||
| 			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() | ||||
| 	if cfg.Mattermost.Enable { | ||||
| 		b.Bridges = append(b.Bridges, bmattermost.New(cfg, c)) | ||||
| 	} | ||||
| 	flog.irc.Info("Trying IRC connection") | ||||
| 	b.i = b.createIRC(name) | ||||
| 	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 | ||||
| 	if cfg.Xmpp.Enable { | ||||
| 		b.Bridges = append(b.Bridges, bxmpp.New(cfg, c)) | ||||
| 	} | ||||
| 	i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection) | ||||
| 	err := i.Connect(b.Config.IRC.Server) | ||||
| 	if err != nil { | ||||
| 		flog.irc.Fatal(err) | ||||
| 	if cfg.Gitter.Enable { | ||||
| 		b.Bridges = append(b.Bridges, bgitter.New(cfg, c)) | ||||
| 	} | ||||
| 	return i | ||||
| } | ||||
|  | ||||
| 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) | ||||
| 	if cfg.Slack.Enable { | ||||
| 		b.Bridges = append(b.Bridges, bslack.New(cfg, c)) | ||||
| 	} | ||||
| 	i.AddCallback("*", b.handleOther) | ||||
| 	b.setupChannels() | ||||
| } | ||||
|  | ||||
| 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) | ||||
| 	if len(b.Bridges) < 2 { | ||||
| 		log.Fatalf("only %d sections enabled. Need at least 2 sections enabled (eg [IRC] and [mattermost]", len(b.Bridges)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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] | ||||
| 	for _, br := range b.Bridges { | ||||
| 		br.Connect() | ||||
| 	} | ||||
| 	if exp.ReplaceAllString(parts[0], "") == b.ircNick { | ||||
| 		switch command { | ||||
| 		case "users": | ||||
| 			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) | ||||
| 	b.mapChannels() | ||||
| 	b.mapIgnores() | ||||
| 	b.handleReceive(c) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleMatterHook(mchan chan *MMMessage) { | ||||
| func (b *Bridge) handleReceive(c chan config.Message) { | ||||
| 	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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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 | ||||
| 		select { | ||||
| 		case msg := <-c: | ||||
| 			for _, br := range b.Bridges { | ||||
| 				b.handleMessage(msg, br) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleMatter() { | ||||
| 	flog.mm.Infof("Choosing Mattermost connection type %s", b.kind) | ||||
| 	mchan := make(chan *MMMessage) | ||||
| 	if b.kind == Legacy { | ||||
| 		go b.handleMatterHook(mchan) | ||||
| 	} else { | ||||
| 		go b.handleMatterClient(mchan) | ||||
| func (b *Bridge) mapChannels() error { | ||||
| 	for _, val := range b.Config.Channel { | ||||
| 		m := make(map[string]string) | ||||
| 		m["irc"] = val.IRC | ||||
| 		m["mattermost"] = val.Mattermost | ||||
| 		m["xmpp"] = val.Xmpp | ||||
| 		m["gitter"] = val.Gitter | ||||
| 		m["slack"] = val.Slack | ||||
| 		b.Channels = append(b.Channels, m) | ||||
| 	} | ||||
| 	flog.mm.Info("Start listening for Mattermost messages") | ||||
| 	for message := range mchan { | ||||
| 		var username string | ||||
| 		if b.ignoreMessage(message.Username, message.Text, "mattermost") { | ||||
| 			continue | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bridge) mapIgnores() { | ||||
| 	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 != "" { | ||||
| 			username = strings.Replace(b.Config.IRC.RemoteNickFormat, "{NICK}", message.Username, -1) | ||||
| 		} | ||||
| 		cmds := strings.Fields(message.Text) | ||||
| 		// empty message | ||||
| 		if len(cmds) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		cmd := cmds[0] | ||||
| 		switch cmd { | ||||
| 		case "!users": | ||||
| 			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) | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleMessage(msg config.Message, dest Bridger) { | ||||
| 	if b.ignoreMessage(&msg) { | ||||
| 		return | ||||
| 	} | ||||
| 	if dest.Name() != msg.Origin { | ||||
| 		msg.Channel = b.getDestChannel(&msg, dest.Name()) | ||||
| 		if msg.Channel == "" { | ||||
| 			return | ||||
| 		} | ||||
| 		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 { | ||||
| 	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 | ||||
| 	} | ||||
| func (b *Bridge) ignoreMessage(msg *config.Message) bool { | ||||
| 	// should we discard messages ? | ||||
| 	for _, entry := range ignoreNicks { | ||||
| 		if nick == entry { | ||||
| 	for _, entry := range b.ignoreNicks[msg.Origin] { | ||||
| 		if msg.Username == entry { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	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 ( | ||||
| 	"gopkg.in/gcfg.v1" | ||||
| @@ -6,6 +6,13 @@ import ( | ||||
| 	"log" | ||||
| ) | ||||
| 
 | ||||
| type Message struct { | ||||
| 	Text     string | ||||
| 	Channel  string | ||||
| 	Username string | ||||
| 	Origin   string | ||||
| } | ||||
| 
 | ||||
| type Config struct { | ||||
| 	IRC struct { | ||||
| 		UseTLS           bool | ||||
| @@ -19,6 +26,14 @@ type Config struct { | ||||
| 		NickServPassword string | ||||
| 		RemoteNickFormat string | ||||
| 		IgnoreNicks      string | ||||
| 		Enable           bool | ||||
| 	} | ||||
| 	Gitter struct { | ||||
| 		Enable           bool | ||||
| 		IgnoreNicks      string | ||||
| 		Nick             string | ||||
| 		RemoteNickFormat string | ||||
| 		Token            string | ||||
| 	} | ||||
| 	Mattermost struct { | ||||
| 		URL                    string | ||||
| @@ -34,16 +49,47 @@ type Config struct { | ||||
| 		Team                   string | ||||
| 		Login                  string | ||||
| 		Password               string | ||||
| 		RemoteNickFormat       *string | ||||
| 		RemoteNickFormat       string | ||||
| 		IgnoreNicks            string | ||||
| 		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 { | ||||
| 		IRC        string | ||||
| 		Mattermost string | ||||
| 		Xmpp       string | ||||
| 		Gitter     string | ||||
| 		Slack      string | ||||
| 	} | ||||
| 	General struct { | ||||
| 		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 ( | ||||
| 	"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] | ||||
| #Enable enables this bridge | ||||
| #OPTIONAL (default false) | ||||
| Enable=true | ||||
| #irc server to connect to.  | ||||
| #REQUIRED | ||||
| Server="irc.freenode.net:6667" | ||||
| @@ -13,7 +16,7 @@ UseTLS=false | ||||
|  | ||||
| #Enable SASL (PLAIN) authentication. (freenode requires this from eg AWS hosts) | ||||
| #It uses NickServNick and NickServPassword as login and password | ||||
| #OPTIONAL (deefault false) | ||||
| #OPTIONAL (default false) | ||||
| UseSASL=false | ||||
|  | ||||
| #Enable to not verify the certificate on your irc server. i | ||||
| @@ -31,21 +34,55 @@ Nick="matterbot" | ||||
| NickServNick="nickserv" | ||||
| 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. | ||||
| #OPTIONAL (default NICK:) | ||||
| RemoteNickFormat="{NICK}: " | ||||
| #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||
| #OPTIONAL (default {BRIDGE}-{NICK}) | ||||
| RemoteNickFormat="[{BRIDGE}] <{NICK}>  | ||||
|  | ||||
| #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 | ||||
| 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] | ||||
| #Enable enables this bridge | ||||
| #OPTIONAL (default false) | ||||
| Enable=true | ||||
|  | ||||
| #### Settings for webhook matterbridge. | ||||
| #### These settings will not be used when using -plus switch which doesn't use  | ||||
| #### webhooks. | ||||
| @@ -83,7 +120,7 @@ Team="yourteam" | ||||
| Login="yourlogin" | ||||
| 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) | ||||
| NoTLS=false | ||||
|  | ||||
| @@ -98,18 +135,19 @@ SkipTLSVerify=true | ||||
| #OPTIONAL (default 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  | ||||
| #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  | ||||
| #OPTIONAL (default 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. | ||||
| #OPTIONAL (default irc-NICK) | ||||
| RemoteNickFormat="irc-{NICK}" | ||||
| #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 mattermost.  | ||||
| #Possible options are "table" and "plain" | ||||
| @@ -119,7 +157,95 @@ NickFormatter=plain | ||||
| #OPTIONAL (default 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  | ||||
| IgnoreNicks="mmbot spammer2" | ||||
|  | ||||
| @@ -130,14 +256,24 @@ IgnoreNicks="mmbot spammer2" | ||||
| #The name is just an identifier for you. | ||||
| #REQUIRED (at least 1 channel) | ||||
| [Channel "channel1"]  | ||||
| #Choose the IRC channel to send mattermost messages to. | ||||
| #Choose the IRC channel to send messages to. | ||||
| IRC="#off-topic" | ||||
| #Choose the mattermost channel to send IRC messages to. | ||||
| #Choose the mattermost channel to messages to. | ||||
| 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"] | ||||
| IRC="#testing" | ||||
| mattermost="testing" | ||||
| xmpp="testing" | ||||
| gitter="user/repo" | ||||
| slack="testing" | ||||
|  | ||||
| ################################################################### | ||||
| #general | ||||
| @@ -146,3 +282,6 @@ mattermost="testing" | ||||
| #request your API key on https://github.com/giphy/GiphyAPI. This is a public beta key.  | ||||
| #OPTIONAL | ||||
| GiphyApiKey="dc6zaTOxFJmzC" | ||||
|  | ||||
| #Enabling plus means you'll use the API version instead of the webhooks one | ||||
| Plus=false | ||||
|   | ||||
| @@ -4,10 +4,11 @@ import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| var version = "0.5.0-beta2" | ||||
| var version = "0.6.1" | ||||
|  | ||||
| func init() { | ||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||
| @@ -17,7 +18,7 @@ func main() { | ||||
| 	flagConfig := flag.String("conf", "matterbridge.conf", "config file") | ||||
| 	flagDebug := flag.Bool("debug", false, "enable debug") | ||||
| 	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() | ||||
| 	if *flagVersion { | ||||
| 		fmt.Println("version:", version) | ||||
| @@ -29,10 +30,12 @@ func main() { | ||||
| 		log.SetLevel(log.DebugLevel) | ||||
| 	} | ||||
| 	fmt.Println("running version", version) | ||||
| 	cfg := config.NewConfig(*flagConfig) | ||||
| 	if *flagPlus { | ||||
| 		bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "") | ||||
| 	} else { | ||||
| 		bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "legacy") | ||||
| 		cfg.General.Plus = true | ||||
| 	} | ||||
| 	err := bridge.NewBridge(cfg) | ||||
| 	if err != nil { | ||||
| 		log.Debugf("starting bridge failed %#v", err) | ||||
| 	} | ||||
| 	select {} | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package matterclient | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"net/http/cookiejar" | ||||
| @@ -27,7 +28,7 @@ type Credentials struct { | ||||
| } | ||||
|  | ||||
| type Message struct { | ||||
| 	Raw      *model.Message | ||||
| 	Raw      *model.WebSocketEvent | ||||
| 	Post     *model.Post | ||||
| 	Team     string | ||||
| 	Channel  string | ||||
| @@ -49,14 +50,16 @@ type MMClient struct { | ||||
| 	Team        *Team | ||||
| 	OtherTeams  []*Team | ||||
| 	Client      *model.Client | ||||
| 	WsClient    *websocket.Conn | ||||
| 	WsQuit      bool | ||||
| 	WsAway      bool | ||||
| 	WsConnected bool | ||||
| 	User        *model.User | ||||
| 	Users       map[string]*model.User | ||||
| 	MessageChan chan *Message | ||||
| 	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 { | ||||
| @@ -151,7 +154,7 @@ func (m *MMClient) Login() error { | ||||
| 	m.Client.SetTeamId(m.Team.Id) | ||||
|  | ||||
| 	// 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.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) | ||||
|  | ||||
| @@ -169,6 +172,8 @@ func (m *MMClient) Login() error { | ||||
| 	} | ||||
| 	b.Reset() | ||||
|  | ||||
| 	m.WsSequence = 1 | ||||
| 	m.WsPingChan = make(chan *model.WebSocketResponse) | ||||
| 	// only start to parse WS messages when login is completely done | ||||
| 	m.WsConnected = true | ||||
|  | ||||
| @@ -180,7 +185,6 @@ func (m *MMClient) Logout() error { | ||||
| 	m.WsQuit = true | ||||
| 	m.WsClient.Close() | ||||
| 	m.WsClient.UnderlyingConn().Close() | ||||
| 	m.WsClient = nil | ||||
| 	_, err := m.Client.Logout() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -190,42 +194,46 @@ func (m *MMClient) Logout() error { | ||||
|  | ||||
| func (m *MMClient) WsReceiver() { | ||||
| 	for { | ||||
| 		var rmsg model.Message | ||||
| 		var rawMsg json.RawMessage | ||||
| 		var err error | ||||
|  | ||||
| 		if m.WsQuit { | ||||
| 			m.log.Debug("exiting WsReceiver") | ||||
| 			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) | ||||
| 			// reconnect | ||||
| 			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 | ||||
| 		} | ||||
| 		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 | ||||
| 		} | ||||
| 		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) { | ||||
| 	switch rmsg.Raw.Action { | ||||
| 	case model.ACTION_POSTED: | ||||
| 	switch rmsg.Raw.Event { | ||||
| 	case model.WEBSOCKET_EVENT_POSTED: | ||||
| 		m.parseActionPost(rmsg) | ||||
| 		/* | ||||
| 			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) { | ||||
| 	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 | ||||
| 	if m.GetUser(data.UserId) == nil { | ||||
| 		m.UpdateUsers() | ||||
| @@ -246,7 +263,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) { | ||||
| 	rmsg.Channel = m.GetChannelName(data.ChannelId) | ||||
| 	rmsg.Team = m.GetTeamName(rmsg.Raw.TeamId) | ||||
| 	// direct message | ||||
| 	if data.Type == "D" { | ||||
| 	if rmsg.Raw.Data["channel_type"] == "D" { | ||||
| 		rmsg.Channel = m.GetUser(data.UserId).Username | ||||
| 	} | ||||
| 	rmsg.Text = data.Message | ||||
| @@ -255,7 +272,10 @@ func (m *MMClient) parseActionPost(rmsg *Message) { | ||||
| } | ||||
|  | ||||
| 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.Users = mmusers.Data.(map[string]*model.User) | ||||
| 	m.Unlock() | ||||
| @@ -263,8 +283,14 @@ func (m *MMClient) UpdateUsers() error { | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateChannels() error { | ||||
| 	mmchannels, _ := m.Client.GetChannels("") | ||||
| 	mmchannels2, _ := m.Client.GetMoreChannels("") | ||||
| 	mmchannels, err := m.Client.GetChannels("") | ||||
| 	if err != nil { | ||||
| 		return errors.New(err.DetailedError) | ||||
| 	} | ||||
| 	mmchannels2, err := m.Client.GetMoreChannels("") | ||||
| 	if err != nil { | ||||
| 		return errors.New(err.DetailedError) | ||||
| 	} | ||||
| 	m.Lock() | ||||
| 	m.Team.Channels = mmchannels.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) { | ||||
| 	m.log.Debugf("posting lastview %#v", channelId) | ||||
| 	_, err := m.Client.UpdateLastViewedAt(channelId) | ||||
| 	_, err := m.Client.UpdateLastViewedAt(channelId, true) | ||||
| 	if err != nil { | ||||
| 		m.log.Error(err) | ||||
| 	} | ||||
| @@ -498,6 +524,7 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string { | ||||
| 	var channels []*model.Channel | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		channels = append(channels, t.Channels.Channels...) | ||||
| 		channels = append(channels, t.MoreChannels.Channels...) | ||||
| 		for _, c := range channels { | ||||
| 			if c.Id == channelId { | ||||
| 				return t.Id | ||||
| @@ -534,6 +561,42 @@ func (m *MMClient) GetUser(userId string) *model.User { | ||||
| 	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 | ||||
| func (m *MMClient) initUser() error { | ||||
| 	m.Lock() | ||||
| @@ -568,3 +631,14 @@ func (m *MMClient) initUser() error { | ||||
| 	} | ||||
| 	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) | ||||
| type IMessage struct { | ||||
| 	BotID       string `schema:"bot_id"` | ||||
| 	BotName     string `schema:"bot_name"` | ||||
| 	Token       string `schema:"token"` | ||||
| 	TeamID      string `schema:"team_id"` | ||||
| 	TeamDomain  string `schema:"team_domain"` | ||||
| @@ -36,6 +38,8 @@ type IMessage struct { | ||||
| 	UserID      string `schema:"user_id"` | ||||
| 	UserName    string `schema:"user_name"` | ||||
| 	PostId      string `schema:"post_id"` | ||||
| 	RawText     string `schema:"raw_text"` | ||||
| 	ServiceId   string `schema:"service_id"` | ||||
| 	Text        string `schema:"text"` | ||||
| 	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 | ||||
| 	Syncronize() *model.AppError | ||||
| 	StartLdapSyncJob() | ||||
| 	SyncNow() | ||||
| 	RunTest() *model.AppError | ||||
| 	GetAllLdapUsers() ([]*model.User, *model.AppError) | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 	AuthCode     string `json:"auth_code"` | ||||
| 	ClientId     string `json:"client_id"` | ||||
| 	UserId       string `json:"user_id"` | ||||
| 	Token        string `json:"token"` | ||||
| 	RefreshToken string `json:"refresh_token"` | ||||
| 	RedirectUri  string `json:"redirect_uri"` | ||||
| 	ExpiresAt    int64  `json:"expires_at"` | ||||
| } | ||||
|  | ||||
| type AccessResponse struct { | ||||
| @@ -33,8 +35,12 @@ type AccessResponse struct { | ||||
| // correctly. | ||||
| func (ad *AccessData) IsValid() *AppError { | ||||
|  | ||||
| 	if len(ad.AuthCode) == 0 || len(ad.AuthCode) > 128 { | ||||
| 		return NewLocAppError("AccessData.IsValid", "model.access.is_valid.auth_code.app_error", nil, "") | ||||
| 	if len(ad.ClientId) == 0 || len(ad.ClientId) > 26 { | ||||
| 		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 { | ||||
| @@ -52,6 +58,19 @@ func (ad *AccessData) IsValid() *AppError { | ||||
| 	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 { | ||||
| 	b, err := json.Marshal(ad) | ||||
| 	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 ( | ||||
| 	AUTHCODE_EXPIRE_TIME   = 60 * 10 // 10 minutes | ||||
| 	AUTHCODE_RESPONSE_TYPE = "code" | ||||
| 	DEFAULT_SCOPE          = "user" | ||||
| ) | ||||
|  | ||||
| type AuthData struct { | ||||
| @@ -71,6 +72,10 @@ func (ad *AuthData) PreSave() { | ||||
| 	if ad.CreateAt == 0 { | ||||
| 		ad.CreateAt = GetMillis() | ||||
| 	} | ||||
|  | ||||
| 	if len(ad.Scope) == 0 { | ||||
| 		ad.Scope = DEFAULT_SCOPE | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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() | ||||
| } | ||||
|  | ||||
| func (o *Channel) PreExport() { | ||||
| } | ||||
|  | ||||
| func GetDMNameFromIds(userId1, userId2 string) string { | ||||
| 	if userId1 > userId2 { | ||||
| 		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 ( | ||||
| 	HEADER_REQUEST_ID         = "X-Request-ID" | ||||
| 	HEADER_VERSION_ID         = "X-Version-ID" | ||||
| 	HEADER_CLUSTER_ID         = "X-Cluster-ID" | ||||
| 	HEADER_ETAG_SERVER        = "ETag" | ||||
| 	HEADER_ETAG_CLIENT        = "If-None-Match" | ||||
| 	HEADER_FORWARDED          = "X-Forwarded-For" | ||||
| @@ -32,6 +33,9 @@ const ( | ||||
| 	HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" | ||||
| 	STATUS                    = "status" | ||||
| 	STATUS_OK                 = "OK" | ||||
| 	STATUS_FAIL               = "FAIL" | ||||
|  | ||||
| 	CLIENT_DIR = "webapp/dist" | ||||
|  | ||||
| 	API_URL_SUFFIX_V1 = "/api/v1" | ||||
| 	API_URL_SUFFIX_V3 = "/api/v3" | ||||
| @@ -276,6 +280,9 @@ func (c *Client) GetPing() (map[string]string, *AppError) { | ||||
|  | ||||
| // 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) { | ||||
| 	m := make(map[string]string) | ||||
| 	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) { | ||||
| 	if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiGet("/teams/all", "", ""); err != nil { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiGet("/teams/all_team_listings", "", ""); err != nil { | ||||
| 		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) { | ||||
| 	m := make(map[string]string) | ||||
| 	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) { | ||||
| 	data := make(map[string]string) | ||||
| 	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) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/update", team.ToJson()); err != nil { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil { | ||||
| 		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) { | ||||
|  | ||||
| 	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) { | ||||
| 	if r, err := c.DoApiGet("/users/"+id+"/get", "", etag); err != nil { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiGet("/users/me", "", etag); err != nil { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil { | ||||
| 		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) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["id"] = id | ||||
| @@ -509,6 +544,8 @@ func (c *Client) LoginById(id string, password string) (*Result, *AppError) { | ||||
| 	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) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["login_id"] = loginId | ||||
| @@ -516,6 +553,7 @@ func (c *Client) Login(loginId string, password string) (*Result, *AppError) { | ||||
| 	return c.login(m) | ||||
| } | ||||
|  | ||||
| // LoginByLdap authenticates a user by LDAP id and password. | ||||
| func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["login_id"] = loginId | ||||
| @@ -524,6 +562,9 @@ func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppErro | ||||
| 	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) { | ||||
| 	m := make(map[string]string) | ||||
| 	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) { | ||||
| 	if r, err := c.DoApiPost("/users/logout", ""); err != nil { | ||||
| 		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) { | ||||
| 	m := make(map[string]string) | ||||
| 	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) { | ||||
| 	if r, err := c.DoApiGet("/users/generate_mfa_qr", "", ""); err != nil { | ||||
| 		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) { | ||||
| 	m := make(map[string]interface{}) | ||||
| 	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) { | ||||
| 	if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiGet("/admin/compliance_reports", "", ""); err != nil { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/update_last_viewed_at", ""); err != nil { | ||||
| // UpdateLastViewedAt will mark a channel as read. | ||||
| // 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 | ||||
| 	} else { | ||||
| 		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) { | ||||
| 	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) { | ||||
| 	if r, err := c.DoApiPost("/users/status", ArrayToJson(data)); err != nil { | ||||
| // GetStatuses returns a map of string statuses using user id as the key | ||||
| 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 | ||||
| 	} else { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil { | ||||
| 		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) { | ||||
| 	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 | ||||
| @@ -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) { | ||||
| 	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 | ||||
| 	} else { | ||||
| 		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) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -1648,3 +1854,47 @@ func (c *Client) DeleteEmoji(id string) (bool, *AppError) { | ||||
| func (c *Client) GetCustomEmojiImageUrl(id string) string { | ||||
| 	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 ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	CONN_SECURITY_NONE     = "" | ||||
| 	CONN_SECURITY_PLAIN    = "PLAIN" | ||||
| 	CONN_SECURITY_TLS      = "TLS" | ||||
| 	CONN_SECURITY_STARTTLS = "STARTTLS" | ||||
|  | ||||
| @@ -22,8 +24,9 @@ const ( | ||||
| 	PASSWORD_MAXIMUM_LENGTH = 64 | ||||
| 	PASSWORD_MINIMUM_LENGTH = 5 | ||||
|  | ||||
| 	SERVICE_GITLAB = "gitlab" | ||||
| 	SERVICE_GOOGLE = "google" | ||||
| 	SERVICE_GITLAB    = "gitlab" | ||||
| 	SERVICE_GOOGLE    = "google" | ||||
| 	SERVICE_OFFICE365 = "office365" | ||||
|  | ||||
| 	WEBSERVER_MODE_REGULAR  = "regular" | ||||
| 	WEBSERVER_MODE_GZIP     = "gzip" | ||||
| @@ -44,9 +47,15 @@ const ( | ||||
| 	RESTRICT_EMOJI_CREATION_ALL          = "all" | ||||
| 	RESTRICT_EMOJI_CREATION_ADMIN        = "admin" | ||||
| 	RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN = "system_admin" | ||||
|  | ||||
| 	EMAIL_BATCHING_BUFFER_SIZE = 256 | ||||
| 	EMAIL_BATCHING_INTERVAL    = 30 | ||||
|  | ||||
| 	SITENAME_MAX_LENGTH = 30 | ||||
| ) | ||||
|  | ||||
| type ServiceSettings struct { | ||||
| 	SiteURL                           *string | ||||
| 	ListenAddress                     string | ||||
| 	MaximumLoginAttempts              int | ||||
| 	SegmentDeveloperKey               string | ||||
| @@ -75,6 +84,12 @@ type ServiceSettings struct { | ||||
| 	RestrictCustomEmojiCreation       *string | ||||
| } | ||||
|  | ||||
| type ClusterSettings struct { | ||||
| 	Enable                 *bool | ||||
| 	InterNodeListenAddress *string | ||||
| 	InterNodeUrls          []string | ||||
| } | ||||
|  | ||||
| type SSOSettings struct { | ||||
| 	Enable          bool | ||||
| 	Secret          string | ||||
| @@ -103,6 +118,7 @@ type LogSettings struct { | ||||
| 	FileFormat             string | ||||
| 	FileLocation           string | ||||
| 	EnableWebhookDebugging bool | ||||
| 	EnableDiagnostics      *bool | ||||
| } | ||||
|  | ||||
| type PasswordSettings struct { | ||||
| @@ -118,7 +134,7 @@ type FileSettings struct { | ||||
| 	DriverName                 string | ||||
| 	Directory                  string | ||||
| 	EnablePublicLink           bool | ||||
| 	PublicLinkSalt             string | ||||
| 	PublicLinkSalt             *string | ||||
| 	ThumbnailWidth             int | ||||
| 	ThumbnailHeight            int | ||||
| 	PreviewWidth               int | ||||
| @@ -155,6 +171,9 @@ type EmailSettings struct { | ||||
| 	SendPushNotifications    *bool | ||||
| 	PushNotificationServer   *string | ||||
| 	PushNotificationContents *string | ||||
| 	EnableEmailBatching      *bool | ||||
| 	EmailBatchingBufferSize  *int | ||||
| 	EmailBatchingInterval    *int | ||||
| } | ||||
|  | ||||
| type RateLimitSettings struct { | ||||
| @@ -189,10 +208,12 @@ type TeamSettings struct { | ||||
| 	RestrictTeamNames                *bool | ||||
| 	EnableCustomBrand                *bool | ||||
| 	CustomBrandText                  *string | ||||
| 	CustomDescriptionText            *string | ||||
| 	RestrictDirectMessage            *string | ||||
| 	RestrictTeamInvite               *string | ||||
| 	RestrictPublicChannelManagement  *string | ||||
| 	RestrictPrivateChannelManagement *string | ||||
| 	UserStatusAwayTimeout            *int64 | ||||
| } | ||||
|  | ||||
| type LdapSettings struct { | ||||
| @@ -265,6 +286,12 @@ type SamlSettings struct { | ||||
| 	LoginButtonText *string | ||||
| } | ||||
|  | ||||
| type NativeAppSettings struct { | ||||
| 	AppDownloadLink        *string | ||||
| 	AndroidAppDownloadLink *string | ||||
| 	IosAppDownloadLink     *string | ||||
| } | ||||
|  | ||||
| type Config struct { | ||||
| 	ServiceSettings      ServiceSettings | ||||
| 	TeamSettings         TeamSettings | ||||
| @@ -278,10 +305,13 @@ type Config struct { | ||||
| 	SupportSettings      SupportSettings | ||||
| 	GitLabSettings       SSOSettings | ||||
| 	GoogleSettings       SSOSettings | ||||
| 	Office365Settings    SSOSettings | ||||
| 	LdapSettings         LdapSettings | ||||
| 	ComplianceSettings   ComplianceSettings | ||||
| 	LocalizationSettings LocalizationSettings | ||||
| 	SamlSettings         SamlSettings | ||||
| 	NativeAppSettings    NativeAppSettings | ||||
| 	ClusterSettings      ClusterSettings | ||||
| } | ||||
|  | ||||
| func (o *Config) ToJson() string { | ||||
| @@ -299,6 +329,8 @@ func (o *Config) GetSSOService(service string) *SSOSettings { | ||||
| 		return &o.GitLabSettings | ||||
| 	case SERVICE_GOOGLE: | ||||
| 		return &o.GoogleSettings | ||||
| 	case SERVICE_OFFICE365: | ||||
| 		return &o.Office365Settings | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| @@ -326,8 +358,9 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.FileSettings.MaxFileSize = 52428800 // 50 MB | ||||
| 	} | ||||
|  | ||||
| 	if len(o.FileSettings.PublicLinkSalt) == 0 { | ||||
| 		o.FileSettings.PublicLinkSalt = NewRandomString(32) | ||||
| 	if len(*o.FileSettings.PublicLinkSalt) == 0 { | ||||
| 		o.FileSettings.PublicLinkSalt = new(string) | ||||
| 		*o.FileSettings.PublicLinkSalt = NewRandomString(32) | ||||
| 	} | ||||
|  | ||||
| 	if o.FileSettings.AmazonS3LocationConstraint == nil { | ||||
| @@ -348,6 +381,11 @@ func (o *Config) SetDefaults() { | ||||
| 		o.EmailSettings.PasswordResetSalt = NewRandomString(32) | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.SiteURL == nil { | ||||
| 		o.ServiceSettings.SiteURL = new(string) | ||||
| 		*o.ServiceSettings.SiteURL = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.EnableDeveloper == nil { | ||||
| 		o.ServiceSettings.EnableDeveloper = new(bool) | ||||
| 		*o.ServiceSettings.EnableDeveloper = false | ||||
| @@ -408,6 +446,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.TeamSettings.CustomBrandText = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.CustomDescriptionText == nil { | ||||
| 		o.TeamSettings.CustomDescriptionText = new(string) | ||||
| 		*o.TeamSettings.CustomDescriptionText = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.EnableOpenServer == nil { | ||||
| 		o.TeamSettings.EnableOpenServer = new(bool) | ||||
| 		*o.TeamSettings.EnableOpenServer = false | ||||
| @@ -433,6 +476,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.TeamSettings.RestrictPrivateChannelManagement = PERMISSIONS_ALL | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.UserStatusAwayTimeout == nil { | ||||
| 		o.TeamSettings.UserStatusAwayTimeout = new(int64) | ||||
| 		*o.TeamSettings.UserStatusAwayTimeout = 300 | ||||
| 	} | ||||
|  | ||||
| 	if o.EmailSettings.EnableSignInWithEmail == nil { | ||||
| 		o.EmailSettings.EnableSignInWithEmail = new(bool) | ||||
|  | ||||
| @@ -468,13 +516,28 @@ func (o *Config) SetDefaults() { | ||||
| 		*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) { | ||||
| 		o.SupportSettings.TermsOfServiceLink = nil | ||||
| 	} | ||||
|  | ||||
| 	if o.SupportSettings.TermsOfServiceLink == nil { | ||||
| 		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) { | ||||
| @@ -483,7 +546,7 @@ func (o *Config) SetDefaults() { | ||||
|  | ||||
| 	if o.SupportSettings.PrivacyPolicyLink == nil { | ||||
| 		o.SupportSettings.PrivacyPolicyLink = new(string) | ||||
| 		*o.SupportSettings.PrivacyPolicyLink = "/static/help/privacy.html" | ||||
| 		*o.SupportSettings.PrivacyPolicyLink = "" | ||||
| 	} | ||||
|  | ||||
| 	if !IsSafeLink(o.SupportSettings.AboutLink) { | ||||
| @@ -492,7 +555,7 @@ func (o *Config) SetDefaults() { | ||||
|  | ||||
| 	if o.SupportSettings.AboutLink == nil { | ||||
| 		o.SupportSettings.AboutLink = new(string) | ||||
| 		*o.SupportSettings.AboutLink = "/static/help/about.html" | ||||
| 		*o.SupportSettings.AboutLink = "" | ||||
| 	} | ||||
|  | ||||
| 	if !IsSafeLink(o.SupportSettings.HelpLink) { | ||||
| @@ -501,7 +564,7 @@ func (o *Config) SetDefaults() { | ||||
|  | ||||
| 	if o.SupportSettings.HelpLink == nil { | ||||
| 		o.SupportSettings.HelpLink = new(string) | ||||
| 		*o.SupportSettings.HelpLink = "/static/help/help.html" | ||||
| 		*o.SupportSettings.HelpLink = "" | ||||
| 	} | ||||
|  | ||||
| 	if !IsSafeLink(o.SupportSettings.ReportAProblemLink) { | ||||
| @@ -510,7 +573,7 @@ func (o *Config) SetDefaults() { | ||||
|  | ||||
| 	if o.SupportSettings.ReportAProblemLink == nil { | ||||
| 		o.SupportSettings.ReportAProblemLink = new(string) | ||||
| 		*o.SupportSettings.ReportAProblemLink = "/static/help/report_problem.html" | ||||
| 		*o.SupportSettings.ReportAProblemLink = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SupportSettings.SupportEmail == nil { | ||||
| @@ -675,6 +738,20 @@ func (o *Config) SetDefaults() { | ||||
| 		*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 { | ||||
| 		o.ComplianceSettings.Enable = new(bool) | ||||
| 		*o.ComplianceSettings.Enable = false | ||||
| @@ -705,6 +782,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.LocalizationSettings.AvailableLocales = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LogSettings.EnableDiagnostics == nil { | ||||
| 		o.LogSettings.EnableDiagnostics = new(bool) | ||||
| 		*o.LogSettings.EnableDiagnostics = true | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.Enable == nil { | ||||
| 		o.SamlSettings.Enable = new(bool) | ||||
| 		*o.SamlSettings.Enable = false | ||||
| @@ -784,6 +866,21 @@ func (o *Config) SetDefaults() { | ||||
| 		o.SamlSettings.LocaleAttribute = new(string) | ||||
| 		*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 { | ||||
| @@ -792,10 +889,24 @@ func (o *Config) IsValid() *AppError { | ||||
| 		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 { | ||||
| 		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 { | ||||
| 		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, "") | ||||
| 	} | ||||
|  | ||||
| 	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, "") | ||||
| 	} | ||||
|  | ||||
| 	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, "") | ||||
| 	} | ||||
|  | ||||
| @@ -872,6 +983,14 @@ func (o *Config) IsValid() *AppError { | ||||
| 		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 { | ||||
| 		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.LdapServer == "" || | ||||
| 			*o.LdapSettings.BaseDN == "" || | ||||
| 			*o.LdapSettings.BindUsername == "" || | ||||
| 			*o.LdapSettings.BindPassword == "" || | ||||
| 			*o.LdapSettings.FirstNameAttribute == "" || | ||||
| 			*o.LdapSettings.LastNameAttribute == "" || | ||||
| 			*o.LdapSettings.EmailAttribute == "" || | ||||
| 			*o.LdapSettings.UsernameAttribute == "" || | ||||
| 			*o.LdapSettings.IdAttribute == "" { | ||||
| 			return NewLocAppError("Config.IsValid", "Required LDAP field missing", nil, "") | ||||
| 		if *o.LdapSettings.LdapServer == "" { | ||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_server", nil, "") | ||||
| 		} | ||||
|  | ||||
| 		if *o.LdapSettings.BaseDN == "" { | ||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_basedn", nil, "") | ||||
| 		} | ||||
|  | ||||
| 		if *o.LdapSettings.EmailAttribute == "" { | ||||
| 			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 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, "") | ||||
| 		} | ||||
|  | ||||
| @@ -927,14 +1054,6 @@ func (o *Config) IsValid() *AppError { | ||||
| 			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 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, "") | ||||
| @@ -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}, "") | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| @@ -976,7 +1099,7 @@ func (o *Config) Sanitize() { | ||||
| 		*o.LdapSettings.BindPassword = FAKE_SETTING | ||||
| 	} | ||||
|  | ||||
| 	o.FileSettings.PublicLinkSalt = FAKE_SETTING | ||||
| 	*o.FileSettings.PublicLinkSalt = FAKE_SETTING | ||||
| 	if len(o.FileSettings.AmazonS3SecretAccessKey) > 0 { | ||||
| 		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) | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 	return fmt.Sprintf( | ||||
| 		"%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"` | ||||
| 	LDAP                 *bool `json:"ldap"` | ||||
| 	MFA                  *bool `json:"mfa"` | ||||
| 	GoogleSSO            *bool `json:"google_sso"` | ||||
| 	GoogleOAuth          *bool `json:"google_oauth"` | ||||
| 	Office365OAuth       *bool `json:"office365_oauth"` | ||||
| 	Compliance           *bool `json:"compliance"` | ||||
| 	Cluster              *bool `json:"cluster"` | ||||
| 	CustomBrand          *bool `json:"custom_brand"` | ||||
| 	MHPNS                *bool `json:"mhpns"` | ||||
| 	SAML                 *bool `json:"saml"` | ||||
| @@ -44,6 +46,22 @@ type Features struct { | ||||
| 	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() { | ||||
| 	if f.FutureFeatures == nil { | ||||
| 		f.FutureFeatures = new(bool) | ||||
| @@ -65,9 +83,14 @@ func (f *Features) SetDefaults() { | ||||
| 		*f.MFA = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.GoogleSSO == nil { | ||||
| 		f.GoogleSSO = new(bool) | ||||
| 		*f.GoogleSSO = *f.FutureFeatures | ||||
| 	if f.GoogleOAuth == nil { | ||||
| 		f.GoogleOAuth = new(bool) | ||||
| 		*f.GoogleOAuth = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.Office365OAuth == nil { | ||||
| 		f.Office365OAuth = new(bool) | ||||
| 		*f.Office365OAuth = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.Compliance == nil { | ||||
| @@ -75,6 +98,11 @@ func (f *Features) SetDefaults() { | ||||
| 		*f.Compliance = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.Cluster == nil { | ||||
| 		f.Cluster = new(bool) | ||||
| 		*f.Cluster = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.CustomBrand == nil { | ||||
| 		f.CustomBrand = new(bool) | ||||
| 		*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"` | ||||
| 	Name         string      `json:"name"` | ||||
| 	Description  string      `json:"description"` | ||||
| 	IconURL      string      `json:"icon_url"` | ||||
| 	CallbackUrls StringArray `json:"callback_urls"` | ||||
| 	Homepage     string      `json:"homepage"` | ||||
| 	IsTrusted    bool        `json:"is_trusted"` | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 	} | ||||
|  | ||||
| @@ -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) | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| @@ -85,10 +99,6 @@ func (a *OAuthApp) PreSave() { | ||||
|  | ||||
| 	a.CreateAt = GetMillis() | ||||
| 	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. | ||||
| @@ -157,3 +167,23 @@ func OAuthAppMapFromJson(data io.Reader) map[string]*OAuthApp { | ||||
| 		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" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type OutgoingWebhook struct { | ||||
| @@ -21,6 +22,7 @@ type OutgoingWebhook struct { | ||||
| 	ChannelId    string      `json:"channel_id"` | ||||
| 	TeamId       string      `json:"team_id"` | ||||
| 	TriggerWords StringArray `json:"trigger_words"` | ||||
| 	TriggerWhen  int         `json:"trigger_when"` | ||||
| 	CallbackURLs StringArray `json:"callback_urls"` | ||||
| 	DisplayName  string      `json:"display_name"` | ||||
| 	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, "") | ||||
| 	} | ||||
|  | ||||
| 	if o.TriggerWhen > 1 { | ||||
| 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -204,3 +210,17 @@ func (o *OutgoingWebhook) HasTriggerWord(word string) bool { | ||||
|  | ||||
| 	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_SYSTEM_GENERIC        = "system_generic" | ||||
| 	POST_JOIN_LEAVE            = "system_join_leave" | ||||
| 	POST_ADD_REMOVE            = "system_add_remove" | ||||
| 	POST_HEADER_CHANGE         = "system_header_change" | ||||
| 	POST_CHANNEL_DELETED       = "system_channel_deleted" | ||||
| 	POST_EPHEMERAL             = "system_ephemeral" | ||||
| @@ -109,7 +110,7 @@ func (o *Post) IsValid() *AppError { | ||||
| 	} | ||||
|  | ||||
| 	// 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) | ||||
| 	} | ||||
|  | ||||
| @@ -162,9 +163,6 @@ func (o *Post) AddProp(key string, value interface{}) { | ||||
| 	o.Props[key] = value | ||||
| } | ||||
|  | ||||
| func (o *Post) PreExport() { | ||||
| } | ||||
|  | ||||
| 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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										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 ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
|  | ||||
| @@ -13,12 +15,28 @@ const ( | ||||
| 	PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show" | ||||
| 	PREFERENCE_CATEGORY_TUTORIAL_STEPS      = "tutorial_step" | ||||
| 	PREFERENCE_CATEGORY_ADVANCED_SETTINGS   = "advanced_settings" | ||||
| 	PREFERENCE_CATEGORY_FLAGGED_POST        = "flagged_post" | ||||
|  | ||||
| 	PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings" | ||||
| 	PREFERENCE_NAME_COLLAPSE_SETTING     = "collapse_previews" | ||||
| 	PREFERENCE_CATEGORY_DISPLAY_SETTINGS   = "display_settings" | ||||
| 	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_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 { | ||||
| @@ -57,13 +75,48 @@ func (o *Preference) IsValid() *AppError { | ||||
| 		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) | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| 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 ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	PUSH_NOTIFY_APPLE   = "apple" | ||||
| 	PUSH_NOTIFY_ANDROID = "android" | ||||
|  | ||||
| 	PUSH_TYPE_MESSAGE = "message" | ||||
| 	PUSH_TYPE_CLEAR   = "clear" | ||||
|  | ||||
| 	CATEGORY_DM = "DIRECT_MESSAGE" | ||||
|  | ||||
| 	MHPNS = "https://push.mattermost.com" | ||||
| @@ -28,6 +32,7 @@ type PushNotification struct { | ||||
| 	ContentAvailable int    `json:"cont_ava"` | ||||
| 	ChannelId        string `json:"channel_id"` | ||||
| 	ChannelName      string `json:"channel_name"` | ||||
| 	Type             string `json:"type"` | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	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 ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -83,7 +84,11 @@ func (me *Session) IsExpired() bool { | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| @@ -105,6 +110,11 @@ func (me *Session) GetTeamByTeamId(teamId string) *TeamMember { | ||||
| 	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 { | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| func (o *Team) PreExport() { | ||||
| } | ||||
|  | ||||
| func (o *Team) Sanitize() { | ||||
| 	o.Email = "" | ||||
| 	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 ( | ||||
| 	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_MENTION        = "mention" | ||||
| 	USER_NOTIFY_NONE           = "none" | ||||
| @@ -44,18 +39,16 @@ type User struct { | ||||
| 	FirstName          string    `json:"first_name"` | ||||
| 	LastName           string    `json:"last_name"` | ||||
| 	Roles              string    `json:"roles"` | ||||
| 	LastActivityAt     int64     `json:"last_activity_at,omitempty"` | ||||
| 	LastPingAt         int64     `json:"last_ping_at,omitempty"` | ||||
| 	AllowMarketing     bool      `json:"allow_marketing,omitempty"` | ||||
| 	Props              StringMap `json:"props,omitempty"` | ||||
| 	NotifyProps        StringMap `json:"notify_props,omitempty"` | ||||
| 	ThemeProps         StringMap `json:"theme_props,omitempty"` | ||||
| 	LastPasswordUpdate int64     `json:"last_password_update,omitempty"` | ||||
| 	LastPictureUpdate  int64     `json:"last_picture_update,omitempty"` | ||||
| 	FailedAttempts     int       `json:"failed_attempts,omitempty"` | ||||
| 	Locale             string    `json:"locale"` | ||||
| 	MfaActive          bool      `json:"mfa_active,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 | ||||
| @@ -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) | ||||
| 	} | ||||
|  | ||||
| 	if len(u.ThemeProps) > 2000 { | ||||
| 		return NewLocAppError("User.IsValid", "model.user.is_valid.theme.app_error", nil, "user_id="+u.Id) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -179,21 +168,6 @@ func (u *User) PreUpdate() { | ||||
| 		} | ||||
| 		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() { | ||||
| @@ -242,14 +216,6 @@ func (u *User) Etag(showFullName, showEmail bool) string { | ||||
| 	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 | ||||
| func (u *User) Sanitize(options map[string]bool) { | ||||
| 	u.Password = "" | ||||
| @@ -270,7 +236,6 @@ func (u *User) Sanitize(options map[string]bool) { | ||||
| } | ||||
|  | ||||
| func (u *User) ClearNonProfileFields() { | ||||
| 	u.UpdateAt = 0 | ||||
| 	u.Password = "" | ||||
| 	u.AuthData = new(string) | ||||
| 	*u.AuthData = "" | ||||
| @@ -278,16 +243,20 @@ func (u *User) ClearNonProfileFields() { | ||||
| 	u.MfaActive = false | ||||
| 	u.MfaSecret = "" | ||||
| 	u.EmailVerified = false | ||||
| 	u.LastPingAt = 0 | ||||
| 	u.AllowMarketing = false | ||||
| 	u.Props = StringMap{} | ||||
| 	u.NotifyProps = StringMap{} | ||||
| 	u.ThemeProps = StringMap{} | ||||
| 	u.LastPasswordUpdate = 0 | ||||
| 	u.LastPictureUpdate = 0 | ||||
| 	u.FailedAttempts = 0 | ||||
| } | ||||
|  | ||||
| func (u *User) SanitizeProfile(options map[string]bool) { | ||||
| 	u.ClearNonProfileFields() | ||||
|  | ||||
| 	u.Sanitize(options) | ||||
| } | ||||
|  | ||||
| func (u *User) MakeNonNil() { | ||||
| 	if u.Props == nil { | ||||
| 		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 { | ||||
|  | ||||
| 	roles := strings.Split(userRoles, " ") | ||||
| @@ -392,17 +379,6 @@ func (u *User) IsLDAPUser() bool { | ||||
| 	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 | ||||
| func UserFromJson(data io.Reader) *User { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| @@ -461,6 +437,7 @@ var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`) | ||||
| var restrictedUsernames = []string{ | ||||
| 	"all", | ||||
| 	"channel", | ||||
| 	"matterbot", | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 	Id            string                 `json:"id"` | ||||
| 	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 | ||||
| 	RequestId     string                 `json:"request_id"`     // The RequestId that's also set in the header | ||||
| 	StatusCode    int                    `json:"status_code"`    // The http status code | ||||
| 	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 | ||||
| 	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 | ||||
| 	RequestId     string                 `json:"request_id,omitempty"`  // The RequestId that's also set in the header | ||||
| 	StatusCode    int                    `json:"status_code,omitempty"` // The http status code | ||||
| 	Where         string                 `json:"-"`                     // The function where it happened in the form of Struct.Func | ||||
| 	IsOAuth       bool                   `json:"is_oauth,omitempty"`    // Whether the error is OAuth specific | ||||
| 	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 | ||||
| // release at the front of the list. | ||||
| var versions = []string{ | ||||
| 	"3.4.0", | ||||
| 	"3.3.0", | ||||
| 	"3.2.0", | ||||
| 	"3.1.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