forked from lug/matterbridge
		
	Compare commits
	
		
			18 Commits
		
	
	
		
			v0.4
			...
			v0.5.0-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 055d12e3ef | ||
|   | b49429d722 | ||
|   | 815c7f8d64 | ||
|   | c879f79456 | ||
|   | 3bc25f4707 | ||
|   | 300cfe044a | ||
|   | fb586f4a96 | ||
|   | ced371bece | ||
|   | a87cac1982 | ||
|   | 8fb5c7afa6 | ||
|   | aceb830378 | ||
|   | 0f2976c5ce | ||
|   | 78b17977c5 | ||
|   | 6ec77e06ea | ||
|   | e48db67649 | ||
|   | e03f331f55 | ||
|   | ff5aeeb1e1 | ||
|   | 33844fa60c | 
							
								
								
									
										11
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| FROM alpine:edge | ||||
| ENTRYPOINT ["/bin/matterbridge"] | ||||
|  | ||||
| COPY . /go/src/github.com/42wim/matterbridge | ||||
| RUN apk update && apk add go git \ | ||||
|         && 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 | ||||
							
								
								
									
										95
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										95
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,14 +1,23 @@ | ||||
| # matterbridge | ||||
|  | ||||
| Simple bridge between mattermost and IRC. Uses the in/outgoing webhooks.   | ||||
| Relays public channel messages between mattermost and IRC.   | ||||
| Simple bridge between mattermost and IRC.  | ||||
|  | ||||
| Requires mattermost 1.2.0+ | ||||
| * Relays public channel messages between mattermost and IRC. | ||||
| * Supports multiple mattermost and irc channels. | ||||
| * Matterbridge -plus also works with private groups on your mattermost. | ||||
|  | ||||
| There is also [matterbridge-plus] (https://github.com/42wim/matterbridge-plus) which uses the mattermost API and needs a dedicated user (bot). But requires no incoming/outgoing webhook setup.  | ||||
| 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) | ||||
|  | ||||
| ## 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. | ||||
|  | ||||
| ## binaries | ||||
| Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/tag/v0.4) | ||||
| Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/tag/v0.5-beta1) | ||||
|  | ||||
| ## 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) | ||||
| @@ -31,73 +40,26 @@ matterbridge | ||||
| 3) Now you can run matterbridge.  | ||||
|  | ||||
| ``` | ||||
| Usage of matterbridge: | ||||
|   -conf="matterbridge.conf": config file | ||||
| Usage of ./matterbridge: | ||||
|   -conf string | ||||
|         config file (default "matterbridge.conf") | ||||
|   -debug | ||||
|         enable debug | ||||
|   -plus | ||||
|         running using API instead of webhooks | ||||
|   -version | ||||
|         show version | ||||
| ``` | ||||
|  | ||||
| Matterbridge will: | ||||
| * start a webserver listening on the port specified in the configuration. | ||||
| * connect to specified irc server and channel. | ||||
| * send messages from mattermost to irc and vice versa, messages in mattermost will appear with irc-nick | ||||
|  | ||||
| ## config | ||||
| ### matterbridge | ||||
| matterbridge looks for matterbridge.conf in current directory. (use -conf to specify another file) | ||||
|  | ||||
| Look at matterbridge.conf.sample for an example | ||||
|  | ||||
|  | ||||
| ``` | ||||
| [IRC] | ||||
| server="irc.freenode.net" | ||||
| port=6667 | ||||
| UseTLS=false | ||||
| SkipTLSVerify=true | ||||
| nick="matterbot" | ||||
| channel="#matterbridge" | ||||
| UseSlackCircumfix=false | ||||
|  | ||||
| [mattermost] | ||||
| #url is your incoming webhook url (account settings - integrations - incoming webhooks) | ||||
| url="http://mattermost.yourdomain.com/hooks/incomingwebhookkey"   | ||||
| #port the bridge webserver will listen on | ||||
| port=9999 | ||||
| #address the webserver will bind to | ||||
| BindAddress="0.0.0.0" | ||||
| showjoinpart=true #show irc users joining and parting | ||||
| #the token you get from the outgoing webhook in mattermost. If empty no token check will be done. | ||||
| #if you use multiple IRC channel (see below, this must be empty!) | ||||
| token=yourtokenfrommattermost | ||||
| #disable certificate checking (selfsigned certificates) | ||||
| #SkipTLSVerify=true | ||||
| #whether to prefix messages from IRC to mattermost with the sender's nick. Useful if username overrides for incoming webhooks isn't enabled on the mattermost server | ||||
| PrefixMessagesWithNick=false | ||||
| #how to format the list of IRC nicks when displayed in mattermost. Possible options are "table" and "plain" | ||||
| NickFormatter=plain | ||||
| #how many nicks to list per row for formatters that support this | ||||
| NicksPerRow=4 | ||||
| #Freenode nickserv | ||||
| NickServNick="nickserv" | ||||
| #Password for nickserv | ||||
| NickServPassword="secret" | ||||
|  | ||||
| #multiple channel config | ||||
| #token you can find in your outgoing webhook | ||||
| [Token "outgoingwebhooktoken1"]  | ||||
| IRCChannel="#off-topic" | ||||
| MMChannel="off-topic" | ||||
|  | ||||
| [Token "outgoingwebhooktoken2"] | ||||
| IRCChannel="#testing" | ||||
| MMChannel="testing" | ||||
|  | ||||
| [general] | ||||
| #request your API key on https://github.com/giphy/GiphyAPI. This is a public beta key | ||||
| GiphyApiKey="dc6zaTOxFJmzC" | ||||
| ``` | ||||
| Look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for an example. | ||||
|  | ||||
| ### mattermost | ||||
| You'll have to configure the incoming en outgoing webhooks.  | ||||
| #### webhooks version | ||||
| You'll have to configure the incoming and outgoing webhooks.  | ||||
|  | ||||
| * incoming webhooks | ||||
| Go to "account settings" - integrations - "incoming webhooks".   | ||||
| @@ -108,5 +70,8 @@ This URL should be set in the matterbridge.conf in the [mattermost] section (see | ||||
| Go to "account settings" - integrations - "outgoing webhooks".   | ||||
| Choose a channel (the same as the one from incoming webhooks) and fill in the address and port of the server matterbridge will run on.   | ||||
|  | ||||
| e.g. http://192.168.1.1:9999 (9999 is the port specified in [mattermost] section of matterbridge.conf) | ||||
| e.g. http://192.168.1.1:9999 (192.168.1.1:9999 is the BindAddress specified in [mattermost] section of matterbridge.conf) | ||||
|  | ||||
| #### plus version | ||||
| You'll have to create a new dedicated user on your mattermost instance. | ||||
| Specify the login and password in [mattermost] section of matterbridge.conf | ||||
|   | ||||
							
								
								
									
										404
									
								
								bridge/bridge.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								bridge/bridge.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,404 @@ | ||||
| package bridge | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"github.com/42wim/matterbridge/matterclient" | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| type FancyLog struct { | ||||
| 	irc *log.Entry | ||||
| 	mm  *log.Entry | ||||
| } | ||||
|  | ||||
| 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() | ||||
| 	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 | ||||
| 	} | ||||
| 	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() | ||||
| 	} | ||||
| 	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.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 { | ||||
| 		flog.irc.Fatal(err) | ||||
| 	} | ||||
| 	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) | ||||
| 	} | ||||
| 	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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleIrcBotCommand(event *irc.Event) bool { | ||||
| 	parts := strings.Fields(event.Message()) | ||||
| 	exp, _ := regexp.Compile("[:,]+$") | ||||
| 	channel := event.Arguments[0] | ||||
| 	command := "" | ||||
| 	if len(parts) == 2 { | ||||
| 		command = parts[1] | ||||
| 	} | ||||
| 	if exp.ReplaceAllString(parts[0], "") == b.ircNick { | ||||
| 		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) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bridge) 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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleMatterClient(mchan chan *MMMessage) { | ||||
| 	for message := range b.mc.MessageChan { | ||||
| 		// do not post our own messages back to irc | ||||
| 		if message.Raw.Action == "posted" && b.mc.User.Username != message.Username { | ||||
| 			flog.mm.Debugf("receiving from matterclient %#v", message) | ||||
| 			m := &MMMessage{} | ||||
| 			m.Username = message.Username | ||||
| 			m.Channel = message.Channel | ||||
| 			m.Text = message.Text | ||||
| 			mchan <- m | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleMatter() { | ||||
| 	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) | ||||
| 	} | ||||
| 	flog.mm.Info("Start listening for Mattermost messages") | ||||
| 	for message := range mchan { | ||||
| 		var username string | ||||
| 		if b.ignoreMessage(message.Username, message.Text, "mattermost") { | ||||
| 			continue | ||||
| 		} | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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 | ||||
| 	} | ||||
| 	// should we discard messages ? | ||||
| 	for _, entry := range ignoreNicks { | ||||
| 		if nick == entry { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
							
								
								
									
										60
									
								
								bridge/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								bridge/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| package bridge | ||||
|  | ||||
| import ( | ||||
| 	"gopkg.in/gcfg.v1" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| ) | ||||
|  | ||||
| type Config struct { | ||||
| 	IRC struct { | ||||
| 		UseTLS           bool | ||||
| 		SkipTLSVerify    bool | ||||
| 		Server           string | ||||
| 		Nick             string | ||||
| 		Password         string | ||||
| 		Channel          string | ||||
| 		NickServNick     string | ||||
| 		NickServPassword string | ||||
| 		RemoteNickFormat string | ||||
| 		IgnoreNicks      string | ||||
| 	} | ||||
| 	Mattermost struct { | ||||
| 		URL                    string | ||||
| 		ShowJoinPart           bool | ||||
| 		IconURL                string | ||||
| 		SkipTLSVerify          bool | ||||
| 		BindAddress            string | ||||
| 		Channel                string | ||||
| 		PrefixMessagesWithNick bool | ||||
| 		NicksPerRow            int | ||||
| 		NickFormatter          string | ||||
| 		Server                 string | ||||
| 		Team                   string | ||||
| 		Login                  string | ||||
| 		Password               string | ||||
| 		RemoteNickFormat       *string | ||||
| 		IgnoreNicks            string | ||||
| 		NoTLS                  bool | ||||
| 	} | ||||
| 	Channel map[string]*struct { | ||||
| 		IRC        string | ||||
| 		Mattermost string | ||||
| 	} | ||||
| 	General struct { | ||||
| 		GiphyAPIKey string | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func NewConfig(cfgfile string) *Config { | ||||
| 	var cfg Config | ||||
| 	content, err := ioutil.ReadFile(cfgfile) | ||||
| 	if err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| 	err = gcfg.ReadStringInto(&cfg, string(content)) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Failed to parse "+cfgfile+":", err) | ||||
| 	} | ||||
| 	return &cfg | ||||
| } | ||||
							
								
								
									
										59
									
								
								bridge/helper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								bridge/helper.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| package bridge | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func tableformatter(nicks []string, nicksPerRow int, continued bool) string { | ||||
| 	result := "|IRC users" | ||||
| 	if continued { | ||||
| 		result = "|(continued)" | ||||
| 	} | ||||
| 	for i := 0; i < 2; i++ { | ||||
| 		for j := 1; j <= nicksPerRow && j <= len(nicks); j++ { | ||||
| 			if i == 0 { | ||||
| 				result += "|" | ||||
| 			} else { | ||||
| 				result += ":-|" | ||||
| 			} | ||||
| 		} | ||||
| 		result += "\r\n|" | ||||
| 	} | ||||
| 	result += nicks[0] + "|" | ||||
| 	for i := 1; i < len(nicks); i++ { | ||||
| 		if i%nicksPerRow == 0 { | ||||
| 			result += "\r\n|" + nicks[i] + "|" | ||||
| 		} else { | ||||
| 			result += nicks[i] + "|" | ||||
| 		} | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func plainformatter(nicks []string, nicksPerRow int) string { | ||||
| 	return strings.Join(nicks, ", ") + " currently on IRC" | ||||
| } | ||||
|  | ||||
| func IsMarkup(message string) bool { | ||||
| 	switch message[0] { | ||||
| 	case '|': | ||||
| 		fallthrough | ||||
| 	case '#': | ||||
| 		fallthrough | ||||
| 	case '_': | ||||
| 		fallthrough | ||||
| 	case '*': | ||||
| 		fallthrough | ||||
| 	case '~': | ||||
| 		fallthrough | ||||
| 	case '-': | ||||
| 		fallthrough | ||||
| 	case ':': | ||||
| 		fallthrough | ||||
| 	case '>': | ||||
| 		fallthrough | ||||
| 	case '=': | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| @@ -1,37 +1,142 @@ | ||||
| #This is configuration for matterbridge. | ||||
| ################################################################### | ||||
| #IRC section | ||||
| ################################################################### | ||||
| [IRC] | ||||
| server="irc.freenode.net" | ||||
| port=6667 | ||||
| #irc server to connect to.  | ||||
| #REQUIRED | ||||
| Server="irc.freenode.net:6667" | ||||
|  | ||||
| #Enable to use TLS connection to your irc server.  | ||||
| #OPTIONAL (default false) | ||||
| UseTLS=false | ||||
|  | ||||
| #Enable to not verify the certificate on your irc server. i | ||||
| #e.g. when using selfsigned certificates | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
| nick="matterbot" | ||||
| channel="#matterbridge" | ||||
| UseSlackCircumfix=false | ||||
|  | ||||
| #Your nick on irc.  | ||||
| #REQUIRED | ||||
| Nick="matterbot" | ||||
|  | ||||
| #If you registered your bot with a service like Nickserv on freenode.  | ||||
| #OPTIONAL | ||||
| NickServNick="nickserv" | ||||
| NickServPassword="secret" | ||||
|  | ||||
| #RemoteNickFormat defines how Mattermost users appear on irc | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||
| #OPTIONAL (default NICK:) | ||||
| RemoteNickFormat="{NICK}: " | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to mattermost. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| ################################################################### | ||||
| #mattermost section | ||||
| ################################################################### | ||||
|  | ||||
| [mattermost] | ||||
| url="http://yourdomain/hooks/yourhookkey" | ||||
| port=9999 | ||||
| showjoinpart=true | ||||
| #remove token when using multiple channels! | ||||
| token=yourtokenfrommattermost | ||||
| #### Settings for webhook matterbridge. | ||||
| #### These settings will not be used when using -plus switch which doesn't use  | ||||
| #### webhooks. | ||||
|  | ||||
| #Url is your incoming webhook url as specified in mattermost.  | ||||
| #See account settings - integrations - incoming webhooks on mattermost. | ||||
| #REQUIRED | ||||
| URL="https://yourdomain/hooks/yourhookkey" | ||||
|  | ||||
| #Address to listen on for outgoing webhook requests from mattermost. | ||||
| #See account settings - integrations - outgoing webhooks on mattermost. | ||||
| #This setting will not be used when using -plus switch which doesn't use  | ||||
| #webhooks | ||||
| #REQUIRED | ||||
| BindAddress="0.0.0.0:9999" | ||||
|  | ||||
| #Icon that will be showed in mattermost.  | ||||
| #OPTIONAL | ||||
| IconURL="http://youricon.png" | ||||
| #SkipTLSVerify=true | ||||
| #BindAddress="0.0.0.0" | ||||
|  | ||||
| #### Settings for matterbridge -plus | ||||
| #### Thse settings will only be used when using the -plus switch. | ||||
|  | ||||
| #The mattermost hostname.  | ||||
| #REQUIRED | ||||
| Server="yourmattermostserver.domain" | ||||
|  | ||||
| #Your team on mattermost.  | ||||
| #REQUIRED | ||||
| Team="yourteam" | ||||
|  | ||||
| #login/pass of your bot.  | ||||
| #Use a dedicated user for this and not your own!  | ||||
| #REQUIRED | ||||
| Login="yourlogin" | ||||
| Password="yourpass" | ||||
|  | ||||
| #Disable to make a http connection to your mattermost.  | ||||
| #OPTIONAL (default false) | ||||
| NoTLS=false | ||||
|  | ||||
| #### Shared settings for matterbridge and -plus | ||||
|  | ||||
| #Enable to not verify the certificate on your mattermost server.  | ||||
| #e.g. when using selfsigned certificates | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
|  | ||||
| #Enable to show IRC joins/parts in mattermost.  | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| #Whether to prefix messages from IRC 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,  | ||||
| #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  | ||||
| #OPTIONAL (default false) | ||||
| PrefixMessagesWithNick=false | ||||
|  | ||||
| #RemoteNickFormat defines how IRC users appear on Mattermost.  | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||
| #OPTIONAL (default irc-NICK) | ||||
| RemoteNickFormat="irc-{NICK}" | ||||
|  | ||||
| #how to format the list of IRC nicks when displayed in mattermost.  | ||||
| #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 | ||||
| #NickServNick="nickserv" | ||||
| #NickServPassword="secret" | ||||
|  | ||||
| [general] | ||||
| GiphyAPIKey=dc6zaTOxFJmzC | ||||
| #Nicks you want to ignore. Messages from those users will not be sent to IRC.  | ||||
| #OPTIONAL  | ||||
| IgnoreNicks="mmbot spammer2" | ||||
|  | ||||
| ################################################################### | ||||
| #multiple channel config | ||||
| #token you can find in your outgoing webhook | ||||
| [Token "outgoingwebhooktoken1"]  | ||||
| IRCChannel="#off-topic" | ||||
| MMChannel="off-topic" | ||||
| ################################################################### | ||||
| #You can specify multiple channels.  | ||||
| #The name is just an identifier for you. | ||||
| #REQUIRED (at least 1 channel) | ||||
| [Channel "channel1"]  | ||||
| #Choose the IRC channel to send mattermost messages to. | ||||
| IRC="#off-topic" | ||||
| #Choose the mattermost channel to send IRC messages to. | ||||
| mattermost="off-topic" | ||||
|  | ||||
| [Token "outgoingwebhooktoken2"] | ||||
| IRCChannel="#testing" | ||||
| MMChannel="testing" | ||||
| [Channel "testchannel"] | ||||
| IRC="#testing" | ||||
| mattermost="testing" | ||||
|  | ||||
| ################################################################### | ||||
| #general | ||||
| ################################################################### | ||||
| [general] | ||||
| #request your API key on https://github.com/giphy/GiphyAPI. This is a public beta key.  | ||||
| #OPTIONAL | ||||
| GiphyApiKey="dc6zaTOxFJmzC" | ||||
|   | ||||
| @@ -2,10 +2,13 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"github.com/42wim/matterbridge-plus/bridge" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| var version = "0.5.0-beta1" | ||||
|  | ||||
| func init() { | ||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||
| } | ||||
| @@ -13,11 +16,23 @@ func init() { | ||||
| 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") | ||||
| 	flag.Parse() | ||||
| 	if *flagVersion { | ||||
| 		fmt.Println("version:", version) | ||||
| 		return | ||||
| 	} | ||||
| 	flag.Parse() | ||||
| 	if *flagDebug { | ||||
| 		log.Info("enabling debug") | ||||
| 		log.SetLevel(log.DebugLevel) | ||||
| 	} | ||||
| 	fmt.Println("running version", version) | ||||
| 	if *flagPlus { | ||||
| 		bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "") | ||||
| 	} else { | ||||
| 		bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "legacy") | ||||
| 	} | ||||
| 	select {} | ||||
| } | ||||
|   | ||||
							
								
								
									
										570
									
								
								matterclient/matterclient.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										570
									
								
								matterclient/matterclient.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,570 @@ | ||||
| package matterclient | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"net/http/cookiejar" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
|  | ||||
| 	"github.com/gorilla/websocket" | ||||
| 	"github.com/jpillora/backoff" | ||||
| 	"github.com/mattermost/platform/model" | ||||
| ) | ||||
|  | ||||
| type Credentials struct { | ||||
| 	Login         string | ||||
| 	Team          string | ||||
| 	Pass          string | ||||
| 	Server        string | ||||
| 	NoTLS         bool | ||||
| 	SkipTLSVerify bool | ||||
| } | ||||
|  | ||||
| type Message struct { | ||||
| 	Raw      *model.Message | ||||
| 	Post     *model.Post | ||||
| 	Team     string | ||||
| 	Channel  string | ||||
| 	Username string | ||||
| 	Text     string | ||||
| } | ||||
|  | ||||
| type Team struct { | ||||
| 	Team         *model.Team | ||||
| 	Id           string | ||||
| 	Channels     *model.ChannelList | ||||
| 	MoreChannels *model.ChannelList | ||||
| 	Users        map[string]*model.User | ||||
| } | ||||
|  | ||||
| type MMClient struct { | ||||
| 	sync.RWMutex | ||||
| 	*Credentials | ||||
| 	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 | ||||
| } | ||||
|  | ||||
| func New(login, pass, team, server string) *MMClient { | ||||
| 	cred := &Credentials{Login: login, Pass: pass, Team: team, Server: server} | ||||
| 	mmclient := &MMClient{Credentials: cred, MessageChan: make(chan *Message, 100), Users: make(map[string]*model.User)} | ||||
| 	mmclient.log = log.WithFields(log.Fields{"module": "matterclient"}) | ||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||
| 	return mmclient | ||||
| } | ||||
|  | ||||
| func (m *MMClient) SetLogLevel(level string) { | ||||
| 	l, err := log.ParseLevel(level) | ||||
| 	if err != nil { | ||||
| 		log.SetLevel(log.InfoLevel) | ||||
| 		return | ||||
| 	} | ||||
| 	log.SetLevel(l) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) Login() error { | ||||
| 	m.WsConnected = false | ||||
| 	if m.WsQuit { | ||||
| 		return nil | ||||
| 	} | ||||
| 	b := &backoff.Backoff{ | ||||
| 		Min:    time.Second, | ||||
| 		Max:    5 * time.Minute, | ||||
| 		Jitter: true, | ||||
| 	} | ||||
| 	uriScheme := "https://" | ||||
| 	wsScheme := "wss://" | ||||
| 	if m.NoTLS { | ||||
| 		uriScheme = "http://" | ||||
| 		wsScheme = "ws://" | ||||
| 	} | ||||
| 	// login to mattermost | ||||
| 	m.Client = model.NewClient(uriScheme + m.Credentials.Server) | ||||
| 	m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} | ||||
| 	var myinfo *model.Result | ||||
| 	var appErr *model.AppError | ||||
| 	var logmsg = "trying login" | ||||
| 	for { | ||||
| 		m.log.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server) | ||||
| 		if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) { | ||||
| 			m.log.Debugf(logmsg+" with %s", model.SESSION_COOKIE_TOKEN) | ||||
| 			token := strings.Split(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN+"=") | ||||
| 			if len(token) != 2 { | ||||
| 				return errors.New("incorrect MMAUTHTOKEN. valid input is MMAUTHTOKEN=yourtoken") | ||||
| 			} | ||||
| 			m.Client.HttpClient.Jar = m.createCookieJar(token[1]) | ||||
| 			m.Client.MockSession(token[1]) | ||||
| 			myinfo, appErr = m.Client.GetMe("") | ||||
| 			if appErr != nil { | ||||
| 				return errors.New(appErr.DetailedError) | ||||
| 			} | ||||
| 			if myinfo.Data.(*model.User) == nil { | ||||
| 				m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass) | ||||
| 				return errors.New("invalid " + model.SESSION_COOKIE_TOKEN) | ||||
| 			} | ||||
| 		} else { | ||||
| 			myinfo, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) | ||||
| 		} | ||||
| 		if appErr != nil { | ||||
| 			d := b.Duration() | ||||
| 			m.log.Debug(appErr.DetailedError) | ||||
| 			if !strings.Contains(appErr.DetailedError, "connection refused") && | ||||
| 				!strings.Contains(appErr.DetailedError, "invalid character") { | ||||
| 				if appErr.Message == "" { | ||||
| 					return errors.New(appErr.DetailedError) | ||||
| 				} | ||||
| 				return errors.New(appErr.Message) | ||||
| 			} | ||||
| 			m.log.Debugf("LOGIN: %s, reconnecting in %s", appErr, d) | ||||
| 			time.Sleep(d) | ||||
| 			logmsg = "retrying login" | ||||
| 			continue | ||||
| 		} | ||||
| 		break | ||||
| 	} | ||||
| 	// reset timer | ||||
| 	b.Reset() | ||||
|  | ||||
| 	err := m.initUser() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// set our team id as default route | ||||
| 	m.Client.SetTeamId(m.Team.Id) | ||||
| 	if m.Team == nil { | ||||
| 		return errors.New("team not found") | ||||
| 	} | ||||
|  | ||||
| 	// setup websocket connection | ||||
| 	wsurl := wsScheme + m.Credentials.Server + "/api/v3/users/websocket" | ||||
| 	header := http.Header{} | ||||
| 	header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) | ||||
|  | ||||
| 	m.log.Debug("WsClient: making connection") | ||||
| 	for { | ||||
| 		wsDialer := &websocket.Dialer{Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} | ||||
| 		m.WsClient, _, err = wsDialer.Dial(wsurl, header) | ||||
| 		if err != nil { | ||||
| 			d := b.Duration() | ||||
| 			m.log.Debugf("WSS: %s, reconnecting in %s", err, d) | ||||
| 			time.Sleep(d) | ||||
| 			continue | ||||
| 		} | ||||
| 		break | ||||
| 	} | ||||
| 	b.Reset() | ||||
|  | ||||
| 	// only start to parse WS messages when login is completely done | ||||
| 	m.WsConnected = true | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *MMClient) Logout() error { | ||||
| 	m.log.Debugf("logout as %s (team: %s) on %s", m.Credentials.Login, m.Credentials.Team, m.Credentials.Server) | ||||
| 	m.WsQuit = true | ||||
| 	m.WsClient.Close() | ||||
| 	m.WsClient.UnderlyingConn().Close() | ||||
| 	m.WsClient = nil | ||||
| 	_, err := m.Client.Logout() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *MMClient) WsReceiver() { | ||||
| 	var rmsg model.Message | ||||
| 	for { | ||||
| 		if m.WsQuit { | ||||
| 			m.log.Debug("exiting WsReceiver") | ||||
| 			return | ||||
| 		} | ||||
| 		if err := m.WsClient.ReadJSON(&rmsg); err != nil { | ||||
| 			m.log.Error("error:", err) | ||||
| 			// reconnect | ||||
| 			m.Login() | ||||
| 		} | ||||
| 		// we're not fully logged in yet. | ||||
| 		if !m.WsConnected { | ||||
| 			continue | ||||
| 		} | ||||
| 		if rmsg.Action == "ping" { | ||||
| 			m.handleWsPing() | ||||
| 			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: | ||||
| 		m.parseActionPost(rmsg) | ||||
| 		/* | ||||
| 			case model.ACTION_USER_REMOVED: | ||||
| 				m.handleWsActionUserRemoved(&rmsg) | ||||
| 			case model.ACTION_USER_ADDED: | ||||
| 				m.handleWsActionUserAdded(&rmsg) | ||||
| 		*/ | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *MMClient) parseActionPost(rmsg *Message) { | ||||
| 	data := model.PostFromJson(strings.NewReader(rmsg.Raw.Props["post"])) | ||||
| 	// we don't have the user, refresh the userlist | ||||
| 	if m.GetUser(data.UserId) == nil { | ||||
| 		m.UpdateUsers() | ||||
| 	} | ||||
| 	rmsg.Username = m.GetUser(data.UserId).Username | ||||
| 	rmsg.Channel = m.GetChannelName(data.ChannelId) | ||||
| 	rmsg.Team = m.GetTeamName(rmsg.Raw.TeamId) | ||||
| 	// direct message | ||||
| 	if data.Type == "D" { | ||||
| 		rmsg.Channel = m.GetUser(data.UserId).Username | ||||
| 	} | ||||
| 	rmsg.Text = data.Message | ||||
| 	rmsg.Post = data | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateUsers() error { | ||||
| 	mmusers, _ := m.Client.GetProfilesForDirectMessageList(m.Team.Id) | ||||
| 	m.Lock() | ||||
| 	m.Users = mmusers.Data.(map[string]*model.User) | ||||
| 	m.Unlock() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateChannels() error { | ||||
| 	mmchannels, _ := m.Client.GetChannels("") | ||||
| 	mmchannels2, _ := m.Client.GetMoreChannels("") | ||||
| 	m.Lock() | ||||
| 	m.Team.Channels = mmchannels.Data.(*model.ChannelList) | ||||
| 	m.Team.MoreChannels = mmchannels2.Data.(*model.ChannelList) | ||||
| 	m.Unlock() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetChannelName(channelId string) string { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) { | ||||
| 			if channel.Id == channelId { | ||||
| 				return channel.Name | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetChannelId(name string, teamId string) string { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	if teamId == "" { | ||||
| 		teamId = m.Team.Id | ||||
| 	} | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		if t.Id == teamId { | ||||
| 			for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) { | ||||
| 				if channel.Name == name { | ||||
| 					return channel.Id | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetChannelHeader(channelId string) string { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) { | ||||
| 			if channel.Id == channelId { | ||||
| 				return channel.Header | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *MMClient) PostMessage(channelId string, text string) { | ||||
| 	post := &model.Post{ChannelId: channelId, Message: text} | ||||
| 	m.Client.CreatePost(post) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) JoinChannel(channelId string) error { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	for _, c := range m.Team.Channels.Channels { | ||||
| 		if c.Id == channelId { | ||||
| 			m.log.Debug("Not joining ", channelId, " already joined.") | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	m.log.Debug("Joining ", channelId) | ||||
| 	_, err := m.Client.JoinChannel(channelId) | ||||
| 	if err != nil { | ||||
| 		return errors.New("failed to join") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetPostsSince(channelId string, time int64) *model.PostList { | ||||
| 	res, err := m.Client.GetPostsSince(channelId, time) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return res.Data.(*model.PostList) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) SearchPosts(query string) *model.PostList { | ||||
| 	res, err := m.Client.SearchPosts(query, false) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return res.Data.(*model.PostList) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList { | ||||
| 	res, err := m.Client.GetPosts(channelId, 0, limit, "") | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return res.Data.(*model.PostList) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetPublicLink(filename string) string { | ||||
| 	res, err := m.Client.GetPublicLink(filename) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return res.Data.(string) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetPublicLinks(filenames []string) []string { | ||||
| 	var output []string | ||||
| 	for _, f := range filenames { | ||||
| 		res, err := m.Client.GetPublicLink(f) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		output = append(output, res.Data.(string)) | ||||
| 	} | ||||
| 	return output | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateChannelHeader(channelId string, header string) { | ||||
| 	data := make(map[string]string) | ||||
| 	data["channel_id"] = channelId | ||||
| 	data["channel_header"] = header | ||||
| 	m.log.Debugf("updating channelheader %#v, %#v", channelId, header) | ||||
| 	_, err := m.Client.UpdateChannelHeader(data) | ||||
| 	if err != nil { | ||||
| 		log.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateLastViewed(channelId string) { | ||||
| 	m.log.Debugf("posting lastview %#v", channelId) | ||||
| 	_, err := m.Client.UpdateLastViewedAt(channelId) | ||||
| 	if err != nil { | ||||
| 		m.log.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UsernamesInChannel(channelId string) []string { | ||||
| 	ceiRes, err := m.Client.GetChannelExtraInfo(channelId, 5000, "") | ||||
| 	if err != nil { | ||||
| 		m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, err) | ||||
| 		return []string{} | ||||
| 	} | ||||
| 	extra := ceiRes.Data.(*model.ChannelExtra) | ||||
| 	result := []string{} | ||||
| 	for _, member := range extra.Members { | ||||
| 		result = append(result, member.Username) | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func (m *MMClient) createCookieJar(token string) *cookiejar.Jar { | ||||
| 	var cookies []*http.Cookie | ||||
| 	jar, _ := cookiejar.New(nil) | ||||
| 	firstCookie := &http.Cookie{ | ||||
| 		Name:   "MMAUTHTOKEN", | ||||
| 		Value:  token, | ||||
| 		Path:   "/", | ||||
| 		Domain: m.Credentials.Server, | ||||
| 	} | ||||
| 	cookies = append(cookies, firstCookie) | ||||
| 	cookieURL, _ := url.Parse("https://" + m.Credentials.Server) | ||||
| 	jar.SetCookies(cookieURL, cookies) | ||||
| 	return jar | ||||
| } | ||||
|  | ||||
| // SendDirectMessage sends a direct message to specified user | ||||
| func (m *MMClient) SendDirectMessage(toUserId string, msg string) { | ||||
| 	m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg) | ||||
| 	// create DM channel (only happens on first message) | ||||
| 	_, err := m.Client.CreateDirectChannel(toUserId) | ||||
| 	if err != nil { | ||||
| 		m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, err) | ||||
| 	} | ||||
| 	channelName := model.GetDMNameFromIds(toUserId, m.User.Id) | ||||
|  | ||||
| 	// update our channels | ||||
| 	mmchannels, _ := m.Client.GetChannels("") | ||||
| 	m.Lock() | ||||
| 	m.Team.Channels = mmchannels.Data.(*model.ChannelList) | ||||
| 	m.Unlock() | ||||
|  | ||||
| 	// build & send the message | ||||
| 	msg = strings.Replace(msg, "\r", "", -1) | ||||
| 	post := &model.Post{ChannelId: m.GetChannelId(channelName, ""), Message: msg} | ||||
| 	m.Client.CreatePost(post) | ||||
| } | ||||
|  | ||||
| // GetTeamName returns the name of the specified teamId | ||||
| func (m *MMClient) GetTeamName(teamId string) string { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		if t.Id == teamId { | ||||
| 			return t.Team.Name | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| // GetChannels returns all channels we're members off | ||||
| func (m *MMClient) GetChannels() []*model.Channel { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	var channels []*model.Channel | ||||
| 	// our primary team channels first | ||||
| 	channels = append(channels, m.Team.Channels.Channels...) | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		if t.Id != m.Team.Id { | ||||
| 			channels = append(channels, t.Channels.Channels...) | ||||
| 		} | ||||
| 	} | ||||
| 	return channels | ||||
| } | ||||
|  | ||||
| // GetMoreChannels returns existing channels where we're not a member off. | ||||
| func (m *MMClient) GetMoreChannels() []*model.Channel { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	var channels []*model.Channel | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		channels = append(channels, t.MoreChannels.Channels...) | ||||
| 	} | ||||
| 	return channels | ||||
| } | ||||
|  | ||||
| // GetTeamFromChannel returns teamId belonging to channel (DM channels have no teamId). | ||||
| func (m *MMClient) GetTeamFromChannel(channelId string) string { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	var channels []*model.Channel | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		channels = append(channels, t.Channels.Channels...) | ||||
| 		for _, c := range channels { | ||||
| 			if c.Id == channelId { | ||||
| 				return t.Id | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetLastViewedAt(channelId string) int64 { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		if _, ok := t.Channels.Members[channelId]; ok { | ||||
| 			return t.Channels.Members[channelId].LastViewedAt | ||||
| 		} | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetUsers() map[string]*model.User { | ||||
| 	users := make(map[string]*model.User) | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	for k, v := range m.Users { | ||||
| 		users[k] = v | ||||
| 	} | ||||
| 	return users | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetUser(userId string) *model.User { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	return m.Users[userId] | ||||
| } | ||||
|  | ||||
| // initialize user and teams | ||||
| func (m *MMClient) initUser() error { | ||||
| 	m.Lock() | ||||
| 	defer m.Unlock() | ||||
| 	m.log.Debug("initUser()") | ||||
| 	initLoad, err := m.Client.GetInitialLoad() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	initData := initLoad.Data.(*model.InitialLoad) | ||||
| 	m.User = initData.User | ||||
| 	// we only load all team data on initial login. | ||||
| 	// all other updates are for channels from our (primary) team only. | ||||
| 	m.log.Debug("initUser(): loading all team data") | ||||
| 	for _, v := range initData.Teams { | ||||
| 		m.Client.SetTeamId(v.Id) | ||||
| 		mmusers, _ := m.Client.GetProfiles(v.Id, "") | ||||
| 		t := &Team{Team: v, Users: mmusers.Data.(map[string]*model.User), Id: v.Id} | ||||
| 		mmchannels, _ := m.Client.GetChannels("") | ||||
| 		t.Channels = mmchannels.Data.(*model.ChannelList) | ||||
| 		mmchannels, _ = m.Client.GetMoreChannels("") | ||||
| 		t.MoreChannels = mmchannels.Data.(*model.ChannelList) | ||||
| 		m.OtherTeams = append(m.OtherTeams, t) | ||||
| 		if v.Name == m.Credentials.Team { | ||||
| 			m.Team = t | ||||
| 			m.log.Debugf("initUser(): found our team %s (id: %s)", v.Name, v.Id) | ||||
| 		} | ||||
| 		// add all users | ||||
| 		for k, v := range t.Users { | ||||
| 			m.Users[k] = v | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -10,8 +10,8 @@ import ( | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| // OMessage for mattermost incoming webhook. (send to mattermost) | ||||
| @@ -31,11 +31,11 @@ type IMessage struct { | ||||
| 	TeamID      string `schema:"team_id"` | ||||
| 	TeamDomain  string `schema:"team_domain"` | ||||
| 	ChannelID   string `schema:"channel_id"` | ||||
| 	ServiceID   string `schema:"service_id"` | ||||
| 	ChannelName string `schema:"channel_name"` | ||||
| 	Timestamp   string `schema:"timestamp"` | ||||
| 	UserID      string `schema:"user_id"` | ||||
| 	UserName    string `schema:"user_name"` | ||||
| 	PostId      string `schema:"post_id"` | ||||
| 	Text        string `schema:"text"` | ||||
| 	TriggerWord string `schema:"trigger_word"` | ||||
| } | ||||
| @@ -51,7 +51,6 @@ type Client struct { | ||||
|  | ||||
| // Config for client. | ||||
| type Config struct { | ||||
| 	Port               int    // Port to listen on. | ||||
| 	BindAddress        string // Address to listen on | ||||
| 	Token              string // Only allow this token from Mattermost. (Allow everything when empty) | ||||
| 	InsecureSkipVerify bool   // disable certificate checking | ||||
| @@ -61,10 +60,10 @@ type Config struct { | ||||
| // New Mattermost client. | ||||
| func New(url string, config Config) *Client { | ||||
| 	c := &Client{Url: url, In: make(chan IMessage), Out: make(chan OMessage), Config: config} | ||||
| 	if c.Port == 0 { | ||||
| 		c.Port = 9999 | ||||
| 	_, _, err := net.SplitHostPort(c.BindAddress) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("incorrect bindaddress %s", c.BindAddress) | ||||
| 	} | ||||
| 	c.BindAddress += ":" | ||||
| 	tr := &http.Transport{ | ||||
| 		TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}, | ||||
| 	} | ||||
| @@ -79,8 +78,8 @@ func New(url string, config Config) *Client { | ||||
| func (c *Client) StartServer() { | ||||
| 	mux := http.NewServeMux() | ||||
| 	mux.Handle("/", c) | ||||
| 	log.Printf("Listening on http://%v:%v...\n", c.BindAddress, c.Port) | ||||
| 	if err := http.ListenAndServe((c.BindAddress + strconv.Itoa(c.Port)), mux); err != nil { | ||||
| 	log.Printf("Listening on http://%v...\n", c.BindAddress) | ||||
| 	if err := http.ListenAndServe(c.BindAddress, mux); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										50
									
								
								migration.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								migration.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| # 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" | ||||
| ``` | ||||
							
								
								
									
										155
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										155
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -23,6 +23,7 @@ type MMhook struct { | ||||
| type MMapi struct { | ||||
| 	mc            *matterclient.MMClient | ||||
| 	mmMap         map[string]string | ||||
| 	mmIgnoreNicks []string | ||||
| } | ||||
|  | ||||
| type MMirc struct { | ||||
| @@ -30,6 +31,7 @@ type MMirc struct { | ||||
| 	ircNick        string | ||||
| 	ircMap         map[string]string | ||||
| 	names          map[string][]string | ||||
| 	ircIgnoreNicks []string | ||||
| } | ||||
|  | ||||
| type MMMessage struct { | ||||
| @@ -53,6 +55,8 @@ type FancyLog struct { | ||||
|  | ||||
| var flog FancyLog | ||||
|  | ||||
| const Legacy = "legacy" | ||||
|  | ||||
| func initFLog() { | ||||
| 	flog.irc = log.WithFields(log.Fields{"module": "irc"}) | ||||
| 	flog.mm = log.WithFields(log.Fields{"module": "mattermost"}) | ||||
| @@ -66,7 +70,9 @@ func NewBridge(name string, config *Config, kind string) *Bridge { | ||||
| 	b.ircNick = b.Config.IRC.Nick | ||||
| 	b.ircMap = make(map[string]string) | ||||
| 	b.MMirc.names = make(map[string][]string) | ||||
| 	if kind == "legacy" { | ||||
| 	b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks) | ||||
| 	b.mmIgnoreNicks = strings.Fields(b.Config.Mattermost.IgnoreNicks) | ||||
| 	if kind == Legacy { | ||||
| 		if len(b.Config.Token) > 0 { | ||||
| 			for _, val := range b.Config.Token { | ||||
| 				b.ircMap[val.IRCChannel] = val.MMChannel | ||||
| @@ -87,10 +93,14 @@ func NewBridge(name string, config *Config, kind string) *Bridge { | ||||
| 		} | ||||
| 		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.Fatal("Can not connect", err) | ||||
| 		} | ||||
| 		flog.mm.Info("Login ok") | ||||
| 		b.mc.JoinChannel(b.Config.Mattermost.Channel) | ||||
| 		if len(b.Config.Channel) > 0 { | ||||
| 			for _, val := range b.Config.Channel { | ||||
| @@ -99,7 +109,9 @@ func NewBridge(name string, config *Config, kind string) *Bridge { | ||||
| 		} | ||||
| 		go b.mc.WsReceiver() | ||||
| 	} | ||||
| 	flog.irc.Info("Trying IRC connection") | ||||
| 	b.i = b.createIRC(name) | ||||
| 	flog.irc.Info("Connection succeeded") | ||||
| 	go b.handleMatter() | ||||
| 	return b | ||||
| } | ||||
| @@ -111,37 +123,51 @@ func (b *Bridge) createIRC(name string) *irc.Connection { | ||||
| 	if b.Config.IRC.Password != "" { | ||||
| 		i.Password = b.Config.IRC.Password | ||||
| 	} | ||||
| 	i.AddCallback("*", b.handleOther) | ||||
| 	i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection) | ||||
| 	i.Connect(b.Config.IRC.Server + ":" + strconv.Itoa(b.Config.IRC.Port)) | ||||
| 	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) | ||||
| 	} | ||||
| 	i.AddCallback("*", b.handleOther) | ||||
| 	b.setupChannels() | ||||
| } | ||||
|  | ||||
| func (b *Bridge) setupChannels() { | ||||
| 	i := b.i | ||||
| 	flog.irc.Info("Joining ", b.Config.IRC.Channel, " as ", b.ircNick) | ||||
| 	if b.Config.IRC.Channel != "" { | ||||
| 		flog.irc.Infof("Joining %s as %s", b.Config.IRC.Channel, b.ircNick) | ||||
| 		i.Join(b.Config.IRC.Channel) | ||||
| 	if b.kind == "legacy" { | ||||
| 	} | ||||
| 	if b.kind == Legacy { | ||||
| 		for _, val := range b.Config.Token { | ||||
| 			flog.irc.Info("Joining ", val.IRCChannel, " as ", b.ircNick) | ||||
| 			flog.irc.Infof("Joining %s as %s", val.IRCChannel, b.ircNick) | ||||
| 			i.Join(val.IRCChannel) | ||||
| 		} | ||||
| 	} else { | ||||
| 		for _, val := range b.Config.Channel { | ||||
| 			flog.irc.Info("Joining ", val.IRC, " as ", b.ircNick) | ||||
| 			flog.irc.Infof("Joining %s as %s", val.IRC, b.ircNick) | ||||
| 			i.Join(val.IRC) | ||||
| 		} | ||||
| 	} | ||||
| 	i.AddCallback("PRIVMSG", b.handlePrivMsg) | ||||
| 	i.AddCallback("CTCP_ACTION", b.handlePrivMsg) | ||||
| 	if b.Config.Mattermost.ShowJoinPart { | ||||
| 		i.AddCallback("JOIN", b.handleJoinPart) | ||||
| 		i.AddCallback("PART", b.handleJoinPart) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleIrcBotCommand(event *irc.Event) bool { | ||||
| @@ -177,6 +203,9 @@ func (b *Bridge) ircNickFormat(nick string) string { | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handlePrivMsg(event *irc.Event) { | ||||
| 	if b.ignoreMessage(event.Nick, event.Message(), "irc") { | ||||
| 		return | ||||
| 	} | ||||
| 	if b.handleIrcBotCommand(event) { | ||||
| 		return | ||||
| 	} | ||||
| @@ -238,81 +267,21 @@ func (b *Bridge) endNames(event *irc.Event) { | ||||
| 	b.MMirc.names[channel] = nil | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleTopicWhoTime(event *irc.Event) bool { | ||||
| func (b *Bridge) handleTopicWhoTime(event *irc.Event) { | ||||
| 	parts := strings.Split(event.Arguments[2], "!") | ||||
| 	t_i, err := strconv.ParseInt(event.Arguments[3], 10, 64) | ||||
| 	t, err := strconv.ParseInt(event.Arguments[3], 10, 64) | ||||
| 	if err != nil { | ||||
| 		flog.irc.Errorf("Invalid time stamp: %s", event.Arguments[3]) | ||||
| 		return false | ||||
| 	} | ||||
| 	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_i, 0)) | ||||
| 	return true | ||||
| 	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) | ||||
| 	switch event.Code { | ||||
| 	case ircm.RPL_WELCOME: | ||||
| 		b.handleNewConnection(event) | ||||
| 	case ircm.RPL_ENDOFNAMES: | ||||
| 		b.endNames(event) | ||||
| 	case ircm.RPL_NAMREPLY: | ||||
| 		b.storeNames(event) | ||||
| 	case ircm.RPL_ISUPPORT: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LUSEROP: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LUSERUNKNOWN: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LUSERCHANNELS: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_MYINFO: | ||||
| 		flog.irc.Infof("%s: %s", event.Code, strings.Join(event.Arguments[1:], " ")) | ||||
| 	case ircm.RPL_YOURHOST: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_CREATED: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_STATSDLINE: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LUSERCLIENT: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LUSERME: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LOCALUSERS: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_GLOBALUSERS: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_MOTD: | ||||
| 		flog.irc.Infof("%s: %s", event.Code, event.Message()) | ||||
| 		// flog.irc.Info(event.Message()) | ||||
| 	case ircm.RPL_TOPIC: | ||||
| 		flog.irc.Infof("%s: Topic for %s: %s", event.Code, event.Arguments[1], event.Message()) | ||||
| 	case ircm.RPL_TOPICWHOTIME: | ||||
| 		if !b.handleTopicWhoTime(event) { | ||||
| 			break | ||||
| 		} | ||||
| 	case ircm.MODE: | ||||
| 		flog.irc.Infof("%s: %s %s", event.Code, event.Arguments[1], event.Arguments[0]) | ||||
| 	case ircm.JOIN: | ||||
| 		fallthrough | ||||
| 	case ircm.PING: | ||||
| 		fallthrough | ||||
| 	case ircm.PONG: | ||||
| 		flog.irc.Infof("%s: %s", event.Code, event.Message()) | ||||
| 	case ircm.RPL_ENDOFMOTD: | ||||
| 	case ircm.RPL_MOTDSTART: | ||||
| 	case ircm.ERR_NICKNAMEINUSE: | ||||
| 		flog.irc.Warn(event.Message()) | ||||
| 	case ircm.NOTICE: | ||||
| 		b.handleNotice(event) | ||||
| 	default: | ||||
| 		flog.irc.Infof("UNKNOWN EVENT: %#v", event) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bridge) Send(nick string, message string, channel string) error { | ||||
| @@ -327,7 +296,7 @@ func (b *Bridge) SendType(nick string, message string, channel string, mtype str | ||||
| 			message = nick + " " + message | ||||
| 		} | ||||
| 	} | ||||
| 	if b.kind == "legacy" { | ||||
| 	if b.kind == Legacy { | ||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL} | ||||
| 		matterMessage.Channel = channel | ||||
| 		matterMessage.UserName = nick | ||||
| @@ -371,24 +340,34 @@ func (b *Bridge) handleMatterClient(mchan chan *MMMessage) { | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleMatter() { | ||||
| 	flog.mm.Infof("Choosing Mattermost connection type %s", b.kind) | ||||
| 	mchan := make(chan *MMMessage) | ||||
| 	if b.kind == "legacy" { | ||||
| 	if b.kind == Legacy { | ||||
| 		go b.handleMatterHook(mchan) | ||||
| 	} else { | ||||
| 		go b.handleMatterClient(mchan) | ||||
| 	} | ||||
| 	flog.mm.Info("Start listening for Mattermost messages") | ||||
| 	for message := range mchan { | ||||
| 		var username string | ||||
| 		if b.ignoreMessage(message.Username, message.Text, "mattermost") { | ||||
| 			continue | ||||
| 		} | ||||
| 		username = message.Username + ": " | ||||
| 		if b.Config.IRC.RemoteNickFormat != "" { | ||||
| 			username = strings.Replace(b.Config.IRC.RemoteNickFormat, "{NICK}", message.Username, -1) | ||||
| 		} else if b.Config.IRC.UseSlackCircumfix { | ||||
| 			username = "<" + message.Username + "> " | ||||
| 		} | ||||
| 		cmd := strings.Fields(message.Text)[0] | ||||
| 		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) | ||||
| 			flog.mm.Info("Received !users from ", message.Username) | ||||
| 			b.i.SendRaw("NAMES " + b.getIRCChannel(message.Channel)) | ||||
| 			continue | ||||
| 		case "!gif": | ||||
| @@ -425,7 +404,7 @@ func (b *Bridge) getMMChannel(ircChannel string) string { | ||||
| } | ||||
|  | ||||
| func (b *Bridge) getIRCChannel(channel string) string { | ||||
| 	if b.kind == "legacy" { | ||||
| 	if b.kind == Legacy { | ||||
| 		ircchannel := b.Config.IRC.Channel | ||||
| 		_, ok := b.Config.Token[channel] | ||||
| 		if ok { | ||||
| @@ -439,3 +418,17 @@ func (b *Bridge) getIRCChannel(channel string) string { | ||||
| 	} | ||||
| 	return ircchannel | ||||
| } | ||||
|  | ||||
| func (b *Bridge) ignoreMessage(nick string, message string, protocol string) bool { | ||||
| 	var ignoreNicks = b.mmIgnoreNicks | ||||
| 	if protocol == "irc" { | ||||
| 		ignoreNicks = b.ircIgnoreNicks | ||||
| 	} | ||||
| 	// should we discard messages ? | ||||
| 	for _, entry := range ignoreNicks { | ||||
| 		if nick == entry { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|   | ||||
							
								
								
									
										3
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -19,6 +19,7 @@ type Config struct { | ||||
| 		NickServNick      string | ||||
| 		NickServPassword  string | ||||
| 		RemoteNickFormat  string | ||||
| 		IgnoreNicks       string | ||||
| 	} | ||||
| 	Mattermost struct { | ||||
| 		URL                    string | ||||
| @@ -37,6 +38,8 @@ type Config struct { | ||||
| 		Login                  string | ||||
| 		Password               string | ||||
| 		RemoteNickFormat       *string | ||||
| 		IgnoreNicks            string | ||||
| 		NoTLS                  bool | ||||
| 	} | ||||
| 	Token map[string]*struct { | ||||
| 		IRCChannel string | ||||
|   | ||||
							
								
								
									
										155
									
								
								vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										155
									
								
								vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,9 +1,12 @@ | ||||
| package matterclient | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"net/http" | ||||
| 	"net/http/cookiejar" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| @@ -18,6 +21,7 @@ type Credentials struct { | ||||
| 	Pass          string | ||||
| 	Server        string | ||||
| 	NoTLS         bool | ||||
| 	SkipTLSVerify bool | ||||
| } | ||||
|  | ||||
| type Message struct { | ||||
| @@ -33,6 +37,8 @@ type MMClient struct { | ||||
| 	*Credentials | ||||
| 	Client       *model.Client | ||||
| 	WsClient     *websocket.Conn | ||||
| 	WsQuit       bool | ||||
| 	WsAway       bool | ||||
| 	Channels     *model.ChannelList | ||||
| 	MoreChannels *model.ChannelList | ||||
| 	User         *model.User | ||||
| @@ -60,6 +66,9 @@ func (m *MMClient) SetLogLevel(level string) { | ||||
| } | ||||
|  | ||||
| func (m *MMClient) Login() error { | ||||
| 	if m.WsQuit { | ||||
| 		return nil | ||||
| 	} | ||||
| 	b := &backoff.Backoff{ | ||||
| 		Min:    time.Second, | ||||
| 		Max:    5 * time.Minute, | ||||
| @@ -73,12 +82,25 @@ func (m *MMClient) Login() error { | ||||
| 	} | ||||
| 	// login to mattermost | ||||
| 	m.Client = model.NewClient(uriScheme + m.Credentials.Server) | ||||
| 	m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} | ||||
| 	var myinfo *model.Result | ||||
| 	var appErr *model.AppError | ||||
| 	var logmsg = "trying login" | ||||
| 	for { | ||||
| 		m.log.Debugf(logmsg+" %s %s %s", m.Credentials.Team, m.Credentials.Login, m.Credentials.Server) | ||||
| 		m.log.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server) | ||||
| 		if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) { | ||||
| 			m.log.Debugf(logmsg+" with ", model.SESSION_COOKIE_TOKEN) | ||||
| 			token := strings.Split(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN+"=") | ||||
| 			m.Client.HttpClient.Jar = m.createCookieJar(token[1]) | ||||
| 			m.Client.MockSession(token[1]) | ||||
| 			myinfo, appErr = m.Client.GetMe("") | ||||
| 			if myinfo.Data.(*model.User) == nil { | ||||
| 				m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass) | ||||
| 				return errors.New("invalid " + model.SESSION_COOKIE_TOKEN) | ||||
| 			} | ||||
| 		} else { | ||||
| 			myinfo, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) | ||||
| 		} | ||||
| 		if appErr != nil { | ||||
| 			d := b.Duration() | ||||
| 			m.log.Debug(appErr.DetailedError) | ||||
| @@ -89,7 +111,7 @@ func (m *MMClient) Login() error { | ||||
| 				} | ||||
| 				return errors.New(appErr.Message) | ||||
| 			} | ||||
| 			m.log.Debug("LOGIN: %s, reconnecting in %s", appErr, d) | ||||
| 			m.log.Debugf("LOGIN: %s, reconnecting in %s", appErr, d) | ||||
| 			time.Sleep(d) | ||||
| 			logmsg = "retrying login" | ||||
| 			continue | ||||
| @@ -98,15 +120,16 @@ func (m *MMClient) Login() error { | ||||
| 	} | ||||
| 	// reset timer | ||||
| 	b.Reset() | ||||
| 	m.User = myinfo.Data.(*model.User) | ||||
|  | ||||
| 	teamdata, _ := m.Client.GetAllTeamListings() | ||||
| 	teams := teamdata.Data.(map[string]*model.Team) | ||||
| 	for k, v := range teams { | ||||
| 	initLoad, _ := m.Client.GetInitialLoad() | ||||
| 	initData := initLoad.Data.(*model.InitialLoad) | ||||
| 	m.User = initData.User | ||||
| 	for _, v := range initData.Teams { | ||||
| 		m.log.Debugf("trying %s (id: %s)", v.Name, v.Id) | ||||
| 		if v.Name == m.Credentials.Team { | ||||
| 			m.Client.SetTeamId(k) | ||||
| 			m.Client.SetTeamId(v.Id) | ||||
| 			m.Team = v | ||||
| 			m.log.Debug("GetallTeamListings: found id ", k) | ||||
| 			m.log.Debugf("GetallTeamListings: found id %s for team %s", v.Id, v.Name) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| @@ -119,13 +142,14 @@ func (m *MMClient) Login() error { | ||||
| 	header := http.Header{} | ||||
| 	header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) | ||||
|  | ||||
| 	var WsClient *websocket.Conn | ||||
| 	m.log.Debug("WsClient: making connection") | ||||
| 	var err error | ||||
| 	for { | ||||
| 		WsClient, _, err = websocket.DefaultDialer.Dial(wsurl, header) | ||||
| 		wsDialer := &websocket.Dialer{Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} | ||||
| 		m.WsClient, _, err = wsDialer.Dial(wsurl, header) | ||||
| 		if err != nil { | ||||
| 			d := b.Duration() | ||||
| 			log.Printf("WSS: %s, reconnecting in %s", err, d) | ||||
| 			m.log.Debugf("WSS: %s, reconnecting in %s", err, d) | ||||
| 			time.Sleep(d) | ||||
| 			continue | ||||
| 		} | ||||
| @@ -133,8 +157,6 @@ func (m *MMClient) Login() error { | ||||
| 	} | ||||
| 	b.Reset() | ||||
|  | ||||
| 	m.WsClient = WsClient | ||||
|  | ||||
| 	// populating users | ||||
| 	m.UpdateUsers() | ||||
|  | ||||
| @@ -147,12 +169,19 @@ func (m *MMClient) Login() error { | ||||
| func (m *MMClient) WsReceiver() { | ||||
| 	var rmsg model.Message | ||||
| 	for { | ||||
| 		if m.WsQuit { | ||||
| 			m.log.Debug("exiting WsReceiver") | ||||
| 			return | ||||
| 		} | ||||
| 		if err := m.WsClient.ReadJSON(&rmsg); err != nil { | ||||
| 			log.Println("error:", err) | ||||
| 			m.log.Error("error:", err) | ||||
| 			// reconnect | ||||
| 			m.Login() | ||||
| 		} | ||||
| 		//log.Printf("WsReceiver: %#v", rmsg) | ||||
| 		if rmsg.Action == "ping" { | ||||
| 			m.handleWsPing() | ||||
| 			continue | ||||
| 		} | ||||
| 		msg := &Message{Raw: &rmsg, Team: m.Credentials.Team} | ||||
| 		m.parseMessage(msg) | ||||
| 		m.MessageChan <- msg | ||||
| @@ -160,6 +189,14 @@ func (m *MMClient) WsReceiver() { | ||||
|  | ||||
| } | ||||
|  | ||||
| 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: | ||||
| @@ -198,7 +235,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) { | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateUsers() error { | ||||
| 	mmusers, _ := m.Client.GetProfiles(m.Client.GetTeamId(), "") | ||||
| 	mmusers, _ := m.Client.GetProfilesForDirectMessageList(m.Team.Id) | ||||
| 	m.Users = mmusers.Data.(map[string]*model.User) | ||||
| 	return nil | ||||
| } | ||||
| @@ -294,29 +331,49 @@ func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList { | ||||
| 	return res.Data.(*model.PostList) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetPublicLink(filename string) string { | ||||
| 	res, err := m.Client.GetPublicLink(filename) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return res.Data.(string) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetPublicLinks(filenames []string) []string { | ||||
| 	var output []string | ||||
| 	for _, f := range filenames { | ||||
| 		res, err := m.Client.GetPublicLink(f) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		output = append(output, res.Data.(string)) | ||||
| 	} | ||||
| 	return output | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateChannelHeader(channelId string, header string) { | ||||
| 	data := make(map[string]string) | ||||
| 	data["channel_id"] = channelId | ||||
| 	data["channel_header"] = header | ||||
| 	log.Printf("updating channelheader %#v, %#v", channelId, header) | ||||
| 	m.log.Debugf("updating channelheader %#v, %#v", channelId, header) | ||||
| 	_, err := m.Client.UpdateChannelHeader(data) | ||||
| 	if err != nil { | ||||
| 		log.Print(err) | ||||
| 		log.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateLastViewed(channelId string) { | ||||
| 	log.Printf("posting lastview %#v", channelId) | ||||
| 	m.log.Debugf("posting lastview %#v", channelId) | ||||
| 	_, err := m.Client.UpdateLastViewedAt(channelId) | ||||
| 	if err != nil { | ||||
| 		log.Print(err) | ||||
| 		m.log.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UsernamesInChannel(channelName string) []string { | ||||
| 	ceiRes, err := m.Client.GetChannelExtraInfo(m.GetChannelId(channelName), 5000, "") | ||||
| 	if err != nil { | ||||
| 		log.Errorf("UsernamesInChannel(%s) failed: %s", channelName, err) | ||||
| 		m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelName, err) | ||||
| 		return []string{} | ||||
| 	} | ||||
| 	extra := ceiRes.Data.(*model.ChannelExtra) | ||||
| @@ -326,3 +383,59 @@ func (m *MMClient) UsernamesInChannel(channelName string) []string { | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func (m *MMClient) createCookieJar(token string) *cookiejar.Jar { | ||||
| 	var cookies []*http.Cookie | ||||
| 	jar, _ := cookiejar.New(nil) | ||||
| 	firstCookie := &http.Cookie{ | ||||
| 		Name:   "MMAUTHTOKEN", | ||||
| 		Value:  token, | ||||
| 		Path:   "/", | ||||
| 		Domain: m.Credentials.Server, | ||||
| 	} | ||||
| 	cookies = append(cookies, firstCookie) | ||||
| 	cookieURL, _ := url.Parse("https://" + m.Credentials.Server) | ||||
| 	jar.SetCookies(cookieURL, cookies) | ||||
| 	return jar | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetOtherUserDM(channel string) *model.User { | ||||
| 	m.UpdateUsers() | ||||
| 	var rcvuser *model.User | ||||
| 	if strings.Contains(channel, "__") { | ||||
| 		rcvusers := strings.Split(channel, "__") | ||||
| 		if rcvusers[0] != m.User.Id { | ||||
| 			rcvuser = m.Users[rcvusers[0]] | ||||
| 		} else { | ||||
| 			rcvuser = m.Users[rcvusers[1]] | ||||
| 		} | ||||
| 	} | ||||
| 	return rcvuser | ||||
| } | ||||
|  | ||||
| func (m *MMClient) SendDirectMessage(toUserId string, msg string) { | ||||
| 	m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg) | ||||
| 	var channel string | ||||
| 	// We don't have a DM with this user yet. | ||||
| 	if m.GetChannelId(toUserId+"__"+m.User.Id) == "" && m.GetChannelId(m.User.Id+"__"+toUserId) == "" { | ||||
| 		// create DM channel | ||||
| 		_, err := m.Client.CreateDirectChannel(toUserId) | ||||
| 		if err != nil { | ||||
| 			m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, err) | ||||
| 		} | ||||
| 		// update our channels | ||||
| 		mmchannels, _ := m.Client.GetChannels("") | ||||
| 		m.Channels = mmchannels.Data.(*model.ChannelList) | ||||
| 	} | ||||
|  | ||||
| 	// build the channel name | ||||
| 	if toUserId > m.User.Id { | ||||
| 		channel = m.User.Id + "__" + toUserId | ||||
| 	} else { | ||||
| 		channel = toUserId + "__" + m.User.Id | ||||
| 	} | ||||
| 	// build & send the message | ||||
| 	msg = strings.Replace(msg, "\r", "", -1) | ||||
| 	post := &model.Post{ChannelId: m.GetChannelId(channel), Message: msg} | ||||
| 	m.Client.CreatePost(post) | ||||
| } | ||||
|   | ||||
							
								
								
									
										202
									
								
								vendor/github.com/42wim/matterbridge/matterhook/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										202
									
								
								vendor/github.com/42wim/matterbridge/matterhook/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,202 +0,0 @@ | ||||
|                                  Apache License | ||||
|                            Version 2.0, January 2004 | ||||
|                         http://www.apache.org/licenses/ | ||||
|  | ||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||
|  | ||||
|    1. Definitions. | ||||
|  | ||||
|       "License" shall mean the terms and conditions for use, reproduction, | ||||
|       and distribution as defined by Sections 1 through 9 of this document. | ||||
|  | ||||
|       "Licensor" shall mean the copyright owner or entity authorized by | ||||
|       the copyright owner that is granting the License. | ||||
|  | ||||
|       "Legal Entity" shall mean the union of the acting entity and all | ||||
|       other entities that control, are controlled by, or are under common | ||||
|       control with that entity. For the purposes of this definition, | ||||
|       "control" means (i) the power, direct or indirect, to cause the | ||||
|       direction or management of such entity, whether by contract or | ||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||
|       outstanding shares, or (iii) beneficial ownership of such entity. | ||||
|  | ||||
|       "You" (or "Your") shall mean an individual or Legal Entity | ||||
|       exercising permissions granted by this License. | ||||
|  | ||||
|       "Source" form shall mean the preferred form for making modifications, | ||||
|       including but not limited to software source code, documentation | ||||
|       source, and configuration files. | ||||
|  | ||||
|       "Object" form shall mean any form resulting from mechanical | ||||
|       transformation or translation of a Source form, including but | ||||
|       not limited to compiled object code, generated documentation, | ||||
|       and conversions to other media types. | ||||
|  | ||||
|       "Work" shall mean the work of authorship, whether in Source or | ||||
|       Object form, made available under the License, as indicated by a | ||||
|       copyright notice that is included in or attached to the work | ||||
|       (an example is provided in the Appendix below). | ||||
|  | ||||
|       "Derivative Works" shall mean any work, whether in Source or Object | ||||
|       form, that is based on (or derived from) the Work and for which the | ||||
|       editorial revisions, annotations, elaborations, or other modifications | ||||
|       represent, as a whole, an original work of authorship. For the purposes | ||||
|       of this License, Derivative Works shall not include works that remain | ||||
|       separable from, or merely link (or bind by name) to the interfaces of, | ||||
|       the Work and Derivative Works thereof. | ||||
|  | ||||
|       "Contribution" shall mean any work of authorship, including | ||||
|       the original version of the Work and any modifications or additions | ||||
|       to that Work or Derivative Works thereof, that is intentionally | ||||
|       submitted to Licensor for inclusion in the Work by the copyright owner | ||||
|       or by an individual or Legal Entity authorized to submit on behalf of | ||||
|       the copyright owner. For the purposes of this definition, "submitted" | ||||
|       means any form of electronic, verbal, or written communication sent | ||||
|       to the Licensor or its representatives, including but not limited to | ||||
|       communication on electronic mailing lists, source code control systems, | ||||
|       and issue tracking systems that are managed by, or on behalf of, the | ||||
|       Licensor for the purpose of discussing and improving the Work, but | ||||
|       excluding communication that is conspicuously marked or otherwise | ||||
|       designated in writing by the copyright owner as "Not a Contribution." | ||||
|  | ||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||
|       on behalf of whom a Contribution has been received by Licensor and | ||||
|       subsequently incorporated within the Work. | ||||
|  | ||||
|    2. Grant of Copyright License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       copyright license to reproduce, prepare Derivative Works of, | ||||
|       publicly display, publicly perform, sublicense, and distribute the | ||||
|       Work and such Derivative Works in Source or Object form. | ||||
|  | ||||
|    3. Grant of Patent License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       (except as stated in this section) patent license to make, have made, | ||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||
|       where such license applies only to those patent claims licensable | ||||
|       by such Contributor that are necessarily infringed by their | ||||
|       Contribution(s) alone or by combination of their Contribution(s) | ||||
|       with the Work to which such Contribution(s) was submitted. If You | ||||
|       institute patent litigation against any entity (including a | ||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||
|       or a Contribution incorporated within the Work constitutes direct | ||||
|       or contributory patent infringement, then any patent licenses | ||||
|       granted to You under this License for that Work shall terminate | ||||
|       as of the date such litigation is filed. | ||||
|  | ||||
|    4. Redistribution. You may reproduce and distribute copies of the | ||||
|       Work or Derivative Works thereof in any medium, with or without | ||||
|       modifications, and in Source or Object form, provided that You | ||||
|       meet the following conditions: | ||||
|  | ||||
|       (a) You must give any other recipients of the Work or | ||||
|           Derivative Works a copy of this License; and | ||||
|  | ||||
|       (b) You must cause any modified files to carry prominent notices | ||||
|           stating that You changed the files; and | ||||
|  | ||||
|       (c) You must retain, in the Source form of any Derivative Works | ||||
|           that You distribute, all copyright, patent, trademark, and | ||||
|           attribution notices from the Source form of the Work, | ||||
|           excluding those notices that do not pertain to any part of | ||||
|           the Derivative Works; and | ||||
|  | ||||
|       (d) If the Work includes a "NOTICE" text file as part of its | ||||
|           distribution, then any Derivative Works that You distribute must | ||||
|           include a readable copy of the attribution notices contained | ||||
|           within such NOTICE file, excluding those notices that do not | ||||
|           pertain to any part of the Derivative Works, in at least one | ||||
|           of the following places: within a NOTICE text file distributed | ||||
|           as part of the Derivative Works; within the Source form or | ||||
|           documentation, if provided along with the Derivative Works; or, | ||||
|           within a display generated by the Derivative Works, if and | ||||
|           wherever such third-party notices normally appear. The contents | ||||
|           of the NOTICE file are for informational purposes only and | ||||
|           do not modify the License. You may add Your own attribution | ||||
|           notices within Derivative Works that You distribute, alongside | ||||
|           or as an addendum to the NOTICE text from the Work, provided | ||||
|           that such additional attribution notices cannot be construed | ||||
|           as modifying the License. | ||||
|  | ||||
|       You may add Your own copyright statement to Your modifications and | ||||
|       may provide additional or different license terms and conditions | ||||
|       for use, reproduction, or distribution of Your modifications, or | ||||
|       for any such Derivative Works as a whole, provided Your use, | ||||
|       reproduction, and distribution of the Work otherwise complies with | ||||
|       the conditions stated in this License. | ||||
|  | ||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||
|       any Contribution intentionally submitted for inclusion in the Work | ||||
|       by You to the Licensor shall be under the terms and conditions of | ||||
|       this License, without any additional terms or conditions. | ||||
|       Notwithstanding the above, nothing herein shall supersede or modify | ||||
|       the terms of any separate license agreement you may have executed | ||||
|       with Licensor regarding such Contributions. | ||||
|  | ||||
|    6. Trademarks. This License does not grant permission to use the trade | ||||
|       names, trademarks, service marks, or product names of the Licensor, | ||||
|       except as required for reasonable and customary use in describing the | ||||
|       origin of the Work and reproducing the content of the NOTICE file. | ||||
|  | ||||
|    7. Disclaimer of Warranty. Unless required by applicable law or | ||||
|       agreed to in writing, Licensor provides the Work (and each | ||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||
|       implied, including, without limitation, any warranties or conditions | ||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||
|       appropriateness of using or redistributing the Work and assume any | ||||
|       risks associated with Your exercise of permissions under this License. | ||||
|  | ||||
|    8. Limitation of Liability. In no event and under no legal theory, | ||||
|       whether in tort (including negligence), contract, or otherwise, | ||||
|       unless required by applicable law (such as deliberate and grossly | ||||
|       negligent acts) or agreed to in writing, shall any Contributor be | ||||
|       liable to You for damages, including any direct, indirect, special, | ||||
|       incidental, or consequential damages of any character arising as a | ||||
|       result of this License or out of the use or inability to use the | ||||
|       Work (including but not limited to damages for loss of goodwill, | ||||
|       work stoppage, computer failure or malfunction, or any and all | ||||
|       other commercial damages or losses), even if such Contributor | ||||
|       has been advised of the possibility of such damages. | ||||
|  | ||||
|    9. Accepting Warranty or Additional Liability. While redistributing | ||||
|       the Work or Derivative Works thereof, You may choose to offer, | ||||
|       and charge a fee for, acceptance of support, warranty, indemnity, | ||||
|       or other liability obligations and/or rights consistent with this | ||||
|       License. However, in accepting such obligations, You may act only | ||||
|       on Your own behalf and on Your sole responsibility, not on behalf | ||||
|       of any other Contributor, and only if You agree to indemnify, | ||||
|       defend, and hold each Contributor harmless for any liability | ||||
|       incurred by, or claims asserted against, such Contributor by reason | ||||
|       of your accepting any such warranty or additional liability. | ||||
|  | ||||
|    END OF TERMS AND CONDITIONS | ||||
|  | ||||
|    APPENDIX: How to apply the Apache License to your work. | ||||
|  | ||||
|       To apply the Apache License to your work, attach the following | ||||
|       boilerplate notice, with the fields enclosed by brackets "{}" | ||||
|       replaced with your own identifying information. (Don't include | ||||
|       the brackets!)  The text should be enclosed in the appropriate | ||||
|       comment syntax for the file format. We also recommend that a | ||||
|       file or class name and description of purpose be included on the | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
|  | ||||
|    Copyright {yyyy} {name of copyright owner} | ||||
|  | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
|  | ||||
|        http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
|  | ||||
							
								
								
									
										154
									
								
								vendor/github.com/42wim/matterbridge/matterhook/matterhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										154
									
								
								vendor/github.com/42wim/matterbridge/matterhook/matterhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,154 +0,0 @@ | ||||
| //Package matterhook provides interaction with mattermost incoming/outgoing webhooks | ||||
| package matterhook | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/tls" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/gorilla/schema" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| // OMessage for mattermost incoming webhook. (send to mattermost) | ||||
| type OMessage struct { | ||||
| 	Channel     string      `json:"channel,omitempty"` | ||||
| 	IconURL     string      `json:"icon_url,omitempty"` | ||||
| 	IconEmoji   string      `json:"icon_emoji,omitempty"` | ||||
| 	UserName    string      `json:"username,omitempty"` | ||||
| 	Text        string      `json:"text"` | ||||
| 	Attachments interface{} `json:"attachments,omitempty"` | ||||
| 	Type        string      `json:"type,omitempty"` | ||||
| } | ||||
|  | ||||
| // IMessage for mattermost outgoing webhook. (received from mattermost) | ||||
| type IMessage struct { | ||||
| 	Token       string `schema:"token"` | ||||
| 	TeamID      string `schema:"team_id"` | ||||
| 	TeamDomain  string `schema:"team_domain"` | ||||
| 	ChannelID   string `schema:"channel_id"` | ||||
| 	ServiceID   string `schema:"service_id"` | ||||
| 	ChannelName string `schema:"channel_name"` | ||||
| 	Timestamp   string `schema:"timestamp"` | ||||
| 	UserID      string `schema:"user_id"` | ||||
| 	UserName    string `schema:"user_name"` | ||||
| 	Text        string `schema:"text"` | ||||
| 	TriggerWord string `schema:"trigger_word"` | ||||
| } | ||||
|  | ||||
| // Client for Mattermost. | ||||
| type Client struct { | ||||
| 	Url        string // URL for incoming webhooks on mattermost. | ||||
| 	In         chan IMessage | ||||
| 	Out        chan OMessage | ||||
| 	httpclient *http.Client | ||||
| 	Config | ||||
| } | ||||
|  | ||||
| // Config for client. | ||||
| type Config struct { | ||||
| 	Port               int    // Port to listen on. | ||||
| 	BindAddress        string // Address to listen on | ||||
| 	Token              string // Only allow this token from Mattermost. (Allow everything when empty) | ||||
| 	InsecureSkipVerify bool   // disable certificate checking | ||||
| 	DisableServer      bool   // Do not start server for outgoing webhooks from Mattermost. | ||||
| } | ||||
|  | ||||
| // New Mattermost client. | ||||
| func New(url string, config Config) *Client { | ||||
| 	c := &Client{Url: url, In: make(chan IMessage), Out: make(chan OMessage), Config: config} | ||||
| 	if c.Port == 0 { | ||||
| 		c.Port = 9999 | ||||
| 	} | ||||
| 	c.BindAddress += ":" | ||||
| 	tr := &http.Transport{ | ||||
| 		TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}, | ||||
| 	} | ||||
| 	c.httpclient = &http.Client{Transport: tr} | ||||
| 	if !c.DisableServer { | ||||
| 		go c.StartServer() | ||||
| 	} | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| // StartServer starts a webserver listening for incoming mattermost POSTS. | ||||
| func (c *Client) StartServer() { | ||||
| 	mux := http.NewServeMux() | ||||
| 	mux.Handle("/", c) | ||||
| 	log.Printf("Listening on http://%v:%v...\n", c.BindAddress, c.Port) | ||||
| 	if err := http.ListenAndServe((c.BindAddress + strconv.Itoa(c.Port)), mux); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ServeHTTP implementation. | ||||
| func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	if r.Method != "POST" { | ||||
| 		log.Println("invalid " + r.Method + " connection from " + r.RemoteAddr) | ||||
| 		http.NotFound(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 	msg := IMessage{} | ||||
| 	err := r.ParseForm() | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 		http.NotFound(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 	defer r.Body.Close() | ||||
| 	decoder := schema.NewDecoder() | ||||
| 	err = decoder.Decode(&msg, r.PostForm) | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 		http.NotFound(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 	if msg.Token == "" { | ||||
| 		log.Println("no token from " + r.RemoteAddr) | ||||
| 		http.NotFound(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 	if c.Token != "" { | ||||
| 		if msg.Token != c.Token { | ||||
| 			log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr) | ||||
| 			http.NotFound(w, r) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	c.In <- msg | ||||
| } | ||||
|  | ||||
| // Receive returns an incoming message from mattermost outgoing webhooks URL. | ||||
| func (c *Client) Receive() IMessage { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case msg := <-c.In: | ||||
| 			return msg | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Send sends a msg to mattermost incoming webhooks URL. | ||||
| func (c *Client) Send(msg OMessage) error { | ||||
| 	buf, err := json.Marshal(msg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	resp, err := c.httpclient.Post(c.Url, "application/json", bytes.NewReader(buf)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	// Read entire body to completion to re-use keep-alive connections. | ||||
| 	io.Copy(ioutil.Discard, resp.Body) | ||||
|  | ||||
| 	if resp.StatusCode != 200 { | ||||
| 		return fmt.Errorf("unexpected status code: %d", resp.StatusCode) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										24
									
								
								vendor/github.com/mattermost/platform/einterfaces/brand.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/mattermost/platform/einterfaces/brand.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package einterfaces | ||||
|  | ||||
| import ( | ||||
| 	"github.com/mattermost/platform/model" | ||||
| 	"mime/multipart" | ||||
| ) | ||||
|  | ||||
| type BrandInterface interface { | ||||
| 	SaveBrandImage(*multipart.FileHeader) *model.AppError | ||||
| 	GetBrandImage() ([]byte, *model.AppError) | ||||
| } | ||||
|  | ||||
| var theBrandInterface BrandInterface | ||||
|  | ||||
| func RegisterBrandInterface(newInterface BrandInterface) { | ||||
| 	theBrandInterface = newInterface | ||||
| } | ||||
|  | ||||
| func GetBrandInterface() BrandInterface { | ||||
| 	return theBrandInterface | ||||
| } | ||||
							
								
								
									
										23
									
								
								vendor/github.com/mattermost/platform/einterfaces/compliance.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/mattermost/platform/einterfaces/compliance.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package einterfaces | ||||
|  | ||||
| import ( | ||||
| 	"github.com/mattermost/platform/model" | ||||
| ) | ||||
|  | ||||
| type ComplianceInterface interface { | ||||
| 	StartComplianceDailyJob() | ||||
| 	RunComplianceJob(job *model.Compliance) *model.AppError | ||||
| } | ||||
|  | ||||
| var theComplianceInterface ComplianceInterface | ||||
|  | ||||
| func RegisterComplianceInterface(newInterface ComplianceInterface) { | ||||
| 	theComplianceInterface = newInterface | ||||
| } | ||||
|  | ||||
| func GetComplianceInterface() ComplianceInterface { | ||||
| 	return theComplianceInterface | ||||
| } | ||||
							
								
								
									
										2
									
								
								vendor/github.com/mattermost/platform/einterfaces/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/mattermost/platform/einterfaces/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -13,6 +13,8 @@ type LdapInterface interface { | ||||
| 	CheckPassword(id string, password string) *model.AppError | ||||
| 	SwitchToLdap(userId, ldapId, ldapPassword string) *model.AppError | ||||
| 	ValidateFilter(filter string) *model.AppError | ||||
| 	Syncronize() *model.AppError | ||||
| 	StartLdapSyncJob() | ||||
| } | ||||
|  | ||||
| var theLdapInterface LdapInterface | ||||
|   | ||||
							
								
								
									
										25
									
								
								vendor/github.com/mattermost/platform/einterfaces/mfa.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/mattermost/platform/einterfaces/mfa.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package einterfaces | ||||
|  | ||||
| import ( | ||||
| 	"github.com/mattermost/platform/model" | ||||
| ) | ||||
|  | ||||
| type MfaInterface interface { | ||||
| 	GenerateQrCode(user *model.User) ([]byte, *model.AppError) | ||||
| 	Activate(user *model.User, token string) *model.AppError | ||||
| 	Deactivate(userId string) *model.AppError | ||||
| 	ValidateToken(secret, token string) (bool, *model.AppError) | ||||
| } | ||||
|  | ||||
| var theMfaInterface MfaInterface | ||||
|  | ||||
| func RegisterMfaInterface(newInterface MfaInterface) { | ||||
| 	theMfaInterface = newInterface | ||||
| } | ||||
|  | ||||
| func GetMfaInterface() MfaInterface { | ||||
| 	return theMfaInterface | ||||
| } | ||||
							
								
								
									
										234
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										234
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -28,6 +28,8 @@ const ( | ||||
| 	HEADER_AUTH               = "Authorization" | ||||
| 	HEADER_REQUESTED_WITH     = "X-Requested-With" | ||||
| 	HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" | ||||
| 	STATUS                    = "status" | ||||
| 	STATUS_OK                 = "OK" | ||||
|  | ||||
| 	API_URL_SUFFIX_V1 = "/api/v1" | ||||
| 	API_URL_SUFFIX_V3 = "/api/v3" | ||||
| @@ -47,12 +49,22 @@ type Client struct { | ||||
| 	AuthToken     string | ||||
| 	AuthType      string | ||||
| 	TeamId        string | ||||
| 	RequestId     string | ||||
| 	Etag          string | ||||
| 	ServerVersion string | ||||
| } | ||||
|  | ||||
| // NewClient constructs a new client with convienence methods for talking to | ||||
| // the server. | ||||
| func NewClient(url string) *Client { | ||||
| 	return &Client{url, url + API_URL_SUFFIX, &http.Client{}, "", "", ""} | ||||
| 	return &Client{url, url + API_URL_SUFFIX, &http.Client{}, "", "", "", "", "", ""} | ||||
| } | ||||
|  | ||||
| func closeBody(r *http.Response) { | ||||
| 	if r.Body != nil { | ||||
| 		ioutil.ReadAll(r.Body) | ||||
| 		r.Body.Close() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) SetOAuthToken(token string) { | ||||
| @@ -94,6 +106,10 @@ func (c *Client) GetChannelNameRoute(channelName string) string { | ||||
| 	return fmt.Sprintf("/teams/%v/channels/name/%v", c.GetTeamId(), channelName) | ||||
| } | ||||
|  | ||||
| func (c *Client) GetGeneralRoute() string { | ||||
| 	return "/general" | ||||
| } | ||||
|  | ||||
| func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) { | ||||
| 	rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data)) | ||||
| 	rq.Header.Set("Content-Type", contentType) | ||||
| @@ -101,6 +117,7 @@ func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppErro | ||||
| 	if rp, err := c.HttpClient.Do(rq); err != nil { | ||||
| 		return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) | ||||
| 	} else if rp.StatusCode >= 300 { | ||||
| 		defer closeBody(rp) | ||||
| 		return nil, AppErrorFromJson(rp.Body) | ||||
| 	} else { | ||||
| 		return rp, nil | ||||
| @@ -117,6 +134,7 @@ func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) | ||||
| 	if rp, err := c.HttpClient.Do(rq); err != nil { | ||||
| 		return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) | ||||
| 	} else if rp.StatusCode >= 300 { | ||||
| 		defer closeBody(rp) | ||||
| 		return nil, AppErrorFromJson(rp.Body) | ||||
| 	} else { | ||||
| 		return rp, nil | ||||
| @@ -139,6 +157,7 @@ func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, | ||||
| 	} else if rp.StatusCode == 304 { | ||||
| 		return rp, nil | ||||
| 	} else if rp.StatusCode >= 300 { | ||||
| 		defer closeBody(rp) | ||||
| 		return rp, AppErrorFromJson(rp.Body) | ||||
| 	} else { | ||||
| 		return rp, nil | ||||
| @@ -155,6 +174,7 @@ func getCookie(name string, resp *http.Response) *http.Cookie { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Must is a convenience function used for testing. | ||||
| func (c *Client) Must(result *Result, err *AppError) *Result { | ||||
| 	if err != nil { | ||||
| 		l4g.Close() | ||||
| @@ -165,6 +185,80 @@ func (c *Client) Must(result *Result, err *AppError) *Result { | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // CheckStatusOK is a convenience function for checking the return of Web Service | ||||
| // call that return the a map of status=OK. | ||||
| func (c *Client) CheckStatusOK(r *http.Response) bool { | ||||
| 	m := MapFromJson(r.Body) | ||||
| 	defer closeBody(r) | ||||
|  | ||||
| 	if m != nil && m[STATUS] == STATUS_OK { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (c *Client) fillInExtraProperties(r *http.Response) { | ||||
| 	c.RequestId = r.Header.Get(HEADER_REQUEST_ID) | ||||
| 	c.Etag = r.Header.Get(HEADER_ETAG_SERVER) | ||||
| 	c.ServerVersion = r.Header.Get(HEADER_VERSION_ID) | ||||
| } | ||||
|  | ||||
| func (c *Client) clearExtraProperties() { | ||||
| 	c.RequestId = "" | ||||
| 	c.Etag = "" | ||||
| 	c.ServerVersion = "" | ||||
| } | ||||
|  | ||||
| // General Routes Section | ||||
|  | ||||
| // GetClientProperties returns properties needed by the client to show/hide | ||||
| // certian features.  It returns a map of strings. | ||||
| func (c *Client) GetClientProperties() (map[string]string, *AppError) { | ||||
| 	c.clearExtraProperties() | ||||
| 	if r, err := c.DoApiGet(c.GetGeneralRoute()+"/client_props", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return MapFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // LogClient is a convenience Web Service call so clients can log messages into | ||||
| // the server-side logs.  For example we typically log javascript error messages | ||||
| // into the server-side.  It returns true if the logging was successful. | ||||
| func (c *Client) LogClient(message string) (bool, *AppError) { | ||||
| 	c.clearExtraProperties() | ||||
| 	m := make(map[string]string) | ||||
| 	m["level"] = "ERROR" | ||||
| 	m["message"] = message | ||||
|  | ||||
| 	if r, err := c.DoApiPost(c.GetGeneralRoute()+"/log_client", MapToJson(m)); err != nil { | ||||
| 		return false, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return c.CheckStatusOK(r), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetPing returns a map of strings with server time, server version, and node Id. | ||||
| // Systems that want to check on health status of the server should check the | ||||
| // url /api/v3/ping for a 200 status response. | ||||
| func (c *Client) GetPing() (map[string]string, *AppError) { | ||||
| 	c.clearExtraProperties() | ||||
| 	if r, err := c.DoApiGet(c.GetGeneralRoute()+"/ping", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return MapFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Team Routes Section | ||||
|  | ||||
| func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["email"] = email | ||||
| @@ -172,6 +266,7 @@ func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppErro | ||||
| 	if r, err := c.DoApiPost("/teams/signup", MapToJson(m)); 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 | ||||
| 	} | ||||
| @@ -181,6 +276,7 @@ func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppErro | ||||
| 	if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamSignupFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -190,6 +286,7 @@ func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -199,7 +296,7 @@ func (c *Client) GetAllTeams() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/teams/all", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
|  | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -209,7 +306,7 @@ func (c *Client) GetAllTeamListings() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/teams/all_team_listings", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
|  | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamMapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -225,7 +322,7 @@ func (c *Client) FindTeamByName(name string) (*Result, *AppError) { | ||||
| 		if body, _ := ioutil.ReadAll(r.Body); string(body) == "true" { | ||||
| 			val = true | ||||
| 		} | ||||
|  | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), val}, nil | ||||
| 	} | ||||
| @@ -237,6 +334,7 @@ func (c *Client) AddUserToTeam(userId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/add_user_to_team", 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 | ||||
| 	} | ||||
| @@ -250,6 +348,7 @@ func (c *Client) AddUserToTeamFromInvite(hash, dataToHash, inviteId string) (*Re | ||||
| 	if r, err := c.DoApiPost("/teams/add_user_to_team_from_invite", 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), TeamFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -259,6 +358,7 @@ func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/invite_members", invites.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), InvitesFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -268,6 +368,7 @@ func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/update", team.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 | ||||
| 	} | ||||
| @@ -277,6 +378,7 @@ func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -289,6 +391,7 @@ func (c *Client) CreateUserWithInvite(user *User, hash string, data string, invi | ||||
| 	if r, err := c.DoApiPost(url, user.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -298,6 +401,7 @@ func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Re | ||||
| 	if r, err := c.DoApiPost("/users/create?d="+url.QueryEscape(data)+"&h="+hash, user.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -307,6 +411,7 @@ func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/"+id+"/get", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -316,6 +421,7 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/me", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -325,6 +431,7 @@ func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppEr | ||||
| 	if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+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 | ||||
| 	} | ||||
| @@ -334,6 +441,7 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); 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 | ||||
| 	} | ||||
| @@ -343,6 +451,7 @@ func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/direct_profiles", "", etag); 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 | ||||
| 	} | ||||
| @@ -390,6 +499,7 @@ func (c *Client) login(m map[string]string) (*Result, *AppError) { | ||||
| 			NewLocAppError("/users/login", "model.client.login.app_error", nil, "") | ||||
| 		} | ||||
|  | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -403,6 +513,7 @@ func (c *Client) Logout() (*Result, *AppError) { | ||||
| 		c.AuthType = HEADER_BEARER | ||||
| 		c.TeamId = "" | ||||
|  | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -415,6 +526,7 @@ func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/mfa", MapToJson(m)); 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 | ||||
| 	} | ||||
| @@ -424,6 +536,7 @@ func (c *Client) GenerateMfaQrCode() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/generate_mfa_qr", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), r.Body}, nil | ||||
| 	} | ||||
| @@ -437,6 +550,7 @@ func (c *Client) UpdateMfa(activate bool, token string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/update_mfa", StringInterfaceToJson(m)); 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 | ||||
| 	} | ||||
| @@ -449,6 +563,7 @@ func (c *Client) AdminResetMfa(userId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/admin/reset_mfa", MapToJson(m)); 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 | ||||
| 	} | ||||
| @@ -461,6 +576,7 @@ func (c *Client) RevokeSession(sessionAltId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/revoke_session", MapToJson(m)); 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 | ||||
| 	} | ||||
| @@ -470,6 +586,7 @@ func (c *Client) GetSessions(id string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/"+id+"/sessions", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), SessionsFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -479,6 +596,7 @@ func (c *Client) EmailToOAuth(m map[string]string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/claim/email_to_sso", MapToJson(m)); 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 | ||||
| 	} | ||||
| @@ -488,6 +606,7 @@ func (c *Client) OAuthToEmail(m map[string]string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/claim/oauth_to_email", MapToJson(m)); 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 | ||||
| 	} | ||||
| @@ -497,6 +616,7 @@ func (c *Client) LDAPToEmail(m map[string]string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/claim/ldap_to_email", MapToJson(m)); 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 | ||||
| 	} | ||||
| @@ -506,6 +626,7 @@ func (c *Client) EmailToLDAP(m map[string]string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/claim/ldap_to_email", MapToJson(m)); 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 | ||||
| 	} | ||||
| @@ -519,6 +640,7 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", MapToJson(m)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), CommandResponseFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -528,6 +650,7 @@ func (c *Client) ListCommands() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/commands/list", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -537,6 +660,7 @@ func (c *Client) ListTeamCommands() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/commands/list_team_commands", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), CommandListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -546,6 +670,7 @@ func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/create", cmd.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -555,6 +680,7 @@ func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/regen_token", 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), CommandFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -564,6 +690,7 @@ func (c *Client) DeleteCommand(data map[string]string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/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 | ||||
| 	} | ||||
| @@ -573,6 +700,7 @@ func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/"+id+"/audits", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -582,6 +710,7 @@ func (c *Client) GetLogs() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/admin/logs", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ArrayFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -591,42 +720,64 @@ func (c *Client) GetAllAudits() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetClientProperties() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/admin/client_props", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetConfig() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/admin/config", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ConfigFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ReloadConfig will reload the config.json file from disk.  Properties | ||||
| // requiring a server restart will still need a server restart.  You must | ||||
| // have the system admin role to call this method.  It will return status=OK | ||||
| // if it's successfully reloaded the config file, otherwise check the returned error. | ||||
| func (c *Client) ReloadConfig() (bool, *AppError) { | ||||
| 	c.clearExtraProperties() | ||||
| 	if r, err := c.DoApiGet("/admin/reload_config", "", ""); err != nil { | ||||
| 		return false, err | ||||
| 	} else { | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return c.CheckStatusOK(r), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) SaveConfig(config *Config) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/admin/save_config", 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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RecycleDatabaseConnection will attempt to recycle the database connections. | ||||
| // You must have the system admin role to call this method.  It will return status=OK | ||||
| // if it's successfully recycled the connections, otherwise check the returned error. | ||||
| func (c *Client) RecycleDatabaseConnection() (bool, *AppError) { | ||||
| 	c.clearExtraProperties() | ||||
| 	if r, err := c.DoApiGet("/admin/recycle_db_conn", "", ""); err != nil { | ||||
| 		return false, err | ||||
| 	} else { | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return c.CheckStatusOK(r), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) TestEmail(config *Config) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/admin/test_email", 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 | ||||
| 	} | ||||
| @@ -636,6 +787,7 @@ func (c *Client) GetComplianceReports() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/admin/compliance_reports", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), CompliancesFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -645,6 +797,7 @@ func (c *Client) SaveComplianceReport(job *Compliance) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/admin/save_compliance_report", job.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ComplianceFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -661,8 +814,10 @@ func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) { | ||||
| 	if rp, err := c.HttpClient.Do(rq); err != nil { | ||||
| 		return nil, NewLocAppError("/admin/download_compliance_report", "model.client.connecting.app_error", nil, err.Error()) | ||||
| 	} else if rp.StatusCode >= 300 { | ||||
| 		defer rp.Body.Close() | ||||
| 		return nil, AppErrorFromJson(rp.Body) | ||||
| 	} else { | ||||
| 		defer closeBody(rp) | ||||
| 		return &Result{rp.Header.Get(HEADER_REQUEST_ID), | ||||
| 			rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil | ||||
| 	} | ||||
| @@ -672,6 +827,7 @@ func (c *Client) GetTeamAnalytics(teamId, name string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/admin/analytics/"+teamId+"/"+name, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -681,6 +837,7 @@ func (c *Client) GetSystemAnalytics(name string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/admin/analytics/"+name, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), AnalyticsRowsFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -690,6 +847,7 @@ func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create", channel.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -701,6 +859,7 @@ func (c *Client) CreateDirectChannel(userId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create_direct", 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), ChannelFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -710,6 +869,7 @@ func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update", channel.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -719,6 +879,7 @@ func (c *Client) UpdateChannelHeader(data map[string]string) (*Result, *AppError | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_header", 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), ChannelFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -728,6 +889,7 @@ func (c *Client) UpdateChannelPurpose(data map[string]string) (*Result, *AppErro | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_purpose", 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), ChannelFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -737,6 +899,7 @@ func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/update_notify_props", 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 | ||||
| 	} | ||||
| @@ -746,6 +909,7 @@ func (c *Client) GetChannels(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -755,6 +919,7 @@ func (c *Client) GetChannel(id, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelDataFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -764,6 +929,7 @@ func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/more", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -773,6 +939,7 @@ func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/counts", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelCountsFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -849,6 +1016,7 @@ func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (* | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelExtraFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -858,6 +1026,7 @@ func (c *Client) CreatePost(post *Post) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/create", post.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -867,6 +1036,7 @@ func (c *Client) UpdatePost(post *Post) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/update", post.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -876,6 +1046,7 @@ func (c *Client) GetPosts(channelId string, offset int, limit int, etag string) | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/page/%v/%v", offset, limit), "", etag); 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 | ||||
| 	} | ||||
| @@ -885,6 +1056,7 @@ func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/since/%v", time), "", ""); 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 | ||||
| 	} | ||||
| @@ -894,6 +1066,7 @@ func (c *Client) GetPostsBefore(channelId string, postid string, offset int, lim | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/before/%v/%v", postid, offset, limit), "", etag); 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 | ||||
| 	} | ||||
| @@ -903,6 +1076,7 @@ func (c *Client) GetPostsAfter(channelId string, postid string, offset int, limi | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/posts/%v/after/%v/%v", postid, offset, limit), "", etag); 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 | ||||
| 	} | ||||
| @@ -912,6 +1086,7 @@ func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/get", postId), "", etag); 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 | ||||
| 	} | ||||
| @@ -921,6 +1096,7 @@ func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/delete", postId), ""); 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 | ||||
| 	} | ||||
| @@ -933,6 +1109,7 @@ func (c *Client) SearchPosts(terms string, isOrSearch bool) (*Result, *AppError) | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/posts/search", StringInterfaceToJson(data)); 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 | ||||
| 	} | ||||
| @@ -959,6 +1136,7 @@ func (c *Client) uploadFile(url string, data []byte, contentType string) (*Resul | ||||
| 	} else if rp.StatusCode >= 300 { | ||||
| 		return nil, AppErrorFromJson(rp.Body) | ||||
| 	} else { | ||||
| 		defer closeBody(rp) | ||||
| 		return &Result{rp.Header.Get(HEADER_REQUEST_ID), | ||||
| 			rp.Header.Get(HEADER_ETAG_SERVER), FileUploadResponseFromJson(rp.Body)}, nil | ||||
| 	} | ||||
| @@ -981,6 +1159,7 @@ func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) { | ||||
| 	} else if rp.StatusCode >= 300 { | ||||
| 		return nil, AppErrorFromJson(rp.Body) | ||||
| 	} else { | ||||
| 		defer closeBody(rp) | ||||
| 		return &Result{rp.Header.Get(HEADER_REQUEST_ID), | ||||
| 			rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil | ||||
| 	} | ||||
| @@ -999,6 +1178,7 @@ func (c *Client) GetFileInfo(url string) (*Result, *AppError) { | ||||
| 	} else if rp.StatusCode >= 300 { | ||||
| 		return nil, AppErrorFromJson(rp.Body) | ||||
| 	} else { | ||||
| 		defer closeBody(rp) | ||||
| 		return &Result{rp.Header.Get(HEADER_REQUEST_ID), | ||||
| 			rp.Header.Get(HEADER_ETAG_SERVER), FileInfoFromJson(rp.Body)}, nil | ||||
| 	} | ||||
| @@ -1008,6 +1188,7 @@ func (c *Client) GetPublicLink(filename string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/files/get_public_link", MapToJson(map[string]string{"filename": filename})); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), StringFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1017,6 +1198,7 @@ func (c *Client) UpdateUser(user *User) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/update", user.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1026,6 +1208,7 @@ func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/update_roles", 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 | ||||
| 	} | ||||
| @@ -1037,6 +1220,7 @@ func (c *Client) AttachDeviceId(deviceId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/attach_device", 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), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1049,6 +1233,7 @@ func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/update_active", 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), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1058,6 +1243,7 @@ func (c *Client) UpdateUserNotify(data map[string]string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/update_notify", 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), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1072,6 +1258,7 @@ func (c *Client) UpdateUserPassword(userId, currentPassword, newPassword string) | ||||
| 	if r, err := c.DoApiPost("/users/newpassword", 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 | ||||
| 	} | ||||
| @@ -1083,6 +1270,7 @@ func (c *Client) SendPasswordReset(email string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/send_password_reset", 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 | ||||
| 	} | ||||
| @@ -1095,6 +1283,7 @@ func (c *Client) ResetPassword(code, newPassword string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/reset_password", 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 | ||||
| 	} | ||||
| @@ -1107,6 +1296,7 @@ func (c *Client) AdminResetPassword(userId, newPassword string) (*Result, *AppEr | ||||
| 	if r, err := c.DoApiPost("/admin/reset_password", 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 | ||||
| 	} | ||||
| @@ -1116,6 +1306,7 @@ func (c *Client) GetStatuses(data []string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/status", ArrayToJson(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 | ||||
| 	} | ||||
| @@ -1125,6 +1316,7 @@ func (c *Client) GetMyTeam(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/me", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1134,6 +1326,7 @@ func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/teams/members/"+teamId, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1143,6 +1336,7 @@ func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/oauth/register", app.ToJson()); 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 | ||||
| 	} | ||||
| @@ -1152,6 +1346,7 @@ func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (* | ||||
| 	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 | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1161,6 +1356,7 @@ func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/oauth/access_token", data.Encode()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), AccessResponseFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1170,6 +1366,7 @@ func (c *Client) CreateIncomingWebhook(hook *IncomingWebhook) (*Result, *AppErro | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/create", hook.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1190,6 +1387,7 @@ func (c *Client) DeleteIncomingWebhook(id string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/incoming/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 | ||||
| 	} | ||||
| @@ -1199,6 +1397,7 @@ func (c *Client) ListIncomingWebhooks() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/hooks/incoming/list", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), IncomingWebhookListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1208,6 +1407,7 @@ func (c *Client) GetAllPreferences() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/preferences/", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		preferences, _ := PreferencesFromJson(r.Body) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil | ||||
| 	} | ||||
| @@ -1226,6 +1426,7 @@ func (c *Client) GetPreference(category string, name string) (*Result, *AppError | ||||
| 	if r, err := c.DoApiGet("/preferences/"+category+"/"+name, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), PreferenceFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
| @@ -1234,6 +1435,7 @@ func (c *Client) GetPreferenceCategory(category string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/preferences/"+category, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		preferences, _ := PreferencesFromJson(r.Body) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil | ||||
| 	} | ||||
| @@ -1243,6 +1445,7 @@ func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppErro | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1254,6 +1457,7 @@ func (c *Client) DeleteOutgoingWebhook(id string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/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 | ||||
| 	} | ||||
| @@ -1263,6 +1467,7 @@ func (c *Client) ListOutgoingWebhooks() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/hooks/outgoing/list", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), OutgoingWebhookListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1274,6 +1479,7 @@ func (c *Client) RegenOutgoingWebhookToken(id string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/regen_token", 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), OutgoingWebhookFromJson(r.Body)}, nil | ||||
| 	} | ||||
| @@ -1288,6 +1494,7 @@ func (c *Client) GetClientLicenceConfig(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/license/client_config", "", etag); 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 | ||||
| 	} | ||||
| @@ -1297,6 +1504,7 @@ func (c *Client) GetInitialLoad() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/initial_load", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), InitialLoadFromJson(r.Body)}, nil | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										5
									
								
								vendor/github.com/mattermost/platform/model/command.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/mattermost/platform/model/command.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,11 +6,14 @@ package model | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	COMMAND_METHOD_POST = "P" | ||||
| 	COMMAND_METHOD_GET  = "G" | ||||
| 	MIN_TRIGGER_LENGTH  = 1 | ||||
| 	MAX_TRIGGER_LENGTH  = 128 | ||||
| ) | ||||
|  | ||||
| type Command struct { | ||||
| @@ -99,7 +102,7 @@ func (o *Command) IsValid() *AppError { | ||||
| 		return NewLocAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(o.Trigger) == 0 || len(o.Trigger) > 128 { | ||||
| 	if len(o.Trigger) < MIN_TRIGGER_LENGTH || len(o.Trigger) > MAX_TRIGGER_LENGTH || strings.Index(o.Trigger, "/") == 0 || strings.Contains(o.Trigger, " ") { | ||||
| 		return NewLocAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										132
									
								
								vendor/github.com/mattermost/platform/model/compliance.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								vendor/github.com/mattermost/platform/model/compliance.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	COMPLIANCE_STATUS_CREATED  = "created" | ||||
| 	COMPLIANCE_STATUS_RUNNING  = "running" | ||||
| 	COMPLIANCE_STATUS_FINISHED = "finished" | ||||
| 	COMPLIANCE_STATUS_FAILED   = "failed" | ||||
| 	COMPLIANCE_STATUS_REMOVED  = "removed" | ||||
|  | ||||
| 	COMPLIANCE_TYPE_DAILY = "daily" | ||||
| 	COMPLIANCE_TYPE_ADHOC = "adhoc" | ||||
| ) | ||||
|  | ||||
| type Compliance struct { | ||||
| 	Id       string `json:"id"` | ||||
| 	CreateAt int64  `json:"create_at"` | ||||
| 	UserId   string `json:"user_id"` | ||||
| 	Status   string `json:"status"` | ||||
| 	Count    int    `json:"count"` | ||||
| 	Desc     string `json:"desc"` | ||||
| 	Type     string `json:"type"` | ||||
| 	StartAt  int64  `json:"start_at"` | ||||
| 	EndAt    int64  `json:"end_at"` | ||||
| 	Keywords string `json:"keywords"` | ||||
| 	Emails   string `json:"emails"` | ||||
| } | ||||
|  | ||||
| type Compliances []Compliance | ||||
|  | ||||
| func (o *Compliance) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (me *Compliance) PreSave() { | ||||
| 	if me.Id == "" { | ||||
| 		me.Id = NewId() | ||||
| 	} | ||||
|  | ||||
| 	if me.Status == "" { | ||||
| 		me.Status = COMPLIANCE_STATUS_CREATED | ||||
| 	} | ||||
|  | ||||
| 	me.Count = 0 | ||||
| 	me.Emails = strings.ToLower(me.Emails) | ||||
| 	me.Keywords = strings.ToLower(me.Keywords) | ||||
|  | ||||
| 	me.CreateAt = GetMillis() | ||||
| } | ||||
|  | ||||
| func (me *Compliance) JobName() string { | ||||
| 	jobName := me.Type | ||||
| 	if me.Type == COMPLIANCE_TYPE_DAILY { | ||||
| 		jobName += "-" + me.Desc | ||||
| 	} | ||||
|  | ||||
| 	jobName += "-" + me.Id | ||||
|  | ||||
| 	return jobName | ||||
| } | ||||
|  | ||||
| func (me *Compliance) IsValid() *AppError { | ||||
|  | ||||
| 	if len(me.Id) != 26 { | ||||
| 		return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if me.CreateAt == 0 { | ||||
| 		return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.create_at.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(me.Desc) > 512 || len(me.Desc) == 0 { | ||||
| 		return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.desc.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if me.StartAt == 0 { | ||||
| 		return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.start_at.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if me.EndAt == 0 { | ||||
| 		return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.end_at.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if me.EndAt <= me.StartAt { | ||||
| 		return NewLocAppError("Compliance.IsValid", "model.compliance.is_valid.start_end_at.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func ComplianceFromJson(data io.Reader) *Compliance { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o Compliance | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o Compliances) ToJson() string { | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		return "[]" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func CompliancesFromJson(data io.Reader) Compliances { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o Compliances | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										104
									
								
								vendor/github.com/mattermost/platform/model/compliance_post.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								vendor/github.com/mattermost/platform/model/compliance_post.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type CompliancePost struct { | ||||
|  | ||||
| 	// From Team | ||||
| 	TeamName        string | ||||
| 	TeamDisplayName string | ||||
|  | ||||
| 	// From Channel | ||||
| 	ChannelName        string | ||||
| 	ChannelDisplayName string | ||||
|  | ||||
| 	// From User | ||||
| 	UserUsername string | ||||
| 	UserEmail    string | ||||
| 	UserNickname string | ||||
|  | ||||
| 	// From Post | ||||
| 	PostId         string | ||||
| 	PostCreateAt   int64 | ||||
| 	PostUpdateAt   int64 | ||||
| 	PostDeleteAt   int64 | ||||
| 	PostRootId     string | ||||
| 	PostParentId   string | ||||
| 	PostOriginalId string | ||||
| 	PostMessage    string | ||||
| 	PostType       string | ||||
| 	PostProps      string | ||||
| 	PostHashtags   string | ||||
| 	PostFilenames  string | ||||
| } | ||||
|  | ||||
| func CompliancePostHeader() []string { | ||||
| 	return []string{ | ||||
| 		"TeamName", | ||||
| 		"TeamDisplayName", | ||||
|  | ||||
| 		"ChannelName", | ||||
| 		"ChannelDisplayName", | ||||
|  | ||||
| 		"UserUsername", | ||||
| 		"UserEmail", | ||||
| 		"UserNickname", | ||||
|  | ||||
| 		"PostId", | ||||
| 		"PostCreateAt", | ||||
| 		"PostUpdateAt", | ||||
| 		"PostDeleteAt", | ||||
| 		"PostRootId", | ||||
| 		"PostParentId", | ||||
| 		"PostOriginalId", | ||||
| 		"PostMessage", | ||||
| 		"PostType", | ||||
| 		"PostProps", | ||||
| 		"PostHashtags", | ||||
| 		"PostFilenames", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (me *CompliancePost) Row() []string { | ||||
|  | ||||
| 	postDeleteAt := "" | ||||
| 	if me.PostDeleteAt > 0 { | ||||
| 		postDeleteAt = time.Unix(0, me.PostDeleteAt*int64(1000*1000)).Format(time.RFC3339) | ||||
| 	} | ||||
|  | ||||
| 	postUpdateAt := "" | ||||
| 	if me.PostUpdateAt != me.PostCreateAt { | ||||
| 		postUpdateAt = time.Unix(0, me.PostUpdateAt*int64(1000*1000)).Format(time.RFC3339) | ||||
| 	} | ||||
|  | ||||
| 	return []string{ | ||||
| 		me.TeamName, | ||||
| 		me.TeamDisplayName, | ||||
|  | ||||
| 		me.ChannelName, | ||||
| 		me.ChannelDisplayName, | ||||
|  | ||||
| 		me.UserUsername, | ||||
| 		me.UserEmail, | ||||
| 		me.UserNickname, | ||||
|  | ||||
| 		me.PostId, | ||||
| 		time.Unix(0, me.PostCreateAt*int64(1000*1000)).Format(time.RFC3339), | ||||
| 		postUpdateAt, | ||||
| 		postDeleteAt, | ||||
|  | ||||
| 		me.PostRootId, | ||||
| 		me.PostParentId, | ||||
| 		me.PostOriginalId, | ||||
| 		me.PostMessage, | ||||
| 		me.PostType, | ||||
| 		me.PostProps, | ||||
| 		me.PostHashtags, | ||||
| 		me.PostFilenames, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										134
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										134
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,6 +6,7 @@ package model | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -35,6 +36,15 @@ const ( | ||||
| 	FAKE_SETTING = "********************************" | ||||
| ) | ||||
|  | ||||
| // should match the values in webapp/i18n/i18n.jsx | ||||
| var LOCALES = []string{ | ||||
| 	"en", | ||||
| 	"es", | ||||
| 	"fr", | ||||
| 	"ja", | ||||
| 	"pt-BR", | ||||
| } | ||||
|  | ||||
| type ServiceSettings struct { | ||||
| 	ListenAddress                     string | ||||
| 	MaximumLoginAttempts              int | ||||
| @@ -92,6 +102,7 @@ type LogSettings struct { | ||||
| } | ||||
|  | ||||
| type FileSettings struct { | ||||
| 	MaxFileSize                *int64 | ||||
| 	DriverName                 string | ||||
| 	Directory                  string | ||||
| 	EnablePublicLink           bool | ||||
| @@ -189,6 +200,9 @@ type LdapSettings struct { | ||||
| 	NicknameAttribute  *string | ||||
| 	IdAttribute        *string | ||||
|  | ||||
| 	// Syncronization | ||||
| 	SyncIntervalMinutes *int | ||||
|  | ||||
| 	// Advanced | ||||
| 	SkipCertificateVerification *bool | ||||
| 	QueryTimeout                *int | ||||
| @@ -203,6 +217,12 @@ type ComplianceSettings struct { | ||||
| 	EnableDaily *bool | ||||
| } | ||||
|  | ||||
| type LocalizationSettings struct { | ||||
| 	DefaultServerLocale *string | ||||
| 	DefaultClientLocale *string | ||||
| 	AvailableLocales    *string | ||||
| } | ||||
|  | ||||
| type Config struct { | ||||
| 	ServiceSettings      ServiceSettings | ||||
| 	TeamSettings         TeamSettings | ||||
| @@ -217,6 +237,7 @@ type Config struct { | ||||
| 	GoogleSettings       SSOSettings | ||||
| 	LdapSettings         LdapSettings | ||||
| 	ComplianceSettings   ComplianceSettings | ||||
| 	LocalizationSettings LocalizationSettings | ||||
| } | ||||
|  | ||||
| func (o *Config) ToJson() string { | ||||
| @@ -256,6 +277,11 @@ func (o *Config) SetDefaults() { | ||||
| 		o.SqlSettings.AtRestEncryptKey = NewRandomString(32) | ||||
| 	} | ||||
|  | ||||
| 	if o.FileSettings.MaxFileSize == nil { | ||||
| 		o.FileSettings.MaxFileSize = new(int64) | ||||
| 		*o.FileSettings.MaxFileSize = 52428800 // 50 MB | ||||
| 	} | ||||
|  | ||||
| 	if len(o.FileSettings.PublicLinkSalt) == 0 { | ||||
| 		o.FileSettings.PublicLinkSalt = NewRandomString(32) | ||||
| 	} | ||||
| @@ -403,26 +429,86 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.SupportSettings.SupportEmail = "feedback@mattermost.com" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.LdapPort == nil { | ||||
| 		o.LdapSettings.LdapPort = new(int) | ||||
| 		*o.LdapSettings.LdapPort = 389 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.QueryTimeout == nil { | ||||
| 		o.LdapSettings.QueryTimeout = new(int) | ||||
| 		*o.LdapSettings.QueryTimeout = 60 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.Enable == nil { | ||||
| 		o.LdapSettings.Enable = new(bool) | ||||
| 		*o.LdapSettings.Enable = false | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.LdapServer == nil { | ||||
| 		o.LdapSettings.LdapServer = new(string) | ||||
| 		*o.LdapSettings.LdapServer = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.LdapPort == nil { | ||||
| 		o.LdapSettings.LdapPort = new(int) | ||||
| 		*o.LdapSettings.LdapPort = 389 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.ConnectionSecurity == nil { | ||||
| 		o.LdapSettings.ConnectionSecurity = new(string) | ||||
| 		*o.LdapSettings.ConnectionSecurity = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.BaseDN == nil { | ||||
| 		o.LdapSettings.BaseDN = new(string) | ||||
| 		*o.LdapSettings.BaseDN = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.BindUsername == nil { | ||||
| 		o.LdapSettings.BindUsername = new(string) | ||||
| 		*o.LdapSettings.BindUsername = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.BindPassword == nil { | ||||
| 		o.LdapSettings.BindPassword = new(string) | ||||
| 		*o.LdapSettings.BindPassword = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.UserFilter == nil { | ||||
| 		o.LdapSettings.UserFilter = new(string) | ||||
| 		*o.LdapSettings.UserFilter = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.FirstNameAttribute == nil { | ||||
| 		o.LdapSettings.FirstNameAttribute = new(string) | ||||
| 		*o.LdapSettings.FirstNameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.LastNameAttribute == nil { | ||||
| 		o.LdapSettings.LastNameAttribute = new(string) | ||||
| 		*o.LdapSettings.LastNameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.EmailAttribute == nil { | ||||
| 		o.LdapSettings.EmailAttribute = new(string) | ||||
| 		*o.LdapSettings.EmailAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.NicknameAttribute == nil { | ||||
| 		o.LdapSettings.NicknameAttribute = new(string) | ||||
| 		*o.LdapSettings.NicknameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.IdAttribute == nil { | ||||
| 		o.LdapSettings.IdAttribute = new(string) | ||||
| 		*o.LdapSettings.IdAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.SyncIntervalMinutes == nil { | ||||
| 		o.LdapSettings.SyncIntervalMinutes = new(int) | ||||
| 		*o.LdapSettings.SyncIntervalMinutes = 60 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.SkipCertificateVerification == nil { | ||||
| 		o.LdapSettings.SkipCertificateVerification = new(bool) | ||||
| 		*o.LdapSettings.SkipCertificateVerification = false | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.QueryTimeout == nil { | ||||
| 		o.LdapSettings.QueryTimeout = new(int) | ||||
| 		*o.LdapSettings.QueryTimeout = 60 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.LoginFieldName == nil { | ||||
| 		o.LdapSettings.LoginFieldName = new(string) | ||||
| 		*o.LdapSettings.LoginFieldName = "" | ||||
| @@ -493,19 +579,19 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.ComplianceSettings.EnableDaily = false | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.ConnectionSecurity == nil { | ||||
| 		o.LdapSettings.ConnectionSecurity = new(string) | ||||
| 		*o.LdapSettings.ConnectionSecurity = "" | ||||
| 	if o.LocalizationSettings.DefaultServerLocale == nil { | ||||
| 		o.LocalizationSettings.DefaultServerLocale = new(string) | ||||
| 		*o.LocalizationSettings.DefaultServerLocale = DEFAULT_LOCALE | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.SkipCertificateVerification == nil { | ||||
| 		o.LdapSettings.SkipCertificateVerification = new(bool) | ||||
| 		*o.LdapSettings.SkipCertificateVerification = false | ||||
| 	if o.LocalizationSettings.DefaultClientLocale == nil { | ||||
| 		o.LocalizationSettings.DefaultClientLocale = new(string) | ||||
| 		*o.LocalizationSettings.DefaultClientLocale = DEFAULT_LOCALE | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.NicknameAttribute == nil { | ||||
| 		o.LdapSettings.NicknameAttribute = new(string) | ||||
| 		*o.LdapSettings.NicknameAttribute = "" | ||||
| 	if o.LocalizationSettings.AvailableLocales == nil { | ||||
| 		o.LocalizationSettings.AvailableLocales = new(string) | ||||
| 		*o.LocalizationSettings.AvailableLocales = strings.Join(LOCALES, ",") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -547,6 +633,10 @@ func (o *Config) IsValid() *AppError { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_max_conn.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.FileSettings.MaxFileSize <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.max_file_size.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if !(o.FileSettings.DriverName == IMAGE_DRIVER_LOCAL || o.FileSettings.DriverName == IMAGE_DRIVER_S3) { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "") | ||||
| 	} | ||||
| @@ -603,6 +693,10 @@ func (o *Config) IsValid() *AppError { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_security.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.LdapSettings.SyncIntervalMinutes <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_sync_interval.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -615,7 +709,7 @@ func (o *Config) GetSanitizeOptions() map[string]bool { | ||||
| } | ||||
|  | ||||
| func (o *Config) Sanitize() { | ||||
| 	if &o.LdapSettings != nil && len(*o.LdapSettings.BindPassword) > 0 { | ||||
| 	if o.LdapSettings.BindPassword != nil && len(*o.LdapSettings.BindPassword) > 0 { | ||||
| 		*o.LdapSettings.BindPassword = FAKE_SETTING | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										4
									
								
								vendor/github.com/mattermost/platform/model/file.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/mattermost/platform/model/file.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -8,10 +8,6 @@ import ( | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	MAX_FILE_SIZE = 50000000 // 50 MB | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	IMAGE_EXTENSIONS = [5]string{".jpg", ".jpeg", ".gif", ".bmp", ".png"} | ||||
| 	IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff"} | ||||
|   | ||||
							
								
								
									
										8
									
								
								vendor/github.com/mattermost/platform/model/gitlab.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/mattermost/platform/model/gitlab.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| const ( | ||||
| 	USER_AUTH_SERVICE_GITLAB = "gitlab" | ||||
| ) | ||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/model/gitlab/gitlab.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/gitlab/gitlab.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -47,7 +47,8 @@ func userFromGitLabUser(glu *GitLabUser) *model.User { | ||||
| 	} | ||||
| 	strings.TrimSpace(user.Email) | ||||
| 	user.Email = glu.Email | ||||
| 	*user.AuthData = strconv.FormatInt(glu.Id, 10) | ||||
| 	userId := strconv.FormatInt(glu.Id, 10) | ||||
| 	user.AuthData = &userId | ||||
| 	user.AuthService = model.USER_AUTH_SERVICE_GITLAB | ||||
|  | ||||
| 	return user | ||||
|   | ||||
							
								
								
									
										135
									
								
								vendor/github.com/mattermost/platform/model/incoming_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										135
									
								
								vendor/github.com/mattermost/platform/model/incoming_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -4,13 +4,15 @@ | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	DEFAULT_WEBHOOK_USERNAME = "webhook" | ||||
| 	DEFAULT_WEBHOOK_ICON     = "/static/images/webhook_icon.jpg" | ||||
| ) | ||||
|  | ||||
| type IncomingWebhook struct { | ||||
| @@ -125,13 +127,136 @@ func (o *IncomingWebhook) PreUpdate() { | ||||
| 	o.UpdateAt = GetMillis() | ||||
| } | ||||
|  | ||||
| func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| // escapeControlCharsFromPayload escapes control chars (\n, \t) from a byte slice. | ||||
| // Context: | ||||
| // JSON strings are not supposed to contain control characters such as \n, \t, | ||||
| // ... but some incoming webhooks might still send invalid JSON and we want to | ||||
| // try to handle that. An example invalid JSON string from an incoming webhook | ||||
| // might look like this (strings for both "text" and "fallback" attributes are | ||||
| // invalid JSON strings because they contain unescaped newlines and tabs): | ||||
| //  `{ | ||||
| //    "text": "this is a test | ||||
| //						 that contains a newline and tabs", | ||||
| //    "attachments": [ | ||||
| //      { | ||||
| //        "fallback": "Required plain-text summary of the attachment | ||||
| //										that contains a newline and tabs", | ||||
| //        "color": "#36a64f", | ||||
| //  			... | ||||
| //        "text": "Optional text that appears within the attachment | ||||
| //								 that contains a newline and tabs", | ||||
| //  			... | ||||
| //        "thumb_url": "http://example.com/path/to/thumb.png" | ||||
| //      } | ||||
| //    ] | ||||
| //  }` | ||||
| // This function will search for `"key": "value"` pairs, and escape \n, \t | ||||
| // from the value. | ||||
| func escapeControlCharsFromPayload(by []byte) []byte { | ||||
| 	// we'll search for `"text": "..."` or `"fallback": "..."`, ... | ||||
| 	keys := "text|fallback|pretext|author_name|title|value" | ||||
|  | ||||
| 	// the regexp reads like this: | ||||
| 	// (?s): this flag let . match \n (default is false) | ||||
| 	// "(keys)": we search for the keys defined above | ||||
| 	// \s*:\s*: followed by 0..n spaces/tabs, a colon then 0..n spaces/tabs | ||||
| 	// ": a double-quote | ||||
| 	// (\\"|[^"])*: any number of times the `\"` string or any char but a double-quote | ||||
| 	// ": a double-quote | ||||
| 	r := `(?s)"(` + keys + `)"\s*:\s*"(\\"|[^"])*"` | ||||
| 	re := regexp.MustCompile(r) | ||||
|  | ||||
| 	// the function that will escape \n and \t on the regexp matches | ||||
| 	repl := func(b []byte) []byte { | ||||
| 		if bytes.Contains(b, []byte("\n")) { | ||||
| 			b = bytes.Replace(b, []byte("\n"), []byte("\\n"), -1) | ||||
| 		} | ||||
| 		if bytes.Contains(b, []byte("\t")) { | ||||
| 			b = bytes.Replace(b, []byte("\t"), []byte("\\t"), -1) | ||||
| 		} | ||||
|  | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	return re.ReplaceAllFunc(by, repl) | ||||
| } | ||||
|  | ||||
| func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) { | ||||
| 	decoder := json.NewDecoder(bytes.NewReader(by)) | ||||
| 	var o IncomingWebhookRequest | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 		return &o, nil | ||||
| 	} else { | ||||
| 		return nil | ||||
| 		return nil, err | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // To mention @channel via a webhook in Slack, the message should contain | ||||
| // <!channel>, as explained at the bottom of this article: | ||||
| // https://get.slack.help/hc/en-us/articles/202009646-Making-announcements | ||||
| func expandAnnouncement(text string) string { | ||||
| 	c1 := "<!channel>" | ||||
| 	c2 := "@channel" | ||||
| 	if strings.Contains(text, c1) { | ||||
| 		return strings.Replace(text, c1, c2, -1) | ||||
| 	} | ||||
| 	return text | ||||
| } | ||||
|  | ||||
| // Expand announcements in incoming webhooks from Slack. Those announcements | ||||
| // can be found in the text attribute, or in the pretext, text, title and value | ||||
| // attributes of the attachment structure. The Slack attachment structure is | ||||
| // documented here: https://api.slack.com/docs/attachments | ||||
| func expandAnnouncements(i *IncomingWebhookRequest) { | ||||
| 	i.Text = expandAnnouncement(i.Text) | ||||
|  | ||||
| 	if i.Attachments != nil { | ||||
| 		attachments := i.Attachments.([]interface{}) | ||||
| 		for _, attachment := range attachments { | ||||
| 			a := attachment.(map[string]interface{}) | ||||
|  | ||||
| 			if a["pretext"] != nil { | ||||
| 				a["pretext"] = expandAnnouncement(a["pretext"].(string)) | ||||
| 			} | ||||
|  | ||||
| 			if a["text"] != nil { | ||||
| 				a["text"] = expandAnnouncement(a["text"].(string)) | ||||
| 			} | ||||
|  | ||||
| 			if a["title"] != nil { | ||||
| 				a["title"] = expandAnnouncement(a["title"].(string)) | ||||
| 			} | ||||
|  | ||||
| 			if a["fields"] != nil { | ||||
| 				fields := a["fields"].([]interface{}) | ||||
| 				for _, field := range fields { | ||||
| 					f := field.(map[string]interface{}) | ||||
| 					if f["value"] != nil { | ||||
| 						f["value"] = expandAnnouncement(f["value"].(string)) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest { | ||||
| 	buf := new(bytes.Buffer) | ||||
| 	buf.ReadFrom(data) | ||||
| 	by := buf.Bytes() | ||||
|  | ||||
| 	// Try to decode the JSON data. Only if it fails, try to escape control | ||||
| 	// characters from the strings contained in the JSON data. | ||||
| 	o, err := decodeIncomingWebhookRequest(by) | ||||
| 	if err != nil { | ||||
| 		o, err = decodeIncomingWebhookRequest(escapeControlCharsFromPayload(by)) | ||||
| 		if err != nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	expandAnnouncements(o) | ||||
|  | ||||
| 	return o | ||||
| } | ||||
|   | ||||
							
								
								
									
										40
									
								
								vendor/github.com/mattermost/platform/model/initial_load.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/mattermost/platform/model/initial_load.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type InitialLoad struct { | ||||
| 	User           *User             `json:"user"` | ||||
| 	TeamMembers    []*TeamMember     `json:"team_members"` | ||||
| 	Teams          []*Team           `json:"teams"` | ||||
| 	DirectProfiles map[string]*User  `json:"direct_profiles"` | ||||
| 	Preferences    Preferences       `json:"preferences"` | ||||
| 	ClientCfg      map[string]string `json:"client_cfg"` | ||||
| 	LicenseCfg     map[string]string `json:"license_cfg"` | ||||
| 	NoAccounts     bool              `json:"no_accounts"` | ||||
| } | ||||
|  | ||||
| func (me *InitialLoad) ToJson() string { | ||||
| 	b, err := json.Marshal(me) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func InitialLoadFromJson(data io.Reader) *InitialLoad { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o InitialLoad | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										91
									
								
								vendor/github.com/mattermost/platform/model/job.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								vendor/github.com/mattermost/platform/model/job.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type TaskFunc func() | ||||
|  | ||||
| type ScheduledTask struct { | ||||
| 	Name      string        `json:"name"` | ||||
| 	Interval  time.Duration `json:"interval"` | ||||
| 	Recurring bool          `json:"recurring"` | ||||
| 	function  TaskFunc      `json:",omitempty"` | ||||
| 	timer     *time.Timer   `json:",omitempty"` | ||||
| } | ||||
|  | ||||
| var tasks = make(map[string]*ScheduledTask) | ||||
|  | ||||
| func addTask(task *ScheduledTask) { | ||||
| 	tasks[task.Name] = task | ||||
| } | ||||
|  | ||||
| func removeTaskByName(name string) { | ||||
| 	delete(tasks, name) | ||||
| } | ||||
|  | ||||
| func getTaskByName(name string) *ScheduledTask { | ||||
| 	return tasks[name] | ||||
| } | ||||
|  | ||||
| func GetAllTasks() *map[string]*ScheduledTask { | ||||
| 	return &tasks | ||||
| } | ||||
|  | ||||
| func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask { | ||||
| 	task := &ScheduledTask{ | ||||
| 		Name:      name, | ||||
| 		Interval:  timeToExecution, | ||||
| 		Recurring: false, | ||||
| 		function:  function, | ||||
| 	} | ||||
|  | ||||
| 	taskRunner := func() { | ||||
| 		go task.function() | ||||
| 		removeTaskByName(task.Name) | ||||
| 	} | ||||
|  | ||||
| 	task.timer = time.AfterFunc(timeToExecution, taskRunner) | ||||
|  | ||||
| 	addTask(task) | ||||
|  | ||||
| 	return task | ||||
| } | ||||
|  | ||||
| func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask { | ||||
| 	task := &ScheduledTask{ | ||||
| 		Name:      name, | ||||
| 		Interval:  interval, | ||||
| 		Recurring: true, | ||||
| 		function:  function, | ||||
| 	} | ||||
|  | ||||
| 	taskRecurer := func() { | ||||
| 		go task.function() | ||||
| 		task.timer.Reset(task.Interval) | ||||
| 	} | ||||
|  | ||||
| 	task.timer = time.AfterFunc(interval, taskRecurer) | ||||
|  | ||||
| 	addTask(task) | ||||
|  | ||||
| 	return task | ||||
| } | ||||
|  | ||||
| func (task *ScheduledTask) Cancel() { | ||||
| 	task.timer.Stop() | ||||
| 	removeTaskByName(task.Name) | ||||
| } | ||||
|  | ||||
| func (task *ScheduledTask) String() string { | ||||
| 	return fmt.Sprintf( | ||||
| 		"%s\nInterval: %s\nRecurring: %t\n", | ||||
| 		task.Name, | ||||
| 		task.Interval.String(), | ||||
| 		task.Recurring, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										8
									
								
								vendor/github.com/mattermost/platform/model/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/mattermost/platform/model/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| const ( | ||||
| 	USER_AUTH_SERVICE_LDAP = "ldap" | ||||
| ) | ||||
							
								
								
									
										47
									
								
								vendor/github.com/mattermost/platform/model/outgoing_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										47
									
								
								vendor/github.com/mattermost/platform/model/outgoing_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -7,6 +7,8 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| type OutgoingWebhook struct { | ||||
| @@ -22,6 +24,47 @@ type OutgoingWebhook struct { | ||||
| 	CallbackURLs StringArray `json:"callback_urls"` | ||||
| 	DisplayName  string      `json:"display_name"` | ||||
| 	Description  string      `json:"description"` | ||||
| 	ContentType  string      `json:"content_type"` | ||||
| } | ||||
|  | ||||
| type OutgoingWebhookPayload struct { | ||||
| 	Token       string `json:"token"` | ||||
| 	TeamId      string `json:"team_id"` | ||||
| 	TeamDomain  string `json:"team_domain"` | ||||
| 	ChannelId   string `json:"channel_id"` | ||||
| 	ChannelName string `json:"channel_name"` | ||||
| 	Timestamp   int64  `json:"timestamp"` | ||||
| 	UserId      string `json:"user_id"` | ||||
| 	UserName    string `json:"user_name"` | ||||
| 	PostId      string `json:"post_id"` | ||||
| 	Text        string `json:"text"` | ||||
| 	TriggerWord string `json:"trigger_word"` | ||||
| } | ||||
|  | ||||
| func (o *OutgoingWebhookPayload) ToJSON() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *OutgoingWebhookPayload) ToFormValues() string { | ||||
| 	v := url.Values{} | ||||
| 	v.Set("token", o.Token) | ||||
| 	v.Set("team_id", o.TeamId) | ||||
| 	v.Set("team_domain", o.TeamDomain) | ||||
| 	v.Set("channel_id", o.ChannelId) | ||||
| 	v.Set("channel_name", o.ChannelName) | ||||
| 	v.Set("timestamp", strconv.FormatInt(o.Timestamp/1000, 10)) | ||||
| 	v.Set("user_id", o.UserId) | ||||
| 	v.Set("user_name", o.UserName) | ||||
| 	v.Set("post_id", o.PostId) | ||||
| 	v.Set("text", o.Text) | ||||
| 	v.Set("trigger_word", o.TriggerWord) | ||||
|  | ||||
| 	return v.Encode() | ||||
| } | ||||
|  | ||||
| func (o *OutgoingWebhook) ToJson() string { | ||||
| @@ -124,6 +167,10 @@ func (o *OutgoingWebhook) IsValid() *AppError { | ||||
| 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(o.ContentType) > 128 { | ||||
| 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										37
									
								
								vendor/github.com/mattermost/platform/model/password_recovery.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								vendor/github.com/mattermost/platform/model/password_recovery.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| const ( | ||||
| 	PASSWORD_RECOVERY_CODE_SIZE  = 128 | ||||
| 	PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour | ||||
| ) | ||||
|  | ||||
| type PasswordRecovery struct { | ||||
| 	UserId   string | ||||
| 	Code     string | ||||
| 	CreateAt int64 | ||||
| } | ||||
|  | ||||
| func (p *PasswordRecovery) IsValid() *AppError { | ||||
|  | ||||
| 	if len(p.UserId) != 26 { | ||||
| 		return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.user_id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(p.Code) != PASSWORD_RECOVERY_CODE_SIZE { | ||||
| 		return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.code.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if p.CreateAt == 0 { | ||||
| 		return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.create_at.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *PasswordRecovery) PreSave() { | ||||
| 	p.Code = NewRandomString(PASSWORD_RECOVERY_CODE_SIZE) | ||||
| 	p.CreateAt = GetMillis() | ||||
| } | ||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/model/preference.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/preference.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -14,6 +14,9 @@ const ( | ||||
| 	PREFERENCE_CATEGORY_TUTORIAL_STEPS      = "tutorial_step" | ||||
| 	PREFERENCE_CATEGORY_ADVANCED_SETTINGS   = "advanced_settings" | ||||
|  | ||||
| 	PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings" | ||||
| 	PREFERENCE_NAME_COLLAPSE_SETTING     = "collapse_previews" | ||||
|  | ||||
| 	PREFERENCE_CATEGORY_LAST     = "last" | ||||
| 	PREFERENCE_NAME_LAST_CHANNEL = "channel" | ||||
| ) | ||||
|   | ||||
							
								
								
									
										8
									
								
								vendor/github.com/mattermost/platform/model/search_params.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/mattermost/platform/model/search_params.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -4,9 +4,13 @@ | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`) | ||||
| var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`) | ||||
|  | ||||
| type SearchParams struct { | ||||
| 	Terms      string | ||||
| 	IsHashtag  bool | ||||
| @@ -91,8 +95,8 @@ func parseSearchFlags(input []string) ([]string, [][2]string) { | ||||
|  | ||||
| 		if !isFlag { | ||||
| 			// trim off surrounding punctuation (note that we leave trailing asterisks to allow wildcards) | ||||
| 			word = puncStart.ReplaceAllString(word, "") | ||||
| 			word = puncEndWildcard.ReplaceAllString(word, "") | ||||
| 			word = searchTermPuncStart.ReplaceAllString(word, "") | ||||
| 			word = searchTermPuncEnd.ReplaceAllString(word, "") | ||||
|  | ||||
| 			// and remove extra pound #s | ||||
| 			word = hashtagStart.ReplaceAllString(word, "#") | ||||
|   | ||||
							
								
								
									
										120
									
								
								vendor/github.com/mattermost/platform/model/team_member.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								vendor/github.com/mattermost/platform/model/team_member.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	ROLE_TEAM_ADMIN = "admin" | ||||
| ) | ||||
|  | ||||
| type TeamMember struct { | ||||
| 	TeamId string `json:"team_id"` | ||||
| 	UserId string `json:"user_id"` | ||||
| 	Roles  string `json:"roles"` | ||||
| } | ||||
|  | ||||
| func (o *TeamMember) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TeamMemberFromJson(data io.Reader) *TeamMember { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o TeamMember | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TeamMembersToJson(o []*TeamMember) string { | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		return "[]" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TeamMembersFromJson(data io.Reader) []*TeamMember { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o []*TeamMember | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func IsValidTeamRoles(teamRoles string) bool { | ||||
|  | ||||
| 	roles := strings.Split(teamRoles, " ") | ||||
|  | ||||
| 	for _, r := range roles { | ||||
| 		if !isValidTeamRole(r) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func isValidTeamRole(role string) bool { | ||||
| 	if role == "" { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if role == ROLE_TEAM_ADMIN { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func IsInTeamRole(teamRoles string, inRole string) bool { | ||||
| 	roles := strings.Split(teamRoles, " ") | ||||
|  | ||||
| 	for _, r := range roles { | ||||
| 		if r == inRole { | ||||
| 			return true | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (o *TeamMember) IsTeamAdmin() bool { | ||||
| 	return IsInTeamRole(o.Roles, ROLE_TEAM_ADMIN) | ||||
| } | ||||
|  | ||||
| func (o *TeamMember) IsValid() *AppError { | ||||
|  | ||||
| 	if len(o.TeamId) != 26 { | ||||
| 		return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.team_id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(o.UserId) != 26 { | ||||
| 		return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.user_id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	for _, role := range strings.Split(o.Roles, " ") { | ||||
| 		if !(role == "" || role == ROLE_TEAM_ADMIN) { | ||||
| 			return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.role.app_error", nil, "role="+role) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										18
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -136,7 +136,6 @@ func (u *User) PreSave() { | ||||
|  | ||||
| 	u.Username = strings.ToLower(u.Username) | ||||
| 	u.Email = strings.ToLower(u.Email) | ||||
| 	u.Locale = strings.ToLower(u.Locale) | ||||
|  | ||||
| 	u.CreateAt = GetMillis() | ||||
| 	u.UpdateAt = u.CreateAt | ||||
| @@ -166,7 +165,6 @@ func (u *User) PreSave() { | ||||
| func (u *User) PreUpdate() { | ||||
| 	u.Username = strings.ToLower(u.Username) | ||||
| 	u.Email = strings.ToLower(u.Email) | ||||
| 	u.Locale = strings.ToLower(u.Locale) | ||||
| 	u.UpdateAt = GetMillis() | ||||
|  | ||||
| 	if u.AuthData != nil && *u.AuthData == "" { | ||||
| @@ -186,11 +184,27 @@ 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() { | ||||
| 	u.NotifyProps = make(map[string]string) | ||||
| 	u.NotifyProps["email"] = "true" | ||||
| 	u.NotifyProps["push"] = USER_NOTIFY_MENTION | ||||
| 	u.NotifyProps["desktop"] = USER_NOTIFY_ALL | ||||
| 	u.NotifyProps["desktop_sound"] = "true" | ||||
| 	u.NotifyProps["mention_keys"] = u.Username + ",@" + u.Username | ||||
|   | ||||
							
								
								
									
										5
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -315,10 +315,9 @@ func Etag(parts ...interface{}) string { | ||||
| } | ||||
|  | ||||
| var validHashtag = regexp.MustCompile(`^(#[A-Za-zäöüÄÖÜß]+[A-Za-z0-9äöüÄÖÜß_\-]*[A-Za-z0-9äöüÄÖÜß])$`) | ||||
| var puncStart = regexp.MustCompile(`^[.,()&$!\?\[\]{}':;\\<>\-+=%^*|]+`) | ||||
| var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) | ||||
| var hashtagStart = regexp.MustCompile(`^#{2,}`) | ||||
| var puncEnd = regexp.MustCompile(`[.,()&$#!\?\[\]{}':;\\<>\-+=%^*|]+$`) | ||||
| var puncEndWildcard = regexp.MustCompile(`[.,()&$#!\?\[\]{}':;\\<>\-+=%^|]+$`) | ||||
| var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) | ||||
|  | ||||
| func ParseHashtags(text string) (string, string) { | ||||
| 	words := strings.Fields(text) | ||||
|   | ||||
							
								
								
									
										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,7 @@ import ( | ||||
| // It should be maitained in chronological order with most current | ||||
| // release at the front of the list. | ||||
| var versions = []string{ | ||||
| 	"3.1.0", | ||||
| 	"3.0.0", | ||||
| 	"2.2.0", | ||||
| 	"2.1.0", | ||||
| @@ -33,6 +34,7 @@ var CurrentVersion string = versions[0] | ||||
| var BuildNumber string | ||||
| var BuildDate string | ||||
| var BuildHash string | ||||
| var BuildHashEnterprise string | ||||
| var BuildEnterpriseReady string | ||||
| var versionsWithoutHotFixes []string | ||||
|  | ||||
|   | ||||
							
								
								
									
										27
									
								
								vendor/golang.org/x/net/lex/httplex/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/golang.org/x/net/lex/httplex/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. | ||||
							
								
								
									
										312
									
								
								vendor/golang.org/x/net/lex/httplex/httplex.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								vendor/golang.org/x/net/lex/httplex/httplex.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,312 @@ | ||||
| // Copyright 2016 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Package httplex contains rules around lexical matters of various | ||||
| // HTTP-related specifications. | ||||
| // | ||||
| // This package is shared by the standard library (which vendors it) | ||||
| // and x/net/http2. It comes with no API stability promise. | ||||
| package httplex | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
|  | ||||
| var isTokenTable = [127]bool{ | ||||
| 	'!':  true, | ||||
| 	'#':  true, | ||||
| 	'$':  true, | ||||
| 	'%':  true, | ||||
| 	'&':  true, | ||||
| 	'\'': true, | ||||
| 	'*':  true, | ||||
| 	'+':  true, | ||||
| 	'-':  true, | ||||
| 	'.':  true, | ||||
| 	'0':  true, | ||||
| 	'1':  true, | ||||
| 	'2':  true, | ||||
| 	'3':  true, | ||||
| 	'4':  true, | ||||
| 	'5':  true, | ||||
| 	'6':  true, | ||||
| 	'7':  true, | ||||
| 	'8':  true, | ||||
| 	'9':  true, | ||||
| 	'A':  true, | ||||
| 	'B':  true, | ||||
| 	'C':  true, | ||||
| 	'D':  true, | ||||
| 	'E':  true, | ||||
| 	'F':  true, | ||||
| 	'G':  true, | ||||
| 	'H':  true, | ||||
| 	'I':  true, | ||||
| 	'J':  true, | ||||
| 	'K':  true, | ||||
| 	'L':  true, | ||||
| 	'M':  true, | ||||
| 	'N':  true, | ||||
| 	'O':  true, | ||||
| 	'P':  true, | ||||
| 	'Q':  true, | ||||
| 	'R':  true, | ||||
| 	'S':  true, | ||||
| 	'T':  true, | ||||
| 	'U':  true, | ||||
| 	'W':  true, | ||||
| 	'V':  true, | ||||
| 	'X':  true, | ||||
| 	'Y':  true, | ||||
| 	'Z':  true, | ||||
| 	'^':  true, | ||||
| 	'_':  true, | ||||
| 	'`':  true, | ||||
| 	'a':  true, | ||||
| 	'b':  true, | ||||
| 	'c':  true, | ||||
| 	'd':  true, | ||||
| 	'e':  true, | ||||
| 	'f':  true, | ||||
| 	'g':  true, | ||||
| 	'h':  true, | ||||
| 	'i':  true, | ||||
| 	'j':  true, | ||||
| 	'k':  true, | ||||
| 	'l':  true, | ||||
| 	'm':  true, | ||||
| 	'n':  true, | ||||
| 	'o':  true, | ||||
| 	'p':  true, | ||||
| 	'q':  true, | ||||
| 	'r':  true, | ||||
| 	's':  true, | ||||
| 	't':  true, | ||||
| 	'u':  true, | ||||
| 	'v':  true, | ||||
| 	'w':  true, | ||||
| 	'x':  true, | ||||
| 	'y':  true, | ||||
| 	'z':  true, | ||||
| 	'|':  true, | ||||
| 	'~':  true, | ||||
| } | ||||
|  | ||||
| func IsTokenRune(r rune) bool { | ||||
| 	i := int(r) | ||||
| 	return i < len(isTokenTable) && isTokenTable[i] | ||||
| } | ||||
|  | ||||
| func isNotToken(r rune) bool { | ||||
| 	return !IsTokenRune(r) | ||||
| } | ||||
|  | ||||
| // HeaderValuesContainsToken reports whether any string in values | ||||
| // contains the provided token, ASCII case-insensitively. | ||||
| func HeaderValuesContainsToken(values []string, token string) bool { | ||||
| 	for _, v := range values { | ||||
| 		if headerValueContainsToken(v, token) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // isOWS reports whether b is an optional whitespace byte, as defined | ||||
| // by RFC 7230 section 3.2.3. | ||||
| func isOWS(b byte) bool { return b == ' ' || b == '\t' } | ||||
|  | ||||
| // trimOWS returns x with all optional whitespace removes from the | ||||
| // beginning and end. | ||||
| func trimOWS(x string) string { | ||||
| 	// TODO: consider using strings.Trim(x, " \t") instead, | ||||
| 	// if and when it's fast enough. See issue 10292. | ||||
| 	// But this ASCII-only code will probably always beat UTF-8 | ||||
| 	// aware code. | ||||
| 	for len(x) > 0 && isOWS(x[0]) { | ||||
| 		x = x[1:] | ||||
| 	} | ||||
| 	for len(x) > 0 && isOWS(x[len(x)-1]) { | ||||
| 		x = x[:len(x)-1] | ||||
| 	} | ||||
| 	return x | ||||
| } | ||||
|  | ||||
| // headerValueContainsToken reports whether v (assumed to be a | ||||
| // 0#element, in the ABNF extension described in RFC 7230 section 7) | ||||
| // contains token amongst its comma-separated tokens, ASCII | ||||
| // case-insensitively. | ||||
| func headerValueContainsToken(v string, token string) bool { | ||||
| 	v = trimOWS(v) | ||||
| 	if comma := strings.IndexByte(v, ','); comma != -1 { | ||||
| 		return tokenEqual(trimOWS(v[:comma]), token) || headerValueContainsToken(v[comma+1:], token) | ||||
| 	} | ||||
| 	return tokenEqual(v, token) | ||||
| } | ||||
|  | ||||
| // lowerASCII returns the ASCII lowercase version of b. | ||||
| func lowerASCII(b byte) byte { | ||||
| 	if 'A' <= b && b <= 'Z' { | ||||
| 		return b + ('a' - 'A') | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // tokenEqual reports whether t1 and t2 are equal, ASCII case-insensitively. | ||||
| func tokenEqual(t1, t2 string) bool { | ||||
| 	if len(t1) != len(t2) { | ||||
| 		return false | ||||
| 	} | ||||
| 	for i, b := range t1 { | ||||
| 		if b >= utf8.RuneSelf { | ||||
| 			// No UTF-8 or non-ASCII allowed in tokens. | ||||
| 			return false | ||||
| 		} | ||||
| 		if lowerASCII(byte(b)) != lowerASCII(t2[i]) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // isLWS reports whether b is linear white space, according | ||||
| // to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 | ||||
| //      LWS            = [CRLF] 1*( SP | HT ) | ||||
| func isLWS(b byte) bool { return b == ' ' || b == '\t' } | ||||
|  | ||||
| // isCTL reports whether b is a control byte, according | ||||
| // to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 | ||||
| //      CTL            = <any US-ASCII control character | ||||
| //                       (octets 0 - 31) and DEL (127)> | ||||
| func isCTL(b byte) bool { | ||||
| 	const del = 0x7f // a CTL | ||||
| 	return b < ' ' || b == del | ||||
| } | ||||
|  | ||||
| // ValidHeaderFieldName reports whether v is a valid HTTP/1.x header name. | ||||
| // HTTP/2 imposes the additional restriction that uppercase ASCII | ||||
| // letters are not allowed. | ||||
| // | ||||
| //  RFC 7230 says: | ||||
| //   header-field   = field-name ":" OWS field-value OWS | ||||
| //   field-name     = token | ||||
| //   token          = 1*tchar | ||||
| //   tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / | ||||
| //           "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA | ||||
| func ValidHeaderFieldName(v string) bool { | ||||
| 	if len(v) == 0 { | ||||
| 		return false | ||||
| 	} | ||||
| 	for _, r := range v { | ||||
| 		if !IsTokenRune(r) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // ValidHostHeader reports whether h is a valid host header. | ||||
| func ValidHostHeader(h string) bool { | ||||
| 	// The latest spec is actually this: | ||||
| 	// | ||||
| 	// http://tools.ietf.org/html/rfc7230#section-5.4 | ||||
| 	//     Host = uri-host [ ":" port ] | ||||
| 	// | ||||
| 	// Where uri-host is: | ||||
| 	//     http://tools.ietf.org/html/rfc3986#section-3.2.2 | ||||
| 	// | ||||
| 	// But we're going to be much more lenient for now and just | ||||
| 	// search for any byte that's not a valid byte in any of those | ||||
| 	// expressions. | ||||
| 	for i := 0; i < len(h); i++ { | ||||
| 		if !validHostByte[h[i]] { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // See the validHostHeader comment. | ||||
| var validHostByte = [256]bool{ | ||||
| 	'0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, | ||||
| 	'8': true, '9': true, | ||||
|  | ||||
| 	'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true, | ||||
| 	'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true, | ||||
| 	'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true, | ||||
| 	'y': true, 'z': true, | ||||
|  | ||||
| 	'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true, | ||||
| 	'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true, | ||||
| 	'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true, | ||||
| 	'Y': true, 'Z': true, | ||||
|  | ||||
| 	'!':  true, // sub-delims | ||||
| 	'$':  true, // sub-delims | ||||
| 	'%':  true, // pct-encoded (and used in IPv6 zones) | ||||
| 	'&':  true, // sub-delims | ||||
| 	'(':  true, // sub-delims | ||||
| 	')':  true, // sub-delims | ||||
| 	'*':  true, // sub-delims | ||||
| 	'+':  true, // sub-delims | ||||
| 	',':  true, // sub-delims | ||||
| 	'-':  true, // unreserved | ||||
| 	'.':  true, // unreserved | ||||
| 	':':  true, // IPv6address + Host expression's optional port | ||||
| 	';':  true, // sub-delims | ||||
| 	'=':  true, // sub-delims | ||||
| 	'[':  true, | ||||
| 	'\'': true, // sub-delims | ||||
| 	']':  true, | ||||
| 	'_':  true, // unreserved | ||||
| 	'~':  true, // unreserved | ||||
| } | ||||
|  | ||||
| // ValidHeaderFieldValue reports whether v is a valid "field-value" according to | ||||
| // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 : | ||||
| // | ||||
| //        message-header = field-name ":" [ field-value ] | ||||
| //        field-value    = *( field-content | LWS ) | ||||
| //        field-content  = <the OCTETs making up the field-value | ||||
| //                         and consisting of either *TEXT or combinations | ||||
| //                         of token, separators, and quoted-string> | ||||
| // | ||||
| // http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 : | ||||
| // | ||||
| //        TEXT           = <any OCTET except CTLs, | ||||
| //                          but including LWS> | ||||
| //        LWS            = [CRLF] 1*( SP | HT ) | ||||
| //        CTL            = <any US-ASCII control character | ||||
| //                         (octets 0 - 31) and DEL (127)> | ||||
| // | ||||
| // RFC 7230 says: | ||||
| //  field-value    = *( field-content / obs-fold ) | ||||
| //  obj-fold       =  N/A to http2, and deprecated | ||||
| //  field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ] | ||||
| //  field-vchar    = VCHAR / obs-text | ||||
| //  obs-text       = %x80-FF | ||||
| //  VCHAR          = "any visible [USASCII] character" | ||||
| // | ||||
| // http2 further says: "Similarly, HTTP/2 allows header field values | ||||
| // that are not valid. While most of the values that can be encoded | ||||
| // will not alter header field parsing, carriage return (CR, ASCII | ||||
| // 0xd), line feed (LF, ASCII 0xa), and the zero character (NUL, ASCII | ||||
| // 0x0) might be exploited by an attacker if they are translated | ||||
| // verbatim. Any request or response that contains a character not | ||||
| // permitted in a header field value MUST be treated as malformed | ||||
| // (Section 8.1.2.6). Valid characters are defined by the | ||||
| // field-content ABNF rule in Section 3.2 of [RFC7230]." | ||||
| // | ||||
| // This function does not (yet?) properly handle the rejection of | ||||
| // strings that begin or end with SP or HTAB. | ||||
| func ValidHeaderFieldValue(v string) bool { | ||||
| 	for i := 0; i < len(v); i++ { | ||||
| 		b := v[i] | ||||
| 		if isCTL(b) && !isLWS(b) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
							
								
								
									
										30
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										30
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							| @@ -5,7 +5,7 @@ | ||||
| 			"importpath": "github.com/42wim/matterbridge-plus/bridge", | ||||
| 			"repository": "https://github.com/42wim/matterbridge-plus", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "e48ce1d820967506c7ea21539f6c88f7a1377d0f", | ||||
| 			"revision": "63c7caabf4b92fa47060cf37beb0ed814cefbaea", | ||||
| 			"branch": "master", | ||||
| 			"path": "/bridge", | ||||
| 			"notests": true | ||||
| @@ -14,20 +14,11 @@ | ||||
| 			"importpath": "github.com/42wim/matterbridge-plus/matterclient", | ||||
| 			"repository": "https://github.com/42wim/matterbridge-plus", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "e48ce1d820967506c7ea21539f6c88f7a1377d0f", | ||||
| 			"revision": "63c7caabf4b92fa47060cf37beb0ed814cefbaea", | ||||
| 			"branch": "master", | ||||
| 			"path": "/matterclient", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"importpath": "github.com/42wim/matterbridge/matterhook", | ||||
| 			"repository": "https://github.com/42wim/matterbridge", | ||||
| 			"vcs": "", | ||||
| 			"revision": "6b18257185b1830bd2eff83fae30bdd2055f78b0", | ||||
| 			"branch": "master", | ||||
| 			"path": "/matterhook", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"importpath": "github.com/Sirupsen/logrus", | ||||
| 			"repository": "https://github.com/Sirupsen/logrus", | ||||
| @@ -72,8 +63,8 @@ | ||||
| 			"importpath": "github.com/mattermost/platform/einterfaces", | ||||
| 			"repository": "https://github.com/mattermost/platform", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "9d94869cc6a0fb9f051879437c104ccd76094380", | ||||
| 			"branch": "HEAD", | ||||
| 			"revision": "974238231b9cdbd39a825ec8e9299fbb0b51f6b8", | ||||
| 			"branch": "release-3.1", | ||||
| 			"path": "/einterfaces", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @@ -81,8 +72,8 @@ | ||||
| 			"importpath": "github.com/mattermost/platform/model", | ||||
| 			"repository": "https://github.com/mattermost/platform", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "9d94869cc6a0fb9f051879437c104ccd76094380", | ||||
| 			"branch": "HEAD", | ||||
| 			"revision": "974238231b9cdbd39a825ec8e9299fbb0b51f6b8", | ||||
| 			"branch": "release-3.1", | ||||
| 			"path": "/model", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @@ -154,6 +145,15 @@ | ||||
| 			"path": "/http2/hpack", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"importpath": "golang.org/x/net/lex/httplex", | ||||
| 			"repository": "https://go.googlesource.com/net", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "bc3663df0ac92f928d419e31e0d2af22e683a5a2", | ||||
| 			"branch": "master", | ||||
| 			"path": "/lex/httplex", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"importpath": "gopkg.in/gcfg.v1", | ||||
| 			"repository": "https://gopkg.in/gcfg.v1", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user