Compare commits
	
		
			53 Commits
		
	
	
		
			v0.4.1
			...
			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 | 
							
								
								
									
										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" | ||||
| ``` | ||||
							
								
								
									
										169
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										169
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -21,15 +21,17 @@ type MMhook struct { | ||||
| } | ||||
|  | ||||
| type MMapi struct { | ||||
| 	mc    *matterclient.MMClient | ||||
| 	mmMap map[string]string | ||||
| 	mc            *matterclient.MMClient | ||||
| 	mmMap         map[string]string | ||||
| 	mmIgnoreNicks []string | ||||
| } | ||||
|  | ||||
| type MMirc struct { | ||||
| 	i       *irc.Connection | ||||
| 	ircNick string | ||||
| 	ircMap  map[string]string | ||||
| 	names   map[string][]string | ||||
| 	i              *irc.Connection | ||||
| 	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) | ||||
| 	i.Join(b.Config.IRC.Channel) | ||||
| 	if b.kind == "legacy" { | ||||
| 	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 { | ||||
| 		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 | ||||
|   | ||||
							
								
								
									
										167
									
								
								vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										167
									
								
								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" | ||||
|  | ||||
| @@ -13,11 +16,12 @@ import ( | ||||
| ) | ||||
|  | ||||
| type Credentials struct { | ||||
| 	Login  string | ||||
| 	Team   string | ||||
| 	Pass   string | ||||
| 	Server string | ||||
| 	NoTLS  bool | ||||
| 	Login         string | ||||
| 	Team          string | ||||
| 	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) | ||||
| 		myinfo, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) | ||||
| 		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 | ||||
| } | ||||
							
								
								
									
										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 | ||||
| } | ||||
							
								
								
									
										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/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 | ||||
|   | ||||
							
								
								
									
										563
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										563
									
								
								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, "") | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										561
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										561
									
								
								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" | ||||
|  | ||||
| 	SERVICE_GITLAB = "gitlab" | ||||
| 	SERVICE_GOOGLE = "google" | ||||
| 	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 { | ||||
| @@ -83,15 +107,25 @@ type SqlSettings struct { | ||||
| } | ||||
|  | ||||
| type LogSettings struct { | ||||
| 	EnableConsole bool | ||||
| 	ConsoleLevel  string | ||||
| 	EnableFile    bool | ||||
| 	FileLevel     string | ||||
| 	FileFormat    string | ||||
| 	FileLocation  string | ||||
| 	EnableConsole          bool | ||||
| 	ConsoleLevel           string | ||||
| 	EnableFile             bool | ||||
| 	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 | ||||
| @@ -156,16 +191,21 @@ type SupportSettings struct { | ||||
| } | ||||
|  | ||||
| type TeamSettings struct { | ||||
| 	SiteName                  string | ||||
| 	MaxUsersPerTeam           int | ||||
| 	EnableTeamCreation        bool | ||||
| 	EnableUserCreation        bool | ||||
| 	EnableOpenServer          *bool | ||||
| 	RestrictCreationToDomains string | ||||
| 	RestrictTeamNames         *bool | ||||
| 	EnableCustomBrand         *bool | ||||
| 	CustomBrandText           *string | ||||
| 	RestrictDirectMessage     *string | ||||
| 	SiteName                         string | ||||
| 	MaxUsersPerTeam                  int | ||||
| 	EnableTeamCreation               bool | ||||
| 	EnableUserCreation               bool | ||||
| 	EnableOpenServer                 *bool | ||||
| 	RestrictCreationToDomains        string | ||||
| 	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,20 +247,63 @@ 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 | ||||
| 	FileSettings       FileSettings | ||||
| 	EmailSettings      EmailSettings | ||||
| 	RateLimitSettings  RateLimitSettings | ||||
| 	PrivacySettings    PrivacySettings | ||||
| 	SupportSettings    SupportSettings | ||||
| 	GitLabSettings     SSOSettings | ||||
| 	GoogleSettings     SSOSettings | ||||
| 	LdapSettings       LdapSettings | ||||
| 	ComplianceSettings ComplianceSettings | ||||
| 	ServiceSettings      ServiceSettings | ||||
| 	TeamSettings         TeamSettings | ||||
| 	SqlSettings          SqlSettings | ||||
| 	LogSettings          LogSettings | ||||
| 	PasswordSettings     PasswordSettings | ||||
| 	FileSettings         FileSettings | ||||
| 	EmailSettings        EmailSettings | ||||
| 	RateLimitSettings    RateLimitSettings | ||||
| 	PrivacySettings      PrivacySettings | ||||
| 	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"} | ||||
|   | ||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/model/gitlab/gitlab.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/gitlab/gitlab.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -47,7 +47,8 @@ func userFromGitLabUser(glu *GitLabUser) *model.User { | ||||
| 	} | ||||
| 	strings.TrimSpace(user.Email) | ||||
| 	user.Email = glu.Email | ||||
| 	*user.AuthData = strconv.FormatInt(glu.Id, 10) | ||||
| 	userId := strconv.FormatInt(glu.Id, 10) | ||||
| 	user.AuthData = &userId | ||||
| 	user.AuthService = model.USER_AUTH_SERVICE_GITLAB | ||||
|  | ||||
| 	return user | ||||
|   | ||||
							
								
								
									
										135
									
								
								vendor/github.com/mattermost/platform/model/incoming_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										135
									
								
								vendor/github.com/mattermost/platform/model/incoming_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -4,13 +4,15 @@ | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	DEFAULT_WEBHOOK_USERNAME = "webhook" | ||||
| 	DEFAULT_WEBHOOK_ICON     = "/static/images/webhook_icon.jpg" | ||||
| ) | ||||
|  | ||||
| type IncomingWebhook struct { | ||||
| @@ -125,13 +127,136 @@ func (o *IncomingWebhook) PreUpdate() { | ||||
| 	o.UpdateAt = GetMillis() | ||||
| } | ||||
|  | ||||
| func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| // escapeControlCharsFromPayload escapes control chars (\n, \t) from a byte slice. | ||||
| // Context: | ||||
| // JSON strings are not supposed to contain control characters such as \n, \t, | ||||
| // ... but some incoming webhooks might still send invalid JSON and we want to | ||||
| // try to handle that. An example invalid JSON string from an incoming webhook | ||||
| // might look like this (strings for both "text" and "fallback" attributes are | ||||
| // invalid JSON strings because they contain unescaped newlines and tabs): | ||||
| //  `{ | ||||
| //    "text": "this is a test | ||||
| //						 that contains a newline and tabs", | ||||
| //    "attachments": [ | ||||
| //      { | ||||
| //        "fallback": "Required plain-text summary of the attachment | ||||
| //										that contains a newline and tabs", | ||||
| //        "color": "#36a64f", | ||||
| //  			... | ||||
| //        "text": "Optional text that appears within the attachment | ||||
| //								 that contains a newline and tabs", | ||||
| //  			... | ||||
| //        "thumb_url": "http://example.com/path/to/thumb.png" | ||||
| //      } | ||||
| //    ] | ||||
| //  }` | ||||
| // This function will search for `"key": "value"` pairs, and escape \n, \t | ||||
| // from the value. | ||||
| func escapeControlCharsFromPayload(by []byte) []byte { | ||||
| 	// we'll search for `"text": "..."` or `"fallback": "..."`, ... | ||||
| 	keys := "text|fallback|pretext|author_name|title|value" | ||||
|  | ||||
| 	// the regexp reads like this: | ||||
| 	// (?s): this flag let . match \n (default is false) | ||||
| 	// "(keys)": we search for the keys defined above | ||||
| 	// \s*:\s*: followed by 0..n spaces/tabs, a colon then 0..n spaces/tabs | ||||
| 	// ": a double-quote | ||||
| 	// (\\"|[^"])*: any number of times the `\"` string or any char but a double-quote | ||||
| 	// ": a double-quote | ||||
| 	r := `(?s)"(` + keys + `)"\s*:\s*"(\\"|[^"])*"` | ||||
| 	re := regexp.MustCompile(r) | ||||
|  | ||||
| 	// the function that will escape \n and \t on the regexp matches | ||||
| 	repl := func(b []byte) []byte { | ||||
| 		if bytes.Contains(b, []byte("\n")) { | ||||
| 			b = bytes.Replace(b, []byte("\n"), []byte("\\n"), -1) | ||||
| 		} | ||||
| 		if bytes.Contains(b, []byte("\t")) { | ||||
| 			b = bytes.Replace(b, []byte("\t"), []byte("\\t"), -1) | ||||
| 		} | ||||
|  | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	return re.ReplaceAllFunc(by, repl) | ||||
| } | ||||
|  | ||||
| func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) { | ||||
| 	decoder := json.NewDecoder(bytes.NewReader(by)) | ||||
| 	var o IncomingWebhookRequest | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 		return &o, nil | ||||
| 	} else { | ||||
| 		return nil | ||||
| 		return nil, err | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // To mention @channel via a webhook in Slack, the message should contain | ||||
| // <!channel>, as explained at the bottom of this article: | ||||
| // https://get.slack.help/hc/en-us/articles/202009646-Making-announcements | ||||
| func expandAnnouncement(text string) string { | ||||
| 	c1 := "<!channel>" | ||||
| 	c2 := "@channel" | ||||
| 	if strings.Contains(text, c1) { | ||||
| 		return strings.Replace(text, c1, c2, -1) | ||||
| 	} | ||||
| 	return text | ||||
| } | ||||
|  | ||||
| // Expand announcements in incoming webhooks from Slack. Those announcements | ||||
| // can be found in the text attribute, or in the pretext, text, title and value | ||||
| // attributes of the attachment structure. The Slack attachment structure is | ||||
| // documented here: https://api.slack.com/docs/attachments | ||||
| func expandAnnouncements(i *IncomingWebhookRequest) { | ||||
| 	i.Text = expandAnnouncement(i.Text) | ||||
|  | ||||
| 	if i.Attachments != nil { | ||||
| 		attachments := i.Attachments.([]interface{}) | ||||
| 		for _, attachment := range attachments { | ||||
| 			a := attachment.(map[string]interface{}) | ||||
|  | ||||
| 			if a["pretext"] != nil { | ||||
| 				a["pretext"] = expandAnnouncement(a["pretext"].(string)) | ||||
| 			} | ||||
|  | ||||
| 			if a["text"] != nil { | ||||
| 				a["text"] = expandAnnouncement(a["text"].(string)) | ||||
| 			} | ||||
|  | ||||
| 			if a["title"] != nil { | ||||
| 				a["title"] = expandAnnouncement(a["title"].(string)) | ||||
| 			} | ||||
|  | ||||
| 			if a["fields"] != nil { | ||||
| 				fields := a["fields"].([]interface{}) | ||||
| 				for _, field := range fields { | ||||
| 					f := field.(map[string]interface{}) | ||||
| 					if f["value"] != nil { | ||||
| 						f["value"] = expandAnnouncement(f["value"].(string)) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest { | ||||
| 	buf := new(bytes.Buffer) | ||||
| 	buf.ReadFrom(data) | ||||
| 	by := buf.Bytes() | ||||
|  | ||||
| 	// Try to decode the JSON data. Only if it fails, try to escape control | ||||
| 	// characters from the strings contained in the JSON data. | ||||
| 	o, err := decodeIncomingWebhookRequest(by) | ||||
| 	if err != nil { | ||||
| 		o, err = decodeIncomingWebhookRequest(escapeControlCharsFromPayload(by)) | ||||
| 		if err != nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	expandAnnouncements(o) | ||||
|  | ||||
| 	return o | ||||
| } | ||||
|   | ||||
							
								
								
									
										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, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										1
									
								
								vendor/github.com/mattermost/platform/model/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/mattermost/platform/model/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -5,4 +5,5 @@ package model | ||||
|  | ||||
| const ( | ||||
| 	USER_AUTH_SERVICE_LDAP = "ldap" | ||||
| 	LDAP_SYNC_TASK_NAME    = "LDAP Syncronization" | ||||
| ) | ||||
|   | ||||
							
								
								
									
										46
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										46
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -32,14 +32,18 @@ type Customer struct { | ||||
| } | ||||
|  | ||||
| type Features struct { | ||||
| 	Users          *int  `json:"users"` | ||||
| 	LDAP           *bool `json:"ldap"` | ||||
| 	MFA            *bool `json:"mfa"` | ||||
| 	GoogleSSO      *bool `json:"google_sso"` | ||||
| 	Compliance     *bool `json:"compliance"` | ||||
| 	CustomBrand    *bool `json:"custom_brand"` | ||||
| 	MHPNS          *bool `json:"mhpns"` | ||||
| 	FutureFeatures *bool `json:"future_features"` | ||||
| 	Users                *int  `json:"users"` | ||||
| 	LDAP                 *bool `json:"ldap"` | ||||
| 	MFA                  *bool `json:"mfa"` | ||||
| 	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"` | ||||
| } | ||||
|  | ||||
| func (f *Features) SetDefaults() { | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										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, "#") | ||||
|   | ||||
							
								
								
									
										6
									
								
								vendor/github.com/mattermost/platform/model/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								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) { | ||||
| 	me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days)) | ||||
| 	if me.CreateAt == 0 { | ||||
| 		me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days)) | ||||
| 	} else { | ||||
| 		me.ExpiresAt = me.CreateAt + (1000 * 60 * 60 * 24 * int64(days)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (me *Session) AddProp(key string, value string) { | ||||
|   | ||||
							
								
								
									
										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 = "" | ||||
|   | ||||
							
								
								
									
										7
									
								
								vendor/github.com/mattermost/platform/model/team_member.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/mattermost/platform/model/team_member.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -14,9 +14,10 @@ const ( | ||||
| ) | ||||
|  | ||||
| type TeamMember struct { | ||||
| 	TeamId string `json:"team_id"` | ||||
| 	UserId string `json:"user_id"` | ||||
| 	Roles  string `json:"roles"` | ||||
| 	TeamId   string `json:"team_id"` | ||||
| 	UserId   string `json:"user_id"` | ||||
| 	Roles    string `json:"roles"` | ||||
| 	DeleteAt int64  `json:"delete_at"` | ||||
| } | ||||
|  | ||||
| func (o *TeamMember) ToJson() string { | ||||
|   | ||||
							
								
								
									
										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 { | ||||
|   | ||||
							
								
								
									
										24
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								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 | ||||
| @@ -27,12 +34,12 @@ type EncryptStringMap map[string]string | ||||
|  | ||||
| type AppError struct { | ||||
| 	Id            string                 `json:"id"` | ||||
| 	Message       string                 `json:"message"`        // Message to be display to the end user without debugging information | ||||
| 	DetailedError string                 `json:"detailed_error"` // Internal error string to help the developer | ||||
| 	RequestId     string                 `json:"request_id"`     // The RequestId that's also set in the header | ||||
| 	StatusCode    int                    `json:"status_code"`    // The http status code | ||||
| 	Where         string                 `json:"-"`              // The function where it happened in the form of Struct.Func | ||||
| 	IsOAuth       bool                   `json:"is_oauth"`       // Whether the error is OAuth specific | ||||
| 	Message       string                 `json:"message"`               // Message to be display to the end user without debugging information | ||||
| 	DetailedError string                 `json:"detailed_error"`        // Internal error string to help the developer | ||||
| 	RequestId     string                 `json:"request_id,omitempty"`  // The RequestId that's also set in the header | ||||
| 	StatusCode    int                    `json:"status_code,omitempty"` // The http status code | ||||
| 	Where         string                 `json:"-"`                     // The function where it happened in the form of Struct.Func | ||||
| 	IsOAuth       bool                   `json:"is_oauth,omitempty"`    // Whether the error is OAuth specific | ||||
| 	params        map[string]interface{} `json:"-"` | ||||
| } | ||||
|  | ||||
| @@ -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])} | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										24
									
								
								vendor/github.com/thoj/go-ircevent/irc_struct.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/thoj/go-ircevent/irc_struct.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -14,16 +14,20 @@ import ( | ||||
|  | ||||
| type Connection struct { | ||||
| 	sync.WaitGroup | ||||
| 	Debug     bool | ||||
| 	Error     chan error | ||||
| 	Password  string | ||||
| 	UseTLS    bool | ||||
| 	TLSConfig *tls.Config | ||||
| 	Version   string | ||||
| 	Timeout   time.Duration | ||||
| 	PingFreq  time.Duration | ||||
| 	KeepAlive time.Duration | ||||
| 	Server    string | ||||
| 	Debug        bool | ||||
| 	Error        chan error | ||||
| 	Password     string | ||||
| 	UseTLS       bool | ||||
| 	UseSASL      bool | ||||
| 	SASLLogin    string | ||||
| 	SASLPassword string | ||||
| 	SASLMech     string | ||||
| 	TLSConfig    *tls.Config | ||||
| 	Version      string | ||||
| 	Timeout      time.Duration | ||||
| 	PingFreq     time.Duration | ||||
| 	KeepAlive    time.Duration | ||||
| 	Server       string | ||||
|  | ||||
| 	socket net.Conn | ||||
| 	pwrite chan string | ||||
|   | ||||
							
								
								
									
										27
									
								
								vendor/golang.org/x/net/lex/httplex/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/golang.org/x/net/lex/httplex/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| Copyright (c) 2009 The Go Authors. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
|  | ||||
|    * Redistributions of source code must retain the above copyright | ||||
| notice, this list of conditions and the following disclaimer. | ||||
|    * Redistributions in binary form must reproduce the above | ||||
| copyright notice, this list of conditions and the following disclaimer | ||||
| in the documentation and/or other materials provided with the | ||||
| distribution. | ||||
|    * Neither the name of Google Inc. nor the names of its | ||||
| contributors may be used to endorse or promote products derived from | ||||
| this software without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										312
									
								
								vendor/golang.org/x/net/lex/httplex/httplex.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								vendor/golang.org/x/net/lex/httplex/httplex.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,312 @@ | ||||
| // Copyright 2016 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Package httplex contains rules around lexical matters of various | ||||
| // HTTP-related specifications. | ||||
| // | ||||
| // This package is shared by the standard library (which vendors it) | ||||
| // and x/net/http2. It comes with no API stability promise. | ||||
| package httplex | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
|  | ||||
| var isTokenTable = [127]bool{ | ||||
| 	'!':  true, | ||||
| 	'#':  true, | ||||
| 	'$':  true, | ||||
| 	'%':  true, | ||||
| 	'&':  true, | ||||
| 	'\'': true, | ||||
| 	'*':  true, | ||||
| 	'+':  true, | ||||
| 	'-':  true, | ||||
| 	'.':  true, | ||||
| 	'0':  true, | ||||
| 	'1':  true, | ||||
| 	'2':  true, | ||||
| 	'3':  true, | ||||
| 	'4':  true, | ||||
| 	'5':  true, | ||||
| 	'6':  true, | ||||
| 	'7':  true, | ||||
| 	'8':  true, | ||||
| 	'9':  true, | ||||
| 	'A':  true, | ||||
| 	'B':  true, | ||||
| 	'C':  true, | ||||
| 	'D':  true, | ||||
| 	'E':  true, | ||||
| 	'F':  true, | ||||
| 	'G':  true, | ||||
| 	'H':  true, | ||||
| 	'I':  true, | ||||
| 	'J':  true, | ||||
| 	'K':  true, | ||||
| 	'L':  true, | ||||
| 	'M':  true, | ||||
| 	'N':  true, | ||||
| 	'O':  true, | ||||
| 	'P':  true, | ||||
| 	'Q':  true, | ||||
| 	'R':  true, | ||||
| 	'S':  true, | ||||
| 	'T':  true, | ||||
| 	'U':  true, | ||||
| 	'W':  true, | ||||
| 	'V':  true, | ||||
| 	'X':  true, | ||||
| 	'Y':  true, | ||||
| 	'Z':  true, | ||||
| 	'^':  true, | ||||
| 	'_':  true, | ||||
| 	'`':  true, | ||||
| 	'a':  true, | ||||
| 	'b':  true, | ||||
| 	'c':  true, | ||||
| 	'd':  true, | ||||
| 	'e':  true, | ||||
| 	'f':  true, | ||||
| 	'g':  true, | ||||
| 	'h':  true, | ||||
| 	'i':  true, | ||||
| 	'j':  true, | ||||
| 	'k':  true, | ||||
| 	'l':  true, | ||||
| 	'm':  true, | ||||
| 	'n':  true, | ||||
| 	'o':  true, | ||||
| 	'p':  true, | ||||
| 	'q':  true, | ||||
| 	'r':  true, | ||||
| 	's':  true, | ||||
| 	't':  true, | ||||
| 	'u':  true, | ||||
| 	'v':  true, | ||||
| 	'w':  true, | ||||
| 	'x':  true, | ||||
| 	'y':  true, | ||||
| 	'z':  true, | ||||
| 	'|':  true, | ||||
| 	'~':  true, | ||||
| } | ||||
|  | ||||
| func IsTokenRune(r rune) bool { | ||||
| 	i := int(r) | ||||
| 	return i < len(isTokenTable) && isTokenTable[i] | ||||
| } | ||||
|  | ||||
| func isNotToken(r rune) bool { | ||||
| 	return !IsTokenRune(r) | ||||
| } | ||||
|  | ||||
| // HeaderValuesContainsToken reports whether any string in values | ||||
| // contains the provided token, ASCII case-insensitively. | ||||
| func HeaderValuesContainsToken(values []string, token string) bool { | ||||
| 	for _, v := range values { | ||||
| 		if headerValueContainsToken(v, token) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // isOWS reports whether b is an optional whitespace byte, as defined | ||||
| // by RFC 7230 section 3.2.3. | ||||
| func isOWS(b byte) bool { return b == ' ' || b == '\t' } | ||||
|  | ||||
| // trimOWS returns x with all optional whitespace removes from the | ||||
| // beginning and end. | ||||
| func trimOWS(x string) string { | ||||
| 	// TODO: consider using strings.Trim(x, " \t") instead, | ||||
| 	// if and when it's fast enough. See issue 10292. | ||||
| 	// But this ASCII-only code will probably always beat UTF-8 | ||||
| 	// aware code. | ||||
| 	for len(x) > 0 && isOWS(x[0]) { | ||||
| 		x = x[1:] | ||||
| 	} | ||||
| 	for len(x) > 0 && isOWS(x[len(x)-1]) { | ||||
| 		x = x[:len(x)-1] | ||||
| 	} | ||||
| 	return x | ||||
| } | ||||
|  | ||||
| // headerValueContainsToken reports whether v (assumed to be a | ||||
| // 0#element, in the ABNF extension described in RFC 7230 section 7) | ||||
| // contains token amongst its comma-separated tokens, ASCII | ||||
| // case-insensitively. | ||||
| func headerValueContainsToken(v string, token string) bool { | ||||
| 	v = trimOWS(v) | ||||
| 	if comma := strings.IndexByte(v, ','); comma != -1 { | ||||
| 		return tokenEqual(trimOWS(v[:comma]), token) || headerValueContainsToken(v[comma+1:], token) | ||||
| 	} | ||||
| 	return tokenEqual(v, token) | ||||
| } | ||||
|  | ||||
| // lowerASCII returns the ASCII lowercase version of b. | ||||
| func lowerASCII(b byte) byte { | ||||
| 	if 'A' <= b && b <= 'Z' { | ||||
| 		return b + ('a' - 'A') | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // tokenEqual reports whether t1 and t2 are equal, ASCII case-insensitively. | ||||
| func tokenEqual(t1, t2 string) bool { | ||||
| 	if len(t1) != len(t2) { | ||||
| 		return false | ||||
| 	} | ||||
| 	for i, b := range t1 { | ||||
| 		if b >= utf8.RuneSelf { | ||||
| 			// No UTF-8 or non-ASCII allowed in tokens. | ||||
| 			return false | ||||
| 		} | ||||
| 		if lowerASCII(byte(b)) != lowerASCII(t2[i]) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // isLWS reports whether b is linear white space, according | ||||
| // to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 | ||||
| //      LWS            = [CRLF] 1*( SP | HT ) | ||||
| func isLWS(b byte) bool { return b == ' ' || b == '\t' } | ||||
|  | ||||
| // isCTL reports whether b is a control byte, according | ||||
| // to http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 | ||||
| //      CTL            = <any US-ASCII control character | ||||
| //                       (octets 0 - 31) and DEL (127)> | ||||
| func isCTL(b byte) bool { | ||||
| 	const del = 0x7f // a CTL | ||||
| 	return b < ' ' || b == del | ||||
| } | ||||
|  | ||||
| // ValidHeaderFieldName reports whether v is a valid HTTP/1.x header name. | ||||
| // HTTP/2 imposes the additional restriction that uppercase ASCII | ||||
| // letters are not allowed. | ||||
| // | ||||
| //  RFC 7230 says: | ||||
| //   header-field   = field-name ":" OWS field-value OWS | ||||
| //   field-name     = token | ||||
| //   token          = 1*tchar | ||||
| //   tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / | ||||
| //           "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA | ||||
| func ValidHeaderFieldName(v string) bool { | ||||
| 	if len(v) == 0 { | ||||
| 		return false | ||||
| 	} | ||||
| 	for _, r := range v { | ||||
| 		if !IsTokenRune(r) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // ValidHostHeader reports whether h is a valid host header. | ||||
| func ValidHostHeader(h string) bool { | ||||
| 	// The latest spec is actually this: | ||||
| 	// | ||||
| 	// http://tools.ietf.org/html/rfc7230#section-5.4 | ||||
| 	//     Host = uri-host [ ":" port ] | ||||
| 	// | ||||
| 	// Where uri-host is: | ||||
| 	//     http://tools.ietf.org/html/rfc3986#section-3.2.2 | ||||
| 	// | ||||
| 	// But we're going to be much more lenient for now and just | ||||
| 	// search for any byte that's not a valid byte in any of those | ||||
| 	// expressions. | ||||
| 	for i := 0; i < len(h); i++ { | ||||
| 		if !validHostByte[h[i]] { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // See the validHostHeader comment. | ||||
| var validHostByte = [256]bool{ | ||||
| 	'0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, | ||||
| 	'8': true, '9': true, | ||||
|  | ||||
| 	'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true, | ||||
| 	'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true, | ||||
| 	'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true, | ||||
| 	'y': true, 'z': true, | ||||
|  | ||||
| 	'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true, | ||||
| 	'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true, | ||||
| 	'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'V': true, 'W': true, 'X': true, | ||||
| 	'Y': true, 'Z': true, | ||||
|  | ||||
| 	'!':  true, // sub-delims | ||||
| 	'$':  true, // sub-delims | ||||
| 	'%':  true, // pct-encoded (and used in IPv6 zones) | ||||
| 	'&':  true, // sub-delims | ||||
| 	'(':  true, // sub-delims | ||||
| 	')':  true, // sub-delims | ||||
| 	'*':  true, // sub-delims | ||||
| 	'+':  true, // sub-delims | ||||
| 	',':  true, // sub-delims | ||||
| 	'-':  true, // unreserved | ||||
| 	'.':  true, // unreserved | ||||
| 	':':  true, // IPv6address + Host expression's optional port | ||||
| 	';':  true, // sub-delims | ||||
| 	'=':  true, // sub-delims | ||||
| 	'[':  true, | ||||
| 	'\'': true, // sub-delims | ||||
| 	']':  true, | ||||
| 	'_':  true, // unreserved | ||||
| 	'~':  true, // unreserved | ||||
| } | ||||
|  | ||||
| // ValidHeaderFieldValue reports whether v is a valid "field-value" according to | ||||
| // http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 : | ||||
| // | ||||
| //        message-header = field-name ":" [ field-value ] | ||||
| //        field-value    = *( field-content | LWS ) | ||||
| //        field-content  = <the OCTETs making up the field-value | ||||
| //                         and consisting of either *TEXT or combinations | ||||
| //                         of token, separators, and quoted-string> | ||||
| // | ||||
| // http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 : | ||||
| // | ||||
| //        TEXT           = <any OCTET except CTLs, | ||||
| //                          but including LWS> | ||||
| //        LWS            = [CRLF] 1*( SP | HT ) | ||||
| //        CTL            = <any US-ASCII control character | ||||
| //                         (octets 0 - 31) and DEL (127)> | ||||
| // | ||||
| // RFC 7230 says: | ||||
| //  field-value    = *( field-content / obs-fold ) | ||||
| //  obj-fold       =  N/A to http2, and deprecated | ||||
| //  field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ] | ||||
| //  field-vchar    = VCHAR / obs-text | ||||
| //  obs-text       = %x80-FF | ||||
| //  VCHAR          = "any visible [USASCII] character" | ||||
| // | ||||
| // http2 further says: "Similarly, HTTP/2 allows header field values | ||||
| // that are not valid. While most of the values that can be encoded | ||||
| // will not alter header field parsing, carriage return (CR, ASCII | ||||
| // 0xd), line feed (LF, ASCII 0xa), and the zero character (NUL, ASCII | ||||
| // 0x0) might be exploited by an attacker if they are translated | ||||
| // verbatim. Any request or response that contains a character not | ||||
| // permitted in a header field value MUST be treated as malformed | ||||
| // (Section 8.1.2.6). Valid characters are defined by the | ||||
| // field-content ABNF rule in Section 3.2 of [RFC7230]." | ||||
| // | ||||
| // This function does not (yet?) properly handle the rejection of | ||||
| // strings that begin or end with SP or HTAB. | ||||
| func ValidHeaderFieldValue(v string) bool { | ||||
| 	for i := 0; i < len(v); i++ { | ||||
| 		b := v[i] | ||||
| 		if isCTL(b) && !isLWS(b) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
							
								
								
									
										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