Compare commits
	
		
			47 Commits
		
	
	
		
			v0.4.2
			...
			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 | 
| @@ -2,10 +2,10 @@ FROM alpine:edge | ||||
| ENTRYPOINT ["/bin/matterbridge"] | ||||
|  | ||||
| COPY . /go/src/github.com/42wim/matterbridge | ||||
| RUN apk update && apk add go git \ | ||||
| RUN apk update && apk add go git gcc musl-dev ca-certificates \ | ||||
|         && cd /go/src/github.com/42wim/matterbridge \ | ||||
|         && export GOPATH=/go \ | ||||
|         && go get \ | ||||
|         && go build -o /bin/matterbridge \ | ||||
|         && rm -rf /go \ | ||||
|         && apk del --purge git go | ||||
|         && apk del --purge git go gcc musl-dev | ||||
|   | ||||
							
								
								
									
										128
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								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.2) | ||||
| 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,77 +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 | ||||
| #Freenode nickserv | ||||
| NickServNick="nickserv" | ||||
| #Password for nickserv | ||||
| NickServPassword="secret" | ||||
| #Ignore the messages from these nicks. They will not be sent to mattermost | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| [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 | ||||
| #Ignore the messages from these nicks. They will not be sent to irc | ||||
| IgnoreNicks="mmbot spammer2" | ||||
|  | ||||
| #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".   | ||||
| @@ -112,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,39 +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 | ||||
| #NickServNick="nickserv" | ||||
| #NickServPassword="secret" | ||||
|  | ||||
| #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 | ||||
|  | ||||
| #Nicks you want to ignore. Messages from those users will not be bridged. | ||||
| #OPTIONAL  | ||||
| IgnoreNicks="mmbot spammer2" | ||||
|  | ||||
| [general] | ||||
| GiphyAPIKey=dc6zaTOxFJmzC | ||||
| ################################################################### | ||||
| #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 | ||||
|   | ||||
| @@ -3,11 +3,12 @@ package main | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge-plus/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| var Version = "0.4.2" | ||||
| var version = "0.6.0-beta2" | ||||
|  | ||||
| func init() { | ||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||
| @@ -17,9 +18,10 @@ 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) | ||||
| 		fmt.Println("version:", version) | ||||
| 		return | ||||
| 	} | ||||
| 	flag.Parse() | ||||
| @@ -27,7 +29,13 @@ func main() { | ||||
| 		log.Info("enabling debug") | ||||
| 		log.SetLevel(log.DebugLevel) | ||||
| 	} | ||||
| 	fmt.Println("running version", Version) | ||||
| 	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) | ||||
| @@ -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" | ||||
| ``` | ||||
							
								
								
									
										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 | ||||
| } | ||||
							
								
								
									
										2
									
								
								vendor/github.com/mattermost/platform/einterfaces/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/mattermost/platform/einterfaces/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -15,6 +15,8 @@ type LdapInterface interface { | ||||
| 	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 | ||||
|   | ||||
							
								
								
									
										317
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										317
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -7,7 +7,9 @@ import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	l4g "github.com/alecthomas/log4go" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"mime/multipart" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| @@ -18,6 +20,7 @@ import ( | ||||
| const ( | ||||
| 	HEADER_REQUEST_ID         = "X-Request-ID" | ||||
| 	HEADER_VERSION_ID         = "X-Version-ID" | ||||
| 	HEADER_CLUSTER_ID         = "X-Cluster-ID" | ||||
| 	HEADER_ETAG_SERVER        = "ETag" | ||||
| 	HEADER_ETAG_CLIENT        = "If-None-Match" | ||||
| 	HEADER_FORWARDED          = "X-Forwarded-For" | ||||
| @@ -30,6 +33,7 @@ const ( | ||||
| 	HEADER_REQUESTED_WITH_XML = "XMLHttpRequest" | ||||
| 	STATUS                    = "status" | ||||
| 	STATUS_OK                 = "OK" | ||||
| 	STATUS_FAIL               = "FAIL" | ||||
|  | ||||
| 	API_URL_SUFFIX_V1 = "/api/v1" | ||||
| 	API_URL_SUFFIX_V3 = "/api/v3" | ||||
| @@ -106,6 +110,10 @@ func (c *Client) GetChannelNameRoute(channelName string) string { | ||||
| 	return fmt.Sprintf("/teams/%v/channels/name/%v", c.GetTeamId(), channelName) | ||||
| } | ||||
|  | ||||
| func (c *Client) GetEmojiRoute() string { | ||||
| 	return "/emoji" | ||||
| } | ||||
|  | ||||
| func (c *Client) GetGeneralRoute() string { | ||||
| 	return "/general" | ||||
| } | ||||
| @@ -185,6 +193,17 @@ func (c *Client) Must(result *Result, err *AppError) *Result { | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // MustGeneric is a convenience function used for testing. | ||||
| func (c *Client) MustGeneric(result interface{}, err *AppError) interface{} { | ||||
| 	if err != nil { | ||||
| 		l4g.Close() | ||||
| 		time.Sleep(time.Second) | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // CheckStatusOK is a convenience function for checking the return of Web Service | ||||
| // call that return the a map of status=OK. | ||||
| func (c *Client) CheckStatusOK(r *http.Response) bool { | ||||
| @@ -259,6 +278,9 @@ func (c *Client) GetPing() (map[string]string, *AppError) { | ||||
|  | ||||
| // Team Routes Section | ||||
|  | ||||
| // SignupTeam sends an email with a team sign-up link to the provided address if email | ||||
| // verification is enabled, otherwise it returns a map with a "follow_link" entry | ||||
| // containing the team sign-up link. | ||||
| func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["email"] = email | ||||
| @@ -272,6 +294,8 @@ func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppErro | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CreateTeamFromSignup creates a team based on the provided TeamSignup struct. On success | ||||
| // it returns the TeamSignup struct. | ||||
| func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -282,6 +306,8 @@ func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppErro | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CreateTeam creates a team based on the provided Team struct. On success it returns | ||||
| // the Team struct with the Id, CreateAt and other server-decided fields populated. | ||||
| func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -292,6 +318,7 @@ func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetAllTeams returns a map of all teams using team ids as the key. | ||||
| func (c *Client) GetAllTeams() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/teams/all", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| @@ -302,6 +329,8 @@ func (c *Client) GetAllTeams() (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetAllTeamListings returns a map of all teams that are available to join | ||||
| // using team ids as the key. Must be authenticated. | ||||
| func (c *Client) GetAllTeamListings() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/teams/all_team_listings", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| @@ -312,6 +341,8 @@ func (c *Client) GetAllTeamListings() (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FindTeamByName returns the strings "true" or "false" depending on if a team | ||||
| // with the provided name was found. | ||||
| func (c *Client) FindTeamByName(name string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["name"] = name | ||||
| @@ -328,10 +359,18 @@ func (c *Client) FindTeamByName(name string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) AddUserToTeam(userId string) (*Result, *AppError) { | ||||
| //  Adds a user directly to the team without sending an invite. | ||||
| //  The teamId and userId are required.  You must be a valid member of the team and/or | ||||
| //  have the correct role to add new users to the team.  Returns a map of user_id=userId | ||||
| //  if successful, otherwise returns an AppError. | ||||
| func (c *Client) AddUserToTeam(teamId string, userId string) (*Result, *AppError) { | ||||
| 	if len(teamId) == 0 { | ||||
| 		teamId = c.GetTeamId() | ||||
| 	} | ||||
|  | ||||
| 	data := make(map[string]string) | ||||
| 	data["user_id"] = userId | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/add_user_to_team", MapToJson(data)); err != nil { | ||||
| 	if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v", teamId)+"/add_user_to_team", MapToJson(data)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| @@ -340,6 +379,8 @@ func (c *Client) AddUserToTeam(userId string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AddUserToTeamFromInvite adds a user to a team based off data provided in an invite link. | ||||
| // Either hash and dataToHash are required or inviteId is required. | ||||
| func (c *Client) AddUserToTeamFromInvite(hash, dataToHash, inviteId string) (*Result, *AppError) { | ||||
| 	data := make(map[string]string) | ||||
| 	data["hash"] = hash | ||||
| @@ -354,6 +395,26 @@ func (c *Client) AddUserToTeamFromInvite(hash, dataToHash, inviteId string) (*Re | ||||
| 	} | ||||
| } | ||||
|  | ||||
| //  Removes a user directly from the team. | ||||
| //  The teamId and userId are required.  You must be a valid member of the team and/or | ||||
| //  have the correct role to remove a user from the team.  Returns a map of user_id=userId | ||||
| //  if successful, otherwise returns an AppError. | ||||
| func (c *Client) RemoveUserFromTeam(teamId string, userId string) (*Result, *AppError) { | ||||
| 	if len(teamId) == 0 { | ||||
| 		teamId = c.GetTeamId() | ||||
| 	} | ||||
|  | ||||
| 	data := make(map[string]string) | ||||
| 	data["user_id"] = userId | ||||
| 	if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v", teamId)+"/remove_user_from_team", MapToJson(data)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/invite_members", invites.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -364,6 +425,9 @@ func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // UpdateTeam updates a team based on the changes in the provided team struct. On success | ||||
| // it returns a sanitized version of the updated team. Must be authenticated as a team admin | ||||
| // for that team or a system admin. | ||||
| func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/update", team.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -374,6 +438,9 @@ func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // User Routes Section | ||||
|  | ||||
| // CreateUser creates a user in the system based on the provided user struct. | ||||
| func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -384,6 +451,8 @@ func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CreateUserWithInvite creates a user based on the provided user struct. Either the hash and | ||||
| // data strings or the inviteId is required from the invite. | ||||
| func (c *Client) CreateUserWithInvite(user *User, hash string, data string, inviteId string) (*Result, *AppError) { | ||||
|  | ||||
| 	url := "/users/create?d=" + url.QueryEscape(data) + "&h=" + url.QueryEscape(hash) + "&iid=" + url.QueryEscape(inviteId) | ||||
| @@ -407,6 +476,7 @@ func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Re | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetUser returns a user based on a provided user id string. Must be authenticated. | ||||
| func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/"+id+"/get", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| @@ -417,6 +487,7 @@ func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetMe returns the current user. | ||||
| func (c *Client) GetMe(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/me", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| @@ -427,6 +498,8 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetProfilesForDirectMessageList returns a map of users for a team that can be direct | ||||
| // messaged, using user id as the key. Must be authenticated. | ||||
| func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| @@ -437,6 +510,8 @@ func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppEr | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetProfiles returns a map of users for a team using user id as the key. Must | ||||
| // be authenticated. | ||||
| func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil { | ||||
| 		return nil, err | ||||
| @@ -447,6 +522,8 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetDirectProfiles gets a map of users that are currently shown in the sidebar, | ||||
| // using user id as the key. Must be authenticated. | ||||
| func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| @@ -457,6 +534,7 @@ func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // LoginById authenticates a user by user id and password. | ||||
| func (c *Client) LoginById(id string, password string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["id"] = id | ||||
| @@ -464,6 +542,8 @@ func (c *Client) LoginById(id string, password string) (*Result, *AppError) { | ||||
| 	return c.login(m) | ||||
| } | ||||
|  | ||||
| // Login authenticates a user by login id, which can be username, email or some sort | ||||
| // of SSO identifier based on configuration, and a password. | ||||
| func (c *Client) Login(loginId string, password string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["login_id"] = loginId | ||||
| @@ -471,6 +551,7 @@ func (c *Client) Login(loginId string, password string) (*Result, *AppError) { | ||||
| 	return c.login(m) | ||||
| } | ||||
|  | ||||
| // LoginByLdap authenticates a user by LDAP id and password. | ||||
| func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["login_id"] = loginId | ||||
| @@ -479,6 +560,9 @@ func (c *Client) LoginByLdap(loginId string, password string) (*Result, *AppErro | ||||
| 	return c.login(m) | ||||
| } | ||||
|  | ||||
| // LoginWithDevice authenticates a user by login id (username, email or some sort | ||||
| // of SSO identifier based on configuration), password and attaches a device id to | ||||
| // the session. | ||||
| func (c *Client) LoginWithDevice(loginId string, password string, deviceId string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["login_id"] = loginId | ||||
| @@ -505,6 +589,7 @@ func (c *Client) login(m map[string]string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Logout terminates the current user's session. | ||||
| func (c *Client) Logout() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/logout", ""); err != nil { | ||||
| 		return nil, err | ||||
| @@ -519,6 +604,9 @@ func (c *Client) Logout() (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CheckMfa returns a map with key "mfa_required" with the string value "true" or "false", | ||||
| // indicating whether MFA is required to log the user in, based on a provided login id | ||||
| // (username, email or some sort of SSO identifier based on configuration). | ||||
| func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["login_id"] = loginId | ||||
| @@ -532,6 +620,8 @@ func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GenerateMfaQrCode returns a QR code imagem containing the secret, to be scanned | ||||
| // by a multi-factor authentication mobile application. Must be authenticated. | ||||
| func (c *Client) GenerateMfaQrCode() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/generate_mfa_qr", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| @@ -542,6 +632,9 @@ func (c *Client) GenerateMfaQrCode() (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // UpdateMfa activates multi-factor authenticates for the current user if activate | ||||
| // is true and a valid token is provided. If activate is false, then token is not | ||||
| // required and multi-factor authentication is disabled for the current user. | ||||
| func (c *Client) UpdateMfa(activate bool, token string) (*Result, *AppError) { | ||||
| 	m := make(map[string]interface{}) | ||||
| 	m["activate"] = activate | ||||
| @@ -716,6 +809,15 @@ func (c *Client) GetLogs() (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetClusterStatus() ([]*ClusterInfo, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/admin/cluster_status", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return ClusterInfosFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetAllAudits() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/admin/audits", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| @@ -843,6 +945,20 @@ func (c *Client) GetSystemAnalytics(name string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Initiate immediate synchronization of LDAP users. | ||||
| // The synchronization will be performed asynchronously and this function will | ||||
| // always return OK unless you don't have permissions. | ||||
| // You must be the system administrator to use this function. | ||||
| func (c *Client) LdapSyncNow() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/admin/ldap_sync_now", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/create", channel.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -949,6 +1065,7 @@ func (c *Client) JoinChannel(id string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/join", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), nil}, nil | ||||
| 	} | ||||
| @@ -958,6 +1075,7 @@ func (c *Client) JoinChannelByName(name string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelNameRoute(name)+"/join", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), nil}, nil | ||||
| 	} | ||||
| @@ -967,6 +1085,7 @@ func (c *Client) LeaveChannel(id string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/leave", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), nil}, nil | ||||
| 	} | ||||
| @@ -976,6 +1095,7 @@ func (c *Client) DeleteChannel(id string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/delete", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), nil}, nil | ||||
| 	} | ||||
| @@ -987,6 +1107,7 @@ func (c *Client) AddChannelMember(id, user_id string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/add", MapToJson(data)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), nil}, nil | ||||
| 	} | ||||
| @@ -998,6 +1119,7 @@ func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/remove", MapToJson(data)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), nil}, nil | ||||
| 	} | ||||
| @@ -1007,6 +1129,7 @@ func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/update_last_viewed_at", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), nil}, nil | ||||
| 	} | ||||
| @@ -1115,6 +1238,18 @@ func (c *Client) SearchPosts(terms string, isOrSearch bool) (*Result, *AppError) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetFlaggedPosts will return a post list of posts that have been flagged by the user. | ||||
| // The page is set by the integer parameters offset and limit. | ||||
| func (c *Client) GetFlaggedPosts(offset int, limit int) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/flagged/%v/%v", offset, limit), "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) { | ||||
| 	return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType) | ||||
| } | ||||
| @@ -1302,8 +1437,9 @@ func (c *Client) AdminResetPassword(userId, newPassword string) (*Result, *AppEr | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetStatuses(data []string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/status", ArrayToJson(data)); err != nil { | ||||
| // GetStatuses returns a map of string statuses using user id as the key | ||||
| func (c *Client) GetStatuses() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/status", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| @@ -1332,6 +1468,8 @@ func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RegisterApp creates a new OAuth2 app to be used with the OAuth2 Provider. On success | ||||
| // it returns the created app. Must be authenticated as a user. | ||||
| func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -1342,6 +1480,9 @@ func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AllowOAuth allows a new session by an OAuth2 App. On success | ||||
| // it returns the url to be redirected back to the app which initiated the oauth2 flow. | ||||
| // Must be authenticated as a user. | ||||
| func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/oauth/allow?response_type="+rspType+"&client_id="+clientId+"&redirect_uri="+url.QueryEscape(redirect)+"&scope="+scope+"&state="+url.QueryEscape(state), "", ""); err != nil { | ||||
| 		return nil, err | ||||
| @@ -1352,8 +1493,47 @@ func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (* | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetOAuthAppsByUser returns the OAuth2 Apps registered by the user. On success | ||||
| // it returns a list of OAuth2 Apps from the same user or all the registered apps if the user | ||||
| // is a System Administrator. Must be authenticated as a user. | ||||
| func (c *Client) GetOAuthAppsByUser() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/oauth/list", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), OAuthAppListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetOAuthAppInfo lookup an OAuth2 App using the client_id. On success | ||||
| // it returns a Sanitized OAuth2 App. Must be authenticated as a user. | ||||
| func (c *Client) GetOAuthAppInfo(clientId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/oauth/app/"+clientId, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // DeleteOAuthApp deletes an OAuth2 app, the app must be deleted by the same user who created it or | ||||
| // a System Administrator. On success returs Status OK. Must be authenticated as a user. | ||||
| func (c *Client) DeleteOAuthApp(id string) (*Result, *AppError) { | ||||
| 	data := make(map[string]string) | ||||
| 	data["id"] = id | ||||
| 	if r, err := c.DoApiPost("/oauth/delete", MapToJson(data)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/oauth/access_token", data.Encode()); err != nil { | ||||
| 	if r, err := c.DoPost("/oauth/access_token", data.Encode(), "application/x-www-form-urlencoded"); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| @@ -1376,6 +1556,7 @@ func (c *Client) PostToWebhook(id, payload string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoPost("/hooks/"+id, payload, "application/x-www-form-urlencoded"); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), nil}, nil | ||||
| 	} | ||||
| @@ -1417,6 +1598,7 @@ func (c *Client) SetPreferences(preferences *Preferences) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/preferences/save", preferences.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), preferences}, nil | ||||
| 	} | ||||
| @@ -1441,6 +1623,16 @@ func (c *Client) GetPreferenceCategory(category string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // DeletePreferences deletes a list of preferences owned by the current user. If successful, | ||||
| // it will return status=ok. Otherwise, an error will be returned. | ||||
| func (c *Client) DeletePreferences(preferences *Preferences) (bool, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/preferences/delete", preferences.ToJson()); err != nil { | ||||
| 		return false, err | ||||
| 	} else { | ||||
| 		return c.CheckStatusOK(r), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) CreateOutgoingWebhook(hook *OutgoingWebhook) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/hooks/outgoing/create", hook.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -1509,3 +1701,118 @@ func (c *Client) GetInitialLoad() (*Result, *AppError) { | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), InitialLoadFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ListEmoji returns a list of all user-created emoji for the server. | ||||
| func (c *Client) ListEmoji() ([]*Emoji, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetEmojiRoute()+"/list", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return EmojiListFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CreateEmoji will save an emoji to the server if the current user has permission | ||||
| // to do so. If successful, the provided emoji will be returned with its Id field | ||||
| // filled in. Otherwise, an error will be returned. | ||||
| func (c *Client) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoji, *AppError) { | ||||
| 	c.clearExtraProperties() | ||||
|  | ||||
| 	body := &bytes.Buffer{} | ||||
| 	writer := multipart.NewWriter(body) | ||||
|  | ||||
| 	if part, err := writer.CreateFormFile("image", filename); err != nil { | ||||
| 		return nil, NewLocAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error()) | ||||
| 	} else if _, err = io.Copy(part, bytes.NewBuffer(image)); err != nil { | ||||
| 		return nil, NewLocAppError("CreateEmoji", "model.client.create_emoji.image.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	if err := writer.WriteField("emoji", emoji.ToJson()); err != nil { | ||||
| 		return nil, NewLocAppError("CreateEmoji", "model.client.create_emoji.emoji.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	if err := writer.Close(); err != nil { | ||||
| 		return nil, NewLocAppError("CreateEmoji", "model.client.create_emoji.writer.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetEmojiRoute()+"/create", body) | ||||
| 	rq.Header.Set("Content-Type", writer.FormDataContentType()) | ||||
|  | ||||
| 	if len(c.AuthToken) > 0 { | ||||
| 		rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) | ||||
| 	} | ||||
|  | ||||
| 	if r, err := c.HttpClient.Do(rq); err != nil { | ||||
| 		return nil, NewLocAppError("CreateEmoji", "model.client.connecting.app_error", nil, err.Error()) | ||||
| 	} else if r.StatusCode >= 300 { | ||||
| 		return nil, AppErrorFromJson(r.Body) | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return EmojiFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // DeleteEmoji will delete an emoji from the server if the current user has permission | ||||
| // to do so. If successful, it will return status=ok. Otherwise, an error will be returned. | ||||
| func (c *Client) DeleteEmoji(id string) (bool, *AppError) { | ||||
| 	data := map[string]string{"id": id} | ||||
|  | ||||
| 	if r, err := c.DoApiPost(c.GetEmojiRoute()+"/delete", MapToJson(data)); err != nil { | ||||
| 		return false, err | ||||
| 	} else { | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return c.CheckStatusOK(r), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetCustomEmojiImageUrl returns the API route that can be used to get the image used by | ||||
| // the given emoji. | ||||
| func (c *Client) GetCustomEmojiImageUrl(id string) string { | ||||
| 	return c.GetEmojiRoute() + "/" + id | ||||
| } | ||||
|  | ||||
| // Uploads a x509 base64 Certificate or Private Key file to be used with SAML. | ||||
| // data byte array is required and needs to be a Multi-Part with 'certificate' as the field name | ||||
| // contentType is also required. Returns nil if succesful, otherwise returns an AppError | ||||
| func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppError { | ||||
| 	url := c.ApiUrl + "/admin/add_certificate" | ||||
| 	rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) | ||||
| 	rq.Header.Set("Content-Type", contentType) | ||||
|  | ||||
| 	if len(c.AuthToken) > 0 { | ||||
| 		rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) | ||||
| 	} | ||||
|  | ||||
| 	if rp, err := c.HttpClient.Do(rq); err != nil { | ||||
| 		return NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) | ||||
| 	} else if rp.StatusCode >= 300 { | ||||
| 		return AppErrorFromJson(rp.Body) | ||||
| 	} else { | ||||
| 		defer closeBody(rp) | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Removes a x509 base64 Certificate or Private Key file used with SAML. | ||||
| // filename is required. Returns nil if successful, otherwise returns an AppError | ||||
| func (c *Client) RemoveCertificateFile(filename string) *AppError { | ||||
| 	if r, err := c.DoApiPost("/admin/remove_certificate", MapToJson(map[string]string{"filename": filename})); err != nil { | ||||
| 		return err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Checks if the x509 base64 Certificates and Private Key files used with SAML exists on the file system. | ||||
| // Returns a map[string]interface{} if successful, otherwise returns an AppError. Must be System Admin authenticated. | ||||
| func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{}, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/admin/remove_certificate", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return StringInterfaceFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										66
									
								
								vendor/github.com/mattermost/platform/model/cluster_info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								vendor/github.com/mattermost/platform/model/cluster_info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type ClusterInfo struct { | ||||
| 	Id                 string `json:"id"` | ||||
| 	Version            string `json:"version"` | ||||
| 	ConfigHash         string `json:"config_hash"` | ||||
| 	InterNodeUrl       string `json:"internode_url"` | ||||
| 	Hostname           string `json:"hostname"` | ||||
| 	LastSuccessfulPing int64  `json:"last_ping"` | ||||
| 	IsAlive            bool   `json:"is_alive"` | ||||
| } | ||||
|  | ||||
| func (me *ClusterInfo) ToJson() string { | ||||
| 	b, err := json.Marshal(me) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ClusterInfoFromJson(data io.Reader) *ClusterInfo { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var me ClusterInfo | ||||
| 	err := decoder.Decode(&me) | ||||
| 	if err == nil { | ||||
| 		return &me | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (me *ClusterInfo) HaveEstablishedInitialContact() bool { | ||||
| 	if me.Id != "" { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func ClusterInfosToJson(objmap []*ClusterInfo) string { | ||||
| 	if b, err := json.Marshal(objmap); err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ClusterInfosFromJson(data io.Reader) []*ClusterInfo { | ||||
| 	decoder := json.NewDecoder(data) | ||||
|  | ||||
| 	var objmap []*ClusterInfo | ||||
| 	if err := decoder.Decode(&objmap); err != nil { | ||||
| 		return make([]*ClusterInfo, 0) | ||||
| 	} else { | ||||
| 		return objmap | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										427
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										427
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,7 +6,7 @@ package model | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -20,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" | ||||
| @@ -33,19 +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 | ||||
| ) | ||||
|  | ||||
| // should match the values in webapp/i18n/i18n.jsx | ||||
| var LOCALES = []string{ | ||||
| 	"en", | ||||
| 	"es", | ||||
| 	"fr", | ||||
| 	"ja", | ||||
| 	"pt-BR", | ||||
| } | ||||
|  | ||||
| type ServiceSettings struct { | ||||
| 	SiteURL                           *string | ||||
| 	ListenAddress                     string | ||||
| 	MaximumLoginAttempts              int | ||||
| 	SegmentDeveloperKey               string | ||||
| @@ -70,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 { | ||||
| @@ -93,12 +107,21 @@ 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 { | ||||
| @@ -132,6 +155,7 @@ type EmailSettings struct { | ||||
| 	RequireEmailVerification bool | ||||
| 	FeedbackName             string | ||||
| 	FeedbackEmail            string | ||||
| 	FeedbackOrganization     *string | ||||
| 	SMTPUsername             string | ||||
| 	SMTPPassword             string | ||||
| 	SMTPServer               string | ||||
| @@ -167,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 { | ||||
| @@ -206,6 +235,7 @@ type LdapSettings struct { | ||||
| 	// Advanced | ||||
| 	SkipCertificateVerification *bool | ||||
| 	QueryTimeout                *int | ||||
| 	MaxPageSize                 *int | ||||
|  | ||||
| 	// Customization | ||||
| 	LoginFieldName *string | ||||
| @@ -223,11 +253,43 @@ type LocalizationSettings struct { | ||||
| 	AvailableLocales    *string | ||||
| } | ||||
|  | ||||
| type SamlSettings struct { | ||||
| 	// Basic | ||||
| 	Enable  *bool | ||||
| 	Verify  *bool | ||||
| 	Encrypt *bool | ||||
|  | ||||
| 	IdpUrl                      *string | ||||
| 	IdpDescriptorUrl            *string | ||||
| 	AssertionConsumerServiceURL *string | ||||
|  | ||||
| 	IdpCertificateFile    *string | ||||
| 	PublicCertificateFile *string | ||||
| 	PrivateKeyFile        *string | ||||
|  | ||||
| 	// User Mapping | ||||
| 	FirstNameAttribute *string | ||||
| 	LastNameAttribute  *string | ||||
| 	EmailAttribute     *string | ||||
| 	UsernameAttribute  *string | ||||
| 	NicknameAttribute  *string | ||||
| 	LocaleAttribute    *string | ||||
|  | ||||
| 	LoginButtonText *string | ||||
| } | ||||
|  | ||||
| type NativeAppSettings struct { | ||||
| 	AppDownloadLink        *string | ||||
| 	AndroidAppDownloadLink *string | ||||
| 	IosAppDownloadLink     *string | ||||
| } | ||||
|  | ||||
| type Config struct { | ||||
| 	ServiceSettings      ServiceSettings | ||||
| 	TeamSettings         TeamSettings | ||||
| 	SqlSettings          SqlSettings | ||||
| 	LogSettings          LogSettings | ||||
| 	PasswordSettings     PasswordSettings | ||||
| 	FileSettings         FileSettings | ||||
| 	EmailSettings        EmailSettings | ||||
| 	RateLimitSettings    RateLimitSettings | ||||
| @@ -235,9 +297,13 @@ type Config struct { | ||||
| 	SupportSettings      SupportSettings | ||||
| 	GitLabSettings       SSOSettings | ||||
| 	GoogleSettings       SSOSettings | ||||
| 	Office365Settings    SSOSettings | ||||
| 	LdapSettings         LdapSettings | ||||
| 	ComplianceSettings   ComplianceSettings | ||||
| 	LocalizationSettings LocalizationSettings | ||||
| 	SamlSettings         SamlSettings | ||||
| 	NativeAppSettings    NativeAppSettings | ||||
| 	ClusterSettings      ClusterSettings | ||||
| } | ||||
|  | ||||
| func (o *Config) ToJson() string { | ||||
| @@ -255,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 | ||||
| @@ -304,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 | ||||
| @@ -324,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 | ||||
| @@ -339,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 | ||||
| @@ -349,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) | ||||
|  | ||||
| @@ -379,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) { | ||||
| @@ -394,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) { | ||||
| @@ -403,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) { | ||||
| @@ -412,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) { | ||||
| @@ -421,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 { | ||||
| @@ -484,6 +612,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*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 = "" | ||||
| @@ -509,6 +642,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.LdapSettings.QueryTimeout = 60 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.MaxPageSize == nil { | ||||
| 		o.LdapSettings.MaxPageSize = new(int) | ||||
| 		*o.LdapSettings.MaxPageSize = 0 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.LoginFieldName == nil { | ||||
| 		o.LdapSettings.LoginFieldName = new(string) | ||||
| 		*o.LdapSettings.LoginFieldName = "" | ||||
| @@ -561,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 { | ||||
| @@ -591,7 +755,102 @@ func (o *Config) SetDefaults() { | ||||
|  | ||||
| 	if o.LocalizationSettings.AvailableLocales == nil { | ||||
| 		o.LocalizationSettings.AvailableLocales = new(string) | ||||
| 		*o.LocalizationSettings.AvailableLocales = strings.Join(LOCALES, ",") | ||||
| 		*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/" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -601,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, "") | ||||
| 	} | ||||
| @@ -697,6 +962,98 @@ func (o *Config) IsValid() *AppError { | ||||
| 		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 | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										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 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										13
									
								
								vendor/github.com/mattermost/platform/model/job.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								vendor/github.com/mattermost/platform/model/job.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -28,8 +28,11 @@ func removeTaskByName(name string) { | ||||
| 	delete(tasks, name) | ||||
| } | ||||
|  | ||||
| func getTaskByName(name string) *ScheduledTask { | ||||
| 	return tasks[name] | ||||
| func GetTaskByName(name string) *ScheduledTask { | ||||
| 	if task, ok := tasks[name]; ok { | ||||
| 		return task | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func GetAllTasks() *map[string]*ScheduledTask { | ||||
| @@ -81,6 +84,12 @@ func (task *ScheduledTask) Cancel() { | ||||
| 	removeTaskByName(task.Name) | ||||
| } | ||||
|  | ||||
| // Executes the task immediatly. A recurring task will be run regularally after interval. | ||||
| func (task *ScheduledTask) Execute() { | ||||
| 	task.function() | ||||
| 	task.timer.Reset(task.Interval) | ||||
| } | ||||
|  | ||||
| func (task *ScheduledTask) String() string { | ||||
| 	return fmt.Sprintf( | ||||
| 		"%s\nInterval: %s\nRecurring: %t\n", | ||||
|   | ||||
							
								
								
									
										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 | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										20
									
								
								vendor/github.com/mattermost/platform/model/outgoing_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/mattermost/platform/model/outgoing_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -9,6 +9,7 @@ import ( | ||||
| 	"io" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type OutgoingWebhook struct { | ||||
| @@ -21,6 +22,7 @@ type OutgoingWebhook struct { | ||||
| 	ChannelId    string      `json:"channel_id"` | ||||
| 	TeamId       string      `json:"team_id"` | ||||
| 	TriggerWords StringArray `json:"trigger_words"` | ||||
| 	TriggerWhen  int         `json:"trigger_when"` | ||||
| 	CallbackURLs StringArray `json:"callback_urls"` | ||||
| 	DisplayName  string      `json:"display_name"` | ||||
| 	Description  string      `json:"description"` | ||||
| @@ -171,6 +173,10 @@ func (o *OutgoingWebhook) IsValid() *AppError { | ||||
| 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if o.TriggerWhen > 1 { | ||||
| 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -204,3 +210,17 @@ func (o *OutgoingWebhook) HasTriggerWord(word string) bool { | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool { | ||||
| 	if len(o.TriggerWords) == 0 || len(word) == 0 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	for _, trigger := range o.TriggerWords { | ||||
| 		if strings.HasPrefix(word, trigger) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|   | ||||
							
								
								
									
										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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										48
									
								
								vendor/github.com/mattermost/platform/model/preference.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										48
									
								
								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,10 +15,17 @@ 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" | ||||
| ) | ||||
| @@ -57,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 | ||||
| } | ||||
							
								
								
									
										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 { | ||||
|   | ||||
							
								
								
									
										63
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										63
									
								
								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 | ||||
| } | ||||
|  | ||||
| @@ -184,21 +167,6 @@ func (u *User) PreUpdate() { | ||||
| 		} | ||||
| 		u.NotifyProps["mention_keys"] = strings.Join(goodKeys, ",") | ||||
| 	} | ||||
|  | ||||
| 	if u.ThemeProps != nil { | ||||
| 		colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`) | ||||
|  | ||||
| 		// blank out any invalid theme values | ||||
| 		for name, value := range u.ThemeProps { | ||||
| 			if name == "image" || name == "type" || name == "codeTheme" { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			if !colorPattern.MatchString(value) { | ||||
| 				u.ThemeProps[name] = "#ffffff" | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (u *User) SetDefaultNotifications() { | ||||
| @@ -208,7 +176,6 @@ func (u *User) SetDefaultNotifications() { | ||||
| 	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 == "" { | ||||
| @@ -244,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 | ||||
| @@ -284,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 | ||||
| @@ -363,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, " ") | ||||
| @@ -398,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) | ||||
| @@ -467,6 +413,7 @@ var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`) | ||||
| var restrictedUsernames = []string{ | ||||
| 	"all", | ||||
| 	"channel", | ||||
| 	"matterbot", | ||||
| } | ||||
|  | ||||
| func IsValidUsername(s string) bool { | ||||
|   | ||||
							
								
								
									
										19
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								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:"-"` | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -13,6 +13,8 @@ import ( | ||||
| // It should be maitained in chronological order with most current | ||||
| // release at the front of the list. | ||||
| var versions = []string{ | ||||
| 	"3.3.0", | ||||
| 	"3.2.0", | ||||
| 	"3.1.0", | ||||
| 	"3.0.0", | ||||
| 	"2.2.0", | ||||
|   | ||||
							
								
								
									
										109
									
								
								vendor/github.com/mattermost/platform/model/websocket_client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								vendor/github.com/mattermost/platform/model/websocket_client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"github.com/gorilla/websocket" | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| type WebSocketClient struct { | ||||
| 	Url             string          // The location of the server like "ws://localhost:8065" | ||||
| 	ApiUrl          string          // The api location of the server like "ws://localhost:8065/api/v3" | ||||
| 	Conn            *websocket.Conn // The WebSocket connection | ||||
| 	AuthToken       string          // The token used to open the WebSocket | ||||
| 	Sequence        int64           // The ever-incrementing sequence attached to each WebSocket action | ||||
| 	EventChannel    chan *WebSocketEvent | ||||
| 	ResponseChannel chan *WebSocketResponse | ||||
| } | ||||
|  | ||||
| // NewWebSocketClient constructs a new WebSocket client with convienence | ||||
| // methods for talking to the server. | ||||
| func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) { | ||||
| 	header := http.Header{} | ||||
| 	header.Set(HEADER_AUTH, "BEARER "+authToken) | ||||
| 	conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", header) | ||||
| 	if err != nil { | ||||
| 		return nil, NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return &WebSocketClient{ | ||||
| 		url, | ||||
| 		url + API_URL_SUFFIX, | ||||
| 		conn, | ||||
| 		authToken, | ||||
| 		1, | ||||
| 		make(chan *WebSocketEvent, 100), | ||||
| 		make(chan *WebSocketResponse, 100), | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (wsc *WebSocketClient) Connect() *AppError { | ||||
| 	header := http.Header{} | ||||
| 	header.Set(HEADER_AUTH, "BEARER "+wsc.AuthToken) | ||||
|  | ||||
| 	var err error | ||||
| 	wsc.Conn, _, err = websocket.DefaultDialer.Dial(wsc.ApiUrl+"/users/websocket", header) | ||||
| 	if err != nil { | ||||
| 		return NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (wsc *WebSocketClient) Close() { | ||||
| 	wsc.Conn.Close() | ||||
| } | ||||
|  | ||||
| func (wsc *WebSocketClient) Listen() { | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			var rawMsg json.RawMessage | ||||
| 			var err error | ||||
| 			if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil { | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			var event WebSocketEvent | ||||
| 			if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { | ||||
| 				wsc.EventChannel <- &event | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			var response WebSocketResponse | ||||
| 			if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { | ||||
| 				wsc.ResponseChannel <- &response | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
|  | ||||
| func (wsc *WebSocketClient) SendMessage(action string, data map[string]interface{}) { | ||||
| 	req := &WebSocketRequest{} | ||||
| 	req.Seq = wsc.Sequence | ||||
| 	req.Action = action | ||||
| 	req.Data = data | ||||
|  | ||||
| 	wsc.Sequence++ | ||||
|  | ||||
| 	wsc.Conn.WriteJSON(req) | ||||
| } | ||||
|  | ||||
| // UserTyping will push a user_typing event out to all connected users | ||||
| // who are in the specified channel | ||||
| func (wsc *WebSocketClient) UserTyping(channelId, parentId string) { | ||||
| 	data := map[string]interface{}{ | ||||
| 		"channel_id": channelId, | ||||
| 		"parent_id":  parentId, | ||||
| 	} | ||||
|  | ||||
| 	wsc.SendMessage("user_typing", data) | ||||
| } | ||||
|  | ||||
| // GetStatuses will return a map of string statuses using user id as the key | ||||
| func (wsc *WebSocketClient) GetStatuses() { | ||||
| 	wsc.SendMessage("get_statuses", nil) | ||||
| } | ||||
							
								
								
									
										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 | ||||
| } | ||||
							
								
								
									
										201
									
								
								vendor/github.com/sromku/go-gitter/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								vendor/github.com/sromku/go-gitter/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,201 @@ | ||||
|                                  Apache License | ||||
|                            Version 2.0, January 2004 | ||||
|                         http://www.apache.org/licenses/ | ||||
|  | ||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||
|  | ||||
|    1. Definitions. | ||||
|  | ||||
|       "License" shall mean the terms and conditions for use, reproduction, | ||||
|       and distribution as defined by Sections 1 through 9 of this document. | ||||
|  | ||||
|       "Licensor" shall mean the copyright owner or entity authorized by | ||||
|       the copyright owner that is granting the License. | ||||
|  | ||||
|       "Legal Entity" shall mean the union of the acting entity and all | ||||
|       other entities that control, are controlled by, or are under common | ||||
|       control with that entity. For the purposes of this definition, | ||||
|       "control" means (i) the power, direct or indirect, to cause the | ||||
|       direction or management of such entity, whether by contract or | ||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||
|       outstanding shares, or (iii) beneficial ownership of such entity. | ||||
|  | ||||
|       "You" (or "Your") shall mean an individual or Legal Entity | ||||
|       exercising permissions granted by this License. | ||||
|  | ||||
|       "Source" form shall mean the preferred form for making modifications, | ||||
|       including but not limited to software source code, documentation | ||||
|       source, and configuration files. | ||||
|  | ||||
|       "Object" form shall mean any form resulting from mechanical | ||||
|       transformation or translation of a Source form, including but | ||||
|       not limited to compiled object code, generated documentation, | ||||
|       and conversions to other media types. | ||||
|  | ||||
|       "Work" shall mean the work of authorship, whether in Source or | ||||
|       Object form, made available under the License, as indicated by a | ||||
|       copyright notice that is included in or attached to the work | ||||
|       (an example is provided in the Appendix below). | ||||
|  | ||||
|       "Derivative Works" shall mean any work, whether in Source or Object | ||||
|       form, that is based on (or derived from) the Work and for which the | ||||
|       editorial revisions, annotations, elaborations, or other modifications | ||||
|       represent, as a whole, an original work of authorship. For the purposes | ||||
|       of this License, Derivative Works shall not include works that remain | ||||
|       separable from, or merely link (or bind by name) to the interfaces of, | ||||
|       the Work and Derivative Works thereof. | ||||
|  | ||||
|       "Contribution" shall mean any work of authorship, including | ||||
|       the original version of the Work and any modifications or additions | ||||
|       to that Work or Derivative Works thereof, that is intentionally | ||||
|       submitted to Licensor for inclusion in the Work by the copyright owner | ||||
|       or by an individual or Legal Entity authorized to submit on behalf of | ||||
|       the copyright owner. For the purposes of this definition, "submitted" | ||||
|       means any form of electronic, verbal, or written communication sent | ||||
|       to the Licensor or its representatives, including but not limited to | ||||
|       communication on electronic mailing lists, source code control systems, | ||||
|       and issue tracking systems that are managed by, or on behalf of, the | ||||
|       Licensor for the purpose of discussing and improving the Work, but | ||||
|       excluding communication that is conspicuously marked or otherwise | ||||
|       designated in writing by the copyright owner as "Not a Contribution." | ||||
|  | ||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||
|       on behalf of whom a Contribution has been received by Licensor and | ||||
|       subsequently incorporated within the Work. | ||||
|  | ||||
|    2. Grant of Copyright License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       copyright license to reproduce, prepare Derivative Works of, | ||||
|       publicly display, publicly perform, sublicense, and distribute the | ||||
|       Work and such Derivative Works in Source or Object form. | ||||
|  | ||||
|    3. Grant of Patent License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       (except as stated in this section) patent license to make, have made, | ||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||
|       where such license applies only to those patent claims licensable | ||||
|       by such Contributor that are necessarily infringed by their | ||||
|       Contribution(s) alone or by combination of their Contribution(s) | ||||
|       with the Work to which such Contribution(s) was submitted. If You | ||||
|       institute patent litigation against any entity (including a | ||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||
|       or a Contribution incorporated within the Work constitutes direct | ||||
|       or contributory patent infringement, then any patent licenses | ||||
|       granted to You under this License for that Work shall terminate | ||||
|       as of the date such litigation is filed. | ||||
|  | ||||
|    4. Redistribution. You may reproduce and distribute copies of the | ||||
|       Work or Derivative Works thereof in any medium, with or without | ||||
|       modifications, and in Source or Object form, provided that You | ||||
|       meet the following conditions: | ||||
|  | ||||
|       (a) You must give any other recipients of the Work or | ||||
|           Derivative Works a copy of this License; and | ||||
|  | ||||
|       (b) You must cause any modified files to carry prominent notices | ||||
|           stating that You changed the files; and | ||||
|  | ||||
|       (c) You must retain, in the Source form of any Derivative Works | ||||
|           that You distribute, all copyright, patent, trademark, and | ||||
|           attribution notices from the Source form of the Work, | ||||
|           excluding those notices that do not pertain to any part of | ||||
|           the Derivative Works; and | ||||
|  | ||||
|       (d) If the Work includes a "NOTICE" text file as part of its | ||||
|           distribution, then any Derivative Works that You distribute must | ||||
|           include a readable copy of the attribution notices contained | ||||
|           within such NOTICE file, excluding those notices that do not | ||||
|           pertain to any part of the Derivative Works, in at least one | ||||
|           of the following places: within a NOTICE text file distributed | ||||
|           as part of the Derivative Works; within the Source form or | ||||
|           documentation, if provided along with the Derivative Works; or, | ||||
|           within a display generated by the Derivative Works, if and | ||||
|           wherever such third-party notices normally appear. The contents | ||||
|           of the NOTICE file are for informational purposes only and | ||||
|           do not modify the License. You may add Your own attribution | ||||
|           notices within Derivative Works that You distribute, alongside | ||||
|           or as an addendum to the NOTICE text from the Work, provided | ||||
|           that such additional attribution notices cannot be construed | ||||
|           as modifying the License. | ||||
|  | ||||
|       You may add Your own copyright statement to Your modifications and | ||||
|       may provide additional or different license terms and conditions | ||||
|       for use, reproduction, or distribution of Your modifications, or | ||||
|       for any such Derivative Works as a whole, provided Your use, | ||||
|       reproduction, and distribution of the Work otherwise complies with | ||||
|       the conditions stated in this License. | ||||
|  | ||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||
|       any Contribution intentionally submitted for inclusion in the Work | ||||
|       by You to the Licensor shall be under the terms and conditions of | ||||
|       this License, without any additional terms or conditions. | ||||
|       Notwithstanding the above, nothing herein shall supersede or modify | ||||
|       the terms of any separate license agreement you may have executed | ||||
|       with Licensor regarding such Contributions. | ||||
|  | ||||
|    6. Trademarks. This License does not grant permission to use the trade | ||||
|       names, trademarks, service marks, or product names of the Licensor, | ||||
|       except as required for reasonable and customary use in describing the | ||||
|       origin of the Work and reproducing the content of the NOTICE file. | ||||
|  | ||||
|    7. Disclaimer of Warranty. Unless required by applicable law or | ||||
|       agreed to in writing, Licensor provides the Work (and each | ||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||
|       implied, including, without limitation, any warranties or conditions | ||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||
|       appropriateness of using or redistributing the Work and assume any | ||||
|       risks associated with Your exercise of permissions under this License. | ||||
|  | ||||
|    8. Limitation of Liability. In no event and under no legal theory, | ||||
|       whether in tort (including negligence), contract, or otherwise, | ||||
|       unless required by applicable law (such as deliberate and grossly | ||||
|       negligent acts) or agreed to in writing, shall any Contributor be | ||||
|       liable to You for damages, including any direct, indirect, special, | ||||
|       incidental, or consequential damages of any character arising as a | ||||
|       result of this License or out of the use or inability to use the | ||||
|       Work (including but not limited to damages for loss of goodwill, | ||||
|       work stoppage, computer failure or malfunction, or any and all | ||||
|       other commercial damages or losses), even if such Contributor | ||||
|       has been advised of the possibility of such damages. | ||||
|  | ||||
|    9. Accepting Warranty or Additional Liability. While redistributing | ||||
|       the Work or Derivative Works thereof, You may choose to offer, | ||||
|       and charge a fee for, acceptance of support, warranty, indemnity, | ||||
|       or other liability obligations and/or rights consistent with this | ||||
|       License. However, in accepting such obligations, You may act only | ||||
|       on Your own behalf and on Your sole responsibility, not on behalf | ||||
|       of any other Contributor, and only if You agree to indemnify, | ||||
|       defend, and hold each Contributor harmless for any liability | ||||
|       incurred by, or claims asserted against, such Contributor by reason | ||||
|       of your accepting any such warranty or additional liability. | ||||
|  | ||||
|    END OF TERMS AND CONDITIONS | ||||
|  | ||||
|    APPENDIX: How to apply the Apache License to your work. | ||||
|  | ||||
|       To apply the Apache License to your work, attach the following | ||||
|       boilerplate notice, with the fields enclosed by brackets "{}" | ||||
|       replaced with your own identifying information. (Don't include | ||||
|       the brackets!)  The text should be enclosed in the appropriate | ||||
|       comment syntax for the file format. We also recommend that a | ||||
|       file or class name and description of purpose be included on the | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
|  | ||||
|    Copyright {yyyy} {name of copyright owner} | ||||
|  | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
|  | ||||
|        http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
							
								
								
									
										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 | ||||
|   | ||||
							
								
								
									
										44
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										44
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							| @@ -63,8 +63,8 @@ | ||||
| 			"importpath": "github.com/mattermost/platform/einterfaces", | ||||
| 			"repository": "https://github.com/mattermost/platform", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "974238231b9cdbd39a825ec8e9299fbb0b51f6b8", | ||||
| 			"branch": "release-3.1", | ||||
| 			"revision": "20735470185e0b0ac1d15b975041ed9a2e0e43bc", | ||||
| 			"branch": "release-3.3", | ||||
| 			"path": "/einterfaces", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @@ -72,11 +72,35 @@ | ||||
| 			"importpath": "github.com/mattermost/platform/model", | ||||
| 			"repository": "https://github.com/mattermost/platform", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "974238231b9cdbd39a825ec8e9299fbb0b51f6b8", | ||||
| 			"branch": "release-3.1", | ||||
| 			"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", | ||||
| @@ -110,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 | ||||
| 		}, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user