forked from lug/matterbridge
		
	Compare commits
	
		
			54 Commits
		
	
	
		
			v0.4
			...
			v0.6.0-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e449a97bd0 | ||
| 
						 | 
					39043f3fa4 | ||
| 
						 | 
					12389d602e | ||
| 
						 | 
					44144587a0 | ||
| 
						 | 
					d0a30e354b | ||
| 
						 | 
					c261dc89d5 | ||
| 
						 | 
					c2c135bca2 | ||
| 
						 | 
					eb20cb237d | ||
| 
						 | 
					106404d32f | ||
| 
						 | 
					e06efbad9f | ||
| 
						 | 
					3311c7f923 | ||
| 
						 | 
					3a6c655dfb | ||
| 
						 | 
					e11d786775 | ||
| 
						 | 
					889b6debc4 | ||
| 
						 | 
					9cb3413d9c | ||
| 
						 | 
					131826e1d1 | ||
| 
						 | 
					96e21dd051 | ||
| 
						 | 
					32e5f396e7 | ||
| 
						 | 
					6c6000dbbd | ||
| 
						 | 
					24defcb970 | ||
| 
						 | 
					a1a11a88b3 | ||
| 
						 | 
					a997ae29ad | ||
| 
						 | 
					ff94796700 | ||
| 
						 | 
					1f72ca4c4e | ||
| 
						 | 
					46faad8b57 | ||
| 
						 | 
					30f30364d5 | ||
| 
						 | 
					073d90da88 | ||
| 
						 | 
					c769e23a9a | ||
| 
						 | 
					9db48f4794 | ||
| 
						 | 
					911c597377 | ||
| 
						 | 
					28244ffd9a | ||
| 
						 | 
					3e38c7945c | ||
| 
						 | 
					79ffb76f6e | ||
| 
						 | 
					5fe4b749cf | ||
| 
						 | 
					6991d85da9 | ||
| 
						 | 
					c1c187a1ab | ||
| 
						 | 
					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 gcc musl-dev ca-certificates \
 | 
			
		||||
        && cd /go/src/github.com/42wim/matterbridge \
 | 
			
		||||
        && export GOPATH=/go \
 | 
			
		||||
        && go get \
 | 
			
		||||
        && go build -o /bin/matterbridge \
 | 
			
		||||
        && rm -rf /go \
 | 
			
		||||
        && apk del --purge git go gcc musl-dev
 | 
			
		||||
							
								
								
									
										124
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										124
									
								
								README.md
									
									
									
									
									
								
							@@ -1,14 +1,40 @@
 | 
			
		||||
# matterbridge
 | 
			
		||||
 | 
			
		||||
Simple bridge between mattermost and IRC. Uses the in/outgoing webhooks.  
 | 
			
		||||
Relays public channel messages between mattermost and IRC.  
 | 
			
		||||
Simple bridge between mattermost, IRC, XMPP and Gitter
 | 
			
		||||
 | 
			
		||||
Requires mattermost 1.2.0+
 | 
			
		||||
* Relays public channel messages between mattermost, IRC, XMPP and Gitter. Pick and mix.
 | 
			
		||||
* Supports multiple channels.
 | 
			
		||||
* Matterbridge -plus also works with private groups on your mattermost.
 | 
			
		||||
 | 
			
		||||
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. 
 | 
			
		||||
Look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for documentation and an example.
 | 
			
		||||
 | 
			
		||||
## Changelog
 | 
			
		||||
Since v0.6.0-beta support for XMPP and Gitter is added. More details in [changelog.md] (https://github.com/42wim/matterbridge/blob/master/changelog.md)
 | 
			
		||||
 | 
			
		||||
## Requirements:
 | 
			
		||||
Accounts to one of the supported bridges
 | 
			
		||||
* [Mattermost] (https://github.com/mattermost/platform/)
 | 
			
		||||
* [IRC] (http://www.mirc.com/servers.html)
 | 
			
		||||
* [XMPP] (https://jabber.org)
 | 
			
		||||
* [Gitter] (https://gitter.im)
 | 
			
		||||
 | 
			
		||||
## binaries
 | 
			
		||||
Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/tag/v0.4)
 | 
			
		||||
Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/)
 | 
			
		||||
* For use with mattermost 3.3.0 [v0.6.0-beta1](https://github.com/42wim/matterircd/releases/tag/v0.6.0-beta1)
 | 
			
		||||
* For use with mattermost 3.0.0-3.2.0 [v0.5.0](https://github.com/42wim/matterircd/releases/tag/v0.5.0)
 | 
			
		||||
 | 
			
		||||
## Compatibility
 | 
			
		||||
### Mattermost 
 | 
			
		||||
* Matterbridge v0.6.0 works with mattermost 3.3.0 and higher [3.3.0 release](https://github.com/mattermost/platform/releases/tag/v3.3.0)
 | 
			
		||||
* Matterbridge v0.5.0 works with mattermost 3.0.0 - 3.2.0 [3.2.0 release](https://github.com/mattermost/platform/releases/tag/v3.2.0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#### Webhooks version
 | 
			
		||||
* Configured incoming/outgoing [webhooks](https://www.mattermost.org/webhooks/) on your mattermost instance.
 | 
			
		||||
 | 
			
		||||
#### Plus (API) version
 | 
			
		||||
* A dedicated user(bot) on your mattermost instance.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## building
 | 
			
		||||
Go 1.6+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH] (https://golang.org/doc/code.html#GOPATH)
 | 
			
		||||
@@ -31,73 +57,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 (deprecated, set Plus flag in [general] config)
 | 
			
		||||
  -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 +87,20 @@ 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
 | 
			
		||||
 | 
			
		||||
## FAQ
 | 
			
		||||
Please look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for more information first. 
 | 
			
		||||
### Mattermost doesn't show the IRC nicks
 | 
			
		||||
If you're running the webhooks version, this can be fixed by either:
 | 
			
		||||
* enabling "override usernames". See [mattermost documentation](http://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks)
 | 
			
		||||
* setting ```PrefixMessagesWithNick``` to ```true``` in ```mattermost``` section of your matterbridge.conf.
 | 
			
		||||
 | 
			
		||||
If you're running the plus version you'll need to:
 | 
			
		||||
* setting ```PrefixMessagesWithNick``` to ```true``` in ```mattermost``` section of your matterbridge.conf.
 | 
			
		||||
 | 
			
		||||
Also look at the ```RemoteNickFormat``` setting.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										142
									
								
								bridge/bridge.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								bridge/bridge.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
package bridge
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	//"fmt"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/gitter"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/irc"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/mattermost"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/xmpp"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Bridge struct {
 | 
			
		||||
	*config.Config
 | 
			
		||||
	Source      string
 | 
			
		||||
	Bridges     []Bridger
 | 
			
		||||
	Channels    []map[string]string
 | 
			
		||||
	ignoreNicks map[string][]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Bridger interface {
 | 
			
		||||
	Send(msg config.Message) error
 | 
			
		||||
	Name() string
 | 
			
		||||
	Connect() error
 | 
			
		||||
	//Command(cmd string) string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewBridge(cfg *config.Config) error {
 | 
			
		||||
	c := make(chan config.Message)
 | 
			
		||||
	b := &Bridge{}
 | 
			
		||||
	b.Config = cfg
 | 
			
		||||
	if cfg.IRC.Enable {
 | 
			
		||||
		b.Bridges = append(b.Bridges, birc.New(cfg, c))
 | 
			
		||||
	}
 | 
			
		||||
	if cfg.Mattermost.Enable {
 | 
			
		||||
		b.Bridges = append(b.Bridges, bmattermost.New(cfg, c))
 | 
			
		||||
	}
 | 
			
		||||
	if cfg.Xmpp.Enable {
 | 
			
		||||
		b.Bridges = append(b.Bridges, bxmpp.New(cfg, c))
 | 
			
		||||
	}
 | 
			
		||||
	if cfg.Gitter.Enable {
 | 
			
		||||
		b.Bridges = append(b.Bridges, bgitter.New(cfg, c))
 | 
			
		||||
	}
 | 
			
		||||
	if len(b.Bridges) < 2 {
 | 
			
		||||
		log.Fatalf("only %d sections enabled. Need at least 2 sections enabled (eg [IRC] and [mattermost]", len(b.Bridges))
 | 
			
		||||
	}
 | 
			
		||||
	for _, br := range b.Bridges {
 | 
			
		||||
		br.Connect()
 | 
			
		||||
	}
 | 
			
		||||
	b.mapChannels()
 | 
			
		||||
	b.mapIgnores()
 | 
			
		||||
	b.handleReceive(c)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) handleReceive(c chan config.Message) {
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case msg := <-c:
 | 
			
		||||
			for _, br := range b.Bridges {
 | 
			
		||||
				b.handleMessage(msg, br)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) mapChannels() error {
 | 
			
		||||
	for _, val := range b.Config.Channel {
 | 
			
		||||
		m := make(map[string]string)
 | 
			
		||||
		m["irc"] = val.IRC
 | 
			
		||||
		m["mattermost"] = val.Mattermost
 | 
			
		||||
		m["xmpp"] = val.Xmpp
 | 
			
		||||
		m["gitter"] = val.Gitter
 | 
			
		||||
		b.Channels = append(b.Channels, m)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) mapIgnores() {
 | 
			
		||||
	m := make(map[string][]string)
 | 
			
		||||
	m["irc"] = strings.Fields(b.Config.IRC.IgnoreNicks)
 | 
			
		||||
	m["mattermost"] = strings.Fields(b.Config.Mattermost.IgnoreNicks)
 | 
			
		||||
	m["xmpp"] = strings.Fields(b.Config.Xmpp.IgnoreNicks)
 | 
			
		||||
	m["gitter"] = strings.Fields(b.Config.Gitter.IgnoreNicks)
 | 
			
		||||
	b.ignoreNicks = m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) getDestChannel(msg *config.Message, dest string) string {
 | 
			
		||||
	for _, v := range b.Channels {
 | 
			
		||||
		if v[msg.Origin] == msg.Channel {
 | 
			
		||||
			return v[dest]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) handleMessage(msg config.Message, dest Bridger) {
 | 
			
		||||
	if b.ignoreMessage(&msg) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if dest.Name() != msg.Origin {
 | 
			
		||||
		msg.Channel = b.getDestChannel(&msg, dest.Name())
 | 
			
		||||
		if msg.Channel == "" {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		b.modifyMessage(&msg, dest.Name())
 | 
			
		||||
		dest.Send(msg)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) ignoreMessage(msg *config.Message) bool {
 | 
			
		||||
	// should we discard messages ?
 | 
			
		||||
	for _, entry := range b.ignoreNicks[msg.Origin] {
 | 
			
		||||
		if msg.Username == entry {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setNickFormat(msg *config.Message, format string) {
 | 
			
		||||
	if format == "" {
 | 
			
		||||
		msg.Username = msg.Origin + "-" + msg.Username + ": "
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	msg.Username = strings.Replace(format, "{NICK}", msg.Username, -1)
 | 
			
		||||
	msg.Username = strings.Replace(msg.Username, "{BRIDGE}", msg.Origin, -1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bridge) modifyMessage(msg *config.Message, dest string) {
 | 
			
		||||
	switch dest {
 | 
			
		||||
	case "irc":
 | 
			
		||||
		setNickFormat(msg, b.Config.IRC.RemoteNickFormat)
 | 
			
		||||
	case "gitter":
 | 
			
		||||
		setNickFormat(msg, b.Config.Gitter.RemoteNickFormat)
 | 
			
		||||
	case "xmpp":
 | 
			
		||||
		setNickFormat(msg, b.Config.Xmpp.RemoteNickFormat)
 | 
			
		||||
	case "mattermost":
 | 
			
		||||
		setNickFormat(msg, b.Config.Mattermost.RemoteNickFormat)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								bridge/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								bridge/config/config.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"gopkg.in/gcfg.v1"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Message struct {
 | 
			
		||||
	Text     string
 | 
			
		||||
	Channel  string
 | 
			
		||||
	Username string
 | 
			
		||||
	Origin   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	IRC struct {
 | 
			
		||||
		UseTLS           bool
 | 
			
		||||
		UseSASL          bool
 | 
			
		||||
		SkipTLSVerify    bool
 | 
			
		||||
		Server           string
 | 
			
		||||
		Nick             string
 | 
			
		||||
		Password         string
 | 
			
		||||
		Channel          string
 | 
			
		||||
		NickServNick     string
 | 
			
		||||
		NickServPassword string
 | 
			
		||||
		RemoteNickFormat string
 | 
			
		||||
		IgnoreNicks      string
 | 
			
		||||
		Enable           bool
 | 
			
		||||
	}
 | 
			
		||||
	Gitter struct {
 | 
			
		||||
		Enable           bool
 | 
			
		||||
		IgnoreNicks      string
 | 
			
		||||
		Nick             string
 | 
			
		||||
		RemoteNickFormat string
 | 
			
		||||
		Token            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
 | 
			
		||||
		Enable                 bool
 | 
			
		||||
	}
 | 
			
		||||
	Xmpp struct {
 | 
			
		||||
		IgnoreNicks      string
 | 
			
		||||
		Jid              string
 | 
			
		||||
		Password         string
 | 
			
		||||
		Server           string
 | 
			
		||||
		Muc              string
 | 
			
		||||
		Nick             string
 | 
			
		||||
		RemoteNickFormat string
 | 
			
		||||
		Enable           bool
 | 
			
		||||
	}
 | 
			
		||||
	Channel map[string]*struct {
 | 
			
		||||
		IRC        string
 | 
			
		||||
		Mattermost string
 | 
			
		||||
		Xmpp       string
 | 
			
		||||
		Gitter     string
 | 
			
		||||
	}
 | 
			
		||||
	General struct {
 | 
			
		||||
		GiphyAPIKey string
 | 
			
		||||
		Xmpp        bool
 | 
			
		||||
		Irc         bool
 | 
			
		||||
		Mattermost  bool
 | 
			
		||||
		Plus        bool
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										110
									
								
								bridge/gitter/gitter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								bridge/gitter/gitter.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
package bgitter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/sromku/go-gitter"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Bgitter struct {
 | 
			
		||||
	c *gitter.Gitter
 | 
			
		||||
	*config.Config
 | 
			
		||||
	Remote chan config.Message
 | 
			
		||||
	Rooms  []gitter.Room
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Message struct {
 | 
			
		||||
	Text     string
 | 
			
		||||
	Channel  string
 | 
			
		||||
	Username string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var flog *log.Entry
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	flog = log.WithFields(log.Fields{"module": "gitter"})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(config *config.Config, c chan config.Message) *Bgitter {
 | 
			
		||||
	b := &Bgitter{}
 | 
			
		||||
	b.Config = config
 | 
			
		||||
	b.Remote = c
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bgitter) Connect() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	flog.Info("Trying Gitter connection")
 | 
			
		||||
	b.c = gitter.New(b.Config.Gitter.Token)
 | 
			
		||||
	_, err = b.c.GetUser()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		flog.Debugf("%#v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	flog.Info("Connection succeeded")
 | 
			
		||||
	b.setupChannels()
 | 
			
		||||
	go b.handleGitter()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bgitter) Name() string {
 | 
			
		||||
	return "gitter"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bgitter) Send(msg config.Message) error {
 | 
			
		||||
	roomID := b.getRoomID(msg.Channel)
 | 
			
		||||
	if roomID == "" {
 | 
			
		||||
		flog.Errorf("Could not find roomID for %v", msg.Channel)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	// add ZWSP because gitter echoes our own messages
 | 
			
		||||
	return b.c.SendMessage(roomID, msg.Username+msg.Text+" ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bgitter) getRoomID(channel string) string {
 | 
			
		||||
	for _, v := range b.Rooms {
 | 
			
		||||
		if v.URI == channel {
 | 
			
		||||
			return v.ID
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bgitter) handleGitter() {
 | 
			
		||||
	for _, val := range b.Config.Channel {
 | 
			
		||||
		room := val.Gitter
 | 
			
		||||
		roomID := b.getRoomID(room)
 | 
			
		||||
		if roomID == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		stream := b.c.Stream(roomID)
 | 
			
		||||
		go b.c.Listen(stream)
 | 
			
		||||
 | 
			
		||||
		go func(stream *gitter.Stream, room string) {
 | 
			
		||||
			for {
 | 
			
		||||
				event := <-stream.Event
 | 
			
		||||
				switch ev := event.Data.(type) {
 | 
			
		||||
				case *gitter.MessageReceived:
 | 
			
		||||
					// check for ZWSP to see if it's not an echo
 | 
			
		||||
					if !strings.HasSuffix(ev.Message.Text, "") {
 | 
			
		||||
						b.Remote <- config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room, Origin: "gitter"}
 | 
			
		||||
					}
 | 
			
		||||
				case *gitter.GitterConnectionClosed:
 | 
			
		||||
					flog.Errorf("connection with gitter closed for room %s", room)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}(stream, room)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bgitter) setupChannels() {
 | 
			
		||||
	b.Rooms, _ = b.c.GetRooms()
 | 
			
		||||
	for _, val := range b.Config.Channel {
 | 
			
		||||
		flog.Infof("Joining %s as %s", val.Gitter, b.Gitter.Nick)
 | 
			
		||||
		_, err := b.c.JoinRoom(val.Gitter)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Errorf("Joining %s failed", val.Gitter)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								bridge/irc/helper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								bridge/irc/helper.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
package birc
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										194
									
								
								bridge/irc/irc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								bridge/irc/irc.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,194 @@
 | 
			
		||||
package birc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	ircm "github.com/sorcix/irc"
 | 
			
		||||
	"github.com/thoj/go-ircevent"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//type Bridge struct {
 | 
			
		||||
type Birc struct {
 | 
			
		||||
	i              *irc.Connection
 | 
			
		||||
	ircNick        string
 | 
			
		||||
	ircMap         map[string]string
 | 
			
		||||
	names          map[string][]string
 | 
			
		||||
	ircIgnoreNicks []string
 | 
			
		||||
	*config.Config
 | 
			
		||||
	Remote chan config.Message
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FancyLog struct {
 | 
			
		||||
	irc *log.Entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var flog FancyLog
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	flog.irc = log.WithFields(log.Fields{"module": "irc"})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(config *config.Config, c chan config.Message) *Birc {
 | 
			
		||||
	b := &Birc{}
 | 
			
		||||
	b.Config = config
 | 
			
		||||
	b.Remote = c
 | 
			
		||||
	b.ircNick = b.Config.IRC.Nick
 | 
			
		||||
	b.ircMap = make(map[string]string)
 | 
			
		||||
	b.names = make(map[string][]string)
 | 
			
		||||
	b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks)
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) Command(msg *config.Message) string {
 | 
			
		||||
	switch msg.Text {
 | 
			
		||||
	case "!users":
 | 
			
		||||
		b.i.SendRaw("NAMES " + msg.Channel)
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) Connect() error {
 | 
			
		||||
	flog.irc.Info("Trying IRC connection")
 | 
			
		||||
	i := irc.IRC(b.Config.IRC.Nick, b.Config.IRC.Nick)
 | 
			
		||||
	i.UseTLS = b.Config.IRC.UseTLS
 | 
			
		||||
	i.UseSASL = b.Config.IRC.UseSASL
 | 
			
		||||
	i.SASLLogin = b.Config.IRC.NickServNick
 | 
			
		||||
	i.SASLPassword = b.Config.IRC.NickServPassword
 | 
			
		||||
	i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.IRC.SkipTLSVerify}
 | 
			
		||||
	if b.Config.IRC.Password != "" {
 | 
			
		||||
		i.Password = b.Config.IRC.Password
 | 
			
		||||
	}
 | 
			
		||||
	i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection)
 | 
			
		||||
	err := i.Connect(b.Config.IRC.Server)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	flog.irc.Info("Connection succeeded")
 | 
			
		||||
	b.i = i
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) Name() string {
 | 
			
		||||
	return "irc"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) Send(msg config.Message) error {
 | 
			
		||||
	if msg.Origin == "irc" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if strings.HasPrefix(msg.Text, "!") {
 | 
			
		||||
		b.Command(&msg)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	b.i.Privmsg(msg.Channel, msg.Username+msg.Text)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) endNames(event *irc.Event) {
 | 
			
		||||
	channel := event.Arguments[1]
 | 
			
		||||
	sort.Strings(b.names[channel])
 | 
			
		||||
	maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow()
 | 
			
		||||
	continued := false
 | 
			
		||||
	for len(b.names[channel]) > maxNamesPerPost {
 | 
			
		||||
		b.Remote <- config.Message{Username: b.ircNick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost], continued), Channel: channel, Origin: "irc"}
 | 
			
		||||
		b.names[channel] = b.names[channel][maxNamesPerPost:]
 | 
			
		||||
		continued = true
 | 
			
		||||
	}
 | 
			
		||||
	b.Remote <- config.Message{Username: b.ircNick, Text: b.formatnicks(b.names[channel], continued), Channel: channel, Origin: "irc"}
 | 
			
		||||
	b.names[channel] = nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) handleNewConnection(event *irc.Event) {
 | 
			
		||||
	flog.irc.Info("Registering callbacks")
 | 
			
		||||
	i := b.i
 | 
			
		||||
	b.ircNick = event.Arguments[0]
 | 
			
		||||
	i.AddCallback("PRIVMSG", b.handlePrivMsg)
 | 
			
		||||
	i.AddCallback("CTCP_ACTION", b.handlePrivMsg)
 | 
			
		||||
	i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime)
 | 
			
		||||
	i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames)
 | 
			
		||||
	i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames)
 | 
			
		||||
	i.AddCallback(ircm.NOTICE, b.handleNotice)
 | 
			
		||||
	i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.irc.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) })
 | 
			
		||||
	i.AddCallback("PING", func(e *irc.Event) {
 | 
			
		||||
		i.SendRaw("PONG :" + e.Message())
 | 
			
		||||
		flog.irc.Debugf("PING/PONG")
 | 
			
		||||
	})
 | 
			
		||||
	if b.Config.Mattermost.ShowJoinPart {
 | 
			
		||||
		i.AddCallback("JOIN", b.handleJoinPart)
 | 
			
		||||
		i.AddCallback("PART", b.handleJoinPart)
 | 
			
		||||
	}
 | 
			
		||||
	i.AddCallback("*", b.handleOther)
 | 
			
		||||
	b.setupChannels()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) handleJoinPart(event *irc.Event) {
 | 
			
		||||
	//b.Send(b.ircNick, b.ircNickFormat(event.Nick)+" "+strings.ToLower(event.Code)+"s "+event.Message(), b.getMMChannel(event.Arguments[0]))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) handleNotice(event *irc.Event) {
 | 
			
		||||
	if strings.Contains(event.Message(), "This nickname is registered") {
 | 
			
		||||
		b.i.Privmsg(b.Config.IRC.NickServNick, "IDENTIFY "+b.Config.IRC.NickServPassword)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) handleOther(event *irc.Event) {
 | 
			
		||||
	flog.irc.Debugf("%#v", event)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) handlePrivMsg(event *irc.Event) {
 | 
			
		||||
	flog.irc.Debugf("handlePrivMsg() %s %s", event.Nick, event.Message())
 | 
			
		||||
	msg := ""
 | 
			
		||||
	if event.Code == "CTCP_ACTION" {
 | 
			
		||||
		msg = event.Nick + " "
 | 
			
		||||
	}
 | 
			
		||||
	msg += event.Message()
 | 
			
		||||
	b.Remote <- config.Message{Username: event.Nick, Text: msg, Channel: event.Arguments[0], Origin: "irc"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) handleTopicWhoTime(event *irc.Event) {
 | 
			
		||||
	parts := strings.Split(event.Arguments[2], "!")
 | 
			
		||||
	t, err := strconv.ParseInt(event.Arguments[3], 10, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		flog.irc.Errorf("Invalid time stamp: %s", event.Arguments[3])
 | 
			
		||||
	}
 | 
			
		||||
	user := parts[0]
 | 
			
		||||
	if len(parts) > 1 {
 | 
			
		||||
		user += " [" + parts[1] + "]"
 | 
			
		||||
	}
 | 
			
		||||
	flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t, 0))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) nicksPerRow() int {
 | 
			
		||||
	if b.Config.Mattermost.NicksPerRow < 1 {
 | 
			
		||||
		return 4
 | 
			
		||||
	}
 | 
			
		||||
	return b.Config.Mattermost.NicksPerRow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) setupChannels() {
 | 
			
		||||
	for _, val := range b.Config.Channel {
 | 
			
		||||
		flog.irc.Infof("Joining %s as %s", val.IRC, b.ircNick)
 | 
			
		||||
		b.i.Join(val.IRC)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) storeNames(event *irc.Event) {
 | 
			
		||||
	channel := event.Arguments[2]
 | 
			
		||||
	b.names[channel] = append(
 | 
			
		||||
		b.names[channel],
 | 
			
		||||
		strings.Split(strings.TrimSpace(event.Message()), " ")...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Birc) formatnicks(nicks []string, continued bool) string {
 | 
			
		||||
	switch b.Config.Mattermost.NickFormatter {
 | 
			
		||||
	case "table":
 | 
			
		||||
		return tableformatter(nicks, b.nicksPerRow(), continued)
 | 
			
		||||
	default:
 | 
			
		||||
		return plainformatter(nicks, b.nicksPerRow())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										174
									
								
								bridge/mattermost/mattermost.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								bridge/mattermost/mattermost.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,174 @@
 | 
			
		||||
package bmattermost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	"github.com/42wim/matterbridge/matterclient"
 | 
			
		||||
	"github.com/42wim/matterbridge/matterhook"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//type Bridge struct {
 | 
			
		||||
type MMhook struct {
 | 
			
		||||
	mh *matterhook.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MMapi struct {
 | 
			
		||||
	mc            *matterclient.MMClient
 | 
			
		||||
	mmMap         map[string]string
 | 
			
		||||
	mmIgnoreNicks []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MMMessage struct {
 | 
			
		||||
	Text     string
 | 
			
		||||
	Channel  string
 | 
			
		||||
	Username string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Bmattermost struct {
 | 
			
		||||
	MMhook
 | 
			
		||||
	MMapi
 | 
			
		||||
	*config.Config
 | 
			
		||||
	Plus   bool
 | 
			
		||||
	Remote chan config.Message
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FancyLog struct {
 | 
			
		||||
	irc  *log.Entry
 | 
			
		||||
	mm   *log.Entry
 | 
			
		||||
	xmpp *log.Entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var flog FancyLog
 | 
			
		||||
 | 
			
		||||
const Legacy = "legacy"
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	flog.irc = log.WithFields(log.Fields{"module": "irc"})
 | 
			
		||||
	flog.mm = log.WithFields(log.Fields{"module": "mattermost"})
 | 
			
		||||
	flog.xmpp = log.WithFields(log.Fields{"module": "xmpp"})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(cfg *config.Config, c chan config.Message) *Bmattermost {
 | 
			
		||||
	b := &Bmattermost{}
 | 
			
		||||
	b.Config = cfg
 | 
			
		||||
	b.Remote = c
 | 
			
		||||
	b.Plus = cfg.General.Plus
 | 
			
		||||
	b.mmMap = make(map[string]string)
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmattermost) Command(cmd string) string {
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmattermost) Connect() error {
 | 
			
		||||
	if !b.Plus {
 | 
			
		||||
		b.mh = matterhook.New(b.Config.Mattermost.URL,
 | 
			
		||||
			matterhook.Config{InsecureSkipVerify: b.Config.Mattermost.SkipTLSVerify,
 | 
			
		||||
				BindAddress: b.Config.Mattermost.BindAddress})
 | 
			
		||||
	} else {
 | 
			
		||||
		b.mc = matterclient.New(b.Config.Mattermost.Login, b.Config.Mattermost.Password,
 | 
			
		||||
			b.Config.Mattermost.Team, b.Config.Mattermost.Server)
 | 
			
		||||
		b.mc.SkipTLSVerify = b.Config.Mattermost.SkipTLSVerify
 | 
			
		||||
		b.mc.NoTLS = b.Config.Mattermost.NoTLS
 | 
			
		||||
		flog.mm.Infof("Trying login %s (team: %s) on %s", b.Config.Mattermost.Login, b.Config.Mattermost.Team, b.Config.Mattermost.Server)
 | 
			
		||||
		err := b.mc.Login()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		flog.mm.Info("Login ok")
 | 
			
		||||
		b.mc.JoinChannel(b.Config.Mattermost.Channel)
 | 
			
		||||
		for _, val := range b.Config.Channel {
 | 
			
		||||
			b.mc.JoinChannel(val.Mattermost)
 | 
			
		||||
		}
 | 
			
		||||
		go b.mc.WsReceiver()
 | 
			
		||||
	}
 | 
			
		||||
	go b.handleMatter()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmattermost) Name() string {
 | 
			
		||||
	return "mattermost"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmattermost) Send(msg config.Message) error {
 | 
			
		||||
	flog.mm.Infof("mattermost send %#v", msg)
 | 
			
		||||
	if msg.Origin != "mattermost" {
 | 
			
		||||
		return b.SendType(msg.Username, msg.Text, msg.Channel, "")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmattermost) SendType(nick string, message string, channel string, mtype string) error {
 | 
			
		||||
	if b.Config.Mattermost.PrefixMessagesWithNick {
 | 
			
		||||
		/*if IsMarkup(message) {
 | 
			
		||||
			message = nick + "\n\n" + message
 | 
			
		||||
		} else {
 | 
			
		||||
		*/
 | 
			
		||||
		message = nick + " " + message
 | 
			
		||||
		//}
 | 
			
		||||
	}
 | 
			
		||||
	if !b.Plus {
 | 
			
		||||
		matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL}
 | 
			
		||||
		matterMessage.Channel = channel
 | 
			
		||||
		matterMessage.UserName = nick
 | 
			
		||||
		matterMessage.Type = mtype
 | 
			
		||||
		matterMessage.Text = message
 | 
			
		||||
		err := b.mh.Send(matterMessage)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			flog.mm.Info(err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		flog.mm.Debug("->mattermost channel: ", channel, " ", message)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	flog.mm.Debug("->mattermost channel plus: ", channel, " ", message)
 | 
			
		||||
	b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmattermost) handleMatter() {
 | 
			
		||||
	flog.mm.Infof("Choosing API based Mattermost connection: %t", b.Plus)
 | 
			
		||||
	mchan := make(chan *MMMessage)
 | 
			
		||||
	if b.Plus {
 | 
			
		||||
		go b.handleMatterClient(mchan)
 | 
			
		||||
	} else {
 | 
			
		||||
		go b.handleMatterHook(mchan)
 | 
			
		||||
	}
 | 
			
		||||
	flog.mm.Info("Start listening for Mattermost messages")
 | 
			
		||||
	for message := range mchan {
 | 
			
		||||
		texts := strings.Split(message.Text, "\n")
 | 
			
		||||
		for _, text := range texts {
 | 
			
		||||
			flog.mm.Debug("Sending message from " + message.Username + " to " + message.Channel)
 | 
			
		||||
			b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: "mattermost"}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
 | 
			
		||||
	for message := range b.mc.MessageChan {
 | 
			
		||||
		// do not post our own messages back to irc
 | 
			
		||||
		if message.Raw.Event == "posted" && b.mc.User.Username != message.Username {
 | 
			
		||||
			flog.mm.Debugf("receiving from matterclient %#v", message)
 | 
			
		||||
			flog.mm.Debugf("receiving from matterclient %#v", message.Raw)
 | 
			
		||||
			m := &MMMessage{}
 | 
			
		||||
			m.Username = message.Username
 | 
			
		||||
			m.Channel = message.Channel
 | 
			
		||||
			m.Text = message.Text
 | 
			
		||||
			mchan <- m
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) {
 | 
			
		||||
	for {
 | 
			
		||||
		message := b.mh.Receive()
 | 
			
		||||
		flog.mm.Debugf("receiving from matterhook %#v", message)
 | 
			
		||||
		m := &MMMessage{}
 | 
			
		||||
		m.Username = message.UserName
 | 
			
		||||
		m.Text = message.Text
 | 
			
		||||
		m.Channel = message.ChannelName
 | 
			
		||||
		mchan <- m
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										133
									
								
								bridge/xmpp/xmpp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								bridge/xmpp/xmpp.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
package bxmpp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/mattn/go-xmpp"
 | 
			
		||||
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Bxmpp struct {
 | 
			
		||||
	xc      *xmpp.Client
 | 
			
		||||
	xmppMap map[string]string
 | 
			
		||||
	*config.Config
 | 
			
		||||
	Remote chan config.Message
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FancyLog struct {
 | 
			
		||||
	xmpp *log.Entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Message struct {
 | 
			
		||||
	Text     string
 | 
			
		||||
	Channel  string
 | 
			
		||||
	Username string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var flog FancyLog
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	flog.xmpp = log.WithFields(log.Fields{"module": "xmpp"})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(config *config.Config, c chan config.Message) *Bxmpp {
 | 
			
		||||
	b := &Bxmpp{}
 | 
			
		||||
	b.xmppMap = make(map[string]string)
 | 
			
		||||
	b.Config = config
 | 
			
		||||
	b.Remote = c
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) Connect() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	flog.xmpp.Info("Trying XMPP connection")
 | 
			
		||||
	b.xc, err = b.createXMPP()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		flog.xmpp.Debugf("%#v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	flog.xmpp.Info("Connection succeeded")
 | 
			
		||||
	b.setupChannels()
 | 
			
		||||
	go b.handleXmpp()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) Name() string {
 | 
			
		||||
	return "xmpp"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) Send(msg config.Message) error {
 | 
			
		||||
	b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Xmpp.Muc, Text: msg.Username + msg.Text})
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
 | 
			
		||||
	options := xmpp.Options{
 | 
			
		||||
		Host:     b.Config.Xmpp.Server,
 | 
			
		||||
		User:     b.Config.Xmpp.Jid,
 | 
			
		||||
		Password: b.Config.Xmpp.Password,
 | 
			
		||||
		NoTLS:    true,
 | 
			
		||||
		StartTLS: true,
 | 
			
		||||
		//StartTLS:      false,
 | 
			
		||||
		Debug:                        true,
 | 
			
		||||
		Session:                      true,
 | 
			
		||||
		Status:                       "",
 | 
			
		||||
		StatusMessage:                "",
 | 
			
		||||
		Resource:                     "",
 | 
			
		||||
		InsecureAllowUnencryptedAuth: false,
 | 
			
		||||
		//InsecureAllowUnencryptedAuth: true,
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	b.xc, err = options.NewClient()
 | 
			
		||||
	return b.xc, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) setupChannels() {
 | 
			
		||||
	for _, val := range b.Config.Channel {
 | 
			
		||||
		flog.xmpp.Infof("Joining %s as %s", val.Xmpp, b.Xmpp.Nick)
 | 
			
		||||
		b.xc.JoinMUCNoHistory(val.Xmpp+"@"+b.Xmpp.Muc, b.Xmpp.Nick)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) xmppKeepAlive() {
 | 
			
		||||
	go func() {
 | 
			
		||||
		ticker := time.NewTicker(90 * time.Second)
 | 
			
		||||
		for {
 | 
			
		||||
			select {
 | 
			
		||||
			case <-ticker.C:
 | 
			
		||||
				b.xc.Send(xmpp.Chat{})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *Bxmpp) handleXmpp() error {
 | 
			
		||||
	for {
 | 
			
		||||
		m, err := b.xc.Recv()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		switch v := m.(type) {
 | 
			
		||||
		case xmpp.Chat:
 | 
			
		||||
			var channel, nick string
 | 
			
		||||
			if v.Type == "groupchat" {
 | 
			
		||||
				s := strings.Split(v.Remote, "@")
 | 
			
		||||
				if len(s) == 2 {
 | 
			
		||||
					channel = s[0]
 | 
			
		||||
				}
 | 
			
		||||
				s = strings.Split(s[1], "/")
 | 
			
		||||
				if len(s) == 2 {
 | 
			
		||||
					nick = s[1]
 | 
			
		||||
				}
 | 
			
		||||
				if nick != b.Xmpp.Nick {
 | 
			
		||||
					flog.xmpp.Infof("sending message to remote %s %s %s", nick, v.Text, channel)
 | 
			
		||||
					b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Origin: "xmpp"}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case xmpp.Presence:
 | 
			
		||||
			// do nothing
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										79
									
								
								changelog.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								changelog.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
# v0.6.0-beta2
 | 
			
		||||
## New features
 | 
			
		||||
* Gitter support added. See matterbridge.conf.sample for more information
 | 
			
		||||
 | 
			
		||||
# v0.6.0-beta1
 | 
			
		||||
## Breaking changes from 0.5 to 0.6
 | 
			
		||||
### commandline
 | 
			
		||||
* -plus switch deprecated. Use ```Plus=true``` or ```Plus``` in ```[general]``` section
 | 
			
		||||
 | 
			
		||||
### IRC section
 | 
			
		||||
* ```Enabled``` added (default false)  
 | 
			
		||||
Add ```Enabled=true``` or ```Enabled``` to the ```[IRC]``` section if you want to enable the IRC bridge
 | 
			
		||||
 | 
			
		||||
### Mattermost section
 | 
			
		||||
* ```Enabled``` added (default false)  
 | 
			
		||||
Add ```Enabled=true``` or ```Enabled``` to the ```[mattermost]``` section if you want to enable the mattermost bridge
 | 
			
		||||
 | 
			
		||||
### General section
 | 
			
		||||
* Use ```Plus=true``` or ```Plus``` in ```[general]``` section to enable the API version of matterbridge
 | 
			
		||||
 | 
			
		||||
## New features
 | 
			
		||||
* Matterbridge now bridges between any specified protocol (not only mattermost anymore) 
 | 
			
		||||
* XMPP support added.  See matterbridge.conf.sample for more information
 | 
			
		||||
* RemoteNickFormat {BRIDGE} variable added  
 | 
			
		||||
You can now add the originating bridge to ```RemoteNickFormat```  
 | 
			
		||||
eg ```RemoteNickFormat="[{BRIDGE}] <{NICK}> "```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# v0.5.0
 | 
			
		||||
## Breaking changes from 0.4 to 0.5 for matterbridge (webhooks version)
 | 
			
		||||
### IRC section
 | 
			
		||||
#### Server
 | 
			
		||||
Port removed, added to server
 | 
			
		||||
```
 | 
			
		||||
server="irc.freenode.net"
 | 
			
		||||
port=6667
 | 
			
		||||
```
 | 
			
		||||
changed to
 | 
			
		||||
```
 | 
			
		||||
server="irc.freenode.net:6667"
 | 
			
		||||
```
 | 
			
		||||
#### Channel
 | 
			
		||||
Removed see Channels section below
 | 
			
		||||
 | 
			
		||||
#### UseSlackCircumfix=true
 | 
			
		||||
Removed, can be done by using ```RemoteNickFormat="<{NICK}> "```
 | 
			
		||||
 | 
			
		||||
### Mattermost section
 | 
			
		||||
#### BindAddress
 | 
			
		||||
Port removed, added to BindAddress
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
BindAddress="0.0.0.0"
 | 
			
		||||
port=9999
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
changed to
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
BindAddress="0.0.0.0:9999"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Token
 | 
			
		||||
Removed
 | 
			
		||||
 | 
			
		||||
### Channels section
 | 
			
		||||
```
 | 
			
		||||
[Token "outgoingwebhooktoken1"] 
 | 
			
		||||
IRCChannel="#off-topic"
 | 
			
		||||
MMChannel="off-topic"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
changed to
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
[Channel "channelnameofchoice"] 
 | 
			
		||||
IRC="#off-topic"
 | 
			
		||||
Mattermost="off-topic"
 | 
			
		||||
```
 | 
			
		||||
@@ -1,37 +1,220 @@
 | 
			
		||||
#This is configuration for matterbridge.
 | 
			
		||||
###################################################################
 | 
			
		||||
#IRC section
 | 
			
		||||
###################################################################
 | 
			
		||||
[IRC]
 | 
			
		||||
server="irc.freenode.net"
 | 
			
		||||
port=6667
 | 
			
		||||
#Enable enables this bridge
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
Enable=true
 | 
			
		||||
#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 SASL (PLAIN) authentication. (freenode requires this from eg AWS hosts)
 | 
			
		||||
#It uses NickServNick and NickServPassword as login and password
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
UseSASL=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. 
 | 
			
		||||
#Also being used when UseSASL=true
 | 
			
		||||
#OPTIONAL
 | 
			
		||||
NickServNick="nickserv"
 | 
			
		||||
NickServPassword="secret"
 | 
			
		||||
 | 
			
		||||
#RemoteNickFormat defines how remote users appear on this bridge 
 | 
			
		||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
 | 
			
		||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
 | 
			
		||||
#OPTIONAL (default {BRIDGE}-{NICK})
 | 
			
		||||
RemoteNickFormat="[{BRIDGE}] <{NICK}> 
 | 
			
		||||
 | 
			
		||||
#Nicks you want to ignore. 
 | 
			
		||||
#Messages from those users will not be sent to other bridges.
 | 
			
		||||
#OPTIONAL
 | 
			
		||||
IgnoreNicks="ircspammer1 ircspammer2"
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#XMPP section
 | 
			
		||||
###################################################################
 | 
			
		||||
[XMPP]
 | 
			
		||||
#Enable enables this bridge
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
Enable=true
 | 
			
		||||
 | 
			
		||||
#xmpp server to connect to. 
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Server="jabber.example.com:5222"
 | 
			
		||||
 | 
			
		||||
#Jid
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Jid="user@example.com"
 | 
			
		||||
 | 
			
		||||
#Password
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Password="yourpass"
 | 
			
		||||
 | 
			
		||||
#MUC
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Muc="conference.jabber.example.com"
 | 
			
		||||
 | 
			
		||||
#Your nick in the rooms
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Nick="xmppbot"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#mattermost section
 | 
			
		||||
###################################################################
 | 
			
		||||
 | 
			
		||||
[mattermost]
 | 
			
		||||
url="http://yourdomain/hooks/yourhookkey"
 | 
			
		||||
port=9999
 | 
			
		||||
showjoinpart=true
 | 
			
		||||
#remove token when using multiple channels!
 | 
			
		||||
token=yourtokenfrommattermost
 | 
			
		||||
#Enable enables this bridge
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
Enable=true
 | 
			
		||||
 | 
			
		||||
#### Settings for webhook matterbridge.
 | 
			
		||||
#### These settings will not be used when using -plus switch which doesn't use 
 | 
			
		||||
#### webhooks.
 | 
			
		||||
 | 
			
		||||
#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"
 | 
			
		||||
 | 
			
		||||
#Enable this to make a http connection (instead of https) 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 other bridges to mattermost with the sender's nick. 
 | 
			
		||||
#Useful if username overrides for incoming webhooks isn't enabled on the 
 | 
			
		||||
#mattermost server. If you set PrefixMessagesWithNick to true, each message 
 | 
			
		||||
#from bridge to Mattermost will by default be prefixed by "bridge-" + nick. You can, 
 | 
			
		||||
#however, modify how the messages appear, by setting (and modifying) RemoteNickFormat 
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
PrefixMessagesWithNick=false
 | 
			
		||||
 | 
			
		||||
#RemoteNickFormat defines how remote users appear on this bridge 
 | 
			
		||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
 | 
			
		||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
 | 
			
		||||
#OPTIONAL (default {BRIDGE}-{NICK})
 | 
			
		||||
RemoteNickFormat="[{BRIDGE}] <{NICK}> 
 | 
			
		||||
 | 
			
		||||
#how to format the list of IRC nicks when displayed in 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 bridged.
 | 
			
		||||
#OPTIONAL 
 | 
			
		||||
IgnoreNicks="mmbot spammer2"
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#Gitter section
 | 
			
		||||
#Best to make a dedicated gitter account for the bot.
 | 
			
		||||
###################################################################
 | 
			
		||||
[Gitter]
 | 
			
		||||
#Enable enables this bridge
 | 
			
		||||
#OPTIONAL (default false)
 | 
			
		||||
Enable=true
 | 
			
		||||
 | 
			
		||||
#Token to connect with Gitter API
 | 
			
		||||
#You can get your token by going to https://developer.gitter.im/docs/welcome and SIGN IN
 | 
			
		||||
#REQUIRED
 | 
			
		||||
Token="Yourtokenhere"
 | 
			
		||||
 | 
			
		||||
#Nicks you want to ignore. Messages of those users will not be bridged.
 | 
			
		||||
#OPTIONAL 
 | 
			
		||||
IgnoreNicks="spammer1 spammer2"
 | 
			
		||||
 | 
			
		||||
#RemoteNickFormat defines how remote users appear on this bridge 
 | 
			
		||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
 | 
			
		||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
 | 
			
		||||
#OPTIONAL (default {BRIDGE}-{NICK})
 | 
			
		||||
RemoteNickFormat="[{BRIDGE}] <{NICK}> 
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#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 messages to.
 | 
			
		||||
IRC="#off-topic"
 | 
			
		||||
#Choose the mattermost channel to messages to.
 | 
			
		||||
mattermost="off-topic"
 | 
			
		||||
#Choose the xmpp channel to send messages to.
 | 
			
		||||
xmpp="off-topic"
 | 
			
		||||
#Choose the Gitter channel to send messages to.
 | 
			
		||||
#Gitter channels are named "user/repo"
 | 
			
		||||
gitter="42wim/matterbridge"
 | 
			
		||||
 | 
			
		||||
[Token "outgoingwebhooktoken2"]
 | 
			
		||||
IRCChannel="#testing"
 | 
			
		||||
MMChannel="testing"
 | 
			
		||||
[Channel "testchannel"]
 | 
			
		||||
IRC="#testing"
 | 
			
		||||
mattermost="testing"
 | 
			
		||||
xmpp="testing"
 | 
			
		||||
gitter="user/repo"
 | 
			
		||||
 | 
			
		||||
###################################################################
 | 
			
		||||
#general
 | 
			
		||||
###################################################################
 | 
			
		||||
[general]
 | 
			
		||||
#request your API key on https://github.com/giphy/GiphyAPI. This is a public beta key. 
 | 
			
		||||
#OPTIONAL
 | 
			
		||||
GiphyApiKey="dc6zaTOxFJmzC"
 | 
			
		||||
 | 
			
		||||
#Enabling plus means you'll use the API version instead of the webhooks one
 | 
			
		||||
Plus=false
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,14 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"github.com/42wim/matterbridge-plus/bridge"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge"
 | 
			
		||||
	"github.com/42wim/matterbridge/bridge/config"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var version = "0.6.0-beta2"
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
 | 
			
		||||
}
 | 
			
		||||
@@ -13,11 +17,25 @@ 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 (deprecated, set Plus flag in [general] config)")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
	if *flagVersion {
 | 
			
		||||
		fmt.Println("version:", version)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
	if *flagDebug {
 | 
			
		||||
		log.Info("enabling debug")
 | 
			
		||||
		log.SetLevel(log.DebugLevel)
 | 
			
		||||
	}
 | 
			
		||||
	bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "legacy")
 | 
			
		||||
	select {}
 | 
			
		||||
	fmt.Println("running version", version)
 | 
			
		||||
	cfg := config.NewConfig(*flagConfig)
 | 
			
		||||
	if *flagPlus {
 | 
			
		||||
		cfg.General.Plus = true
 | 
			
		||||
	}
 | 
			
		||||
	err := bridge.NewBridge(cfg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Debugf("starting bridge failed %#v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										643
									
								
								matterclient/matterclient.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										643
									
								
								matterclient/matterclient.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,643 @@
 | 
			
		||||
package matterclient
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"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.WebSocketEvent
 | 
			
		||||
	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
 | 
			
		||||
	User        *model.User
 | 
			
		||||
	Users       map[string]*model.User
 | 
			
		||||
	MessageChan chan *Message
 | 
			
		||||
	log         *log.Entry
 | 
			
		||||
	WsClient    *websocket.Conn
 | 
			
		||||
	WsQuit      bool
 | 
			
		||||
	WsAway      bool
 | 
			
		||||
	WsConnected bool
 | 
			
		||||
	WsSequence  int64
 | 
			
		||||
	WsPingChan  chan *model.WebSocketResponse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(login, pass, team, server string) *MMClient {
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if m.Team == nil {
 | 
			
		||||
		return errors.New("team not found")
 | 
			
		||||
	}
 | 
			
		||||
	// set our team id as default route
 | 
			
		||||
	m.Client.SetTeamId(m.Team.Id)
 | 
			
		||||
 | 
			
		||||
	// setup websocket connection
 | 
			
		||||
	wsurl := wsScheme + m.Credentials.Server + model.API_URL_SUFFIX + "/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()
 | 
			
		||||
 | 
			
		||||
	m.WsSequence = 1
 | 
			
		||||
	m.WsPingChan = make(chan *model.WebSocketResponse)
 | 
			
		||||
	// 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() {
 | 
			
		||||
	for {
 | 
			
		||||
		var rawMsg json.RawMessage
 | 
			
		||||
		var err error
 | 
			
		||||
 | 
			
		||||
		if !m.WsConnected {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if m.WsQuit {
 | 
			
		||||
			m.log.Debug("exiting WsReceiver")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, rawMsg, err = m.WsClient.ReadMessage(); err != nil {
 | 
			
		||||
			m.log.Error("error:", err)
 | 
			
		||||
			// reconnect
 | 
			
		||||
			m.Login()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var event model.WebSocketEvent
 | 
			
		||||
		if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() {
 | 
			
		||||
			m.log.Debugf("WsReceiver: %#v", event)
 | 
			
		||||
			msg := &Message{Raw: &event, Team: m.Credentials.Team}
 | 
			
		||||
			m.parseMessage(msg)
 | 
			
		||||
			m.MessageChan <- msg
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var response model.WebSocketResponse
 | 
			
		||||
		if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() {
 | 
			
		||||
			m.log.Debugf("WsReceiver: %#v", response)
 | 
			
		||||
			m.parseResponse(response)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) parseMessage(rmsg *Message) {
 | 
			
		||||
	switch rmsg.Raw.Event {
 | 
			
		||||
	case model.WEBSOCKET_EVENT_POSTED:
 | 
			
		||||
		m.parseActionPost(rmsg)
 | 
			
		||||
		/*
 | 
			
		||||
			case model.ACTION_USER_REMOVED:
 | 
			
		||||
				m.handleWsActionUserRemoved(&rmsg)
 | 
			
		||||
			case model.ACTION_USER_ADDED:
 | 
			
		||||
				m.handleWsActionUserAdded(&rmsg)
 | 
			
		||||
		*/
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) parseResponse(rmsg model.WebSocketResponse) {
 | 
			
		||||
	if rmsg.Data != nil {
 | 
			
		||||
		// ping reply
 | 
			
		||||
		if rmsg.Data["text"].(string) == "pong" {
 | 
			
		||||
			m.WsPingChan <- &rmsg
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) parseActionPost(rmsg *Message) {
 | 
			
		||||
	data := model.PostFromJson(strings.NewReader(rmsg.Raw.Data["post"].(string)))
 | 
			
		||||
	// 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 rmsg.Raw.Data["channel_type"] == "D" {
 | 
			
		||||
		rmsg.Channel = m.GetUser(data.UserId).Username
 | 
			
		||||
	}
 | 
			
		||||
	rmsg.Text = data.Message
 | 
			
		||||
	rmsg.Post = data
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) UpdateUsers() error {
 | 
			
		||||
	mmusers, err := m.Client.GetProfilesForDirectMessageList(m.Team.Id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New(err.DetailedError)
 | 
			
		||||
	}
 | 
			
		||||
	m.Lock()
 | 
			
		||||
	m.Users = mmusers.Data.(map[string]*model.User)
 | 
			
		||||
	m.Unlock()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) UpdateChannels() error {
 | 
			
		||||
	mmchannels, err := m.Client.GetChannels("")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New(err.DetailedError)
 | 
			
		||||
	}
 | 
			
		||||
	mmchannels2, err := m.Client.GetMoreChannels("")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.New(err.DetailedError)
 | 
			
		||||
	}
 | 
			
		||||
	m.Lock()
 | 
			
		||||
	m.Team.Channels = mmchannels.Data.(*model.ChannelList)
 | 
			
		||||
	m.Team.MoreChannels = mmchannels2.Data.(*model.ChannelList)
 | 
			
		||||
	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...)
 | 
			
		||||
		channels = append(channels, t.MoreChannels.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]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) GetStatus(userId string) string {
 | 
			
		||||
	res, err := m.Client.GetStatuses()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	status := res.Data.(map[string]string)
 | 
			
		||||
	if status[userId] == model.STATUS_AWAY {
 | 
			
		||||
		return "away"
 | 
			
		||||
	}
 | 
			
		||||
	if status[userId] == model.STATUS_ONLINE {
 | 
			
		||||
		return "online"
 | 
			
		||||
	}
 | 
			
		||||
	return "offline"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) StatusLoop() {
 | 
			
		||||
	for {
 | 
			
		||||
		if m.WsQuit {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if m.WsConnected {
 | 
			
		||||
			m.log.Debug("WS PING")
 | 
			
		||||
			m.sendWSRequest("ping", nil)
 | 
			
		||||
			select {
 | 
			
		||||
			case <-m.WsPingChan:
 | 
			
		||||
				m.log.Debug("WS PONG received")
 | 
			
		||||
			case <-time.After(time.Second * 5):
 | 
			
		||||
				m.Logout()
 | 
			
		||||
				m.WsQuit = false
 | 
			
		||||
				m.Login()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(time.Second * 60)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// initialize user and teams
 | 
			
		||||
func (m *MMClient) initUser() error {
 | 
			
		||||
	m.Lock()
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MMClient) sendWSRequest(action string, data map[string]interface{}) error {
 | 
			
		||||
	req := &model.WebSocketRequest{}
 | 
			
		||||
	req.Seq = m.WsSequence
 | 
			
		||||
	req.Action = action
 | 
			
		||||
	req.Data = data
 | 
			
		||||
	m.WsSequence++
 | 
			
		||||
	m.log.Debugf("sendWsRequest %#v", req)
 | 
			
		||||
	m.WsClient.WriteJSON(req)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -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,15 +60,15 @@ 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
 | 
			
		||||
	}
 | 
			
		||||
	c.BindAddress += ":"
 | 
			
		||||
	tr := &http.Transport{
 | 
			
		||||
		TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify},
 | 
			
		||||
	}
 | 
			
		||||
	c.httpclient = &http.Client{Transport: tr}
 | 
			
		||||
	if !c.DisableServer {
 | 
			
		||||
		_, _, err := net.SplitHostPort(c.BindAddress)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("incorrect bindaddress %s", c.BindAddress)
 | 
			
		||||
		}
 | 
			
		||||
		go c.StartServer()
 | 
			
		||||
	}
 | 
			
		||||
	return c
 | 
			
		||||
@@ -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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								vendor/github.com/mattermost/platform/einterfaces/account_migration.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/mattermost/platform/einterfaces/account_migration.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package einterfaces
 | 
			
		||||
 | 
			
		||||
import "github.com/mattermost/platform/model"
 | 
			
		||||
 | 
			
		||||
type AccountMigrationInterface interface {
 | 
			
		||||
	MigrateToLdap(fromAuthService string, forignUserFieldNameToMatch string) *model.AppError
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var theAccountMigrationInterface AccountMigrationInterface
 | 
			
		||||
 | 
			
		||||
func RegisterAccountMigrationInterface(newInterface AccountMigrationInterface) {
 | 
			
		||||
	theAccountMigrationInterface = newInterface
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetAccountMigrationInterface() AccountMigrationInterface {
 | 
			
		||||
	return theAccountMigrationInterface
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								vendor/github.com/mattermost/platform/einterfaces/cluster.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								vendor/github.com/mattermost/platform/einterfaces/cluster.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package einterfaces
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/mattermost/platform/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ClusterInterface interface {
 | 
			
		||||
	StartInterNodeCommunication()
 | 
			
		||||
	StopInterNodeCommunication()
 | 
			
		||||
	GetClusterInfos() []*model.ClusterInfo
 | 
			
		||||
	RemoveAllSessionsForUserId(userId string)
 | 
			
		||||
	InvalidateCacheForUser(userId string)
 | 
			
		||||
	InvalidateCacheForChannel(channelId string)
 | 
			
		||||
	Publish(event *model.WebSocketEvent)
 | 
			
		||||
	UpdateStatus(status *model.Status)
 | 
			
		||||
	GetLogs() ([]string, *model.AppError)
 | 
			
		||||
	GetClusterId() string
 | 
			
		||||
	ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var theClusterInterface ClusterInterface
 | 
			
		||||
 | 
			
		||||
func RegisterClusterInterface(newInterface ClusterInterface) {
 | 
			
		||||
	theClusterInterface = newInterface
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetClusterInterface() ClusterInterface {
 | 
			
		||||
	return theClusterInterface
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								vendor/github.com/mattermost/platform/einterfaces/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/mattermost/platform/einterfaces/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package einterfaces
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/mattermost/platform/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type EmojiInterface interface {
 | 
			
		||||
	CanUserCreateEmoji(string, []*model.TeamMember) bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var theEmojiInterface EmojiInterface
 | 
			
		||||
 | 
			
		||||
func RegisterEmojiInterface(newInterface EmojiInterface) {
 | 
			
		||||
	theEmojiInterface = newInterface
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetEmojiInterface() EmojiInterface {
 | 
			
		||||
	return theEmojiInterface
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								vendor/github.com/mattermost/platform/einterfaces/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/mattermost/platform/einterfaces/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -13,6 +13,10 @@ 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()
 | 
			
		||||
	SyncNow()
 | 
			
		||||
	GetAllLdapUsers() ([]*model.User, *model.AppError)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								vendor/github.com/mattermost/platform/einterfaces/saml.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/mattermost/platform/einterfaces/saml.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package einterfaces
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/mattermost/platform/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SamlInterface interface {
 | 
			
		||||
	ConfigureSP() *model.AppError
 | 
			
		||||
	BuildRequest(relayState string) (*model.SamlAuthRequest, *model.AppError)
 | 
			
		||||
	DoLogin(encodedXML string, relayState map[string]string) (*model.User, *model.AppError)
 | 
			
		||||
	GetMetadata() (string, *model.AppError)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var theSamlInterface SamlInterface
 | 
			
		||||
 | 
			
		||||
func RegisterSamlInterface(newInterface SamlInterface) {
 | 
			
		||||
	theSamlInterface = newInterface
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetSamlInterface() SamlInterface {
 | 
			
		||||
	return theSamlInterface
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								vendor/github.com/mattermost/platform/model/access.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/mattermost/platform/model/access.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -15,10 +15,12 @@ const (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AccessData struct {
 | 
			
		||||
	AuthCode     string `json:"auth_code"`
 | 
			
		||||
	ClientId     string `json:"client_id"`
 | 
			
		||||
	UserId       string `json:"user_id"`
 | 
			
		||||
	Token        string `json:"token"`
 | 
			
		||||
	RefreshToken string `json:"refresh_token"`
 | 
			
		||||
	RedirectUri  string `json:"redirect_uri"`
 | 
			
		||||
	ExpiresAt    int64  `json:"expires_at"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccessResponse struct {
 | 
			
		||||
@@ -33,8 +35,12 @@ type AccessResponse struct {
 | 
			
		||||
// correctly.
 | 
			
		||||
func (ad *AccessData) IsValid() *AppError {
 | 
			
		||||
 | 
			
		||||
	if len(ad.AuthCode) == 0 || len(ad.AuthCode) > 128 {
 | 
			
		||||
		return NewLocAppError("AccessData.IsValid", "model.access.is_valid.auth_code.app_error", nil, "")
 | 
			
		||||
	if len(ad.ClientId) == 0 || len(ad.ClientId) > 26 {
 | 
			
		||||
		return NewLocAppError("AccessData.IsValid", "model.access.is_valid.client_id.app_error", nil, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(ad.UserId) == 0 || len(ad.UserId) > 26 {
 | 
			
		||||
		return NewLocAppError("AccessData.IsValid", "model.access.is_valid.user_id.app_error", nil, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(ad.Token) != 26 {
 | 
			
		||||
@@ -52,6 +58,19 @@ func (ad *AccessData) IsValid() *AppError {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (me *AccessData) IsExpired() bool {
 | 
			
		||||
 | 
			
		||||
	if me.ExpiresAt <= 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if GetMillis() > me.ExpiresAt {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ad *AccessData) ToJson() string {
 | 
			
		||||
	b, err := json.Marshal(ad)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								vendor/github.com/mattermost/platform/model/authorize.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/mattermost/platform/model/authorize.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -11,6 +11,7 @@ import (
 | 
			
		||||
const (
 | 
			
		||||
	AUTHCODE_EXPIRE_TIME   = 60 * 10 // 10 minutes
 | 
			
		||||
	AUTHCODE_RESPONSE_TYPE = "code"
 | 
			
		||||
	DEFAULT_SCOPE          = "user"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AuthData struct {
 | 
			
		||||
@@ -71,6 +72,10 @@ func (ad *AuthData) PreSave() {
 | 
			
		||||
	if ad.CreateAt == 0 {
 | 
			
		||||
		ad.CreateAt = GetMillis()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(ad.Scope) == 0 {
 | 
			
		||||
		ad.Scope = DEFAULT_SCOPE
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ad *AuthData) ToJson() string {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/model/channel.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/channel.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -124,9 +124,6 @@ func (o *Channel) ExtraUpdated() {
 | 
			
		||||
	o.ExtraUpdateAt = GetMillis()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Channel) PreExport() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetDMNameFromIds(userId1, userId2 string) string {
 | 
			
		||||
	if userId1 > userId2 {
 | 
			
		||||
		return userId2 + "__" + userId1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										551
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										551
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										66
									
								
								vendor/github.com/mattermost/platform/model/cluster_info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								vendor/github.com/mattermost/platform/model/cluster_info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
			
		||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ClusterInfo struct {
 | 
			
		||||
	Id                 string `json:"id"`
 | 
			
		||||
	Version            string `json:"version"`
 | 
			
		||||
	ConfigHash         string `json:"config_hash"`
 | 
			
		||||
	InterNodeUrl       string `json:"internode_url"`
 | 
			
		||||
	Hostname           string `json:"hostname"`
 | 
			
		||||
	LastSuccessfulPing int64  `json:"last_ping"`
 | 
			
		||||
	IsAlive            bool   `json:"is_alive"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (me *ClusterInfo) ToJson() string {
 | 
			
		||||
	b, err := json.Marshal(me)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	} else {
 | 
			
		||||
		return string(b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ClusterInfoFromJson(data io.Reader) *ClusterInfo {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
	var me ClusterInfo
 | 
			
		||||
	err := decoder.Decode(&me)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return &me
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (me *ClusterInfo) HaveEstablishedInitialContact() bool {
 | 
			
		||||
	if me.Id != "" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ClusterInfosToJson(objmap []*ClusterInfo) string {
 | 
			
		||||
	if b, err := json.Marshal(objmap); err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	} else {
 | 
			
		||||
		return string(b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ClusterInfosFromJson(data io.Reader) []*ClusterInfo {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
 | 
			
		||||
	var objmap []*ClusterInfo
 | 
			
		||||
	if err := decoder.Decode(&objmap); err != nil {
 | 
			
		||||
		return make([]*ClusterInfo, 0)
 | 
			
		||||
	} else {
 | 
			
		||||
		return objmap
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										499
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										499
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -6,6 +6,7 @@ package model
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/url"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@@ -19,8 +20,12 @@ const (
 | 
			
		||||
	DATABASE_DRIVER_MYSQL    = "mysql"
 | 
			
		||||
	DATABASE_DRIVER_POSTGRES = "postgres"
 | 
			
		||||
 | 
			
		||||
	PASSWORD_MAXIMUM_LENGTH = 64
 | 
			
		||||
	PASSWORD_MINIMUM_LENGTH = 5
 | 
			
		||||
 | 
			
		||||
	SERVICE_GITLAB    = "gitlab"
 | 
			
		||||
	SERVICE_GOOGLE    = "google"
 | 
			
		||||
	SERVICE_OFFICE365 = "office365"
 | 
			
		||||
 | 
			
		||||
	WEBSERVER_MODE_REGULAR  = "regular"
 | 
			
		||||
	WEBSERVER_MODE_GZIP     = "gzip"
 | 
			
		||||
@@ -32,10 +37,21 @@ const (
 | 
			
		||||
	DIRECT_MESSAGE_ANY  = "any"
 | 
			
		||||
	DIRECT_MESSAGE_TEAM = "team"
 | 
			
		||||
 | 
			
		||||
	PERMISSIONS_ALL          = "all"
 | 
			
		||||
	PERMISSIONS_TEAM_ADMIN   = "team_admin"
 | 
			
		||||
	PERMISSIONS_SYSTEM_ADMIN = "system_admin"
 | 
			
		||||
 | 
			
		||||
	FAKE_SETTING = "********************************"
 | 
			
		||||
 | 
			
		||||
	RESTRICT_EMOJI_CREATION_ALL          = "all"
 | 
			
		||||
	RESTRICT_EMOJI_CREATION_ADMIN        = "admin"
 | 
			
		||||
	RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN = "system_admin"
 | 
			
		||||
 | 
			
		||||
	SITENAME_MAX_LENGTH = 30
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ServiceSettings struct {
 | 
			
		||||
	SiteURL                           *string
 | 
			
		||||
	ListenAddress                     string
 | 
			
		||||
	MaximumLoginAttempts              int
 | 
			
		||||
	SegmentDeveloperKey               string
 | 
			
		||||
@@ -60,6 +76,14 @@ type ServiceSettings struct {
 | 
			
		||||
	WebsocketSecurePort               *int
 | 
			
		||||
	WebsocketPort                     *int
 | 
			
		||||
	WebserverMode                     *string
 | 
			
		||||
	EnableCustomEmoji                 *bool
 | 
			
		||||
	RestrictCustomEmojiCreation       *string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ClusterSettings struct {
 | 
			
		||||
	Enable                 *bool
 | 
			
		||||
	InterNodeListenAddress *string
 | 
			
		||||
	InterNodeUrls          []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SSOSettings struct {
 | 
			
		||||
@@ -89,9 +113,19 @@ type LogSettings struct {
 | 
			
		||||
	FileLevel              string
 | 
			
		||||
	FileFormat             string
 | 
			
		||||
	FileLocation           string
 | 
			
		||||
	EnableWebhookDebugging bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PasswordSettings struct {
 | 
			
		||||
	MinimumLength *int
 | 
			
		||||
	Lowercase     *bool
 | 
			
		||||
	Number        *bool
 | 
			
		||||
	Uppercase     *bool
 | 
			
		||||
	Symbol        *bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FileSettings struct {
 | 
			
		||||
	MaxFileSize                *int64
 | 
			
		||||
	DriverName                 string
 | 
			
		||||
	Directory                  string
 | 
			
		||||
	EnablePublicLink           bool
 | 
			
		||||
@@ -121,6 +155,7 @@ type EmailSettings struct {
 | 
			
		||||
	RequireEmailVerification bool
 | 
			
		||||
	FeedbackName             string
 | 
			
		||||
	FeedbackEmail            string
 | 
			
		||||
	FeedbackOrganization     *string
 | 
			
		||||
	SMTPUsername             string
 | 
			
		||||
	SMTPPassword             string
 | 
			
		||||
	SMTPServer               string
 | 
			
		||||
@@ -165,7 +200,12 @@ type TeamSettings struct {
 | 
			
		||||
	RestrictTeamNames                *bool
 | 
			
		||||
	EnableCustomBrand                *bool
 | 
			
		||||
	CustomBrandText                  *string
 | 
			
		||||
	CustomDescriptionText            *string
 | 
			
		||||
	RestrictDirectMessage            *string
 | 
			
		||||
	RestrictTeamInvite               *string
 | 
			
		||||
	RestrictPublicChannelManagement  *string
 | 
			
		||||
	RestrictPrivateChannelManagement *string
 | 
			
		||||
	UserStatusAwayTimeout            *int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LdapSettings struct {
 | 
			
		||||
@@ -189,9 +229,13 @@ type LdapSettings struct {
 | 
			
		||||
	NicknameAttribute  *string
 | 
			
		||||
	IdAttribute        *string
 | 
			
		||||
 | 
			
		||||
	// Syncronization
 | 
			
		||||
	SyncIntervalMinutes *int
 | 
			
		||||
 | 
			
		||||
	// Advanced
 | 
			
		||||
	SkipCertificateVerification *bool
 | 
			
		||||
	QueryTimeout                *int
 | 
			
		||||
	MaxPageSize                 *int
 | 
			
		||||
 | 
			
		||||
	// Customization
 | 
			
		||||
	LoginFieldName *string
 | 
			
		||||
@@ -203,11 +247,49 @@ type ComplianceSettings struct {
 | 
			
		||||
	EnableDaily *bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LocalizationSettings struct {
 | 
			
		||||
	DefaultServerLocale *string
 | 
			
		||||
	DefaultClientLocale *string
 | 
			
		||||
	AvailableLocales    *string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SamlSettings struct {
 | 
			
		||||
	// Basic
 | 
			
		||||
	Enable  *bool
 | 
			
		||||
	Verify  *bool
 | 
			
		||||
	Encrypt *bool
 | 
			
		||||
 | 
			
		||||
	IdpUrl                      *string
 | 
			
		||||
	IdpDescriptorUrl            *string
 | 
			
		||||
	AssertionConsumerServiceURL *string
 | 
			
		||||
 | 
			
		||||
	IdpCertificateFile    *string
 | 
			
		||||
	PublicCertificateFile *string
 | 
			
		||||
	PrivateKeyFile        *string
 | 
			
		||||
 | 
			
		||||
	// User Mapping
 | 
			
		||||
	FirstNameAttribute *string
 | 
			
		||||
	LastNameAttribute  *string
 | 
			
		||||
	EmailAttribute     *string
 | 
			
		||||
	UsernameAttribute  *string
 | 
			
		||||
	NicknameAttribute  *string
 | 
			
		||||
	LocaleAttribute    *string
 | 
			
		||||
 | 
			
		||||
	LoginButtonText *string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type NativeAppSettings struct {
 | 
			
		||||
	AppDownloadLink        *string
 | 
			
		||||
	AndroidAppDownloadLink *string
 | 
			
		||||
	IosAppDownloadLink     *string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	ServiceSettings      ServiceSettings
 | 
			
		||||
	TeamSettings         TeamSettings
 | 
			
		||||
	SqlSettings          SqlSettings
 | 
			
		||||
	LogSettings          LogSettings
 | 
			
		||||
	PasswordSettings     PasswordSettings
 | 
			
		||||
	FileSettings         FileSettings
 | 
			
		||||
	EmailSettings        EmailSettings
 | 
			
		||||
	RateLimitSettings    RateLimitSettings
 | 
			
		||||
@@ -215,8 +297,13 @@ type Config struct {
 | 
			
		||||
	SupportSettings      SupportSettings
 | 
			
		||||
	GitLabSettings       SSOSettings
 | 
			
		||||
	GoogleSettings       SSOSettings
 | 
			
		||||
	Office365Settings    SSOSettings
 | 
			
		||||
	LdapSettings         LdapSettings
 | 
			
		||||
	ComplianceSettings   ComplianceSettings
 | 
			
		||||
	LocalizationSettings LocalizationSettings
 | 
			
		||||
	SamlSettings         SamlSettings
 | 
			
		||||
	NativeAppSettings    NativeAppSettings
 | 
			
		||||
	ClusterSettings      ClusterSettings
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Config) ToJson() string {
 | 
			
		||||
@@ -234,6 +321,8 @@ func (o *Config) GetSSOService(service string) *SSOSettings {
 | 
			
		||||
		return &o.GitLabSettings
 | 
			
		||||
	case SERVICE_GOOGLE:
 | 
			
		||||
		return &o.GoogleSettings
 | 
			
		||||
	case SERVICE_OFFICE365:
 | 
			
		||||
		return &o.Office365Settings
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -256,6 +345,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)
 | 
			
		||||
	}
 | 
			
		||||
@@ -278,6 +372,11 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
		o.EmailSettings.PasswordResetSalt = NewRandomString(32)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.ServiceSettings.SiteURL == nil {
 | 
			
		||||
		o.ServiceSettings.SiteURL = new(string)
 | 
			
		||||
		*o.ServiceSettings.SiteURL = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.ServiceSettings.EnableDeveloper == nil {
 | 
			
		||||
		o.ServiceSettings.EnableDeveloper = new(bool)
 | 
			
		||||
		*o.ServiceSettings.EnableDeveloper = false
 | 
			
		||||
@@ -298,6 +397,31 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
		*o.ServiceSettings.EnableMultifactorAuthentication = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.PasswordSettings.MinimumLength == nil {
 | 
			
		||||
		o.PasswordSettings.MinimumLength = new(int)
 | 
			
		||||
		*o.PasswordSettings.MinimumLength = PASSWORD_MINIMUM_LENGTH
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.PasswordSettings.Lowercase == nil {
 | 
			
		||||
		o.PasswordSettings.Lowercase = new(bool)
 | 
			
		||||
		*o.PasswordSettings.Lowercase = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.PasswordSettings.Number == nil {
 | 
			
		||||
		o.PasswordSettings.Number = new(bool)
 | 
			
		||||
		*o.PasswordSettings.Number = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.PasswordSettings.Uppercase == nil {
 | 
			
		||||
		o.PasswordSettings.Uppercase = new(bool)
 | 
			
		||||
		*o.PasswordSettings.Uppercase = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.PasswordSettings.Symbol == nil {
 | 
			
		||||
		o.PasswordSettings.Symbol = new(bool)
 | 
			
		||||
		*o.PasswordSettings.Symbol = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.TeamSettings.RestrictTeamNames == nil {
 | 
			
		||||
		o.TeamSettings.RestrictTeamNames = new(bool)
 | 
			
		||||
		*o.TeamSettings.RestrictTeamNames = true
 | 
			
		||||
@@ -313,6 +437,11 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
		*o.TeamSettings.CustomBrandText = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.TeamSettings.CustomDescriptionText == nil {
 | 
			
		||||
		o.TeamSettings.CustomDescriptionText = new(string)
 | 
			
		||||
		*o.TeamSettings.CustomDescriptionText = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.TeamSettings.EnableOpenServer == nil {
 | 
			
		||||
		o.TeamSettings.EnableOpenServer = new(bool)
 | 
			
		||||
		*o.TeamSettings.EnableOpenServer = false
 | 
			
		||||
@@ -323,6 +452,26 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
		*o.TeamSettings.RestrictDirectMessage = DIRECT_MESSAGE_ANY
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.TeamSettings.RestrictTeamInvite == nil {
 | 
			
		||||
		o.TeamSettings.RestrictTeamInvite = new(string)
 | 
			
		||||
		*o.TeamSettings.RestrictTeamInvite = PERMISSIONS_ALL
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.TeamSettings.RestrictPublicChannelManagement == nil {
 | 
			
		||||
		o.TeamSettings.RestrictPublicChannelManagement = new(string)
 | 
			
		||||
		*o.TeamSettings.RestrictPublicChannelManagement = PERMISSIONS_ALL
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.TeamSettings.RestrictPrivateChannelManagement == nil {
 | 
			
		||||
		o.TeamSettings.RestrictPrivateChannelManagement = new(string)
 | 
			
		||||
		*o.TeamSettings.RestrictPrivateChannelManagement = PERMISSIONS_ALL
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.TeamSettings.UserStatusAwayTimeout == nil {
 | 
			
		||||
		o.TeamSettings.UserStatusAwayTimeout = new(int64)
 | 
			
		||||
		*o.TeamSettings.UserStatusAwayTimeout = 300
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.EmailSettings.EnableSignInWithEmail == nil {
 | 
			
		||||
		o.EmailSettings.EnableSignInWithEmail = new(bool)
 | 
			
		||||
 | 
			
		||||
@@ -353,13 +502,18 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
		*o.EmailSettings.PushNotificationContents = GENERIC_NOTIFICATION
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.EmailSettings.FeedbackOrganization == nil {
 | 
			
		||||
		o.EmailSettings.FeedbackOrganization = new(string)
 | 
			
		||||
		*o.EmailSettings.FeedbackOrganization = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !IsSafeLink(o.SupportSettings.TermsOfServiceLink) {
 | 
			
		||||
		o.SupportSettings.TermsOfServiceLink = nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SupportSettings.TermsOfServiceLink == nil {
 | 
			
		||||
		o.SupportSettings.TermsOfServiceLink = new(string)
 | 
			
		||||
		*o.SupportSettings.TermsOfServiceLink = "/static/help/terms.html"
 | 
			
		||||
		*o.SupportSettings.TermsOfServiceLink = "https://about.mattermost.com/default-terms/"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !IsSafeLink(o.SupportSettings.PrivacyPolicyLink) {
 | 
			
		||||
@@ -368,7 +522,7 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
 | 
			
		||||
	if o.SupportSettings.PrivacyPolicyLink == nil {
 | 
			
		||||
		o.SupportSettings.PrivacyPolicyLink = new(string)
 | 
			
		||||
		*o.SupportSettings.PrivacyPolicyLink = "/static/help/privacy.html"
 | 
			
		||||
		*o.SupportSettings.PrivacyPolicyLink = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !IsSafeLink(o.SupportSettings.AboutLink) {
 | 
			
		||||
@@ -377,7 +531,7 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
 | 
			
		||||
	if o.SupportSettings.AboutLink == nil {
 | 
			
		||||
		o.SupportSettings.AboutLink = new(string)
 | 
			
		||||
		*o.SupportSettings.AboutLink = "/static/help/about.html"
 | 
			
		||||
		*o.SupportSettings.AboutLink = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !IsSafeLink(o.SupportSettings.HelpLink) {
 | 
			
		||||
@@ -386,7 +540,7 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
 | 
			
		||||
	if o.SupportSettings.HelpLink == nil {
 | 
			
		||||
		o.SupportSettings.HelpLink = new(string)
 | 
			
		||||
		*o.SupportSettings.HelpLink = "/static/help/help.html"
 | 
			
		||||
		*o.SupportSettings.HelpLink = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !IsSafeLink(o.SupportSettings.ReportAProblemLink) {
 | 
			
		||||
@@ -395,7 +549,7 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
 | 
			
		||||
	if o.SupportSettings.ReportAProblemLink == nil {
 | 
			
		||||
		o.SupportSettings.ReportAProblemLink = new(string)
 | 
			
		||||
		*o.SupportSettings.ReportAProblemLink = "/static/help/report_problem.html"
 | 
			
		||||
		*o.SupportSettings.ReportAProblemLink = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SupportSettings.SupportEmail == nil {
 | 
			
		||||
@@ -403,24 +557,94 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
		*o.SupportSettings.SupportEmail = "feedback@mattermost.com"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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.UsernameAttribute == nil {
 | 
			
		||||
		o.LdapSettings.UsernameAttribute = new(string)
 | 
			
		||||
		*o.LdapSettings.UsernameAttribute = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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.Enable == nil {
 | 
			
		||||
		o.LdapSettings.Enable = new(bool)
 | 
			
		||||
		*o.LdapSettings.Enable = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.LdapSettings.UserFilter == nil {
 | 
			
		||||
		o.LdapSettings.UserFilter = new(string)
 | 
			
		||||
		*o.LdapSettings.UserFilter = ""
 | 
			
		||||
	if o.LdapSettings.MaxPageSize == nil {
 | 
			
		||||
		o.LdapSettings.MaxPageSize = new(int)
 | 
			
		||||
		*o.LdapSettings.MaxPageSize = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.LdapSettings.LoginFieldName == nil {
 | 
			
		||||
@@ -475,7 +699,33 @@ func (o *Config) SetDefaults() {
 | 
			
		||||
 | 
			
		||||
	if o.ServiceSettings.WebserverMode == nil {
 | 
			
		||||
		o.ServiceSettings.WebserverMode = new(string)
 | 
			
		||||
		*o.ServiceSettings.WebserverMode = "regular"
 | 
			
		||||
		*o.ServiceSettings.WebserverMode = "gzip"
 | 
			
		||||
	} else if *o.ServiceSettings.WebserverMode == "regular" {
 | 
			
		||||
		*o.ServiceSettings.WebserverMode = "gzip"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.ServiceSettings.EnableCustomEmoji == nil {
 | 
			
		||||
		o.ServiceSettings.EnableCustomEmoji = new(bool)
 | 
			
		||||
		*o.ServiceSettings.EnableCustomEmoji = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.ServiceSettings.RestrictCustomEmojiCreation == nil {
 | 
			
		||||
		o.ServiceSettings.RestrictCustomEmojiCreation = new(string)
 | 
			
		||||
		*o.ServiceSettings.RestrictCustomEmojiCreation = RESTRICT_EMOJI_CREATION_ALL
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.ClusterSettings.InterNodeListenAddress == nil {
 | 
			
		||||
		o.ClusterSettings.InterNodeListenAddress = new(string)
 | 
			
		||||
		*o.ClusterSettings.InterNodeListenAddress = ":8075"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.ClusterSettings.Enable == nil {
 | 
			
		||||
		o.ClusterSettings.Enable = new(bool)
 | 
			
		||||
		*o.ClusterSettings.Enable = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.ClusterSettings.InterNodeUrls == nil {
 | 
			
		||||
		o.ClusterSettings.InterNodeUrls = []string{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.ComplianceSettings.Enable == nil {
 | 
			
		||||
@@ -493,19 +743,114 @@ 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 = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.Enable == nil {
 | 
			
		||||
		o.SamlSettings.Enable = new(bool)
 | 
			
		||||
		*o.SamlSettings.Enable = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.Verify == nil {
 | 
			
		||||
		o.SamlSettings.Verify = new(bool)
 | 
			
		||||
		*o.SamlSettings.Verify = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.Encrypt == nil {
 | 
			
		||||
		o.SamlSettings.Encrypt = new(bool)
 | 
			
		||||
		*o.SamlSettings.Encrypt = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.IdpUrl == nil {
 | 
			
		||||
		o.SamlSettings.IdpUrl = new(string)
 | 
			
		||||
		*o.SamlSettings.IdpUrl = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.IdpDescriptorUrl == nil {
 | 
			
		||||
		o.SamlSettings.IdpDescriptorUrl = new(string)
 | 
			
		||||
		*o.SamlSettings.IdpDescriptorUrl = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.IdpCertificateFile == nil {
 | 
			
		||||
		o.SamlSettings.IdpCertificateFile = new(string)
 | 
			
		||||
		*o.SamlSettings.IdpCertificateFile = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.PublicCertificateFile == nil {
 | 
			
		||||
		o.SamlSettings.PublicCertificateFile = new(string)
 | 
			
		||||
		*o.SamlSettings.PublicCertificateFile = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.PrivateKeyFile == nil {
 | 
			
		||||
		o.SamlSettings.PrivateKeyFile = new(string)
 | 
			
		||||
		*o.SamlSettings.PrivateKeyFile = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.AssertionConsumerServiceURL == nil {
 | 
			
		||||
		o.SamlSettings.AssertionConsumerServiceURL = new(string)
 | 
			
		||||
		*o.SamlSettings.AssertionConsumerServiceURL = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.LoginButtonText == nil || *o.SamlSettings.LoginButtonText == "" {
 | 
			
		||||
		o.SamlSettings.LoginButtonText = new(string)
 | 
			
		||||
		*o.SamlSettings.LoginButtonText = USER_AUTH_SERVICE_SAML_TEXT
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.FirstNameAttribute == nil {
 | 
			
		||||
		o.SamlSettings.FirstNameAttribute = new(string)
 | 
			
		||||
		*o.SamlSettings.FirstNameAttribute = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.LastNameAttribute == nil {
 | 
			
		||||
		o.SamlSettings.LastNameAttribute = new(string)
 | 
			
		||||
		*o.SamlSettings.LastNameAttribute = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.EmailAttribute == nil {
 | 
			
		||||
		o.SamlSettings.EmailAttribute = new(string)
 | 
			
		||||
		*o.SamlSettings.EmailAttribute = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.UsernameAttribute == nil {
 | 
			
		||||
		o.SamlSettings.UsernameAttribute = new(string)
 | 
			
		||||
		*o.SamlSettings.UsernameAttribute = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.NicknameAttribute == nil {
 | 
			
		||||
		o.SamlSettings.NicknameAttribute = new(string)
 | 
			
		||||
		*o.SamlSettings.NicknameAttribute = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.SamlSettings.LocaleAttribute == nil {
 | 
			
		||||
		o.SamlSettings.LocaleAttribute = new(string)
 | 
			
		||||
		*o.SamlSettings.LocaleAttribute = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.NativeAppSettings.AppDownloadLink == nil {
 | 
			
		||||
		o.NativeAppSettings.AppDownloadLink = new(string)
 | 
			
		||||
		*o.NativeAppSettings.AppDownloadLink = "https://about.mattermost.com/downloads/"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.NativeAppSettings.AndroidAppDownloadLink == nil {
 | 
			
		||||
		o.NativeAppSettings.AndroidAppDownloadLink = new(string)
 | 
			
		||||
		*o.NativeAppSettings.AndroidAppDownloadLink = "https://about.mattermost.com/mattermost-android-app/"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.NativeAppSettings.IosAppDownloadLink == nil {
 | 
			
		||||
		o.NativeAppSettings.IosAppDownloadLink = new(string)
 | 
			
		||||
		*o.NativeAppSettings.IosAppDownloadLink = "https://about.mattermost.com/mattermost-ios-app/"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -515,6 +860,12 @@ func (o *Config) IsValid() *AppError {
 | 
			
		||||
		return NewLocAppError("Config.IsValid", "model.config.is_valid.login_attempts.app_error", nil, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(*o.ServiceSettings.SiteURL) != 0 {
 | 
			
		||||
		if _, err := url.ParseRequestURI(*o.ServiceSettings.SiteURL); err != nil {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.site_url.app_error", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(o.ServiceSettings.ListenAddress) == 0 {
 | 
			
		||||
		return NewLocAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "")
 | 
			
		||||
	}
 | 
			
		||||
@@ -547,6 +898,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 +958,102 @@ 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, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *o.LdapSettings.MaxPageSize < 0 {
 | 
			
		||||
		return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_max_page_size.app_error", nil, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *o.LdapSettings.Enable {
 | 
			
		||||
		if *o.LdapSettings.LdapServer == "" {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_server", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if *o.LdapSettings.BaseDN == "" {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_basedn", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if *o.LdapSettings.FirstNameAttribute == "" {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_firstname", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if *o.LdapSettings.LastNameAttribute == "" {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_lastname", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if *o.LdapSettings.EmailAttribute == "" {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_email", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if *o.LdapSettings.UsernameAttribute == "" {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_username", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if *o.LdapSettings.IdAttribute == "" {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_id", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *o.SamlSettings.Enable {
 | 
			
		||||
		if len(*o.SamlSettings.IdpUrl) == 0 || !IsValidHttpUrl(*o.SamlSettings.IdpUrl) {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_idp_url.app_error", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(*o.SamlSettings.IdpDescriptorUrl) == 0 || !IsValidHttpUrl(*o.SamlSettings.IdpDescriptorUrl) {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_idp_descriptor_url.app_error", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(*o.SamlSettings.IdpCertificateFile) == 0 {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_idp_cert.app_error", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(*o.SamlSettings.EmailAttribute) == 0 {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_email_attribute.app_error", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(*o.SamlSettings.UsernameAttribute) == 0 {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_username_attribute.app_error", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(*o.SamlSettings.FirstNameAttribute) == 0 {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_first_name_attribute.app_error", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(*o.SamlSettings.LastNameAttribute) == 0 {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_last_name_attribute.app_error", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if *o.SamlSettings.Verify {
 | 
			
		||||
			if len(*o.SamlSettings.AssertionConsumerServiceURL) == 0 || !IsValidHttpUrl(*o.SamlSettings.AssertionConsumerServiceURL) {
 | 
			
		||||
				return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_assertion_consumer_service_url.app_error", nil, "")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if *o.SamlSettings.Encrypt {
 | 
			
		||||
			if len(*o.SamlSettings.PrivateKeyFile) == 0 {
 | 
			
		||||
				return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_private_key.app_error", nil, "")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if len(*o.SamlSettings.PublicCertificateFile) == 0 {
 | 
			
		||||
				return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_public_cert.app_error", nil, "")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(*o.SamlSettings.EmailAttribute) == 0 {
 | 
			
		||||
			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_email_attribute.app_error", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *o.PasswordSettings.MinimumLength < PASSWORD_MINIMUM_LENGTH || *o.PasswordSettings.MinimumLength > PASSWORD_MAXIMUM_LENGTH {
 | 
			
		||||
		return NewLocAppError("Config.IsValid", "model.config.is_valid.password_length.app_error", map[string]interface{}{"MinLength": PASSWORD_MINIMUM_LENGTH, "MaxLength": PASSWORD_MAXIMUM_LENGTH}, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(o.TeamSettings.SiteName) > SITENAME_MAX_LENGTH {
 | 
			
		||||
		return NewLocAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SITENAME_MAX_LENGTH}, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -615,7 +1066,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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										95
									
								
								vendor/github.com/mattermost/platform/model/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								vendor/github.com/mattermost/platform/model/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Emoji struct {
 | 
			
		||||
	Id        string `json:"id"`
 | 
			
		||||
	CreateAt  int64  `json:"create_at"`
 | 
			
		||||
	UpdateAt  int64  `json:"update_at"`
 | 
			
		||||
	DeleteAt  int64  `json:"delete_at"`
 | 
			
		||||
	CreatorId string `json:"creator_id"`
 | 
			
		||||
	Name      string `json:"name"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (emoji *Emoji) IsValid() *AppError {
 | 
			
		||||
	if len(emoji.Id) != 26 {
 | 
			
		||||
		return NewLocAppError("Emoji.IsValid", "model.emoji.id.app_error", nil, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if emoji.CreateAt == 0 {
 | 
			
		||||
		return NewLocAppError("Emoji.IsValid", "model.emoji.create_at.app_error", nil, "id="+emoji.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if emoji.UpdateAt == 0 {
 | 
			
		||||
		return NewLocAppError("Emoji.IsValid", "model.emoji.update_at.app_error", nil, "id="+emoji.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(emoji.CreatorId) != 26 {
 | 
			
		||||
		return NewLocAppError("Emoji.IsValid", "model.emoji.user_id.app_error", nil, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(emoji.Name) == 0 || len(emoji.Name) > 64 {
 | 
			
		||||
		return NewLocAppError("Emoji.IsValid", "model.emoji.name.app_error", nil, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (emoji *Emoji) PreSave() {
 | 
			
		||||
	if emoji.Id == "" {
 | 
			
		||||
		emoji.Id = NewId()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emoji.CreateAt = GetMillis()
 | 
			
		||||
	emoji.UpdateAt = emoji.CreateAt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (emoji *Emoji) PreUpdate() {
 | 
			
		||||
	emoji.UpdateAt = GetMillis()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (emoji *Emoji) ToJson() string {
 | 
			
		||||
	b, err := json.Marshal(emoji)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	} else {
 | 
			
		||||
		return string(b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EmojiFromJson(data io.Reader) *Emoji {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
	var emoji Emoji
 | 
			
		||||
	err := decoder.Decode(&emoji)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return &emoji
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EmojiListToJson(emojiList []*Emoji) string {
 | 
			
		||||
	b, err := json.Marshal(emojiList)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	} else {
 | 
			
		||||
		return string(b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EmojiListFromJson(data io.Reader) []*Emoji {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
	var emojiList []*Emoji
 | 
			
		||||
	err := decoder.Decode(&emojiList)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return emojiList
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										133
									
								
								vendor/github.com/mattermost/platform/model/incoming_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										133
									
								
								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, 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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								vendor/github.com/mattermost/platform/model/job.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								vendor/github.com/mattermost/platform/model/job.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
// 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 {
 | 
			
		||||
	if task, ok := tasks[name]; ok {
 | 
			
		||||
		return task
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Executes the task immediatly. A recurring task will be run regularally after interval.
 | 
			
		||||
func (task *ScheduledTask) Execute() {
 | 
			
		||||
	task.function()
 | 
			
		||||
	task.timer.Reset(task.Interval)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (task *ScheduledTask) String() string {
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"%s\nInterval: %s\nRecurring: %t\n",
 | 
			
		||||
		task.Name,
 | 
			
		||||
		task.Interval.String(),
 | 
			
		||||
		task.Recurring,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								vendor/github.com/mattermost/platform/model/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/mattermost/platform/model/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	USER_AUTH_SERVICE_LDAP = "ldap"
 | 
			
		||||
	LDAP_SYNC_TASK_NAME    = "LDAP Syncronization"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										32
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										32
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -35,10 +35,14 @@ type Features struct {
 | 
			
		||||
	Users                *int  `json:"users"`
 | 
			
		||||
	LDAP                 *bool `json:"ldap"`
 | 
			
		||||
	MFA                  *bool `json:"mfa"`
 | 
			
		||||
	GoogleSSO      *bool `json:"google_sso"`
 | 
			
		||||
	GoogleOAuth          *bool `json:"google_oauth"`
 | 
			
		||||
	Office365OAuth       *bool `json:"office365_oauth"`
 | 
			
		||||
	Compliance           *bool `json:"compliance"`
 | 
			
		||||
	Cluster              *bool `json:"cluster"`
 | 
			
		||||
	CustomBrand          *bool `json:"custom_brand"`
 | 
			
		||||
	MHPNS                *bool `json:"mhpns"`
 | 
			
		||||
	SAML                 *bool `json:"saml"`
 | 
			
		||||
	PasswordRequirements *bool `json:"password_requirements"`
 | 
			
		||||
	FutureFeatures       *bool `json:"future_features"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -63,9 +67,14 @@ func (f *Features) SetDefaults() {
 | 
			
		||||
		*f.MFA = *f.FutureFeatures
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if f.GoogleSSO == nil {
 | 
			
		||||
		f.GoogleSSO = new(bool)
 | 
			
		||||
		*f.GoogleSSO = *f.FutureFeatures
 | 
			
		||||
	if f.GoogleOAuth == nil {
 | 
			
		||||
		f.GoogleOAuth = new(bool)
 | 
			
		||||
		*f.GoogleOAuth = *f.FutureFeatures
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if f.Office365OAuth == nil {
 | 
			
		||||
		f.Office365OAuth = new(bool)
 | 
			
		||||
		*f.Office365OAuth = *f.FutureFeatures
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if f.Compliance == nil {
 | 
			
		||||
@@ -73,6 +82,11 @@ func (f *Features) SetDefaults() {
 | 
			
		||||
		*f.Compliance = *f.FutureFeatures
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if f.Cluster == nil {
 | 
			
		||||
		f.Cluster = new(bool)
 | 
			
		||||
		*f.Cluster = *f.FutureFeatures
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if f.CustomBrand == nil {
 | 
			
		||||
		f.CustomBrand = new(bool)
 | 
			
		||||
		*f.CustomBrand = *f.FutureFeatures
 | 
			
		||||
@@ -82,6 +96,16 @@ func (f *Features) SetDefaults() {
 | 
			
		||||
		f.MHPNS = new(bool)
 | 
			
		||||
		*f.MHPNS = *f.FutureFeatures
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if f.SAML == nil {
 | 
			
		||||
		f.SAML = new(bool)
 | 
			
		||||
		*f.SAML = *f.FutureFeatures
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if f.PasswordRequirements == nil {
 | 
			
		||||
		f.PasswordRequirements = new(bool)
 | 
			
		||||
		*f.PasswordRequirements = *f.FutureFeatures
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *License) IsExpired() bool {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								vendor/github.com/mattermost/platform/model/message.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										60
									
								
								vendor/github.com/mattermost/platform/model/message.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,60 +0,0 @@
 | 
			
		||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ACTION_TYPING             = "typing"
 | 
			
		||||
	ACTION_POSTED             = "posted"
 | 
			
		||||
	ACTION_POST_EDITED        = "post_edited"
 | 
			
		||||
	ACTION_POST_DELETED       = "post_deleted"
 | 
			
		||||
	ACTION_CHANNEL_DELETED    = "channel_deleted"
 | 
			
		||||
	ACTION_CHANNEL_VIEWED     = "channel_viewed"
 | 
			
		||||
	ACTION_DIRECT_ADDED       = "direct_added"
 | 
			
		||||
	ACTION_NEW_USER           = "new_user"
 | 
			
		||||
	ACTION_USER_ADDED         = "user_added"
 | 
			
		||||
	ACTION_USER_REMOVED       = "user_removed"
 | 
			
		||||
	ACTION_PREFERENCE_CHANGED = "preference_changed"
 | 
			
		||||
	ACTION_EPHEMERAL_MESSAGE  = "ephemeral_message"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Message struct {
 | 
			
		||||
	TeamId    string            `json:"team_id"`
 | 
			
		||||
	ChannelId string            `json:"channel_id"`
 | 
			
		||||
	UserId    string            `json:"user_id"`
 | 
			
		||||
	Action    string            `json:"action"`
 | 
			
		||||
	Props     map[string]string `json:"props"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *Message) Add(key string, value string) {
 | 
			
		||||
	m.Props[key] = value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewMessage(teamId string, channelId string, userId string, action string) *Message {
 | 
			
		||||
	return &Message{TeamId: teamId, ChannelId: channelId, UserId: userId, Action: action, Props: make(map[string]string)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Message) ToJson() string {
 | 
			
		||||
	b, err := json.Marshal(o)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	} else {
 | 
			
		||||
		return string(b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func MessageFromJson(data io.Reader) *Message {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
	var o Message
 | 
			
		||||
	err := decoder.Decode(&o)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return &o
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								vendor/github.com/mattermost/platform/model/oauth.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/mattermost/platform/model/oauth.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -25,8 +25,10 @@ type OAuthApp struct {
 | 
			
		||||
	ClientSecret string      `json:"client_secret"`
 | 
			
		||||
	Name         string      `json:"name"`
 | 
			
		||||
	Description  string      `json:"description"`
 | 
			
		||||
	IconURL      string      `json:"icon_url"`
 | 
			
		||||
	CallbackUrls StringArray `json:"callback_urls"`
 | 
			
		||||
	Homepage     string      `json:"homepage"`
 | 
			
		||||
	IsTrusted    bool        `json:"is_trusted"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsValid validates the app and returns an error if it isn't configured
 | 
			
		||||
@@ -61,7 +63,13 @@ func (a *OAuthApp) IsValid() *AppError {
 | 
			
		||||
		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(a.Homepage) == 0 || len(a.Homepage) > 256 {
 | 
			
		||||
	for _, callback := range a.CallbackUrls {
 | 
			
		||||
		if !IsValidHttpUrl(callback) {
 | 
			
		||||
			return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) {
 | 
			
		||||
		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -69,6 +77,12 @@ func (a *OAuthApp) IsValid() *AppError {
 | 
			
		||||
		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(a.IconURL) > 0 {
 | 
			
		||||
		if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) {
 | 
			
		||||
			return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -85,10 +99,6 @@ func (a *OAuthApp) PreSave() {
 | 
			
		||||
 | 
			
		||||
	a.CreateAt = GetMillis()
 | 
			
		||||
	a.UpdateAt = a.CreateAt
 | 
			
		||||
 | 
			
		||||
	if len(a.ClientSecret) > 0 {
 | 
			
		||||
		a.ClientSecret = HashPassword(a.ClientSecret)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PreUpdate should be run before updating the app in the db.
 | 
			
		||||
@@ -157,3 +167,23 @@ func OAuthAppMapFromJson(data io.Reader) map[string]*OAuthApp {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func OAuthAppListToJson(l []*OAuthApp) string {
 | 
			
		||||
	b, err := json.Marshal(l)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	} else {
 | 
			
		||||
		return string(b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func OAuthAppListFromJson(data io.Reader) []*OAuthApp {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
	var o []*OAuthApp
 | 
			
		||||
	err := decoder.Decode(&o)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return o
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										67
									
								
								vendor/github.com/mattermost/platform/model/outgoing_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										67
									
								
								vendor/github.com/mattermost/platform/model/outgoing_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -7,6 +7,9 @@ import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type OutgoingWebhook struct {
 | 
			
		||||
@@ -19,9 +22,51 @@ type OutgoingWebhook struct {
 | 
			
		||||
	ChannelId    string      `json:"channel_id"`
 | 
			
		||||
	TeamId       string      `json:"team_id"`
 | 
			
		||||
	TriggerWords StringArray `json:"trigger_words"`
 | 
			
		||||
	TriggerWhen  int         `json:"trigger_when"`
 | 
			
		||||
	CallbackURLs StringArray `json:"callback_urls"`
 | 
			
		||||
	DisplayName  string      `json:"display_name"`
 | 
			
		||||
	Description  string      `json:"description"`
 | 
			
		||||
	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 +169,14 @@ 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, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.TriggerWhen > 1 {
 | 
			
		||||
		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -157,3 +210,17 @@ func (o *OutgoingWebhook) HasTriggerWord(word string) bool {
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
 | 
			
		||||
	if len(o.TriggerWords) == 0 || len(word) == 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, trigger := range o.TriggerWords {
 | 
			
		||||
		if strings.HasPrefix(word, trigger) {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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/post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -162,9 +162,6 @@ func (o *Post) AddProp(key string, value interface{}) {
 | 
			
		||||
	o.Props[key] = value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Post) PreExport() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Post) IsSystemMessage() bool {
 | 
			
		||||
	return len(o.Type) >= len(POST_SYSTEM_MESSAGE_PREFIX) && o.Type[:len(POST_SYSTEM_MESSAGE_PREFIX)] == POST_SYSTEM_MESSAGE_PREFIX
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								vendor/github.com/mattermost/platform/model/preference.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										51
									
								
								vendor/github.com/mattermost/platform/model/preference.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -6,6 +6,8 @@ package model
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -13,6 +15,16 @@ const (
 | 
			
		||||
	PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show"
 | 
			
		||||
	PREFERENCE_CATEGORY_TUTORIAL_STEPS      = "tutorial_step"
 | 
			
		||||
	PREFERENCE_CATEGORY_ADVANCED_SETTINGS   = "advanced_settings"
 | 
			
		||||
	PREFERENCE_CATEGORY_FLAGGED_POST        = "flagged_post"
 | 
			
		||||
 | 
			
		||||
	PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
 | 
			
		||||
	PREFERENCE_NAME_COLLAPSE_SETTING     = "collapse_previews"
 | 
			
		||||
 | 
			
		||||
	PREFERENCE_CATEGORY_THEME = "theme"
 | 
			
		||||
	// the name for theme props is the team id
 | 
			
		||||
 | 
			
		||||
	PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP = "oauth_app"
 | 
			
		||||
	// the name for oauth_app is the client_id and value is the current scope
 | 
			
		||||
 | 
			
		||||
	PREFERENCE_CATEGORY_LAST     = "last"
 | 
			
		||||
	PREFERENCE_NAME_LAST_CHANNEL = "channel"
 | 
			
		||||
@@ -54,13 +66,48 @@ func (o *Preference) IsValid() *AppError {
 | 
			
		||||
		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(o.Name) == 0 || len(o.Name) > 32 {
 | 
			
		||||
	if len(o.Name) > 32 {
 | 
			
		||||
		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if utf8.RuneCountInString(o.Value) > 128 {
 | 
			
		||||
	if utf8.RuneCountInString(o.Value) > 2000 {
 | 
			
		||||
		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Category == PREFERENCE_CATEGORY_THEME {
 | 
			
		||||
		var unused map[string]string
 | 
			
		||||
		if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&unused); err != nil {
 | 
			
		||||
			return NewLocAppError("Preference.IsValid", "model.preference.is_valid.theme.app_error", nil, "value="+o.Value)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Preference) PreUpdate() {
 | 
			
		||||
	if o.Category == PREFERENCE_CATEGORY_THEME {
 | 
			
		||||
		// decode the value of theme (a map of strings to string) and eliminate any invalid values
 | 
			
		||||
		var props map[string]string
 | 
			
		||||
		if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&props); err != nil {
 | 
			
		||||
			// just continue, the invalid preference value should get caught by IsValid before saving
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`)
 | 
			
		||||
 | 
			
		||||
		// blank out any invalid theme values
 | 
			
		||||
		for name, value := range props {
 | 
			
		||||
			if name == "image" || name == "type" || name == "codeTheme" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !colorPattern.MatchString(value) {
 | 
			
		||||
				props[name] = "#ffffff"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if b, err := json.Marshal(props); err == nil {
 | 
			
		||||
			o.Value = string(b)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								vendor/github.com/mattermost/platform/model/saml.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/mattermost/platform/model/saml.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	USER_AUTH_SERVICE_SAML      = "saml"
 | 
			
		||||
	USER_AUTH_SERVICE_SAML_TEXT = "With SAML"
 | 
			
		||||
	SAML_IDP_CERTIFICATE        = 1
 | 
			
		||||
	SAML_PRIVATE_KEY            = 2
 | 
			
		||||
	SAML_PUBLIC_CERT            = 3
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SamlAuthRequest struct {
 | 
			
		||||
	Base64AuthRequest string
 | 
			
		||||
	URL               string
 | 
			
		||||
	RelayState        string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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, "#")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								vendor/github.com/mattermost/platform/model/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/mattermost/platform/model/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -83,7 +83,11 @@ func (me *Session) IsExpired() bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (me *Session) SetExpireInDays(days int) {
 | 
			
		||||
	if me.CreateAt == 0 {
 | 
			
		||||
		me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days))
 | 
			
		||||
	} else {
 | 
			
		||||
		me.ExpiresAt = me.CreateAt + (1000 * 60 * 60 * 24 * int64(days))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (me *Session) AddProp(key string, value string) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								vendor/github.com/mattermost/platform/model/status.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/mattermost/platform/model/status.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	STATUS_OFFLINE    = "offline"
 | 
			
		||||
	STATUS_AWAY       = "away"
 | 
			
		||||
	STATUS_ONLINE     = "online"
 | 
			
		||||
	STATUS_CACHE_SIZE = 10000
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Status struct {
 | 
			
		||||
	UserId         string `json:"user_id"`
 | 
			
		||||
	Status         string `json:"status"`
 | 
			
		||||
	LastActivityAt int64  `json:"last_activity_at"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Status) ToJson() string {
 | 
			
		||||
	b, err := json.Marshal(o)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	} else {
 | 
			
		||||
		return string(b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func StatusFromJson(data io.Reader) *Status {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
	var o Status
 | 
			
		||||
	err := decoder.Decode(&o)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return &o
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/model/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -224,9 +224,6 @@ func CleanTeamName(s string) string {
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Team) PreExport() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *Team) Sanitize() {
 | 
			
		||||
	o.Email = ""
 | 
			
		||||
	o.AllowedDomains = ""
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										121
									
								
								vendor/github.com/mattermost/platform/model/team_member.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								vendor/github.com/mattermost/platform/model/team_member.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
// 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"`
 | 
			
		||||
	DeleteAt int64  `json:"delete_at"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										51
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -16,18 +16,12 @@ import (
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ROLE_SYSTEM_ADMIN          = "system_admin"
 | 
			
		||||
	USER_AWAY_TIMEOUT          = 5 * 60 * 1000 // 5 minutes
 | 
			
		||||
	USER_OFFLINE_TIMEOUT       = 1 * 60 * 1000 // 1 minute
 | 
			
		||||
	USER_OFFLINE               = "offline"
 | 
			
		||||
	USER_AWAY                  = "away"
 | 
			
		||||
	USER_ONLINE                = "online"
 | 
			
		||||
	USER_NOTIFY_ALL            = "all"
 | 
			
		||||
	USER_NOTIFY_MENTION        = "mention"
 | 
			
		||||
	USER_NOTIFY_NONE           = "none"
 | 
			
		||||
	DEFAULT_LOCALE             = "en"
 | 
			
		||||
	USER_AUTH_SERVICE_EMAIL    = "email"
 | 
			
		||||
	USER_AUTH_SERVICE_USERNAME = "username"
 | 
			
		||||
	MIN_PASSWORD_LENGTH        = 5
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type User struct {
 | 
			
		||||
@@ -45,12 +39,9 @@ type User struct {
 | 
			
		||||
	FirstName          string    `json:"first_name"`
 | 
			
		||||
	LastName           string    `json:"last_name"`
 | 
			
		||||
	Roles              string    `json:"roles"`
 | 
			
		||||
	LastActivityAt     int64     `json:"last_activity_at,omitempty"`
 | 
			
		||||
	LastPingAt         int64     `json:"last_ping_at,omitempty"`
 | 
			
		||||
	AllowMarketing     bool      `json:"allow_marketing,omitempty"`
 | 
			
		||||
	Props              StringMap `json:"props,omitempty"`
 | 
			
		||||
	NotifyProps        StringMap `json:"notify_props,omitempty"`
 | 
			
		||||
	ThemeProps         StringMap `json:"theme_props,omitempty"`
 | 
			
		||||
	LastPasswordUpdate int64     `json:"last_password_update,omitempty"`
 | 
			
		||||
	LastPictureUpdate  int64     `json:"last_picture_update,omitempty"`
 | 
			
		||||
	FailedAttempts     int       `json:"failed_attempts,omitempty"`
 | 
			
		||||
@@ -95,10 +86,6 @@ func (u *User) IsValid() *AppError {
 | 
			
		||||
		return NewLocAppError("User.IsValid", "model.user.is_valid.last_name.app_error", nil, "user_id="+u.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(u.Password) > 128 {
 | 
			
		||||
		return NewLocAppError("User.IsValid", "model.user.is_valid.pwd.app_error", nil, "user_id="+u.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if u.AuthData != nil && len(*u.AuthData) > 128 {
 | 
			
		||||
		return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data.app_error", nil, "user_id="+u.Id)
 | 
			
		||||
	}
 | 
			
		||||
@@ -111,10 +98,6 @@ func (u *User) IsValid() *AppError {
 | 
			
		||||
		return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(u.ThemeProps) > 2000 {
 | 
			
		||||
		return NewLocAppError("User.IsValid", "model.user.is_valid.theme.app_error", nil, "user_id="+u.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -136,7 +119,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 +148,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 == "" {
 | 
			
		||||
@@ -191,10 +172,10 @@ func (u *User) PreUpdate() {
 | 
			
		||||
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
 | 
			
		||||
	u.NotifyProps["all"] = "true"
 | 
			
		||||
	u.NotifyProps["channel"] = "true"
 | 
			
		||||
 | 
			
		||||
	if u.FirstName == "" {
 | 
			
		||||
@@ -230,16 +211,8 @@ func (u *User) ToJson() string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generate a valid strong etag so the browser can cache the results
 | 
			
		||||
func (u *User) Etag() string {
 | 
			
		||||
	return Etag(u.Id, u.UpdateAt)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *User) IsOffline() bool {
 | 
			
		||||
	return (GetMillis()-u.LastPingAt) > USER_OFFLINE_TIMEOUT && (GetMillis()-u.LastActivityAt) > USER_OFFLINE_TIMEOUT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *User) IsAway() bool {
 | 
			
		||||
	return (GetMillis() - u.LastActivityAt) > USER_AWAY_TIMEOUT
 | 
			
		||||
func (u *User) Etag(showFullName, showEmail bool) string {
 | 
			
		||||
	return Etag(u.Id, u.UpdateAt, showFullName, showEmail)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove any private data from the user object
 | 
			
		||||
@@ -270,11 +243,9 @@ func (u *User) ClearNonProfileFields() {
 | 
			
		||||
	u.MfaActive = false
 | 
			
		||||
	u.MfaSecret = ""
 | 
			
		||||
	u.EmailVerified = false
 | 
			
		||||
	u.LastPingAt = 0
 | 
			
		||||
	u.AllowMarketing = false
 | 
			
		||||
	u.Props = StringMap{}
 | 
			
		||||
	u.NotifyProps = StringMap{}
 | 
			
		||||
	u.ThemeProps = StringMap{}
 | 
			
		||||
	u.LastPasswordUpdate = 0
 | 
			
		||||
	u.LastPictureUpdate = 0
 | 
			
		||||
	u.FailedAttempts = 0
 | 
			
		||||
@@ -349,13 +320,13 @@ func isValidRole(role string) bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Make sure you acually want to use this function. In context.go there are functions to check permssions
 | 
			
		||||
// Make sure you acually want to use this function. In context.go there are functions to check permissions
 | 
			
		||||
// This function should not be used to check permissions.
 | 
			
		||||
func (u *User) IsInRole(inRole string) bool {
 | 
			
		||||
	return IsInRole(u.Roles, inRole)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Make sure you acually want to use this function. In context.go there are functions to check permssions
 | 
			
		||||
// Make sure you acually want to use this function. In context.go there are functions to check permissions
 | 
			
		||||
// This function should not be used to check permissions.
 | 
			
		||||
func IsInRole(userRoles string, inRole string) bool {
 | 
			
		||||
	roles := strings.Split(userRoles, " ")
 | 
			
		||||
@@ -384,17 +355,6 @@ func (u *User) IsLDAPUser() bool {
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (u *User) PreExport() {
 | 
			
		||||
	u.Password = ""
 | 
			
		||||
	u.AuthData = new(string)
 | 
			
		||||
	*u.AuthData = ""
 | 
			
		||||
	u.LastActivityAt = 0
 | 
			
		||||
	u.LastPingAt = 0
 | 
			
		||||
	u.LastPasswordUpdate = 0
 | 
			
		||||
	u.LastPictureUpdate = 0
 | 
			
		||||
	u.FailedAttempts = 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UserFromJson will decode the input and return a User
 | 
			
		||||
func UserFromJson(data io.Reader) *User {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
@@ -453,6 +413,7 @@ var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`)
 | 
			
		||||
var restrictedUsernames = []string{
 | 
			
		||||
	"all",
 | 
			
		||||
	"channel",
 | 
			
		||||
	"matterbot",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsValidUsername(s string) bool {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -20,6 +20,13 @@ import (
 | 
			
		||||
	"github.com/pborman/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"
 | 
			
		||||
	UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 | 
			
		||||
	NUMBERS           = "0123456789"
 | 
			
		||||
	SYMBOLS           = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type StringInterface map[string]interface{}
 | 
			
		||||
type StringMap map[string]string
 | 
			
		||||
type StringArray []string
 | 
			
		||||
@@ -29,10 +36,10 @@ type AppError struct {
 | 
			
		||||
	Id            string                 `json:"id"`
 | 
			
		||||
	Message       string                 `json:"message"`               // Message to be display to the end user without debugging information
 | 
			
		||||
	DetailedError string                 `json:"detailed_error"`        // Internal error string to help the developer
 | 
			
		||||
	RequestId     string                 `json:"request_id"`     // The RequestId that's also set in the header
 | 
			
		||||
	StatusCode    int                    `json:"status_code"`    // The http status code
 | 
			
		||||
	RequestId     string                 `json:"request_id,omitempty"`  // The RequestId that's also set in the header
 | 
			
		||||
	StatusCode    int                    `json:"status_code,omitempty"` // The http status code
 | 
			
		||||
	Where         string                 `json:"-"`                     // The function where it happened in the form of Struct.Func
 | 
			
		||||
	IsOAuth       bool                   `json:"is_oauth"`       // Whether the error is OAuth specific
 | 
			
		||||
	IsOAuth       bool                   `json:"is_oauth,omitempty"`    // Whether the error is OAuth specific
 | 
			
		||||
	params        map[string]interface{} `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -315,10 +322,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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -13,6 +13,9 @@ import (
 | 
			
		||||
// It should be maitained in chronological order with most current
 | 
			
		||||
// release at the front of the list.
 | 
			
		||||
var versions = []string{
 | 
			
		||||
	"3.3.0",
 | 
			
		||||
	"3.2.0",
 | 
			
		||||
	"3.1.0",
 | 
			
		||||
	"3.0.0",
 | 
			
		||||
	"2.2.0",
 | 
			
		||||
	"2.1.0",
 | 
			
		||||
@@ -33,6 +36,7 @@ var CurrentVersion string = versions[0]
 | 
			
		||||
var BuildNumber string
 | 
			
		||||
var BuildDate string
 | 
			
		||||
var BuildHash string
 | 
			
		||||
var BuildHashEnterprise string
 | 
			
		||||
var BuildEnterpriseReady string
 | 
			
		||||
var versionsWithoutHotFixes []string
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										109
									
								
								vendor/github.com/mattermost/platform/model/websocket_client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								vendor/github.com/mattermost/platform/model/websocket_client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type WebSocketClient struct {
 | 
			
		||||
	Url             string          // The location of the server like "ws://localhost:8065"
 | 
			
		||||
	ApiUrl          string          // The api location of the server like "ws://localhost:8065/api/v3"
 | 
			
		||||
	Conn            *websocket.Conn // The WebSocket connection
 | 
			
		||||
	AuthToken       string          // The token used to open the WebSocket
 | 
			
		||||
	Sequence        int64           // The ever-incrementing sequence attached to each WebSocket action
 | 
			
		||||
	EventChannel    chan *WebSocketEvent
 | 
			
		||||
	ResponseChannel chan *WebSocketResponse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewWebSocketClient constructs a new WebSocket client with convienence
 | 
			
		||||
// methods for talking to the server.
 | 
			
		||||
func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) {
 | 
			
		||||
	header := http.Header{}
 | 
			
		||||
	header.Set(HEADER_AUTH, "BEARER "+authToken)
 | 
			
		||||
	conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", header)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &WebSocketClient{
 | 
			
		||||
		url,
 | 
			
		||||
		url + API_URL_SUFFIX,
 | 
			
		||||
		conn,
 | 
			
		||||
		authToken,
 | 
			
		||||
		1,
 | 
			
		||||
		make(chan *WebSocketEvent, 100),
 | 
			
		||||
		make(chan *WebSocketResponse, 100),
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wsc *WebSocketClient) Connect() *AppError {
 | 
			
		||||
	header := http.Header{}
 | 
			
		||||
	header.Set(HEADER_AUTH, "BEARER "+wsc.AuthToken)
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	wsc.Conn, _, err = websocket.DefaultDialer.Dial(wsc.ApiUrl+"/users/websocket", header)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wsc *WebSocketClient) Close() {
 | 
			
		||||
	wsc.Conn.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wsc *WebSocketClient) Listen() {
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			var rawMsg json.RawMessage
 | 
			
		||||
			var err error
 | 
			
		||||
			if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var event WebSocketEvent
 | 
			
		||||
			if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() {
 | 
			
		||||
				wsc.EventChannel <- &event
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var response WebSocketResponse
 | 
			
		||||
			if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() {
 | 
			
		||||
				wsc.ResponseChannel <- &response
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (wsc *WebSocketClient) SendMessage(action string, data map[string]interface{}) {
 | 
			
		||||
	req := &WebSocketRequest{}
 | 
			
		||||
	req.Seq = wsc.Sequence
 | 
			
		||||
	req.Action = action
 | 
			
		||||
	req.Data = data
 | 
			
		||||
 | 
			
		||||
	wsc.Sequence++
 | 
			
		||||
 | 
			
		||||
	wsc.Conn.WriteJSON(req)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UserTyping will push a user_typing event out to all connected users
 | 
			
		||||
// who are in the specified channel
 | 
			
		||||
func (wsc *WebSocketClient) UserTyping(channelId, parentId string) {
 | 
			
		||||
	data := map[string]interface{}{
 | 
			
		||||
		"channel_id": channelId,
 | 
			
		||||
		"parent_id":  parentId,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wsc.SendMessage("user_typing", data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetStatuses will return a map of string statuses using user id as the key
 | 
			
		||||
func (wsc *WebSocketClient) GetStatuses() {
 | 
			
		||||
	wsc.SendMessage("get_statuses", nil)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								vendor/github.com/mattermost/platform/model/websocket_message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								vendor/github.com/mattermost/platform/model/websocket_message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	WEBSOCKET_EVENT_TYPING             = "typing"
 | 
			
		||||
	WEBSOCKET_EVENT_POSTED             = "posted"
 | 
			
		||||
	WEBSOCKET_EVENT_POST_EDITED        = "post_edited"
 | 
			
		||||
	WEBSOCKET_EVENT_POST_DELETED       = "post_deleted"
 | 
			
		||||
	WEBSOCKET_EVENT_CHANNEL_DELETED    = "channel_deleted"
 | 
			
		||||
	WEBSOCKET_EVENT_CHANNEL_VIEWED     = "channel_viewed"
 | 
			
		||||
	WEBSOCKET_EVENT_DIRECT_ADDED       = "direct_added"
 | 
			
		||||
	WEBSOCKET_EVENT_NEW_USER           = "new_user"
 | 
			
		||||
	WEBSOCKET_EVENT_LEAVE_TEAM         = "leave_team"
 | 
			
		||||
	WEBSOCKET_EVENT_USER_ADDED         = "user_added"
 | 
			
		||||
	WEBSOCKET_EVENT_USER_REMOVED       = "user_removed"
 | 
			
		||||
	WEBSOCKET_EVENT_PREFERENCE_CHANGED = "preference_changed"
 | 
			
		||||
	WEBSOCKET_EVENT_EPHEMERAL_MESSAGE  = "ephemeral_message"
 | 
			
		||||
	WEBSOCKET_EVENT_STATUS_CHANGE      = "status_change"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type WebSocketMessage interface {
 | 
			
		||||
	ToJson() string
 | 
			
		||||
	IsValid() bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type WebSocketEvent struct {
 | 
			
		||||
	TeamId    string                 `json:"team_id"`
 | 
			
		||||
	ChannelId string                 `json:"channel_id"`
 | 
			
		||||
	UserId    string                 `json:"user_id"`
 | 
			
		||||
	Event     string                 `json:"event"`
 | 
			
		||||
	Data      map[string]interface{} `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *WebSocketEvent) Add(key string, value interface{}) {
 | 
			
		||||
	m.Data[key] = value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewWebSocketEvent(teamId string, channelId string, userId string, event string) *WebSocketEvent {
 | 
			
		||||
	return &WebSocketEvent{TeamId: teamId, ChannelId: channelId, UserId: userId, Event: event, Data: make(map[string]interface{})}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *WebSocketEvent) IsValid() bool {
 | 
			
		||||
	return o.Event != ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *WebSocketEvent) ToJson() string {
 | 
			
		||||
	b, err := json.Marshal(o)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	} else {
 | 
			
		||||
		return string(b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WebSocketEventFromJson(data io.Reader) *WebSocketEvent {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
	var o WebSocketEvent
 | 
			
		||||
	err := decoder.Decode(&o)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return &o
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type WebSocketResponse struct {
 | 
			
		||||
	Status   string                 `json:"status"`
 | 
			
		||||
	SeqReply int64                  `json:"seq_reply,omitempty"`
 | 
			
		||||
	Data     map[string]interface{} `json:"data,omitempty"`
 | 
			
		||||
	Error    *AppError              `json:"error,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *WebSocketResponse) Add(key string, value interface{}) {
 | 
			
		||||
	m.Data[key] = value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewWebSocketResponse(status string, seqReply int64, data map[string]interface{}) *WebSocketResponse {
 | 
			
		||||
	return &WebSocketResponse{Status: status, SeqReply: seqReply, Data: data}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewWebSocketError(seqReply int64, err *AppError) *WebSocketResponse {
 | 
			
		||||
	return &WebSocketResponse{Status: STATUS_FAIL, SeqReply: seqReply, Error: err}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *WebSocketResponse) IsValid() bool {
 | 
			
		||||
	return o.Status != ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *WebSocketResponse) ToJson() string {
 | 
			
		||||
	b, err := json.Marshal(o)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	} else {
 | 
			
		||||
		return string(b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
	var o WebSocketResponse
 | 
			
		||||
	err := decoder.Decode(&o)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return &o
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								vendor/github.com/mattermost/platform/model/websocket_request.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								vendor/github.com/mattermost/platform/model/websocket_request.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
 | 
			
		||||
// See License.txt for license information.
 | 
			
		||||
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
 | 
			
		||||
	goi18n "github.com/nicksnyder/go-i18n/i18n"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type WebSocketRequest struct {
 | 
			
		||||
	// Client-provided fields
 | 
			
		||||
	Seq    int64                  `json:"seq"`
 | 
			
		||||
	Action string                 `json:"action"`
 | 
			
		||||
	Data   map[string]interface{} `json:"data"`
 | 
			
		||||
 | 
			
		||||
	// Server-provided fields
 | 
			
		||||
	Session Session              `json:"-"`
 | 
			
		||||
	T       goi18n.TranslateFunc `json:"-"`
 | 
			
		||||
	Locale  string               `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *WebSocketRequest) ToJson() string {
 | 
			
		||||
	b, err := json.Marshal(o)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	} else {
 | 
			
		||||
		return string(b)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func WebSocketRequestFromJson(data io.Reader) *WebSocketRequest {
 | 
			
		||||
	decoder := json.NewDecoder(data)
 | 
			
		||||
	var o WebSocketRequest
 | 
			
		||||
	err := decoder.Decode(&o)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return &o
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
Copyright (c) 2009 The Go Authors. All rights reserved.
 | 
			
		||||
 | 
			
		||||
Redistribution and use in source and binary forms, with or without
 | 
			
		||||
modification, are permitted provided that the following conditions are
 | 
			
		||||
met:
 | 
			
		||||
 | 
			
		||||
   * Redistributions of source code must retain the above copyright
 | 
			
		||||
notice, this list of conditions and the following disclaimer.
 | 
			
		||||
   * Redistributions in binary form must reproduce the above
 | 
			
		||||
copyright notice, this list of conditions and the following disclaimer
 | 
			
		||||
in the documentation and/or other materials provided with the
 | 
			
		||||
distribution.
 | 
			
		||||
   * Neither the name of Google Inc. nor the names of its
 | 
			
		||||
contributors may be used to endorse or promote products derived from
 | 
			
		||||
this software without specific prior written permission.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
			
		||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | 
			
		||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 | 
			
		||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | 
			
		||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
			
		||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
			
		||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
			
		||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
							
								
								
									
										881
									
								
								vendor/github.com/mattn/go-xmpp/xmpp.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										881
									
								
								vendor/github.com/mattn/go-xmpp/xmpp.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,881 @@
 | 
			
		||||
// Copyright 2011 The Go Authors.  All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a BSD-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
// TODO(rsc):
 | 
			
		||||
//	More precise error handling.
 | 
			
		||||
//	Presence functionality.
 | 
			
		||||
// TODO(mattn):
 | 
			
		||||
//  Add proxy authentication.
 | 
			
		||||
 | 
			
		||||
// Package xmpp implements a simple Google Talk client
 | 
			
		||||
// using the XMPP protocol described in RFC 3920 and RFC 3921.
 | 
			
		||||
package xmpp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/md5"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"math/big"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	nsStream  = "http://etherx.jabber.org/streams"
 | 
			
		||||
	nsTLS     = "urn:ietf:params:xml:ns:xmpp-tls"
 | 
			
		||||
	nsSASL    = "urn:ietf:params:xml:ns:xmpp-sasl"
 | 
			
		||||
	nsBind    = "urn:ietf:params:xml:ns:xmpp-bind"
 | 
			
		||||
	nsClient  = "jabber:client"
 | 
			
		||||
	nsSession = "urn:ietf:params:xml:ns:xmpp-session"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Default TLS configuration options
 | 
			
		||||
var DefaultConfig tls.Config
 | 
			
		||||
 | 
			
		||||
// Cookie is a unique XMPP session identifier
 | 
			
		||||
type Cookie uint64
 | 
			
		||||
 | 
			
		||||
func getCookie() Cookie {
 | 
			
		||||
	var buf [8]byte
 | 
			
		||||
	if _, err := rand.Reader.Read(buf[:]); err != nil {
 | 
			
		||||
		panic("Failed to read random bytes: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	return Cookie(binary.LittleEndian.Uint64(buf[:]))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Client holds XMPP connection opitons
 | 
			
		||||
type Client struct {
 | 
			
		||||
	conn   net.Conn // connection to server
 | 
			
		||||
	jid    string   // Jabber ID for our connection
 | 
			
		||||
	domain string
 | 
			
		||||
	p      *xml.Decoder
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) JID() string {
 | 
			
		||||
	return c.jid
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func connect(host, user, passwd string) (net.Conn, error) {
 | 
			
		||||
	addr := host
 | 
			
		||||
 | 
			
		||||
	if strings.TrimSpace(host) == "" {
 | 
			
		||||
		a := strings.SplitN(user, "@", 2)
 | 
			
		||||
		if len(a) == 2 {
 | 
			
		||||
			addr = a[1]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	a := strings.SplitN(host, ":", 2)
 | 
			
		||||
	if len(a) == 1 {
 | 
			
		||||
		addr += ":5222"
 | 
			
		||||
	}
 | 
			
		||||
	proxy := os.Getenv("HTTP_PROXY")
 | 
			
		||||
	if proxy == "" {
 | 
			
		||||
		proxy = os.Getenv("http_proxy")
 | 
			
		||||
	}
 | 
			
		||||
	if proxy != "" {
 | 
			
		||||
		url, err := url.Parse(proxy)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			addr = url.Host
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	c, err := net.Dial("tcp", addr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if proxy != "" {
 | 
			
		||||
		fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\n", host)
 | 
			
		||||
		fmt.Fprintf(c, "Host: %s\r\n", host)
 | 
			
		||||
		fmt.Fprintf(c, "\r\n")
 | 
			
		||||
		br := bufio.NewReader(c)
 | 
			
		||||
		req, _ := http.NewRequest("CONNECT", host, nil)
 | 
			
		||||
		resp, err := http.ReadResponse(br, req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if resp.StatusCode != 200 {
 | 
			
		||||
			f := strings.SplitN(resp.Status, " ", 2)
 | 
			
		||||
			return nil, errors.New(f[1])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return c, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Options are used to specify additional options for new clients, such as a Resource.
 | 
			
		||||
type Options struct {
 | 
			
		||||
	// Host specifies what host to connect to, as either "hostname" or "hostname:port"
 | 
			
		||||
	// If host is not specified, the  DNS SRV should be used to find the host from the domainpart of the JID.
 | 
			
		||||
	// Default the port to 5222.
 | 
			
		||||
	Host string
 | 
			
		||||
 | 
			
		||||
	// User specifies what user to authenticate to the remote server.
 | 
			
		||||
	User string
 | 
			
		||||
 | 
			
		||||
	// Password supplies the password to use for authentication with the remote server.
 | 
			
		||||
	Password string
 | 
			
		||||
 | 
			
		||||
	// Resource specifies an XMPP client resource, like "bot", instead of accepting one
 | 
			
		||||
	// from the server.  Use "" to let the server generate one for your client.
 | 
			
		||||
	Resource string
 | 
			
		||||
 | 
			
		||||
	// OAuthScope provides go-xmpp the required scope for OAuth2 authentication.
 | 
			
		||||
	OAuthScope string
 | 
			
		||||
 | 
			
		||||
	// OAuthToken provides go-xmpp with the required OAuth2 token used to authenticate
 | 
			
		||||
	OAuthToken string
 | 
			
		||||
 | 
			
		||||
	// OAuthXmlNs provides go-xmpp with the required namespaced used for OAuth2 authentication.  This is
 | 
			
		||||
	// provided to the server as the xmlns:auth attribute of the OAuth2 authentication request.
 | 
			
		||||
	OAuthXmlNs string
 | 
			
		||||
 | 
			
		||||
	// TLS Config
 | 
			
		||||
	TLSConfig *tls.Config
 | 
			
		||||
 | 
			
		||||
	// InsecureAllowUnencryptedAuth permits authentication over a TCP connection that has not been promoted to
 | 
			
		||||
	// TLS by STARTTLS; this could leak authentication information over the network, or permit man in the middle
 | 
			
		||||
	// attacks.
 | 
			
		||||
	InsecureAllowUnencryptedAuth bool
 | 
			
		||||
 | 
			
		||||
	// NoTLS directs go-xmpp to not use TLS initially to contact the server; instead, a plain old unencrypted
 | 
			
		||||
	// TCP connection should be used. (Can be combined with StartTLS to support STARTTLS-based servers.)
 | 
			
		||||
	NoTLS bool
 | 
			
		||||
 | 
			
		||||
	// StartTLS directs go-xmpp to STARTTLS if the server supports it; go-xmpp will automatically STARTTLS
 | 
			
		||||
	// if the server requires it regardless of this option.
 | 
			
		||||
	StartTLS bool
 | 
			
		||||
 | 
			
		||||
	// Debug output
 | 
			
		||||
	Debug bool
 | 
			
		||||
 | 
			
		||||
	// Use server sessions
 | 
			
		||||
	Session bool
 | 
			
		||||
 | 
			
		||||
	// Presence Status
 | 
			
		||||
	Status string
 | 
			
		||||
 | 
			
		||||
	// Status message
 | 
			
		||||
	StatusMessage string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewClient establishes a new Client connection based on a set of Options.
 | 
			
		||||
func (o Options) NewClient() (*Client, error) {
 | 
			
		||||
	host := o.Host
 | 
			
		||||
	c, err := connect(host, o.User, o.Password)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.LastIndex(o.Host, ":") > 0 {
 | 
			
		||||
		host = host[:strings.LastIndex(o.Host, ":")]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := new(Client)
 | 
			
		||||
	if o.NoTLS {
 | 
			
		||||
		client.conn = c
 | 
			
		||||
	} else {
 | 
			
		||||
		var tlsconn *tls.Conn
 | 
			
		||||
		if o.TLSConfig != nil {
 | 
			
		||||
			tlsconn = tls.Client(c, o.TLSConfig)
 | 
			
		||||
		} else {
 | 
			
		||||
			DefaultConfig.ServerName = host
 | 
			
		||||
			tlsconn = tls.Client(c, &DefaultConfig)
 | 
			
		||||
		}
 | 
			
		||||
		if err = tlsconn.Handshake(); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		insecureSkipVerify := DefaultConfig.InsecureSkipVerify
 | 
			
		||||
		if o.TLSConfig != nil {
 | 
			
		||||
			insecureSkipVerify = o.TLSConfig.InsecureSkipVerify
 | 
			
		||||
		}
 | 
			
		||||
		if !insecureSkipVerify {
 | 
			
		||||
			if err = tlsconn.VerifyHostname(host); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		client.conn = tlsconn
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := client.init(&o); err != nil {
 | 
			
		||||
		client.Close()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewClient creates a new connection to a host given as "hostname" or "hostname:port".
 | 
			
		||||
// If host is not specified, the  DNS SRV should be used to find the host from the domainpart of the JID.
 | 
			
		||||
// Default the port to 5222.
 | 
			
		||||
func NewClient(host, user, passwd string, debug bool) (*Client, error) {
 | 
			
		||||
	opts := Options{
 | 
			
		||||
		Host:     host,
 | 
			
		||||
		User:     user,
 | 
			
		||||
		Password: passwd,
 | 
			
		||||
		Debug:    debug,
 | 
			
		||||
		Session:  false,
 | 
			
		||||
	}
 | 
			
		||||
	return opts.NewClient()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewClientNoTLS creates a new client without TLS
 | 
			
		||||
func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) {
 | 
			
		||||
	opts := Options{
 | 
			
		||||
		Host:     host,
 | 
			
		||||
		User:     user,
 | 
			
		||||
		Password: passwd,
 | 
			
		||||
		NoTLS:    true,
 | 
			
		||||
		Debug:    debug,
 | 
			
		||||
		Session:  false,
 | 
			
		||||
	}
 | 
			
		||||
	return opts.NewClient()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes the XMPP connection
 | 
			
		||||
func (c *Client) Close() error {
 | 
			
		||||
	if c.conn != (*tls.Conn)(nil) {
 | 
			
		||||
		return c.conn.Close()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string {
 | 
			
		||||
	h := func(text string) []byte {
 | 
			
		||||
		h := md5.New()
 | 
			
		||||
		h.Write([]byte(text))
 | 
			
		||||
		return h.Sum(nil)
 | 
			
		||||
	}
 | 
			
		||||
	hex := func(bytes []byte) string {
 | 
			
		||||
		return fmt.Sprintf("%x", bytes)
 | 
			
		||||
	}
 | 
			
		||||
	kd := func(secret, data string) []byte {
 | 
			
		||||
		return h(secret + ":" + data)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr
 | 
			
		||||
	a2 := authenticate + ":" + digestURI
 | 
			
		||||
	response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2))))
 | 
			
		||||
	return response
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cnonce() string {
 | 
			
		||||
	randSize := big.NewInt(0)
 | 
			
		||||
	randSize.Lsh(big.NewInt(1), 64)
 | 
			
		||||
	cn, err := rand.Int(rand.Reader, randSize)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%016x", cn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) init(o *Options) error {
 | 
			
		||||
 | 
			
		||||
	var domain string
 | 
			
		||||
	var user string
 | 
			
		||||
	a := strings.SplitN(o.User, "@", 2)
 | 
			
		||||
	if len(o.User) > 0 {
 | 
			
		||||
		if len(a) != 2 {
 | 
			
		||||
			return errors.New("xmpp: invalid username (want user@domain): " + o.User)
 | 
			
		||||
		}
 | 
			
		||||
		user = a[0]
 | 
			
		||||
		domain = a[1]
 | 
			
		||||
	} // Otherwise, we'll be attempting ANONYMOUS
 | 
			
		||||
 | 
			
		||||
	// Declare intent to be a jabber client and gather stream features.
 | 
			
		||||
	f, err := c.startStream(o, domain)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If the server requires we STARTTLS, attempt to do so.
 | 
			
		||||
	if f, err = c.startTLSIfRequired(f, o, domain); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.User == "" && o.Password == "" {
 | 
			
		||||
		foundAnonymous := false
 | 
			
		||||
		for _, m := range f.Mechanisms.Mechanism {
 | 
			
		||||
			if m == "ANONYMOUS" {
 | 
			
		||||
				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='ANONYMOUS' />\n", nsSASL)
 | 
			
		||||
				foundAnonymous = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !foundAnonymous {
 | 
			
		||||
			return fmt.Errorf("ANONYMOUS authentication is not an option and username and password were not specified")
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Even digest forms of authentication are unsafe if we do not know that the host
 | 
			
		||||
		// we are talking to is the actual server, and not a man in the middle playing
 | 
			
		||||
		// proxy.
 | 
			
		||||
		if !c.IsEncrypted() && !o.InsecureAllowUnencryptedAuth {
 | 
			
		||||
			return errors.New("refusing to authenticate over unencrypted TCP connection")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		mechanism := ""
 | 
			
		||||
		for _, m := range f.Mechanisms.Mechanism {
 | 
			
		||||
			if m == "X-OAUTH2" && o.OAuthToken != "" && o.OAuthScope != "" {
 | 
			
		||||
				mechanism = m
 | 
			
		||||
				// Oauth authentication: send base64-encoded \x00 user \x00 token.
 | 
			
		||||
				raw := "\x00" + user + "\x00" + o.OAuthToken
 | 
			
		||||
				enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
 | 
			
		||||
				base64.StdEncoding.Encode(enc, []byte(raw))
 | 
			
		||||
				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='X-OAUTH2' auth:service='oauth2' "+
 | 
			
		||||
					"xmlns:auth='%s'>%s</auth>\n", nsSASL, o.OAuthXmlNs, enc)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			if m == "PLAIN" {
 | 
			
		||||
				mechanism = m
 | 
			
		||||
				// Plain authentication: send base64-encoded \x00 user \x00 password.
 | 
			
		||||
				raw := "\x00" + user + "\x00" + o.Password
 | 
			
		||||
				enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
 | 
			
		||||
				base64.StdEncoding.Encode(enc, []byte(raw))
 | 
			
		||||
				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n", nsSASL, enc)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			if m == "DIGEST-MD5" {
 | 
			
		||||
				mechanism = m
 | 
			
		||||
				// Digest-MD5 authentication
 | 
			
		||||
				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", nsSASL)
 | 
			
		||||
				var ch saslChallenge
 | 
			
		||||
				if err = c.p.DecodeElement(&ch, nil); err != nil {
 | 
			
		||||
					return errors.New("unmarshal <challenge>: " + err.Error())
 | 
			
		||||
				}
 | 
			
		||||
				b, err := base64.StdEncoding.DecodeString(string(ch))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				tokens := map[string]string{}
 | 
			
		||||
				for _, token := range strings.Split(string(b), ",") {
 | 
			
		||||
					kv := strings.SplitN(strings.TrimSpace(token), "=", 2)
 | 
			
		||||
					if len(kv) == 2 {
 | 
			
		||||
						if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' {
 | 
			
		||||
							kv[1] = kv[1][1 : len(kv[1])-1]
 | 
			
		||||
						}
 | 
			
		||||
						tokens[kv[0]] = kv[1]
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				realm, _ := tokens["realm"]
 | 
			
		||||
				nonce, _ := tokens["nonce"]
 | 
			
		||||
				qop, _ := tokens["qop"]
 | 
			
		||||
				charset, _ := tokens["charset"]
 | 
			
		||||
				cnonceStr := cnonce()
 | 
			
		||||
				digestURI := "xmpp/" + domain
 | 
			
		||||
				nonceCount := fmt.Sprintf("%08x", 1)
 | 
			
		||||
				digest := saslDigestResponse(user, realm, o.Password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount)
 | 
			
		||||
				message := "username=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr +
 | 
			
		||||
					"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset
 | 
			
		||||
 | 
			
		||||
				fmt.Fprintf(c.conn, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message)))
 | 
			
		||||
 | 
			
		||||
				var rspauth saslRspAuth
 | 
			
		||||
				if err = c.p.DecodeElement(&rspauth, nil); err != nil {
 | 
			
		||||
					return errors.New("unmarshal <challenge>: " + err.Error())
 | 
			
		||||
				}
 | 
			
		||||
				b, err = base64.StdEncoding.DecodeString(string(rspauth))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				fmt.Fprintf(c.conn, "<response xmlns='%s'/>\n", nsSASL)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if mechanism == "" {
 | 
			
		||||
			return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// Next message should be either success or failure.
 | 
			
		||||
	name, val, err := next(c.p)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	switch v := val.(type) {
 | 
			
		||||
	case *saslSuccess:
 | 
			
		||||
	case *saslFailure:
 | 
			
		||||
		// v.Any is type of sub-element in failure,
 | 
			
		||||
		// which gives a description of what failed.
 | 
			
		||||
		return errors.New("auth failure: " + v.Any.Local)
 | 
			
		||||
	default:
 | 
			
		||||
		return errors.New("expected <success> or <failure>, got <" + name.Local + "> in " + name.Space)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now that we're authenticated, we're supposed to start the stream over again.
 | 
			
		||||
	// Declare intent to be a jabber client.
 | 
			
		||||
	if f, err = c.startStream(o, domain); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Generate a unique cookie
 | 
			
		||||
	cookie := getCookie()
 | 
			
		||||
 | 
			
		||||
	// Send IQ message asking to bind to the local user name.
 | 
			
		||||
	if o.Resource == "" {
 | 
			
		||||
		fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'></bind></iq>\n", cookie, nsBind)
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'><resource>%s</resource></bind></iq>\n", cookie, nsBind, o.Resource)
 | 
			
		||||
	}
 | 
			
		||||
	var iq clientIQ
 | 
			
		||||
	if err = c.p.DecodeElement(&iq, nil); err != nil {
 | 
			
		||||
		return errors.New("unmarshal <iq>: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	if &iq.Bind == nil {
 | 
			
		||||
		return errors.New("<iq> result missing <bind>")
 | 
			
		||||
	}
 | 
			
		||||
	c.jid = iq.Bind.Jid // our local id
 | 
			
		||||
	c.domain = domain
 | 
			
		||||
 | 
			
		||||
	if o.Session {
 | 
			
		||||
		//if server support session, open it
 | 
			
		||||
		fmt.Fprintf(c.conn, "<iq to='%s' type='set' id='%x'><session xmlns='%s'/></iq>", xmlEscape(domain), cookie, nsSession)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// We're connected and can now receive and send messages.
 | 
			
		||||
	fmt.Fprintf(c.conn, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", o.Status, o.StatusMessage)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// startTlsIfRequired examines the server's stream features and, if STARTTLS is required or supported, performs the TLS handshake.
 | 
			
		||||
// f will be updated if the handshake completes, as the new stream's features are typically different from the original.
 | 
			
		||||
func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string) (*streamFeatures, error) {
 | 
			
		||||
	// whether we start tls is a matter of opinion: the server's and the user's.
 | 
			
		||||
	switch {
 | 
			
		||||
	case f.StartTLS == nil:
 | 
			
		||||
		// the server does not support STARTTLS
 | 
			
		||||
		return f, nil
 | 
			
		||||
	case f.StartTLS.Required != nil:
 | 
			
		||||
		// the server requires STARTTLS.
 | 
			
		||||
	case !o.StartTLS:
 | 
			
		||||
		// the user wants STARTTLS and the server supports it.
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(c.conn, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n")
 | 
			
		||||
	var k tlsProceed
 | 
			
		||||
	if err = c.p.DecodeElement(&k, nil); err != nil {
 | 
			
		||||
		return f, errors.New("unmarshal <proceed>: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tc := o.TLSConfig
 | 
			
		||||
	if tc == nil {
 | 
			
		||||
		tc = new(tls.Config)
 | 
			
		||||
		*tc = DefaultConfig
 | 
			
		||||
		//TODO(scott): we should consider using the server's address or reverse lookup
 | 
			
		||||
		tc.ServerName = domain
 | 
			
		||||
	}
 | 
			
		||||
	t := tls.Client(c.conn, tc)
 | 
			
		||||
 | 
			
		||||
	if err = t.Handshake(); err != nil {
 | 
			
		||||
		return f, errors.New("starttls handshake: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	c.conn = t
 | 
			
		||||
 | 
			
		||||
	// restart our declaration of XMPP stream intentions.
 | 
			
		||||
	tf, err := c.startStream(o, domain)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return f, err
 | 
			
		||||
	}
 | 
			
		||||
	return tf, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// startStream will start a new XML decoder for the connection, signal the start of a stream to the server and verify that the server has
 | 
			
		||||
// also started the stream; if o.Debug is true, startStream will tee decoded XML data to stderr.  The features advertised by the server
 | 
			
		||||
// will be returned.
 | 
			
		||||
func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) {
 | 
			
		||||
	if o.Debug {
 | 
			
		||||
		c.p = xml.NewDecoder(tee{c.conn, os.Stderr})
 | 
			
		||||
	} else {
 | 
			
		||||
		c.p = xml.NewDecoder(c.conn)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := fmt.Fprintf(c.conn, "<?xml version='1.0'?>\n"+
 | 
			
		||||
		"<stream:stream to='%s' xmlns='%s'\n"+
 | 
			
		||||
		" xmlns:stream='%s' version='1.0'>\n",
 | 
			
		||||
		xmlEscape(domain), nsClient, nsStream)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// We expect the server to start a <stream>.
 | 
			
		||||
	se, err := nextStart(c.p)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if se.Name.Space != nsStream || se.Name.Local != "stream" {
 | 
			
		||||
		return nil, fmt.Errorf("expected <stream> but got <%v> in %v", se.Name.Local, se.Name.Space)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now we're in the stream and can use Unmarshal.
 | 
			
		||||
	// Next message should be <features> to tell us authentication options.
 | 
			
		||||
	// See section 4.6 in RFC 3920.
 | 
			
		||||
	f := new(streamFeatures)
 | 
			
		||||
	if err = c.p.DecodeElement(f, nil); err != nil {
 | 
			
		||||
		return f, errors.New("unmarshal <features>: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	return f, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsEncrypted will return true if the client is connected using a TLS transport, either because it used.
 | 
			
		||||
// TLS to connect from the outset, or because it successfully used STARTTLS to promote a TCP connection to TLS.
 | 
			
		||||
func (c *Client) IsEncrypted() bool {
 | 
			
		||||
	_, ok := c.conn.(*tls.Conn)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Chat is an incoming or outgoing XMPP chat message.
 | 
			
		||||
type Chat struct {
 | 
			
		||||
	Remote string
 | 
			
		||||
	Type   string
 | 
			
		||||
	Text   string
 | 
			
		||||
	Roster Roster
 | 
			
		||||
	Other  []string
 | 
			
		||||
	Stamp  time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Roster []Contact
 | 
			
		||||
 | 
			
		||||
type Contact struct {
 | 
			
		||||
	Remote string
 | 
			
		||||
	Name   string
 | 
			
		||||
	Group  []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Presence is an XMPP presence notification.
 | 
			
		||||
type Presence struct {
 | 
			
		||||
	From   string
 | 
			
		||||
	To     string
 | 
			
		||||
	Type   string
 | 
			
		||||
	Show   string
 | 
			
		||||
	Status string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type IQ struct {
 | 
			
		||||
	ID    string
 | 
			
		||||
	From  string
 | 
			
		||||
	To    string
 | 
			
		||||
	Type  string
 | 
			
		||||
	Query []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Recv waits to receive the next XMPP stanza.
 | 
			
		||||
// Return type is either a presence notification or a chat message.
 | 
			
		||||
func (c *Client) Recv() (stanza interface{}, err error) {
 | 
			
		||||
	for {
 | 
			
		||||
		_, val, err := next(c.p)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return Chat{}, err
 | 
			
		||||
		}
 | 
			
		||||
		switch v := val.(type) {
 | 
			
		||||
		case *clientMessage:
 | 
			
		||||
			stamp, _ := time.Parse(
 | 
			
		||||
				"2006-01-02T15:04:05Z",
 | 
			
		||||
				v.Delay.Stamp,
 | 
			
		||||
			)
 | 
			
		||||
			chat := Chat{
 | 
			
		||||
				Remote: v.From,
 | 
			
		||||
				Type:   v.Type,
 | 
			
		||||
				Text:   v.Body,
 | 
			
		||||
				Other:  v.Other,
 | 
			
		||||
				Stamp:  stamp,
 | 
			
		||||
			}
 | 
			
		||||
			return chat, nil
 | 
			
		||||
		case *clientQuery:
 | 
			
		||||
			var r Roster
 | 
			
		||||
			for _, item := range v.Item {
 | 
			
		||||
				r = append(r, Contact{item.Jid, item.Name, item.Group})
 | 
			
		||||
			}
 | 
			
		||||
			return Chat{Type: "roster", Roster: r}, nil
 | 
			
		||||
		case *clientPresence:
 | 
			
		||||
			return Presence{v.From, v.To, v.Type, v.Show, v.Status}, nil
 | 
			
		||||
		case *clientIQ:
 | 
			
		||||
			return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Send sends the message wrapped inside an XMPP message stanza body.
 | 
			
		||||
func (c *Client) Send(chat Chat) (n int, err error) {
 | 
			
		||||
	return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<body>%s</body></message>",
 | 
			
		||||
		xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendOrg sends the original text without being wrapped in an XMPP message stanza.
 | 
			
		||||
func (c *Client) SendOrg(org string) (n int, err error) {
 | 
			
		||||
	return fmt.Fprint(c.conn, org)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) SendPresence(presence Presence) (n int, err error) {
 | 
			
		||||
	return fmt.Fprintf(c.conn, "<presence from='%s' to='%s'/>", xmlEscape(presence.From), xmlEscape(presence.To))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendHtml sends the message as HTML as defined by XEP-0071
 | 
			
		||||
func (c *Client) SendHtml(chat Chat) (n int, err error) {
 | 
			
		||||
	return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+
 | 
			
		||||
		"<body>%s</body>"+
 | 
			
		||||
		"<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html></message>",
 | 
			
		||||
		xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text), chat.Text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Roster asks for the chat roster.
 | 
			
		||||
func (c *Client) Roster() error {
 | 
			
		||||
	fmt.Fprintf(c.conn, "<iq from='%s' type='get' id='roster1'><query xmlns='jabber:iq:roster'/></iq>\n", xmlEscape(c.jid))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RFC 3920  C.1  Streams name space
 | 
			
		||||
type streamFeatures struct {
 | 
			
		||||
	XMLName    xml.Name `xml:"http://etherx.jabber.org/streams features"`
 | 
			
		||||
	StartTLS   *tlsStartTLS
 | 
			
		||||
	Mechanisms saslMechanisms
 | 
			
		||||
	Bind       bindBind
 | 
			
		||||
	Session    bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type streamError struct {
 | 
			
		||||
	XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
 | 
			
		||||
	Any     xml.Name
 | 
			
		||||
	Text    string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RFC 3920  C.3  TLS name space
 | 
			
		||||
type tlsStartTLS struct {
 | 
			
		||||
	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"`
 | 
			
		||||
	Required *string  `xml:"required"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type tlsProceed struct {
 | 
			
		||||
	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type tlsFailure struct {
 | 
			
		||||
	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls failure"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RFC 3920  C.4  SASL name space
 | 
			
		||||
type saslMechanisms struct {
 | 
			
		||||
	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
 | 
			
		||||
	Mechanism []string `xml:"mechanism"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type saslAuth struct {
 | 
			
		||||
	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
 | 
			
		||||
	Mechanism string   `xml:",attr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type saslChallenge string
 | 
			
		||||
 | 
			
		||||
type saslRspAuth string
 | 
			
		||||
 | 
			
		||||
type saslResponse string
 | 
			
		||||
 | 
			
		||||
type saslAbort struct {
 | 
			
		||||
	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl abort"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type saslSuccess struct {
 | 
			
		||||
	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type saslFailure struct {
 | 
			
		||||
	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
 | 
			
		||||
	Any     xml.Name `xml:",any"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RFC 3920  C.5  Resource binding name space
 | 
			
		||||
type bindBind struct {
 | 
			
		||||
	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
 | 
			
		||||
	Resource string
 | 
			
		||||
	Jid      string `xml:"jid"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RFC 3921  B.1  jabber:client
 | 
			
		||||
type clientMessage struct {
 | 
			
		||||
	XMLName xml.Name `xml:"jabber:client message"`
 | 
			
		||||
	From    string   `xml:"from,attr"`
 | 
			
		||||
	ID      string   `xml:"id,attr"`
 | 
			
		||||
	To      string   `xml:"to,attr"`
 | 
			
		||||
	Type    string   `xml:"type,attr"` // chat, error, groupchat, headline, or normal
 | 
			
		||||
 | 
			
		||||
	// These should technically be []clientText, but string is much more convenient.
 | 
			
		||||
	Subject string `xml:"subject"`
 | 
			
		||||
	Body    string `xml:"body"`
 | 
			
		||||
	Thread  string `xml:"thread"`
 | 
			
		||||
 | 
			
		||||
	// Any hasn't matched element
 | 
			
		||||
	Other []string `xml:",any"`
 | 
			
		||||
 | 
			
		||||
	Delay Delay `xml:"delay"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Delay struct {
 | 
			
		||||
	Stamp string `xml:"stamp,attr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type clientText struct {
 | 
			
		||||
	Lang string `xml:",attr"`
 | 
			
		||||
	Body string `xml:"chardata"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type clientPresence struct {
 | 
			
		||||
	XMLName xml.Name `xml:"jabber:client presence"`
 | 
			
		||||
	From    string   `xml:"from,attr"`
 | 
			
		||||
	ID      string   `xml:"id,attr"`
 | 
			
		||||
	To      string   `xml:"to,attr"`
 | 
			
		||||
	Type    string   `xml:"type,attr"` // error, probe, subscribe, subscribed, unavailable, unsubscribe, unsubscribed
 | 
			
		||||
	Lang    string   `xml:"lang,attr"`
 | 
			
		||||
 | 
			
		||||
	Show     string `xml:"show"`   // away, chat, dnd, xa
 | 
			
		||||
	Status   string `xml:"status"` // sb []clientText
 | 
			
		||||
	Priority string `xml:"priority,attr"`
 | 
			
		||||
	Error    *clientError
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type clientIQ struct { // info/query
 | 
			
		||||
	XMLName xml.Name `xml:"jabber:client iq"`
 | 
			
		||||
	From    string   `xml:"from,attr"`
 | 
			
		||||
	ID      string   `xml:"id,attr"`
 | 
			
		||||
	To      string   `xml:"to,attr"`
 | 
			
		||||
	Type    string   `xml:"type,attr"` // error, get, result, set
 | 
			
		||||
	Query   []byte   `xml:",innerxml"`
 | 
			
		||||
	Error   clientError
 | 
			
		||||
	Bind    bindBind
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type clientError struct {
 | 
			
		||||
	XMLName xml.Name `xml:"jabber:client error"`
 | 
			
		||||
	Code    string   `xml:",attr"`
 | 
			
		||||
	Type    string   `xml:",attr"`
 | 
			
		||||
	Any     xml.Name
 | 
			
		||||
	Text    string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type clientQuery struct {
 | 
			
		||||
	Item []rosterItem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type rosterItem struct {
 | 
			
		||||
	XMLName      xml.Name `xml:"jabber:iq:roster item"`
 | 
			
		||||
	Jid          string   `xml:",attr"`
 | 
			
		||||
	Name         string   `xml:",attr"`
 | 
			
		||||
	Subscription string   `xml:",attr"`
 | 
			
		||||
	Group        []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scan XML token stream to find next StartElement.
 | 
			
		||||
func nextStart(p *xml.Decoder) (xml.StartElement, error) {
 | 
			
		||||
	for {
 | 
			
		||||
		t, err := p.Token()
 | 
			
		||||
		if err != nil && err != io.EOF || t == nil {
 | 
			
		||||
			return xml.StartElement{}, err
 | 
			
		||||
		}
 | 
			
		||||
		switch t := t.(type) {
 | 
			
		||||
		case xml.StartElement:
 | 
			
		||||
			return t, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scan XML token stream for next element and save into val.
 | 
			
		||||
// If val == nil, allocate new element based on proto map.
 | 
			
		||||
// Either way, return val.
 | 
			
		||||
func next(p *xml.Decoder) (xml.Name, interface{}, error) {
 | 
			
		||||
	// Read start element to find out what type we want.
 | 
			
		||||
	se, err := nextStart(p)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xml.Name{}, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Put it in an interface and allocate one.
 | 
			
		||||
	var nv interface{}
 | 
			
		||||
	switch se.Name.Space + " " + se.Name.Local {
 | 
			
		||||
	case nsStream + " features":
 | 
			
		||||
		nv = &streamFeatures{}
 | 
			
		||||
	case nsStream + " error":
 | 
			
		||||
		nv = &streamError{}
 | 
			
		||||
	case nsTLS + " starttls":
 | 
			
		||||
		nv = &tlsStartTLS{}
 | 
			
		||||
	case nsTLS + " proceed":
 | 
			
		||||
		nv = &tlsProceed{}
 | 
			
		||||
	case nsTLS + " failure":
 | 
			
		||||
		nv = &tlsFailure{}
 | 
			
		||||
	case nsSASL + " mechanisms":
 | 
			
		||||
		nv = &saslMechanisms{}
 | 
			
		||||
	case nsSASL + " challenge":
 | 
			
		||||
		nv = ""
 | 
			
		||||
	case nsSASL + " response":
 | 
			
		||||
		nv = ""
 | 
			
		||||
	case nsSASL + " abort":
 | 
			
		||||
		nv = &saslAbort{}
 | 
			
		||||
	case nsSASL + " success":
 | 
			
		||||
		nv = &saslSuccess{}
 | 
			
		||||
	case nsSASL + " failure":
 | 
			
		||||
		nv = &saslFailure{}
 | 
			
		||||
	case nsBind + " bind":
 | 
			
		||||
		nv = &bindBind{}
 | 
			
		||||
	case nsClient + " message":
 | 
			
		||||
		nv = &clientMessage{}
 | 
			
		||||
	case nsClient + " presence":
 | 
			
		||||
		nv = &clientPresence{}
 | 
			
		||||
	case nsClient + " iq":
 | 
			
		||||
		nv = &clientIQ{}
 | 
			
		||||
	case nsClient + " error":
 | 
			
		||||
		nv = &clientError{}
 | 
			
		||||
	default:
 | 
			
		||||
		return xml.Name{}, nil, errors.New("unexpected XMPP message " +
 | 
			
		||||
			se.Name.Space + " <" + se.Name.Local + "/>")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Unmarshal into that storage.
 | 
			
		||||
	if err = p.DecodeElement(nv, &se); err != nil {
 | 
			
		||||
		return xml.Name{}, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return se.Name, nv, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var xmlSpecial = map[byte]string{
 | 
			
		||||
	'<':  "<",
 | 
			
		||||
	'>':  ">",
 | 
			
		||||
	'"':  """,
 | 
			
		||||
	'\'': "'",
 | 
			
		||||
	'&':  "&",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func xmlEscape(s string) string {
 | 
			
		||||
	var b bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(s); i++ {
 | 
			
		||||
		c := s[i]
 | 
			
		||||
		if s, ok := xmlSpecial[c]; ok {
 | 
			
		||||
			b.WriteString(s)
 | 
			
		||||
		} else {
 | 
			
		||||
			b.WriteByte(c)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return b.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type tee struct {
 | 
			
		||||
	r io.Reader
 | 
			
		||||
	w io.Writer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t tee) Read(p []byte) (n int, err error) {
 | 
			
		||||
	n, err = t.r.Read(p)
 | 
			
		||||
	if n > 0 {
 | 
			
		||||
		t.w.Write(p[0:n])
 | 
			
		||||
		t.w.Write([]byte("\n"))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_information_query.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_information_query.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
package xmpp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const IQTypeGet = "get"
 | 
			
		||||
const IQTypeSet = "set"
 | 
			
		||||
const IQTypeResult = "result"
 | 
			
		||||
 | 
			
		||||
func (c *Client) Discovery() (string, error) {
 | 
			
		||||
	const namespace = "http://jabber.org/protocol/disco#items"
 | 
			
		||||
	// use getCookie for a pseudo random id.
 | 
			
		||||
	reqID := strconv.FormatUint(uint64(getCookie()), 10)
 | 
			
		||||
	return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, namespace, "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RawInformationQuery sends an information query request to the server.
 | 
			
		||||
func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) {
 | 
			
		||||
	const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>"
 | 
			
		||||
	_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
 | 
			
		||||
	return id, err
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										134
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_muc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_muc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
			
		||||
// Copyright 2013 Flo Lauber <dev@qatfy.at>.  All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a BSD-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
// TODO(flo):
 | 
			
		||||
//   - support password protected MUC rooms
 | 
			
		||||
//   - cleanup signatures of join/leave functions
 | 
			
		||||
package xmpp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
	"errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	nsMUC     = "http://jabber.org/protocol/muc"
 | 
			
		||||
	nsMUCUser = "http://jabber.org/protocol/muc#user"
 | 
			
		||||
	NoHistory = 0
 | 
			
		||||
	CharHistory = 1
 | 
			
		||||
	StanzaHistory = 2
 | 
			
		||||
	SecondsHistory = 3
 | 
			
		||||
	SinceHistory = 4
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Send sends room topic wrapped inside an XMPP message stanza body.
 | 
			
		||||
func (c *Client) SendTopic(chat Chat) (n int, err error) {
 | 
			
		||||
	return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>",
 | 
			
		||||
		xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) {
 | 
			
		||||
	if nick == "" {
 | 
			
		||||
		nick = c.jid
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+
 | 
			
		||||
		"<x xmlns='%s'>"+
 | 
			
		||||
		"<history maxchars='0'/></x>\n"+
 | 
			
		||||
		"</presence>",
 | 
			
		||||
		xmlEscape(jid), xmlEscape(nick), nsMUC)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// xep-0045 7.2
 | 
			
		||||
func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_date *time.Time) (n int, err error) {
 | 
			
		||||
	if nick == "" {
 | 
			
		||||
		nick = c.jid
 | 
			
		||||
	}
 | 
			
		||||
	switch history_type {
 | 
			
		||||
	case NoHistory:
 | 
			
		||||
		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
 | 
			
		||||
			"<x xmlns='%s' />\n" +
 | 
			
		||||
			"</presence>",
 | 
			
		||||
				xmlEscape(jid), xmlEscape(nick), nsMUC)
 | 
			
		||||
	case CharHistory:
 | 
			
		||||
		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
 | 
			
		||||
			"<x xmlns='%s'>\n" +
 | 
			
		||||
			"<history maxchars='%d'/></x>\n"+
 | 
			
		||||
			"</presence>",
 | 
			
		||||
				xmlEscape(jid), xmlEscape(nick), nsMUC, history)
 | 
			
		||||
	case StanzaHistory:
 | 
			
		||||
		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
 | 
			
		||||
			"<x xmlns='%s'>\n" +
 | 
			
		||||
			"<history maxstanzas='%d'/></x>\n"+
 | 
			
		||||
			"</presence>",
 | 
			
		||||
				xmlEscape(jid), xmlEscape(nick), nsMUC, history)
 | 
			
		||||
	case SecondsHistory:
 | 
			
		||||
		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
 | 
			
		||||
			"<x xmlns='%s'>\n" +
 | 
			
		||||
			"<history seconds='%d'/></x>\n"+
 | 
			
		||||
			"</presence>",
 | 
			
		||||
				xmlEscape(jid), xmlEscape(nick), nsMUC, history)
 | 
			
		||||
	case SinceHistory:
 | 
			
		||||
		if history_date != nil {
 | 
			
		||||
			return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
 | 
			
		||||
				"<x xmlns='%s'>\n" +
 | 
			
		||||
				"<history since='%s'/></x>\n" +
 | 
			
		||||
				"</presence>",
 | 
			
		||||
					xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 0, errors.New("Unknown history option")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// xep-0045 7.2.6
 | 
			
		||||
func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_type, history int, history_date *time.Time) (n int, err error) {
 | 
			
		||||
	if nick == "" {
 | 
			
		||||
		nick = c.jid
 | 
			
		||||
	}
 | 
			
		||||
	switch history_type {
 | 
			
		||||
	case NoHistory:
 | 
			
		||||
		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
 | 
			
		||||
			"<x xmlns='%s'>\n" +
 | 
			
		||||
			"<password>%s</password>\n"+
 | 
			
		||||
			"</presence>",
 | 
			
		||||
				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
 | 
			
		||||
	case CharHistory:
 | 
			
		||||
		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
 | 
			
		||||
			"<x xmlns='%s'>\n" +
 | 
			
		||||
			"<password>%s</password>\n"+
 | 
			
		||||
			"<history maxchars='%d'/></x>\n"+
 | 
			
		||||
			"</presence>",
 | 
			
		||||
				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
 | 
			
		||||
	case StanzaHistory:
 | 
			
		||||
		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
 | 
			
		||||
			"<x xmlns='%s'>\n" +
 | 
			
		||||
			"<password>%s</password>\n"+
 | 
			
		||||
			"<history maxstanzas='%d'/></x>\n"+
 | 
			
		||||
			"</presence>",
 | 
			
		||||
				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
 | 
			
		||||
	case SecondsHistory:
 | 
			
		||||
		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
 | 
			
		||||
			"<x xmlns='%s'>\n" +
 | 
			
		||||
			"<password>%s</password>\n"+
 | 
			
		||||
			"<history seconds='%d'/></x>\n"+
 | 
			
		||||
			"</presence>",
 | 
			
		||||
				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
 | 
			
		||||
	case SinceHistory:
 | 
			
		||||
		if history_date != nil {
 | 
			
		||||
			return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" +
 | 
			
		||||
				"<x xmlns='%s'>\n" +
 | 
			
		||||
				"<password>%s</password>\n"+
 | 
			
		||||
				"<history since='%s'/></x>\n" +
 | 
			
		||||
				"</presence>",
 | 
			
		||||
					xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 0, errors.New("Unknown history option")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// xep-0045 7.14
 | 
			
		||||
func (c *Client) LeaveMUC(jid string) (n int, err error) {
 | 
			
		||||
	return fmt.Fprintf(c.conn, "<presence from='%s' to='%s' type='unavailable' />",
 | 
			
		||||
		c.jid, xmlEscape(jid))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_ping.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_ping.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
package xmpp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (c *Client) PingC2S(jid, server string) error {
 | 
			
		||||
	if jid == "" {
 | 
			
		||||
		jid = c.jid
 | 
			
		||||
	}
 | 
			
		||||
	if server == "" {
 | 
			
		||||
		server = c.domain
 | 
			
		||||
	}
 | 
			
		||||
	_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+
 | 
			
		||||
		"<ping xmlns='urn:xmpp:ping'/>\n"+
 | 
			
		||||
		"</iq>",
 | 
			
		||||
		xmlEscape(jid), xmlEscape(server))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) PingS2S(fromServer, toServer string) error {
 | 
			
		||||
	_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+
 | 
			
		||||
		"<ping xmlns='urn:xmpp:ping'/>\n"+
 | 
			
		||||
		"</iq>",
 | 
			
		||||
		xmlEscape(fromServer), xmlEscape(toServer))
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_subscription.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_subscription.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
package xmpp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (c *Client) ApproveSubscription(jid string) {
 | 
			
		||||
	fmt.Fprintf(c.conn, "<presence to='%s' type='subscribed'/>",
 | 
			
		||||
		xmlEscape(jid))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) RevokeSubscription(jid string) {
 | 
			
		||||
	fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribed'/>",
 | 
			
		||||
		xmlEscape(jid))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Client) RequestSubscription(jid string) {
 | 
			
		||||
	fmt.Fprintf(c.conn, "<presence to='%s' type='subscribe'/>",
 | 
			
		||||
		xmlEscape(jid))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								vendor/github.com/mreiferson/go-httpclient/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/mreiferson/go-httpclient/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
The MIT License (MIT)
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2012 Matt Reiferson
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in
 | 
			
		||||
all copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
THE SOFTWARE.
 | 
			
		||||
							
								
								
									
										237
									
								
								vendor/github.com/mreiferson/go-httpclient/httpclient.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								vendor/github.com/mreiferson/go-httpclient/httpclient.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,237 @@
 | 
			
		||||
/*
 | 
			
		||||
Provides an HTTP Transport that implements the `RoundTripper` interface and
 | 
			
		||||
can be used as a built in replacement for the standard library's, providing:
 | 
			
		||||
 | 
			
		||||
	* connection timeouts
 | 
			
		||||
	* request timeouts
 | 
			
		||||
 | 
			
		||||
This is a thin wrapper around `http.Transport` that sets dial timeouts and uses
 | 
			
		||||
Go's internal timer scheduler to call the Go 1.1+ `CancelRequest()` API.
 | 
			
		||||
*/
 | 
			
		||||
package httpclient
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// returns the current version of the package
 | 
			
		||||
func Version() string {
 | 
			
		||||
	return "0.4.1"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Transport implements the RoundTripper interface and can be used as a replacement
 | 
			
		||||
// for Go's built in http.Transport implementing end-to-end request timeouts.
 | 
			
		||||
//
 | 
			
		||||
// 	transport := &httpclient.Transport{
 | 
			
		||||
// 	    ConnectTimeout: 1*time.Second,
 | 
			
		||||
// 	    ResponseHeaderTimeout: 5*time.Second,
 | 
			
		||||
// 	    RequestTimeout: 10*time.Second,
 | 
			
		||||
// 	}
 | 
			
		||||
// 	defer transport.Close()
 | 
			
		||||
//
 | 
			
		||||
// 	client := &http.Client{Transport: transport}
 | 
			
		||||
// 	req, _ := http.NewRequest("GET", "http://127.0.0.1/test", nil)
 | 
			
		||||
// 	resp, err := client.Do(req)
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 	    return err
 | 
			
		||||
// 	}
 | 
			
		||||
// 	defer resp.Body.Close()
 | 
			
		||||
//
 | 
			
		||||
type Transport struct {
 | 
			
		||||
	// Proxy specifies a function to return a proxy for a given
 | 
			
		||||
	// *http.Request. If the function returns a non-nil error, the
 | 
			
		||||
	// request is aborted with the provided error.
 | 
			
		||||
	// If Proxy is nil or returns a nil *url.URL, no proxy is used.
 | 
			
		||||
	Proxy func(*http.Request) (*url.URL, error)
 | 
			
		||||
 | 
			
		||||
	// Dial specifies the dial function for creating TCP
 | 
			
		||||
	// connections. This will override the Transport's ConnectTimeout and
 | 
			
		||||
	// ReadWriteTimeout settings.
 | 
			
		||||
	// If Dial is nil, a dialer is generated on demand matching the Transport's
 | 
			
		||||
	// options.
 | 
			
		||||
	Dial func(network, addr string) (net.Conn, error)
 | 
			
		||||
 | 
			
		||||
	// TLSClientConfig specifies the TLS configuration to use with
 | 
			
		||||
	// tls.Client. If nil, the default configuration is used.
 | 
			
		||||
	TLSClientConfig *tls.Config
 | 
			
		||||
 | 
			
		||||
	// DisableKeepAlives, if true, prevents re-use of TCP connections
 | 
			
		||||
	// between different HTTP requests.
 | 
			
		||||
	DisableKeepAlives bool
 | 
			
		||||
 | 
			
		||||
	// DisableCompression, if true, prevents the Transport from
 | 
			
		||||
	// requesting compression with an "Accept-Encoding: gzip"
 | 
			
		||||
	// request header when the Request contains no existing
 | 
			
		||||
	// Accept-Encoding value. If the Transport requests gzip on
 | 
			
		||||
	// its own and gets a gzipped response, it's transparently
 | 
			
		||||
	// decoded in the Response.Body. However, if the user
 | 
			
		||||
	// explicitly requested gzip it is not automatically
 | 
			
		||||
	// uncompressed.
 | 
			
		||||
	DisableCompression bool
 | 
			
		||||
 | 
			
		||||
	// MaxIdleConnsPerHost, if non-zero, controls the maximum idle
 | 
			
		||||
	// (keep-alive) to keep per-host.  If zero,
 | 
			
		||||
	// http.DefaultMaxIdleConnsPerHost is used.
 | 
			
		||||
	MaxIdleConnsPerHost int
 | 
			
		||||
 | 
			
		||||
	// ConnectTimeout, if non-zero, is the maximum amount of time a dial will wait for
 | 
			
		||||
	// a connect to complete.
 | 
			
		||||
	ConnectTimeout time.Duration
 | 
			
		||||
 | 
			
		||||
	// ResponseHeaderTimeout, if non-zero, specifies the amount of
 | 
			
		||||
	// time to wait for a server's response headers after fully
 | 
			
		||||
	// writing the request (including its body, if any). This
 | 
			
		||||
	// time does not include the time to read the response body.
 | 
			
		||||
	ResponseHeaderTimeout time.Duration
 | 
			
		||||
 | 
			
		||||
	// RequestTimeout, if non-zero, specifies the amount of time for the entire
 | 
			
		||||
	// request to complete (including all of the above timeouts + entire response body).
 | 
			
		||||
	// This should never be less than the sum total of the above two timeouts.
 | 
			
		||||
	RequestTimeout time.Duration
 | 
			
		||||
 | 
			
		||||
	// ReadWriteTimeout, if non-zero, will set a deadline for every Read and
 | 
			
		||||
	// Write operation on the request connection.
 | 
			
		||||
	ReadWriteTimeout time.Duration
 | 
			
		||||
 | 
			
		||||
	// TCPWriteBufferSize, the size of the operating system's write
 | 
			
		||||
	// buffer associated with the connection.
 | 
			
		||||
	TCPWriteBufferSize int
 | 
			
		||||
 | 
			
		||||
	// TCPReadBuffserSize, the size of the operating system's read
 | 
			
		||||
	// buffer associated with the connection.
 | 
			
		||||
	TCPReadBufferSize int
 | 
			
		||||
 | 
			
		||||
	starter   sync.Once
 | 
			
		||||
	transport *http.Transport
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close cleans up the Transport, currently a no-op
 | 
			
		||||
func (t *Transport) Close() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Transport) lazyStart() {
 | 
			
		||||
	if t.Dial == nil {
 | 
			
		||||
		t.Dial = func(netw, addr string) (net.Conn, error) {
 | 
			
		||||
			c, err := net.DialTimeout(netw, addr, t.ConnectTimeout)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if t.TCPReadBufferSize != 0 || t.TCPWriteBufferSize != 0 {
 | 
			
		||||
				if tcpCon, ok := c.(*net.TCPConn); ok {
 | 
			
		||||
					if t.TCPWriteBufferSize != 0 {
 | 
			
		||||
						if err = tcpCon.SetWriteBuffer(t.TCPWriteBufferSize); err != nil {
 | 
			
		||||
							return nil, err
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					if t.TCPReadBufferSize != 0 {
 | 
			
		||||
						if err = tcpCon.SetReadBuffer(t.TCPReadBufferSize); err != nil {
 | 
			
		||||
							return nil, err
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					err = errors.New("Not Tcp Connection")
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if t.ReadWriteTimeout > 0 {
 | 
			
		||||
				timeoutConn := &rwTimeoutConn{
 | 
			
		||||
					TCPConn:   c.(*net.TCPConn),
 | 
			
		||||
					rwTimeout: t.ReadWriteTimeout,
 | 
			
		||||
				}
 | 
			
		||||
				return timeoutConn, nil
 | 
			
		||||
			}
 | 
			
		||||
			return c, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.transport = &http.Transport{
 | 
			
		||||
		Dial:                  t.Dial,
 | 
			
		||||
		Proxy:                 t.Proxy,
 | 
			
		||||
		TLSClientConfig:       t.TLSClientConfig,
 | 
			
		||||
		DisableKeepAlives:     t.DisableKeepAlives,
 | 
			
		||||
		DisableCompression:    t.DisableCompression,
 | 
			
		||||
		MaxIdleConnsPerHost:   t.MaxIdleConnsPerHost,
 | 
			
		||||
		ResponseHeaderTimeout: t.ResponseHeaderTimeout,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Transport) CancelRequest(req *http.Request) {
 | 
			
		||||
	t.starter.Do(t.lazyStart)
 | 
			
		||||
 | 
			
		||||
	t.transport.CancelRequest(req)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Transport) CloseIdleConnections() {
 | 
			
		||||
	t.starter.Do(t.lazyStart)
 | 
			
		||||
 | 
			
		||||
	t.transport.CloseIdleConnections()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Transport) RegisterProtocol(scheme string, rt http.RoundTripper) {
 | 
			
		||||
	t.starter.Do(t.lazyStart)
 | 
			
		||||
 | 
			
		||||
	t.transport.RegisterProtocol(scheme, rt)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
 | 
			
		||||
	t.starter.Do(t.lazyStart)
 | 
			
		||||
 | 
			
		||||
	if t.RequestTimeout > 0 {
 | 
			
		||||
		timer := time.AfterFunc(t.RequestTimeout, func() {
 | 
			
		||||
			t.transport.CancelRequest(req)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		resp, err = t.transport.RoundTrip(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			timer.Stop()
 | 
			
		||||
		} else {
 | 
			
		||||
			resp.Body = &bodyCloseInterceptor{ReadCloser: resp.Body, timer: timer}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		resp, err = t.transport.RoundTrip(req)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type bodyCloseInterceptor struct {
 | 
			
		||||
	io.ReadCloser
 | 
			
		||||
	timer *time.Timer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (bci *bodyCloseInterceptor) Close() error {
 | 
			
		||||
	bci.timer.Stop()
 | 
			
		||||
	return bci.ReadCloser.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A net.Conn that sets a deadline for every Read or Write operation
 | 
			
		||||
type rwTimeoutConn struct {
 | 
			
		||||
	*net.TCPConn
 | 
			
		||||
	rwTimeout time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *rwTimeoutConn) Read(b []byte) (int, error) {
 | 
			
		||||
	err := c.TCPConn.SetDeadline(time.Now().Add(c.rwTimeout))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return c.TCPConn.Read(b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *rwTimeoutConn) Write(b []byte) (int, error) {
 | 
			
		||||
	err := c.TCPConn.SetDeadline(time.Now().Add(c.rwTimeout))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return c.TCPConn.Write(b)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								vendor/github.com/mrexodia/wray/examples/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/mrexodia/wray/examples/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import "github.com/pythonandchips/wray"
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	wray.RegisterTransports([]wray.Transport{&wray.HttpTransport{}})
 | 
			
		||||
	client := wray.NewFayeClient("http://localhost:5000/faye")
 | 
			
		||||
 | 
			
		||||
	fmt.Println("subscribing")
 | 
			
		||||
	client.Subscribe("/foo", false, func(message wray.Message) {
 | 
			
		||||
		fmt.Println("-------------------------------------------")
 | 
			
		||||
		fmt.Println(message.Data)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	client.Listen()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								vendor/github.com/mrexodia/wray/examples/publish.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/mrexodia/wray/examples/publish.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import "github.com/pythonandchips/wray"
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
  wray.RegisterTransports([]wray.Transport{ &gofaye.HttpTransport{} })
 | 
			
		||||
  client := wray.NewFayeClient("http://localhost:5000/faye")
 | 
			
		||||
 | 
			
		||||
  params := map[string]interface{}{"hello": "from golang"}
 | 
			
		||||
  fmt.Println("sending")
 | 
			
		||||
  client.Publish("/foo", params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										140
									
								
								vendor/github.com/mrexodia/wray/go_faye.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								vendor/github.com/mrexodia/wray/go_faye.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
package wray
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	UNCONNECTED  = 1
 | 
			
		||||
	CONNECTING   = 2
 | 
			
		||||
	CONNECTED    = 3
 | 
			
		||||
	DISCONNECTED = 4
 | 
			
		||||
 | 
			
		||||
	HANDSHAKE = "handshake"
 | 
			
		||||
	RETRY     = "retry"
 | 
			
		||||
	NONE      = "none"
 | 
			
		||||
 | 
			
		||||
	CONNECTION_TIMEOUT = 60.0
 | 
			
		||||
	DEFAULT_RETRY      = 5.0
 | 
			
		||||
	MAX_REQUEST_SIZE   = 2048
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	MANDATORY_CONNECTION_TYPES = []string{"long-polling"}
 | 
			
		||||
	registeredTransports       = []Transport{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type FayeClient struct {
 | 
			
		||||
	state         int
 | 
			
		||||
	url           string
 | 
			
		||||
	subscriptions []Subscription
 | 
			
		||||
	transport     Transport
 | 
			
		||||
	clientId      string
 | 
			
		||||
	schedular     Schedular
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Subscription struct {
 | 
			
		||||
	channel  string
 | 
			
		||||
	callback func(Message)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SubscriptionPromise struct {
 | 
			
		||||
	subscription Subscription
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFayeClient(url string) *FayeClient {
 | 
			
		||||
	schedular := ChannelSchedular{}
 | 
			
		||||
	client := &FayeClient{url: url, state: UNCONNECTED, schedular: schedular}
 | 
			
		||||
	return client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *FayeClient) handshake() {
 | 
			
		||||
	t, err := SelectTransport(self, MANDATORY_CONNECTION_TYPES, []string{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic("No usable transports available")
 | 
			
		||||
	}
 | 
			
		||||
	self.transport = t
 | 
			
		||||
	self.transport.setUrl(self.url)
 | 
			
		||||
	self.state = CONNECTING
 | 
			
		||||
	handshakeParams := map[string]interface{}{"channel": "/meta/handshake",
 | 
			
		||||
		"version":                  "1.0",
 | 
			
		||||
		"supportedConnectionTypes": []string{"long-polling"}}
 | 
			
		||||
	response, err := self.transport.send(handshakeParams)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("Handshake failed. Retry in 10 seconds")
 | 
			
		||||
		self.state = UNCONNECTED
 | 
			
		||||
		self.schedular.wait(10*time.Second, func() {
 | 
			
		||||
			fmt.Println("retying handshake")
 | 
			
		||||
			self.handshake()
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	self.clientId = response.clientId
 | 
			
		||||
	self.state = CONNECTED
 | 
			
		||||
	self.transport, err = SelectTransport(self, response.supportedConnectionTypes, []string{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic("Server does not support any available transports. Supported transports: " + strings.Join(response.supportedConnectionTypes, ","))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *FayeClient) Subscribe(channel string, force bool, callback func(Message)) SubscriptionPromise {
 | 
			
		||||
	if self.state == UNCONNECTED {
 | 
			
		||||
		self.handshake()
 | 
			
		||||
	}
 | 
			
		||||
	subscriptionParams := map[string]interface{}{"channel": "/meta/subscribe", "clientId": self.clientId, "subscription": channel, "id": "1"}
 | 
			
		||||
	subscription := Subscription{channel: channel, callback: callback}
 | 
			
		||||
	//TODO: deal with subscription failures
 | 
			
		||||
	self.transport.send(subscriptionParams)
 | 
			
		||||
	self.subscriptions = append(self.subscriptions, subscription)
 | 
			
		||||
	return SubscriptionPromise{subscription}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *FayeClient) handleResponse(response Response) {
 | 
			
		||||
	for _, message := range response.messages {
 | 
			
		||||
		for _, subscription := range self.subscriptions {
 | 
			
		||||
			matched, _ := filepath.Match(subscription.channel, message.Channel)
 | 
			
		||||
			if matched {
 | 
			
		||||
				go subscription.callback(message)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *FayeClient) connect() {
 | 
			
		||||
	connectParams := map[string]interface{}{"channel": "/meta/connect", "clientId": self.clientId, "connectionType": self.transport.connectionType()}
 | 
			
		||||
	responseChannel := make(chan Response)
 | 
			
		||||
	go func() {
 | 
			
		||||
		response, _ := self.transport.send(connectParams)
 | 
			
		||||
		responseChannel <- response
 | 
			
		||||
	}()
 | 
			
		||||
	self.listen(responseChannel)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *FayeClient) listen(responseChannel chan Response) {
 | 
			
		||||
	response := <-responseChannel
 | 
			
		||||
	if response.successful == true {
 | 
			
		||||
		go self.handleResponse(response)
 | 
			
		||||
	} else {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *FayeClient) Listen() {
 | 
			
		||||
	for {
 | 
			
		||||
		self.connect()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *FayeClient) Publish(channel string, data map[string]interface{}) {
 | 
			
		||||
	if self.state != CONNECTED {
 | 
			
		||||
		self.handshake()
 | 
			
		||||
	}
 | 
			
		||||
	publishParams := map[string]interface{}{"channel": channel, "data": data, "clientId": self.clientId}
 | 
			
		||||
	self.transport.send(publishParams)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RegisterTransports(transports []Transport) {
 | 
			
		||||
	registeredTransports = transports
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								vendor/github.com/mrexodia/wray/http_transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								vendor/github.com/mrexodia/wray/http_transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
package wray
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type HttpTransport struct {
 | 
			
		||||
	url      string
 | 
			
		||||
	SendHook func(data map[string]interface{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self HttpTransport) isUsable(clientUrl string) bool {
 | 
			
		||||
	parsedUrl, err := url.Parse(clientUrl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if parsedUrl.Scheme == "http" || parsedUrl.Scheme == "https" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self HttpTransport) connectionType() string {
 | 
			
		||||
	return "long-polling"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self HttpTransport) send(data map[string]interface{}) (Response, error) {
 | 
			
		||||
	if self.SendHook != nil {
 | 
			
		||||
		self.SendHook(data)
 | 
			
		||||
	}
 | 
			
		||||
	dataBytes, _ := json.Marshal(data)
 | 
			
		||||
	buffer := bytes.NewBuffer(dataBytes)
 | 
			
		||||
	responseData, err := http.Post(self.url, "application/json", buffer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return Response{}, err
 | 
			
		||||
	}
 | 
			
		||||
	if responseData.StatusCode != 200 {
 | 
			
		||||
		return Response{}, errors.New(responseData.Status)
 | 
			
		||||
	}
 | 
			
		||||
	readData, _ := ioutil.ReadAll(responseData.Body)
 | 
			
		||||
	responseData.Body.Close()
 | 
			
		||||
	var jsonData []interface{}
 | 
			
		||||
	json.Unmarshal(readData, &jsonData)
 | 
			
		||||
	response := newResponse(jsonData)
 | 
			
		||||
	return response, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *HttpTransport) setUrl(url string) {
 | 
			
		||||
	self.url = url
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								vendor/github.com/mrexodia/wray/response.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								vendor/github.com/mrexodia/wray/response.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
package wray
 | 
			
		||||
 | 
			
		||||
type Response struct {
 | 
			
		||||
  id string
 | 
			
		||||
  channel string
 | 
			
		||||
  successful bool
 | 
			
		||||
  clientId string
 | 
			
		||||
  supportedConnectionTypes []string
 | 
			
		||||
  messages []Message
 | 
			
		||||
  error error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Message struct {
 | 
			
		||||
  Channel string
 | 
			
		||||
  Id string
 | 
			
		||||
  Data map[string]interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newResponse(data []interface{}) Response {
 | 
			
		||||
  headerData := data[0].(map[string]interface{})
 | 
			
		||||
  messagesData := data[1.:]
 | 
			
		||||
  messages := parseMessages(messagesData)
 | 
			
		||||
  var id string
 | 
			
		||||
  if headerData["id"] != nil {
 | 
			
		||||
    id = headerData["id"].(string)
 | 
			
		||||
  }
 | 
			
		||||
  supportedConnectionTypes := []string{}
 | 
			
		||||
  if headerData["supportedConnectionTypes"] != nil {
 | 
			
		||||
    d := headerData["supportedConnectionTypes"].([]interface{})
 | 
			
		||||
    for _, sct := range(d) {
 | 
			
		||||
      supportedConnectionTypes = append(supportedConnectionTypes, sct.(string))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  var clientId string
 | 
			
		||||
  if headerData["clientId"] != nil {
 | 
			
		||||
    clientId = headerData["clientId"].(string)
 | 
			
		||||
  }
 | 
			
		||||
  return Response{id: id,
 | 
			
		||||
                  clientId: clientId,
 | 
			
		||||
                  channel: headerData["channel"].(string),
 | 
			
		||||
                  successful: headerData["successful"].(bool),
 | 
			
		||||
                  messages: messages,
 | 
			
		||||
                  supportedConnectionTypes: supportedConnectionTypes}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseMessages(data []interface{}) []Message {
 | 
			
		||||
  messages := []Message{}
 | 
			
		||||
  for _, messageData := range(data) {
 | 
			
		||||
    m := messageData.(map[string]interface{})
 | 
			
		||||
    var id string
 | 
			
		||||
    if m["id"] != nil {
 | 
			
		||||
      id = m["id"].(string)
 | 
			
		||||
    }
 | 
			
		||||
    message := Message{Channel: m["channel"].(string),
 | 
			
		||||
                       Id: id,
 | 
			
		||||
                       Data: m["data"].(map[string]interface{})}
 | 
			
		||||
    messages = append(messages, message)
 | 
			
		||||
  }
 | 
			
		||||
  return messages
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								vendor/github.com/mrexodia/wray/schedular.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/mrexodia/wray/schedular.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package wray
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type Schedular interface {
 | 
			
		||||
	wait(time.Duration, func())
 | 
			
		||||
	delay() time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ChannelSchedular struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self ChannelSchedular) wait(delay time.Duration, callback func()) {
 | 
			
		||||
	go func() {
 | 
			
		||||
		time.Sleep(delay)
 | 
			
		||||
		callback()
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self ChannelSchedular) delay() time.Duration {
 | 
			
		||||
	return (1 * time.Minute)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								vendor/github.com/mrexodia/wray/transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/mrexodia/wray/transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
package wray
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Transport interface {
 | 
			
		||||
	isUsable(string) bool
 | 
			
		||||
	connectionType() string
 | 
			
		||||
	send(map[string]interface{}) (Response, error)
 | 
			
		||||
	setUrl(string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SelectTransport(client *FayeClient, transportTypes []string, disabled []string) (Transport, error) {
 | 
			
		||||
	for _, transport := range registeredTransports {
 | 
			
		||||
		if contains(transport.connectionType(), transportTypes) && transport.isUsable(client.url) {
 | 
			
		||||
			return transport, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil, errors.New("No usable transports available")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								vendor/github.com/mrexodia/wray/utils.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/mrexodia/wray/utils.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
package wray
 | 
			
		||||
 | 
			
		||||
func contains(target string, slice []string) bool {
 | 
			
		||||
  for _, t := range(slice) {
 | 
			
		||||
    if t == target {
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
@@ -199,4 +199,3 @@
 | 
			
		||||
   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.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										70
									
								
								vendor/github.com/sromku/go-gitter/faye.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								vendor/github.com/sromku/go-gitter/faye.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
package gitter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/mrexodia/wray"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Faye struct {
 | 
			
		||||
	endpoint string
 | 
			
		||||
	Event    chan Event
 | 
			
		||||
	client   *wray.FayeClient
 | 
			
		||||
	gitter   *Gitter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (gitter *Gitter) Faye(roomID string) *Faye {
 | 
			
		||||
	wray.RegisterTransports([]wray.Transport{
 | 
			
		||||
		&wray.HttpTransport{
 | 
			
		||||
			SendHook: func(data map[string]interface{}) {
 | 
			
		||||
				if channel, ok := data["channel"]; ok && channel == "/meta/handshake" {
 | 
			
		||||
					data["ext"] = map[string]interface{}{"token": gitter.config.token}
 | 
			
		||||
				}
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	return &Faye{
 | 
			
		||||
		endpoint: "/api/v1/rooms/" + roomID + "/chatMessages",
 | 
			
		||||
		Event:    make(chan Event),
 | 
			
		||||
		client:   wray.NewFayeClient(fayeBaseURL),
 | 
			
		||||
		gitter:   gitter,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (faye *Faye) Listen() {
 | 
			
		||||
	defer faye.destroy()
 | 
			
		||||
 | 
			
		||||
	faye.client.Subscribe(faye.endpoint, false, func(message wray.Message) {
 | 
			
		||||
		dataBytes, err := json.Marshal(message.Data["model"])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Printf("JSON Marshal error: %v\n", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		var gitterMessage Message
 | 
			
		||||
		err = json.Unmarshal(dataBytes, &gitterMessage)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Printf("JSON Unmarshal error: %v\n", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		faye.Event <- Event{
 | 
			
		||||
			Data: &MessageReceived{
 | 
			
		||||
				Message: gitterMessage,
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	//TODO: this might be needed in the future
 | 
			
		||||
	/*go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			faye.client.Publish("/api/v1/ping2", map[string]interface{}{"reason": "ping"})
 | 
			
		||||
			time.Sleep(60 * time.Second)
 | 
			
		||||
		}
 | 
			
		||||
	}()*/
 | 
			
		||||
 | 
			
		||||
	faye.client.Listen()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (faye *Faye) destroy() {
 | 
			
		||||
	close(faye.Event)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										367
									
								
								vendor/github.com/sromku/go-gitter/gitter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										367
									
								
								vendor/github.com/sromku/go-gitter/gitter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,367 @@
 | 
			
		||||
// Package gitter is a Go client library for the Gitter API.
 | 
			
		||||
//
 | 
			
		||||
// Author: sromku
 | 
			
		||||
package gitter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/mreiferson/go-httpclient"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	apiBaseURL    = "https://api.gitter.im/v1/"
 | 
			
		||||
	streamBaseURL = "https://stream.gitter.im/v1/"
 | 
			
		||||
	fayeBaseURL   = "https://ws.gitter.im/faye"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Gitter struct {
 | 
			
		||||
	config struct {
 | 
			
		||||
		apiBaseURL    string
 | 
			
		||||
		streamBaseURL string
 | 
			
		||||
		token         string
 | 
			
		||||
		client        *http.Client
 | 
			
		||||
	}
 | 
			
		||||
	debug     bool
 | 
			
		||||
	logWriter io.Writer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New initializes the Gitter API client
 | 
			
		||||
//
 | 
			
		||||
// For example:
 | 
			
		||||
//  api := gitter.New("YOUR_ACCESS_TOKEN")
 | 
			
		||||
func New(token string) *Gitter {
 | 
			
		||||
 | 
			
		||||
	transport := &httpclient.Transport{
 | 
			
		||||
		ConnectTimeout:   5 * time.Second,
 | 
			
		||||
		ReadWriteTimeout: 40 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
	defer transport.Close()
 | 
			
		||||
 | 
			
		||||
	s := &Gitter{}
 | 
			
		||||
	s.config.apiBaseURL = apiBaseURL
 | 
			
		||||
	s.config.streamBaseURL = streamBaseURL
 | 
			
		||||
	s.config.token = token
 | 
			
		||||
	s.config.client = &http.Client{
 | 
			
		||||
		Transport: transport,
 | 
			
		||||
	}
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetClient sets a custom http client. Can be useful in App Engine case.
 | 
			
		||||
func (gitter *Gitter) SetClient(client *http.Client) {
 | 
			
		||||
	gitter.config.client = client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUser returns the current user
 | 
			
		||||
func (gitter *Gitter) GetUser() (*User, error) {
 | 
			
		||||
 | 
			
		||||
	var users []User
 | 
			
		||||
	response, err := gitter.get(gitter.config.apiBaseURL + "user")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = json.Unmarshal(response, &users)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(users) > 0 {
 | 
			
		||||
		return &users[0], nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = APIError{What: "Failed to retrieve current user"}
 | 
			
		||||
	gitter.log(err)
 | 
			
		||||
	return nil, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUserRooms returns a list of Rooms the user is part of
 | 
			
		||||
func (gitter *Gitter) GetUserRooms(userID string) ([]Room, error) {
 | 
			
		||||
 | 
			
		||||
	var rooms []Room
 | 
			
		||||
	response, err := gitter.get(gitter.config.apiBaseURL + "user/" + userID + "/rooms")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = json.Unmarshal(response, &rooms)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return rooms, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetRooms returns a list of rooms the current user is in
 | 
			
		||||
func (gitter *Gitter) GetRooms() ([]Room, error) {
 | 
			
		||||
 | 
			
		||||
	var rooms []Room
 | 
			
		||||
	response, err := gitter.get(gitter.config.apiBaseURL + "rooms")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = json.Unmarshal(response, &rooms)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return rooms, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetRoom returns a room with the passed id
 | 
			
		||||
func (gitter *Gitter) GetRoom(roomID string) (*Room, error) {
 | 
			
		||||
 | 
			
		||||
	var room Room
 | 
			
		||||
	response, err := gitter.get(gitter.config.apiBaseURL + "rooms/" + roomID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = json.Unmarshal(response, &room)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &room, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMessages returns a list of messages in a room.
 | 
			
		||||
// Pagination is optional. You can pass nil or specific pagination params.
 | 
			
		||||
func (gitter *Gitter) GetMessages(roomID string, params *Pagination) ([]Message, error) {
 | 
			
		||||
 | 
			
		||||
	var messages []Message
 | 
			
		||||
	url := gitter.config.apiBaseURL + "rooms/" + roomID + "/chatMessages"
 | 
			
		||||
	if params != nil {
 | 
			
		||||
		url += "?" + params.encode()
 | 
			
		||||
	}
 | 
			
		||||
	response, err := gitter.get(url)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = json.Unmarshal(response, &messages)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return messages, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMessage returns a message in a room.
 | 
			
		||||
func (gitter *Gitter) GetMessage(roomID, messageID string) (*Message, error) {
 | 
			
		||||
 | 
			
		||||
	var message Message
 | 
			
		||||
	response, err := gitter.get(gitter.config.apiBaseURL + "rooms/" + roomID + "/chatMessages/" + messageID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = json.Unmarshal(response, &message)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &message, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SendMessage sends a message to a room
 | 
			
		||||
func (gitter *Gitter) SendMessage(roomID, text string) error {
 | 
			
		||||
 | 
			
		||||
	message := Message{Text: text}
 | 
			
		||||
	body, _ := json.Marshal(message)
 | 
			
		||||
	err := gitter.post(gitter.config.apiBaseURL+"rooms/"+roomID+"/chatMessages", body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JoinRoom joins a room
 | 
			
		||||
func (gitter *Gitter) JoinRoom(uri string) (*Room, error) {
 | 
			
		||||
 | 
			
		||||
	message := Room{URI: uri}
 | 
			
		||||
	body, _ := json.Marshal(message)
 | 
			
		||||
	err := gitter.post(apiBaseURL+"rooms", body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rooms, err := gitter.GetRooms()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, room := range rooms {
 | 
			
		||||
		if room.URI == uri {
 | 
			
		||||
			return &room, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = APIError{What: fmt.Sprintf("Joined room (%v) not found in list of rooms", uri)}
 | 
			
		||||
	gitter.log(err)
 | 
			
		||||
	return nil, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetDebug traces errors if it's set to true.
 | 
			
		||||
func (gitter *Gitter) SetDebug(debug bool, logWriter io.Writer) {
 | 
			
		||||
	gitter.debug = debug
 | 
			
		||||
	gitter.logWriter = logWriter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Pagination params
 | 
			
		||||
type Pagination struct {
 | 
			
		||||
 | 
			
		||||
	// Skip n messages
 | 
			
		||||
	Skip int
 | 
			
		||||
 | 
			
		||||
	// Get messages before beforeId
 | 
			
		||||
	BeforeID string
 | 
			
		||||
 | 
			
		||||
	// Get messages after afterId
 | 
			
		||||
	AfterID string
 | 
			
		||||
 | 
			
		||||
	// Maximum number of messages to return
 | 
			
		||||
	Limit int
 | 
			
		||||
 | 
			
		||||
	// Search query
 | 
			
		||||
	Query string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (messageParams *Pagination) encode() string {
 | 
			
		||||
	values := url.Values{}
 | 
			
		||||
 | 
			
		||||
	if messageParams.AfterID != "" {
 | 
			
		||||
		values.Add("afterId", messageParams.AfterID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if messageParams.BeforeID != "" {
 | 
			
		||||
		values.Add("beforeId", messageParams.BeforeID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if messageParams.Skip > 0 {
 | 
			
		||||
		values.Add("skip", strconv.Itoa(messageParams.Skip))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if messageParams.Limit > 0 {
 | 
			
		||||
		values.Add("limit", strconv.Itoa(messageParams.Limit))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return values.Encode()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (gitter *Gitter) getResponse(url string, stream *Stream) (*http.Response, error) {
 | 
			
		||||
	r, err := http.NewRequest("GET", url, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	r.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	r.Header.Set("Accept", "application/json")
 | 
			
		||||
	r.Header.Set("Authorization", "Bearer "+gitter.config.token)
 | 
			
		||||
	if stream != nil {
 | 
			
		||||
		stream.streamConnection.request = r
 | 
			
		||||
	}
 | 
			
		||||
	response, err := gitter.config.client.Do(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return response, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (gitter *Gitter) get(url string) ([]byte, error) {
 | 
			
		||||
	resp, err := gitter.getResponse(url, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	if resp.StatusCode != http.StatusOK {
 | 
			
		||||
		err = APIError{What: fmt.Sprintf("Status code: %v", resp.StatusCode)}
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	body, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return body, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (gitter *Gitter) post(url string, body []byte) error {
 | 
			
		||||
	r, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	r.Header.Set("Accept", "application/json")
 | 
			
		||||
	r.Header.Set("Authorization", "Bearer "+gitter.config.token)
 | 
			
		||||
 | 
			
		||||
	resp, err := gitter.config.client.Do(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	if resp.StatusCode != http.StatusOK {
 | 
			
		||||
		err = APIError{What: fmt.Sprintf("Status code: %v", resp.StatusCode)}
 | 
			
		||||
		gitter.log(err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (gitter *Gitter) log(a interface{}) {
 | 
			
		||||
	if gitter.debug {
 | 
			
		||||
		log.Println(a)
 | 
			
		||||
		if gitter.logWriter != nil {
 | 
			
		||||
			timestamp := time.Now().Format(time.RFC3339)
 | 
			
		||||
			msg := fmt.Sprintf("%v: %v", timestamp, a)
 | 
			
		||||
			fmt.Fprintln(gitter.logWriter, msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// APIError holds data of errors returned from the API.
 | 
			
		||||
type APIError struct {
 | 
			
		||||
	What string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e APIError) Error() string {
 | 
			
		||||
	return fmt.Sprintf("%v", e.What)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								vendor/github.com/sromku/go-gitter/model.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								vendor/github.com/sromku/go-gitter/model.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
package gitter
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
// A Room in Gitter can represent a GitHub Organization, a GitHub Repository, a Gitter Channel or a One-to-one conversation.
 | 
			
		||||
// In the case of the Organizations and Repositories, the access control policies are inherited from GitHub.
 | 
			
		||||
type Room struct {
 | 
			
		||||
 | 
			
		||||
	// Room ID
 | 
			
		||||
	ID string `json:"id"`
 | 
			
		||||
 | 
			
		||||
	// Room name
 | 
			
		||||
	Name string `json:"name"`
 | 
			
		||||
 | 
			
		||||
	// Room topic. (default: GitHub repo description)
 | 
			
		||||
	Topic string `json:"topic"`
 | 
			
		||||
 | 
			
		||||
	// Room URI on Gitter
 | 
			
		||||
	URI string `json:"uri"`
 | 
			
		||||
 | 
			
		||||
	// Indicates if the room is a one-to-one chat
 | 
			
		||||
	OneToOne bool `json:"oneToOne"`
 | 
			
		||||
 | 
			
		||||
	// Count of users in the room
 | 
			
		||||
	UserCount int `json:"userCount"`
 | 
			
		||||
 | 
			
		||||
	// Number of unread messages for the current user
 | 
			
		||||
	UnreadItems int `json:"unreadItems"`
 | 
			
		||||
 | 
			
		||||
	// Number of unread mentions for the current user
 | 
			
		||||
	Mentions int `json:"mentions"`
 | 
			
		||||
 | 
			
		||||
	// Last time the current user accessed the room in ISO format
 | 
			
		||||
	LastAccessTime time.Time `json:"lastAccessTime"`
 | 
			
		||||
 | 
			
		||||
	// Indicates if the current user has disabled notifications
 | 
			
		||||
	Lurk bool `json:"lurk"`
 | 
			
		||||
 | 
			
		||||
	// Path to the room on gitter
 | 
			
		||||
	URL string `json:"url"`
 | 
			
		||||
 | 
			
		||||
	// Type of the room
 | 
			
		||||
	// - ORG: A room that represents a GitHub Organization.
 | 
			
		||||
	// - REPO: A room that represents a GitHub Repository.
 | 
			
		||||
	// - ONETOONE: A one-to-one chat.
 | 
			
		||||
	// - ORG_CHANNEL: A Gitter channel nested under a GitHub Organization.
 | 
			
		||||
	// - REPO_CHANNEL A Gitter channel nested under a GitHub Repository.
 | 
			
		||||
	// - USER_CHANNEL A Gitter channel nested under a GitHub User.
 | 
			
		||||
	GithubType string `json:"githubType"`
 | 
			
		||||
 | 
			
		||||
	// Tags that define the room
 | 
			
		||||
	Tags []string `json:"tags"`
 | 
			
		||||
 | 
			
		||||
	RoomMember bool `json:"roomMember"`
 | 
			
		||||
 | 
			
		||||
	// Room version.
 | 
			
		||||
	Version int `json:"v"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type User struct {
 | 
			
		||||
 | 
			
		||||
	// Gitter User ID
 | 
			
		||||
	ID string `json:"id"`
 | 
			
		||||
 | 
			
		||||
	// Gitter/GitHub username
 | 
			
		||||
	Username string `json:"username"`
 | 
			
		||||
 | 
			
		||||
	// Gitter/GitHub user real name
 | 
			
		||||
	DisplayName string `json:"displayName"`
 | 
			
		||||
 | 
			
		||||
	// Path to the user on Gitter
 | 
			
		||||
	URL string `json:"url"`
 | 
			
		||||
 | 
			
		||||
	// User avatar URI (small)
 | 
			
		||||
	AvatarURLSmall string `json:"avatarUrlSmall"`
 | 
			
		||||
 | 
			
		||||
	// User avatar URI (medium)
 | 
			
		||||
	AvatarURLMedium string `json:"avatarUrlMedium"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Message struct {
 | 
			
		||||
 | 
			
		||||
	// ID of the message
 | 
			
		||||
	ID string `json:"id"`
 | 
			
		||||
 | 
			
		||||
	// Original message in plain-text/markdown
 | 
			
		||||
	Text string `json:"text"`
 | 
			
		||||
 | 
			
		||||
	// HTML formatted message
 | 
			
		||||
	HTML string `json:"html"`
 | 
			
		||||
 | 
			
		||||
	// ISO formatted date of the message
 | 
			
		||||
	Sent time.Time `json:"sent"`
 | 
			
		||||
 | 
			
		||||
	// ISO formatted date of the message if edited
 | 
			
		||||
	EditedAt time.Time `json:"editedAt"`
 | 
			
		||||
 | 
			
		||||
	// User that sent the message
 | 
			
		||||
	From User `json:"fromUser"`
 | 
			
		||||
 | 
			
		||||
	// Boolean that indicates if the current user has read the message.
 | 
			
		||||
	Unread bool `json:"unread"`
 | 
			
		||||
 | 
			
		||||
	// Number of users that have read the message
 | 
			
		||||
	ReadBy int `json:"readBy"`
 | 
			
		||||
 | 
			
		||||
	// List of URLs present in the message
 | 
			
		||||
	Urls []URL `json:"urls"`
 | 
			
		||||
 | 
			
		||||
	// List of @Mentions in the message
 | 
			
		||||
	Mentions []Mention `json:"mentions"`
 | 
			
		||||
 | 
			
		||||
	// List of #Issues referenced in the message
 | 
			
		||||
	Issues []Issue `json:"issues"`
 | 
			
		||||
 | 
			
		||||
	// Version
 | 
			
		||||
	Version int `json:"v"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Mention holds data about mentioned user in the message
 | 
			
		||||
type Mention struct {
 | 
			
		||||
 | 
			
		||||
	// User's username
 | 
			
		||||
	ScreenName string `json:"screenName"`
 | 
			
		||||
 | 
			
		||||
	// Gitter User ID
 | 
			
		||||
	UserID string `json:"userID"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Issue references issue in the message
 | 
			
		||||
type Issue struct {
 | 
			
		||||
 | 
			
		||||
	// Issue number
 | 
			
		||||
	Number string `json:"number"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// URL presented in the message
 | 
			
		||||
type URL struct {
 | 
			
		||||
 | 
			
		||||
	// URL
 | 
			
		||||
	URL string `json:"url"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										220
									
								
								vendor/github.com/sromku/go-gitter/stream.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								vendor/github.com/sromku/go-gitter/stream.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,220 @@
 | 
			
		||||
package gitter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/mreiferson/go-httpclient"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var defaultConnectionWaitTime time.Duration = 3000 // millis
 | 
			
		||||
var defaultConnectionMaxRetries = 5
 | 
			
		||||
 | 
			
		||||
// Stream initialize stream
 | 
			
		||||
func (gitter *Gitter) Stream(roomID string) *Stream {
 | 
			
		||||
	return &Stream{
 | 
			
		||||
		url:    streamBaseURL + "rooms/" + roomID + "/chatMessages",
 | 
			
		||||
		Event:  make(chan Event),
 | 
			
		||||
		gitter: gitter,
 | 
			
		||||
		streamConnection: gitter.newStreamConnection(
 | 
			
		||||
			defaultConnectionWaitTime,
 | 
			
		||||
			defaultConnectionMaxRetries),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Implemented to conform with https://developer.gitter.im/docs/streaming-api
 | 
			
		||||
func (gitter *Gitter) Listen(stream *Stream) {
 | 
			
		||||
 | 
			
		||||
	defer stream.destroy()
 | 
			
		||||
 | 
			
		||||
	var reader *bufio.Reader
 | 
			
		||||
	var gitterMessage Message
 | 
			
		||||
	lastKeepalive := time.Now().Unix()
 | 
			
		||||
 | 
			
		||||
	// connect
 | 
			
		||||
	stream.connect()
 | 
			
		||||
 | 
			
		||||
Loop:
 | 
			
		||||
	for {
 | 
			
		||||
 | 
			
		||||
		// if closed then stop trying
 | 
			
		||||
		if stream.isClosed() {
 | 
			
		||||
			stream.Event <- Event{
 | 
			
		||||
				Data: &GitterConnectionClosed{},
 | 
			
		||||
			}
 | 
			
		||||
			break Loop
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		resp := stream.getResponse()
 | 
			
		||||
		if resp.StatusCode != 200 {
 | 
			
		||||
			gitter.log(fmt.Sprintf("Unexpected response code %v", resp.StatusCode))
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//"The JSON stream returns messages as JSON objects that are delimited by carriage return (\r)" <- Not true crap it's (\n) only
 | 
			
		||||
		reader = bufio.NewReader(resp.Body)
 | 
			
		||||
		line, err := reader.ReadBytes('\n')
 | 
			
		||||
 | 
			
		||||
		//Check if the line only consists of whitespace
 | 
			
		||||
		onlyWhitespace := true
 | 
			
		||||
		for _, b := range line {
 | 
			
		||||
			if b != ' ' && b != '\t' && b != '\r' && b != '\n' {
 | 
			
		||||
				onlyWhitespace = false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if onlyWhitespace {
 | 
			
		||||
			//"Parsers must be tolerant of occasional extra newline characters placed between messages."
 | 
			
		||||
			currentKeepalive := time.Now().Unix() //interesting behavior of 100+ keepalives per seconds was observed
 | 
			
		||||
			if currentKeepalive-lastKeepalive > 10 {
 | 
			
		||||
				lastKeepalive = currentKeepalive
 | 
			
		||||
				gitter.log("Keepalive was received")
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		} else if stream.isClosed() {
 | 
			
		||||
			gitter.log("Stream closed")
 | 
			
		||||
			continue
 | 
			
		||||
		} else if err != nil {
 | 
			
		||||
			gitter.log("ReadBytes error: " + err.Error())
 | 
			
		||||
			stream.connect()
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// unmarshal the streamed data
 | 
			
		||||
		err = json.Unmarshal(line, &gitterMessage)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			gitter.log("JSON Unmarshal error: " + err.Error())
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// we are here, then we got the good message. pipe it forward.
 | 
			
		||||
		stream.Event <- Event{
 | 
			
		||||
			Data: &MessageReceived{
 | 
			
		||||
				Message: gitterMessage,
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gitter.log("Listening was completed")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Stream holds stream data.
 | 
			
		||||
type Stream struct {
 | 
			
		||||
	url              string
 | 
			
		||||
	Event            chan Event
 | 
			
		||||
	streamConnection *streamConnection
 | 
			
		||||
	gitter           *Gitter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (stream *Stream) destroy() {
 | 
			
		||||
	close(stream.Event)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Event struct {
 | 
			
		||||
	Data interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GitterConnectionClosed struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MessageReceived struct {
 | 
			
		||||
	Message Message
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// connect and try to reconnect with
 | 
			
		||||
func (stream *Stream) connect() {
 | 
			
		||||
 | 
			
		||||
	if stream.streamConnection.retries == stream.streamConnection.currentRetries {
 | 
			
		||||
		stream.Close()
 | 
			
		||||
		stream.gitter.log("Number of retries exceeded the max retries number, we are done here")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res, err := stream.gitter.getResponse(stream.url, stream)
 | 
			
		||||
	if stream.streamConnection.canceled {
 | 
			
		||||
		// do nothing
 | 
			
		||||
	} else if err != nil || res.StatusCode != 200 {
 | 
			
		||||
		stream.gitter.log("Failed to get response, trying reconnect ")
 | 
			
		||||
		stream.gitter.log(err)
 | 
			
		||||
 | 
			
		||||
		// sleep and wait
 | 
			
		||||
		stream.streamConnection.currentRetries++
 | 
			
		||||
		time.Sleep(time.Millisecond * stream.streamConnection.wait * time.Duration(stream.streamConnection.currentRetries))
 | 
			
		||||
 | 
			
		||||
		// connect again
 | 
			
		||||
		stream.Close()
 | 
			
		||||
		stream.connect()
 | 
			
		||||
	} else {
 | 
			
		||||
		stream.gitter.log("Response was received")
 | 
			
		||||
		stream.streamConnection.currentRetries = 0
 | 
			
		||||
		stream.streamConnection.closed = false
 | 
			
		||||
		stream.streamConnection.response = res
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type streamConnection struct {
 | 
			
		||||
 | 
			
		||||
	// connection was closed
 | 
			
		||||
	closed bool
 | 
			
		||||
 | 
			
		||||
	// canceled
 | 
			
		||||
	canceled bool
 | 
			
		||||
 | 
			
		||||
	// wait time till next try
 | 
			
		||||
	wait time.Duration
 | 
			
		||||
 | 
			
		||||
	// max tries to recover
 | 
			
		||||
	retries int
 | 
			
		||||
 | 
			
		||||
	// current streamed response
 | 
			
		||||
	response *http.Response
 | 
			
		||||
 | 
			
		||||
	// current request
 | 
			
		||||
	request *http.Request
 | 
			
		||||
 | 
			
		||||
	// current status
 | 
			
		||||
	currentRetries int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close the stream connection and stop receiving streamed data
 | 
			
		||||
func (stream *Stream) Close() {
 | 
			
		||||
	conn := stream.streamConnection
 | 
			
		||||
	conn.closed = true
 | 
			
		||||
	if conn.response != nil {
 | 
			
		||||
		stream.gitter.log("Stream connection close response")
 | 
			
		||||
		defer conn.response.Body.Close()
 | 
			
		||||
	}
 | 
			
		||||
	if conn.request != nil {
 | 
			
		||||
		stream.gitter.log("Stream connection close request")
 | 
			
		||||
		switch transport := stream.gitter.config.client.Transport.(type) {
 | 
			
		||||
		case *httpclient.Transport:
 | 
			
		||||
			stream.streamConnection.canceled = true
 | 
			
		||||
			transport.CancelRequest(conn.request)
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	conn.currentRetries = 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (stream *Stream) isClosed() bool {
 | 
			
		||||
	return stream.streamConnection.closed
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (stream *Stream) getResponse() *http.Response {
 | 
			
		||||
	return stream.streamConnection.response
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Optional, set stream connection properties
 | 
			
		||||
// wait - time in milliseconds of waiting between reconnections. Will grow exponentially.
 | 
			
		||||
// retries - number of reconnections retries before dropping the stream.
 | 
			
		||||
func (gitter *Gitter) newStreamConnection(wait time.Duration, retries int) *streamConnection {
 | 
			
		||||
	return &streamConnection{
 | 
			
		||||
		closed:  true,
 | 
			
		||||
		wait:    wait,
 | 
			
		||||
		retries: retries,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								vendor/github.com/sromku/go-gitter/test_utils.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/sromku/go-gitter/test_utils.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
package gitter
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"net/url"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	mux    *http.ServeMux
 | 
			
		||||
	gitter *Gitter
 | 
			
		||||
	server *httptest.Server
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func setup() {
 | 
			
		||||
	mux = http.NewServeMux()
 | 
			
		||||
	server = httptest.NewServer(mux)
 | 
			
		||||
 | 
			
		||||
	gitter = New("abc")
 | 
			
		||||
 | 
			
		||||
	// Fake the API and Stream base URLs by using the test
 | 
			
		||||
	// server URL instead.
 | 
			
		||||
	url, _ := url.Parse(server.URL)
 | 
			
		||||
	gitter.config.apiBaseURL = url.String() + "/"
 | 
			
		||||
	gitter.config.streamBaseURL = url.String() + "/"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func teardown() {
 | 
			
		||||
	server.Close()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								vendor/github.com/thoj/go-ircevent/irc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/thoj/go-ircevent/irc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -152,7 +152,6 @@ func (irc *Connection) writeLoop() {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Pings the server if we have not received any messages for 5 minutes
 | 
			
		||||
@@ -439,6 +438,25 @@ func (irc *Connection) Connect(server string) error {
 | 
			
		||||
	if len(irc.Password) > 0 {
 | 
			
		||||
		irc.pwrite <- fmt.Sprintf("PASS %s\r\n", irc.Password)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resChan := make(chan *SASLResult)
 | 
			
		||||
	if irc.UseSASL {
 | 
			
		||||
		irc.setupSASLCallbacks(resChan)
 | 
			
		||||
		irc.pwrite <- fmt.Sprintf("CAP LS\r\n")
 | 
			
		||||
		// request SASL
 | 
			
		||||
		irc.pwrite <- fmt.Sprintf("CAP REQ :sasl\r\n")
 | 
			
		||||
		// if sasl request doesn't complete in 15 seconds, close chan and timeout
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			if res.Failed {
 | 
			
		||||
				close(resChan)
 | 
			
		||||
				return res.Err
 | 
			
		||||
			}
 | 
			
		||||
		case <-time.After(time.Second * 15):
 | 
			
		||||
			close(resChan)
 | 
			
		||||
			return errors.New("SASL setup timed out. This shouldn't happen.")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	irc.pwrite <- fmt.Sprintf("NICK %s\r\n", irc.nick)
 | 
			
		||||
	irc.pwrite <- fmt.Sprintf("USER %s 0.0.0.0 0.0.0.0 :%s\r\n", irc.user, irc.user)
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -466,6 +484,7 @@ func IRC(nick, user string) *Connection {
 | 
			
		||||
		KeepAlive:   4 * time.Minute,
 | 
			
		||||
		Timeout:     1 * time.Minute,
 | 
			
		||||
		PingFreq:    15 * time.Minute,
 | 
			
		||||
		SASLMech:    "PLAIN",
 | 
			
		||||
		QuitMessage: "",
 | 
			
		||||
	}
 | 
			
		||||
	irc.setupCallbacks()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								vendor/github.com/thoj/go-ircevent/irc_callback.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/thoj/go-ircevent/irc_callback.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -33,7 +33,7 @@ func (irc *Connection) RemoveCallback(eventcode string, i int) bool {
 | 
			
		||||
			delete(irc.events[eventcode], i)
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		irc.Log.Printf("Event found, but no callback found at id %s\n", i)
 | 
			
		||||
		irc.Log.Printf("Event found, but no callback found at id %d\n", i)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -64,7 +64,7 @@ func (irc *Connection) ReplaceCallback(eventcode string, i int, callback func(*E
 | 
			
		||||
			event[i] = callback
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		irc.Log.Printf("Event found, but no callback found at id %s\n", i)
 | 
			
		||||
		irc.Log.Printf("Event found, but no callback found at id %d\n", i)
 | 
			
		||||
	}
 | 
			
		||||
	irc.Log.Printf("Event not found. Use AddCallBack\n")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								vendor/github.com/thoj/go-ircevent/irc_sasl.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								vendor/github.com/thoj/go-ircevent/irc_sasl.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
package irc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SASLResult struct {
 | 
			
		||||
	Failed bool
 | 
			
		||||
	Err    error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (irc *Connection) setupSASLCallbacks(result chan<- *SASLResult) {
 | 
			
		||||
	irc.AddCallback("CAP", func(e *Event) {
 | 
			
		||||
		if len(e.Arguments) == 3 {
 | 
			
		||||
			if e.Arguments[1] == "LS" {
 | 
			
		||||
				if !strings.Contains(e.Arguments[2], "sasl") {
 | 
			
		||||
					result <- &SASLResult{true, errors.New("no SASL capability " + e.Arguments[2])}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if e.Arguments[1] == "ACK" {
 | 
			
		||||
				if irc.SASLMech != "PLAIN" {
 | 
			
		||||
					result <- &SASLResult{true, errors.New("only PLAIN is supported")}
 | 
			
		||||
				}
 | 
			
		||||
				irc.SendRaw("AUTHENTICATE " + irc.SASLMech)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	irc.AddCallback("AUTHENTICATE", func(e *Event) {
 | 
			
		||||
		str := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s\x00%s\x00%s", irc.SASLLogin, irc.SASLLogin, irc.SASLPassword)))
 | 
			
		||||
		irc.SendRaw("AUTHENTICATE " + str)
 | 
			
		||||
	})
 | 
			
		||||
	irc.AddCallback("901", func(e *Event) {
 | 
			
		||||
		irc.SendRaw("CAP END")
 | 
			
		||||
		irc.SendRaw("QUIT")
 | 
			
		||||
		result <- &SASLResult{true, errors.New(e.Arguments[1])}
 | 
			
		||||
	})
 | 
			
		||||
	irc.AddCallback("902", func(e *Event) {
 | 
			
		||||
		irc.SendRaw("CAP END")
 | 
			
		||||
		irc.SendRaw("QUIT")
 | 
			
		||||
		result <- &SASLResult{true, errors.New(e.Arguments[1])}
 | 
			
		||||
	})
 | 
			
		||||
	irc.AddCallback("903", func(e *Event) {
 | 
			
		||||
		irc.SendRaw("CAP END")
 | 
			
		||||
		result <- &SASLResult{false, nil}
 | 
			
		||||
	})
 | 
			
		||||
	irc.AddCallback("904", func(e *Event) {
 | 
			
		||||
		irc.SendRaw("CAP END")
 | 
			
		||||
		irc.SendRaw("QUIT")
 | 
			
		||||
		result <- &SASLResult{true, errors.New(e.Arguments[1])}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								vendor/github.com/thoj/go-ircevent/irc_struct.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/thoj/go-ircevent/irc_struct.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -18,6 +18,10 @@ type Connection struct {
 | 
			
		||||
	Error        chan error
 | 
			
		||||
	Password     string
 | 
			
		||||
	UseTLS       bool
 | 
			
		||||
	UseSASL      bool
 | 
			
		||||
	SASLLogin    string
 | 
			
		||||
	SASLPassword string
 | 
			
		||||
	SASLMech     string
 | 
			
		||||
	TLSConfig    *tls.Config
 | 
			
		||||
	Version      string
 | 
			
		||||
	Timeout      time.Duration
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										66
									
								
								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": "20735470185e0b0ac1d15b975041ed9a2e0e43bc",
 | 
			
		||||
			"branch": "release-3.3",
 | 
			
		||||
			"path": "/einterfaces",
 | 
			
		||||
			"notests": true
 | 
			
		||||
		},
 | 
			
		||||
@@ -81,11 +72,35 @@
 | 
			
		||||
			"importpath": "github.com/mattermost/platform/model",
 | 
			
		||||
			"repository": "https://github.com/mattermost/platform",
 | 
			
		||||
			"vcs": "git",
 | 
			
		||||
			"revision": "9d94869cc6a0fb9f051879437c104ccd76094380",
 | 
			
		||||
			"branch": "HEAD",
 | 
			
		||||
			"revision": "20735470185e0b0ac1d15b975041ed9a2e0e43bc",
 | 
			
		||||
			"branch": "release-3.3",
 | 
			
		||||
			"path": "/model",
 | 
			
		||||
			"notests": true
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"importpath": "github.com/mattn/go-xmpp",
 | 
			
		||||
			"repository": "https://github.com/mattn/go-xmpp",
 | 
			
		||||
			"vcs": "git",
 | 
			
		||||
			"revision": "e44d1877bb457f5c3991903e9934a31e55c3a2ad",
 | 
			
		||||
			"branch": "master",
 | 
			
		||||
			"notests": true
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"importpath": "github.com/mreiferson/go-httpclient",
 | 
			
		||||
			"repository": "https://github.com/mreiferson/go-httpclient",
 | 
			
		||||
			"vcs": "git",
 | 
			
		||||
			"revision": "31f0106b4474f14bc441575c19d3a5fa21aa1f6c",
 | 
			
		||||
			"branch": "master",
 | 
			
		||||
			"notests": true
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"importpath": "github.com/mrexodia/wray",
 | 
			
		||||
			"repository": "https://github.com/mrexodia/wray",
 | 
			
		||||
			"vcs": "git",
 | 
			
		||||
			"revision": "78a2c1f284ffe6ada7e2dfbd97c644e0d0f23fea",
 | 
			
		||||
			"branch": "master",
 | 
			
		||||
			"notests": true
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"importpath": "github.com/nicksnyder/go-i18n/i18n",
 | 
			
		||||
			"repository": "https://github.com/nicksnyder/go-i18n",
 | 
			
		||||
@@ -119,11 +134,19 @@
 | 
			
		||||
			"branch": "master",
 | 
			
		||||
			"notests": true
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"importpath": "github.com/sromku/go-gitter",
 | 
			
		||||
			"repository": "https://github.com/sromku/go-gitter",
 | 
			
		||||
			"vcs": "git",
 | 
			
		||||
			"revision": "932bf9af423ac2da1544cb73540b3b08b1bdb181",
 | 
			
		||||
			"branch": "master",
 | 
			
		||||
			"notests": true
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"importpath": "github.com/thoj/go-ircevent",
 | 
			
		||||
			"repository": "https://github.com/thoj/go-ircevent",
 | 
			
		||||
			"vcs": "",
 | 
			
		||||
			"revision": "da78ed515c0f0833e7a92c7cc52898176198e2c1",
 | 
			
		||||
			"vcs": "git",
 | 
			
		||||
			"revision": "98c1902dd2097f38142384167e60206ba26f1585",
 | 
			
		||||
			"branch": "master",
 | 
			
		||||
			"notests": true
 | 
			
		||||
		},
 | 
			
		||||
@@ -154,6 +177,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