forked from lug/matterbridge
		
	Compare commits
	
		
			60 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 6e410b096e | ||
|   | f9e5994348 | ||
|   | ee77272cfd | ||
|   | 16ed2aca6a | ||
|   | 0f530e7902 | ||
|   | 4ed66ce20e | ||
|   | b30e85836e | ||
|   | e449a97bd0 | ||
|   | 39043f3fa4 | ||
|   | 12389d602e | ||
|   | 44144587a0 | ||
|   | d0a30e354b | ||
|   | c261dc89d5 | ||
|   | c2c135bca2 | ||
|   | eb20cb237d | ||
|   | 106404d32f | ||
|   | e06efbad9f | ||
|   | 3311c7f923 | ||
|   | 3a6c655dfb | ||
|   | e11d786775 | ||
|   | 889b6debc4 | ||
|   | 9cb3413d9c | ||
|   | 131826e1d1 | ||
|   | 96e21dd051 | ||
|   | 32e5f396e7 | ||
|   | 6c6000dbbd | ||
|   | 24defcb970 | ||
|   | a1a11a88b3 | ||
|   | a997ae29ad | ||
|   | ff94796700 | ||
|   | 1f72ca4c4e | ||
|   | 46faad8b57 | ||
|   | 30f30364d5 | ||
|   | 073d90da88 | ||
|   | c769e23a9a | ||
|   | 9db48f4794 | ||
|   | 911c597377 | ||
|   | 28244ffd9a | ||
|   | 3e38c7945c | ||
|   | 79ffb76f6e | ||
|   | 5fe4b749cf | ||
|   | 6991d85da9 | ||
|   | c1c187a1ab | ||
|   | 055d12e3ef | ||
|   | b49429d722 | ||
|   | 815c7f8d64 | ||
|   | c879f79456 | ||
|   | 3bc25f4707 | ||
|   | 300cfe044a | ||
|   | fb586f4a96 | ||
|   | ced371bece | ||
|   | a87cac1982 | ||
|   | 8fb5c7afa6 | ||
|   | aceb830378 | ||
|   | 0f2976c5ce | ||
|   | 78b17977c5 | ||
|   | 6ec77e06ea | ||
|   | e48db67649 | ||
|   | e03f331f55 | ||
|   | ff5aeeb1e1 | 
							
								
								
									
										11
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| FROM alpine:edge | ||||
| ENTRYPOINT ["/bin/matterbridge"] | ||||
|  | ||||
| COPY . /go/src/github.com/42wim/matterbridge | ||||
| RUN apk update && apk add go git gcc musl-dev ca-certificates \ | ||||
|         && cd /go/src/github.com/42wim/matterbridge \ | ||||
|         && export GOPATH=/go \ | ||||
|         && go get \ | ||||
|         && go build -o /bin/matterbridge \ | ||||
|         && rm -rf /go \ | ||||
|         && apk del --purge git go gcc musl-dev | ||||
							
								
								
									
										124
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										124
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,14 +1,40 @@ | ||||
| # matterbridge | ||||
|  | ||||
| Simple bridge between mattermost and IRC. Uses the in/outgoing webhooks.   | ||||
| Relays public channel messages between mattermost and IRC.   | ||||
| Simple bridge between mattermost, IRC, XMPP and Gitter | ||||
|  | ||||
| Requires mattermost 1.2.0+ | ||||
| * Relays public channel messages between mattermost, IRC, XMPP and Gitter. Pick and mix. | ||||
| * Supports multiple channels. | ||||
| * Matterbridge -plus also works with private groups on your mattermost. | ||||
|  | ||||
| There is also [matterbridge-plus] (https://github.com/42wim/matterbridge-plus) which uses the mattermost API and needs a dedicated user (bot). But requires no incoming/outgoing webhook setup.  | ||||
| Look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for documentation and an example. | ||||
|  | ||||
| ## Changelog | ||||
| Since v0.6.1 support for XMPP, Gitter and Slack is added. More details in [changelog.md] (https://github.com/42wim/matterbridge/blob/master/changelog.md) | ||||
|  | ||||
| ## Requirements: | ||||
| Accounts to one of the supported bridges | ||||
| * [Mattermost] (https://github.com/mattermost/platform/) | ||||
| * [IRC] (http://www.mirc.com/servers.html) | ||||
| * [XMPP] (https://jabber.org) | ||||
| * [Gitter] (https://gitter.im) | ||||
|  | ||||
| ## binaries | ||||
| Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/tag/v0.4) | ||||
| Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/) | ||||
| * For use with mattermost 3.3.0+ [v0.6.1](https://github.com/42wim/matterircd/releases/tag/v0.6.1) | ||||
| * For use with mattermost 3.0.0-3.2.0 [v0.5.0](https://github.com/42wim/matterircd/releases/tag/v0.5.0) | ||||
|  | ||||
| ## Compatibility | ||||
| ### Mattermost  | ||||
| * Matterbridge v0.6.1 works with mattermost 3.3.0 and higher [3.3.0 release](https://github.com/mattermost/platform/releases/tag/v3.3.0) | ||||
| * Matterbridge v0.5.0 works with mattermost 3.0.0 - 3.2.0 [3.2.0 release](https://github.com/mattermost/platform/releases/tag/v3.2.0) | ||||
|  | ||||
|  | ||||
| #### Webhooks version | ||||
| * Configured incoming/outgoing [webhooks](https://www.mattermost.org/webhooks/) on your mattermost instance. | ||||
|  | ||||
| #### Plus (API) version | ||||
| * A dedicated user(bot) on your mattermost instance. | ||||
|  | ||||
|  | ||||
| ## building | ||||
| Go 1.6+ is required. Make sure you have [Go](https://golang.org/doc/install) properly installed, including setting up your [GOPATH] (https://golang.org/doc/code.html#GOPATH) | ||||
| @@ -31,73 +57,26 @@ matterbridge | ||||
| 3) Now you can run matterbridge.  | ||||
|  | ||||
| ``` | ||||
| Usage of matterbridge: | ||||
|   -conf="matterbridge.conf": config file | ||||
| Usage of ./matterbridge: | ||||
|   -conf string | ||||
|         config file (default "matterbridge.conf") | ||||
|   -debug | ||||
|         enable debug | ||||
|   -plus | ||||
|         running using API instead of webhooks (deprecated, set Plus flag in [general] config) | ||||
|   -version | ||||
|         show version | ||||
| ``` | ||||
|  | ||||
| Matterbridge will: | ||||
| * start a webserver listening on the port specified in the configuration. | ||||
| * connect to specified irc server and channel. | ||||
| * send messages from mattermost to irc and vice versa, messages in mattermost will appear with irc-nick | ||||
|  | ||||
| ## config | ||||
| ### matterbridge | ||||
| matterbridge looks for matterbridge.conf in current directory. (use -conf to specify another file) | ||||
|  | ||||
| Look at matterbridge.conf.sample for an example | ||||
|  | ||||
|  | ||||
| ``` | ||||
| [IRC] | ||||
| server="irc.freenode.net" | ||||
| port=6667 | ||||
| UseTLS=false | ||||
| SkipTLSVerify=true | ||||
| nick="matterbot" | ||||
| channel="#matterbridge" | ||||
| UseSlackCircumfix=false | ||||
|  | ||||
| [mattermost] | ||||
| #url is your incoming webhook url (account settings - integrations - incoming webhooks) | ||||
| url="http://mattermost.yourdomain.com/hooks/incomingwebhookkey"   | ||||
| #port the bridge webserver will listen on | ||||
| port=9999 | ||||
| #address the webserver will bind to | ||||
| BindAddress="0.0.0.0" | ||||
| showjoinpart=true #show irc users joining and parting | ||||
| #the token you get from the outgoing webhook in mattermost. If empty no token check will be done. | ||||
| #if you use multiple IRC channel (see below, this must be empty!) | ||||
| token=yourtokenfrommattermost | ||||
| #disable certificate checking (selfsigned certificates) | ||||
| #SkipTLSVerify=true | ||||
| #whether to prefix messages from IRC to mattermost with the sender's nick. Useful if username overrides for incoming webhooks isn't enabled on the mattermost server | ||||
| PrefixMessagesWithNick=false | ||||
| #how to format the list of IRC nicks when displayed in mattermost. Possible options are "table" and "plain" | ||||
| NickFormatter=plain | ||||
| #how many nicks to list per row for formatters that support this | ||||
| NicksPerRow=4 | ||||
| #Freenode nickserv | ||||
| NickServNick="nickserv" | ||||
| #Password for nickserv | ||||
| NickServPassword="secret" | ||||
|  | ||||
| #multiple channel config | ||||
| #token you can find in your outgoing webhook | ||||
| [Token "outgoingwebhooktoken1"]  | ||||
| IRCChannel="#off-topic" | ||||
| MMChannel="off-topic" | ||||
|  | ||||
| [Token "outgoingwebhooktoken2"] | ||||
| IRCChannel="#testing" | ||||
| MMChannel="testing" | ||||
|  | ||||
| [general] | ||||
| #request your API key on https://github.com/giphy/GiphyAPI. This is a public beta key | ||||
| GiphyApiKey="dc6zaTOxFJmzC" | ||||
| ``` | ||||
| Look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for an example. | ||||
|  | ||||
| ### mattermost | ||||
| You'll have to configure the incoming en outgoing webhooks.  | ||||
| #### webhooks version | ||||
| You'll have to configure the incoming and outgoing webhooks.  | ||||
|  | ||||
| * incoming webhooks | ||||
| Go to "account settings" - integrations - "incoming webhooks".   | ||||
| @@ -108,5 +87,20 @@ This URL should be set in the matterbridge.conf in the [mattermost] section (see | ||||
| Go to "account settings" - integrations - "outgoing webhooks".   | ||||
| Choose a channel (the same as the one from incoming webhooks) and fill in the address and port of the server matterbridge will run on.   | ||||
|  | ||||
| e.g. http://192.168.1.1:9999 (9999 is the port specified in [mattermost] section of matterbridge.conf) | ||||
| e.g. http://192.168.1.1:9999 (192.168.1.1:9999 is the BindAddress specified in [mattermost] section of matterbridge.conf) | ||||
|  | ||||
| #### plus version | ||||
| You'll have to create a new dedicated user on your mattermost instance. | ||||
| Specify the login and password in [mattermost] section of matterbridge.conf | ||||
|  | ||||
| ## FAQ | ||||
| Please look at [matterbridge.conf.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.conf.sample) for more information first.  | ||||
| ### Mattermost doesn't show the IRC nicks | ||||
| If you're running the webhooks version, this can be fixed by either: | ||||
| * enabling "override usernames". See [mattermost documentation](http://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks) | ||||
| * setting ```PrefixMessagesWithNick``` to ```true``` in ```mattermost``` section of your matterbridge.conf. | ||||
|  | ||||
| If you're running the plus version you'll need to: | ||||
| * setting ```PrefixMessagesWithNick``` to ```true``` in ```mattermost``` section of your matterbridge.conf. | ||||
|  | ||||
| Also look at the ```RemoteNickFormat``` setting. | ||||
|   | ||||
							
								
								
									
										151
									
								
								bridge/bridge.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								bridge/bridge.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| 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/slack" | ||||
| 	"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 cfg.Slack.Enable { | ||||
| 		b.Bridges = append(b.Bridges, bslack.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 | ||||
| 		m["slack"] = val.Slack | ||||
| 		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) | ||||
| 	m["slack"] = strings.Fields(b.Config.Slack.IgnoreNicks) | ||||
| 	b.ignoreNicks = m | ||||
| } | ||||
|  | ||||
| func (b *Bridge) getDestChannel(msg *config.Message, dest string) string { | ||||
| 	for _, v := range b.Channels { | ||||
| 		if v[msg.Origin] == msg.Channel { | ||||
| 			return v[dest] | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleMessage(msg config.Message, dest Bridger) { | ||||
| 	if b.ignoreMessage(&msg) { | ||||
| 		return | ||||
| 	} | ||||
| 	if dest.Name() != msg.Origin { | ||||
| 		msg.Channel = b.getDestChannel(&msg, dest.Name()) | ||||
| 		if msg.Channel == "" { | ||||
| 			return | ||||
| 		} | ||||
| 		b.modifyMessage(&msg, dest.Name()) | ||||
| 		log.Debugf("sending %#v from %s to %s", msg, msg.Origin, dest.Name()) | ||||
| 		dest.Send(msg) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bridge) 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) | ||||
| 	case "slack": | ||||
| 		setNickFormat(msg, b.Config.Slack.RemoteNickFormat) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										107
									
								
								bridge/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								bridge/config/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| 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 | ||||
| 	} | ||||
| 	Slack struct { | ||||
| 		BindAddress            string | ||||
| 		Enable                 bool | ||||
| 		IconURL                string | ||||
| 		IgnoreNicks            string | ||||
| 		NickFormatter          string | ||||
| 		NicksPerRow            int | ||||
| 		PrefixMessagesWithNick bool | ||||
| 		RemoteNickFormat       string | ||||
| 		Token                  string | ||||
| 		URL                    string | ||||
| 		UseAPI                 bool | ||||
| 	} | ||||
| 	Xmpp struct { | ||||
| 		IgnoreNicks      string | ||||
| 		Jid              string | ||||
| 		Password         string | ||||
| 		Server           string | ||||
| 		Muc              string | ||||
| 		Nick             string | ||||
| 		RemoteNickFormat string | ||||
| 		Enable           bool | ||||
| 	} | ||||
| 	Channel map[string]*struct { | ||||
| 		IRC        string | ||||
| 		Mattermost string | ||||
| 		Xmpp       string | ||||
| 		Gitter     string | ||||
| 		Slack      string | ||||
| 	} | ||||
| 	General struct { | ||||
| 		GiphyAPIKey string | ||||
| 		Xmpp        bool | ||||
| 		Irc         bool | ||||
| 		Mattermost  bool | ||||
| 		Plus        bool | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										180
									
								
								bridge/slack/slack.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								bridge/slack/slack.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| package bslack | ||||
|  | ||||
| import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/nlopes/slack" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type MMMessage struct { | ||||
| 	Text     string | ||||
| 	Channel  string | ||||
| 	Username string | ||||
| } | ||||
|  | ||||
| type bslack struct { | ||||
| 	mh *matterhook.Client | ||||
| 	sc *slack.Client | ||||
| 	//	MMapi | ||||
| 	*config.Config | ||||
| 	rtm      *slack.RTM | ||||
| 	Plus     bool | ||||
| 	Remote   chan config.Message | ||||
| 	channels []slack.Channel | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": "slack"}) | ||||
| } | ||||
|  | ||||
| func New(cfg *config.Config, c chan config.Message) *bslack { | ||||
| 	b := &bslack{} | ||||
| 	b.Config = cfg | ||||
| 	b.Remote = c | ||||
| 	b.Plus = cfg.Slack.UseAPI | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *bslack) Command(cmd string) string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *bslack) Connect() error { | ||||
| 	if !b.Plus { | ||||
| 		b.mh = matterhook.New(b.Config.Slack.URL, | ||||
| 			matterhook.Config{BindAddress: b.Config.Slack.BindAddress}) | ||||
| 	} else { | ||||
| 		b.sc = slack.New(b.Config.Slack.Token) | ||||
| 		flog.Infof("Trying login on slack with Token") | ||||
| 		/* | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		*/ | ||||
| 		flog.Info("Login ok") | ||||
| 	} | ||||
| 	b.rtm = b.sc.NewRTM() | ||||
| 	go b.rtm.ManageConnection() | ||||
| 	go b.handleSlack() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *bslack) Name() string { | ||||
| 	return "slack" | ||||
| } | ||||
|  | ||||
| func (b *bslack) Send(msg config.Message) error { | ||||
| 	flog.Infof("slack send %#v", msg) | ||||
| 	if msg.Origin != "slack" { | ||||
| 		return b.SendType(msg.Username, msg.Text, msg.Channel, "") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *bslack) SendType(nick string, message string, channel string, mtype string) error { | ||||
| 	if b.Config.Slack.PrefixMessagesWithNick { | ||||
| 		message = nick + " " + message | ||||
| 	} | ||||
| 	if !b.Plus { | ||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.Slack.IconURL} | ||||
| 		matterMessage.Channel = channel | ||||
| 		matterMessage.UserName = nick | ||||
| 		matterMessage.Type = mtype | ||||
| 		matterMessage.Text = message | ||||
| 		err := b.mh.Send(matterMessage) | ||||
| 		if err != nil { | ||||
| 			flog.Info(err) | ||||
| 			return err | ||||
| 		} | ||||
| 		flog.Debug("->slack channel: ", channel, " ", message) | ||||
| 		return nil | ||||
| 	} | ||||
| 	flog.Debugf("sent to slack channel API: %s %s", channel, message) | ||||
| 	newmsg := b.rtm.NewOutgoingMessage(message, b.getChannelByName(channel).ID) | ||||
| 	b.rtm.SendMessage(newmsg) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *bslack) getChannelByName(name string) *slack.Channel { | ||||
| 	if b.channels == nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	for _, channel := range b.channels { | ||||
| 		if channel.Name == name { | ||||
| 			return &channel | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *bslack) handleSlack() { | ||||
| 	flog.Infof("Choosing API based slack connection: %t", b.Plus) | ||||
| 	mchan := make(chan *MMMessage) | ||||
| 	if b.Plus { | ||||
| 		go b.handleSlackClient(mchan) | ||||
| 	} else { | ||||
| 		go b.handleMatterHook(mchan) | ||||
| 	} | ||||
| 	time.Sleep(time.Second) | ||||
| 	flog.Info("Start listening for Slack messages") | ||||
| 	for message := range mchan { | ||||
| 		texts := strings.Split(message.Text, "\n") | ||||
| 		for _, text := range texts { | ||||
| 			flog.Debug("Sending message from " + message.Username + " to " + message.Channel) | ||||
| 			b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: "slack"} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *bslack) handleSlackClient(mchan chan *MMMessage) { | ||||
| 	for msg := range b.rtm.IncomingEvents { | ||||
| 		switch ev := msg.Data.(type) { | ||||
| 		case *slack.MessageEvent: | ||||
| 			flog.Debugf("%#v", ev) | ||||
| 			channel, err := b.rtm.GetChannelInfo(ev.Channel) | ||||
| 			if err != nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			user, err := b.rtm.GetUserInfo(ev.User) | ||||
| 			if err != nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			m := &MMMessage{} | ||||
| 			m.Username = user.Name | ||||
| 			m.Channel = channel.Name | ||||
| 			m.Text = ev.Text | ||||
| 			mchan <- m | ||||
| 		case *slack.OutgoingErrorEvent: | ||||
| 			flog.Debugf("%#v", ev.Error()) | ||||
| 		case *slack.ConnectedEvent: | ||||
| 			b.channels = ev.Info.Channels | ||||
| 			for _, val := range b.Config.Channel { | ||||
| 				channel := b.getChannelByName(val.Slack) | ||||
| 				if channel != nil && !channel.IsMember { | ||||
| 					flog.Infof("Joining %s", val.Slack) | ||||
| 					b.sc.JoinChannel(channel.ID) | ||||
| 				} | ||||
| 			} | ||||
| 		case *slack.InvalidAuthEvent: | ||||
| 			flog.Fatalf("Invalid Token %#v", ev) | ||||
| 		default: | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *bslack) handleMatterHook(mchan chan *MMMessage) { | ||||
| 	for { | ||||
| 		message := b.mh.Receive() | ||||
| 		flog.Debugf("receiving from slack %#v", message) | ||||
| 		m := &MMMessage{} | ||||
| 		m.Username = message.UserName | ||||
| 		m.Text = message.Text | ||||
| 		m.Channel = message.ChannelName | ||||
| 		mchan <- m | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										133
									
								
								bridge/xmpp/xmpp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								bridge/xmpp/xmpp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| package bxmpp | ||||
|  | ||||
| import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/mattn/go-xmpp" | ||||
|  | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type Bxmpp struct { | ||||
| 	xc      *xmpp.Client | ||||
| 	xmppMap map[string]string | ||||
| 	*config.Config | ||||
| 	Remote chan config.Message | ||||
| } | ||||
|  | ||||
| type FancyLog struct { | ||||
| 	xmpp *log.Entry | ||||
| } | ||||
|  | ||||
| type Message struct { | ||||
| 	Text     string | ||||
| 	Channel  string | ||||
| 	Username string | ||||
| } | ||||
|  | ||||
| var flog FancyLog | ||||
|  | ||||
| func init() { | ||||
| 	flog.xmpp = log.WithFields(log.Fields{"module": "xmpp"}) | ||||
| } | ||||
|  | ||||
| func New(config *config.Config, c chan config.Message) *Bxmpp { | ||||
| 	b := &Bxmpp{} | ||||
| 	b.xmppMap = make(map[string]string) | ||||
| 	b.Config = config | ||||
| 	b.Remote = c | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) Connect() error { | ||||
| 	var err error | ||||
| 	flog.xmpp.Info("Trying XMPP connection") | ||||
| 	b.xc, err = b.createXMPP() | ||||
| 	if err != nil { | ||||
| 		flog.xmpp.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.xmpp.Info("Connection succeeded") | ||||
| 	b.setupChannels() | ||||
| 	go b.handleXmpp() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) Name() string { | ||||
| 	return "xmpp" | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) Send(msg config.Message) error { | ||||
| 	b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Xmpp.Muc, Text: msg.Username + msg.Text}) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { | ||||
| 	options := xmpp.Options{ | ||||
| 		Host:     b.Config.Xmpp.Server, | ||||
| 		User:     b.Config.Xmpp.Jid, | ||||
| 		Password: b.Config.Xmpp.Password, | ||||
| 		NoTLS:    true, | ||||
| 		StartTLS: true, | ||||
| 		//StartTLS:      false, | ||||
| 		Debug:                        true, | ||||
| 		Session:                      true, | ||||
| 		Status:                       "", | ||||
| 		StatusMessage:                "", | ||||
| 		Resource:                     "", | ||||
| 		InsecureAllowUnencryptedAuth: false, | ||||
| 		//InsecureAllowUnencryptedAuth: true, | ||||
| 	} | ||||
| 	var err error | ||||
| 	b.xc, err = options.NewClient() | ||||
| 	return b.xc, err | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) setupChannels() { | ||||
| 	for _, val := range b.Config.Channel { | ||||
| 		flog.xmpp.Infof("Joining %s as %s", val.Xmpp, b.Xmpp.Nick) | ||||
| 		b.xc.JoinMUCNoHistory(val.Xmpp+"@"+b.Xmpp.Muc, b.Xmpp.Nick) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) xmppKeepAlive() { | ||||
| 	go func() { | ||||
| 		ticker := time.NewTicker(90 * time.Second) | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-ticker.C: | ||||
| 				b.xc.Send(xmpp.Chat{}) | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) handleXmpp() error { | ||||
| 	for { | ||||
| 		m, err := b.xc.Recv() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		switch v := m.(type) { | ||||
| 		case xmpp.Chat: | ||||
| 			var channel, nick string | ||||
| 			if v.Type == "groupchat" { | ||||
| 				s := strings.Split(v.Remote, "@") | ||||
| 				if len(s) == 2 { | ||||
| 					channel = s[0] | ||||
| 				} | ||||
| 				s = strings.Split(s[1], "/") | ||||
| 				if len(s) == 2 { | ||||
| 					nick = s[1] | ||||
| 				} | ||||
| 				if nick != b.Xmpp.Nick { | ||||
| 					flog.xmpp.Infof("sending message to remote %s %s %s", nick, v.Text, channel) | ||||
| 					b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Origin: "xmpp"} | ||||
| 				} | ||||
| 			} | ||||
| 		case xmpp.Presence: | ||||
| 			// do nothing | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										85
									
								
								changelog.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								changelog.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| # v0.6.1 | ||||
| ## New features | ||||
| * Slack support added.  See matterbridge.conf.sample for more information | ||||
| ## Bugfix | ||||
| * Fix 100% CPU bug on incorrect closed connections | ||||
|  | ||||
| # v0.6.0-beta2 | ||||
| ## New features | ||||
| * Gitter support added.  See matterbridge.conf.sample for more information | ||||
|  | ||||
| # v0.6.0-beta1 | ||||
| ## Breaking changes from 0.5 to 0.6 | ||||
| ### commandline | ||||
| * -plus switch deprecated. Use ```Plus=true``` or ```Plus``` in ```[general]``` section | ||||
|  | ||||
| ### IRC section | ||||
| * ```Enabled``` added (default false)   | ||||
| Add ```Enabled=true``` or ```Enabled``` to the ```[IRC]``` section if you want to enable the IRC bridge | ||||
|  | ||||
| ### Mattermost section | ||||
| * ```Enabled``` added (default false)   | ||||
| Add ```Enabled=true``` or ```Enabled``` to the ```[mattermost]``` section if you want to enable the mattermost bridge | ||||
|  | ||||
| ### General section | ||||
| * Use ```Plus=true``` or ```Plus``` in ```[general]``` section to enable the API version of matterbridge | ||||
|  | ||||
| ## New features | ||||
| * Matterbridge now bridges between any specified protocol (not only mattermost anymore)  | ||||
| * XMPP support added.  See matterbridge.conf.sample for more information | ||||
| * RemoteNickFormat {BRIDGE} variable added   | ||||
| You can now add the originating bridge to ```RemoteNickFormat```   | ||||
| eg ```RemoteNickFormat="[{BRIDGE}] <{NICK}> "``` | ||||
|  | ||||
|  | ||||
| # v0.5.0 | ||||
| ## Breaking changes from 0.4 to 0.5 for matterbridge (webhooks version) | ||||
| ### IRC section | ||||
| #### Server | ||||
| Port removed, added to server | ||||
| ``` | ||||
| server="irc.freenode.net" | ||||
| port=6667 | ||||
| ``` | ||||
| changed to | ||||
| ``` | ||||
| server="irc.freenode.net:6667" | ||||
| ``` | ||||
| #### Channel | ||||
| Removed see Channels section below | ||||
|  | ||||
| #### UseSlackCircumfix=true | ||||
| Removed, can be done by using ```RemoteNickFormat="<{NICK}> "``` | ||||
|  | ||||
| ### Mattermost section | ||||
| #### BindAddress | ||||
| Port removed, added to BindAddress | ||||
|  | ||||
| ``` | ||||
| BindAddress="0.0.0.0" | ||||
| port=9999 | ||||
| ``` | ||||
|  | ||||
| changed to | ||||
|  | ||||
| ``` | ||||
| BindAddress="0.0.0.0:9999" | ||||
| ``` | ||||
|  | ||||
| #### Token | ||||
| Removed | ||||
|  | ||||
| ### Channels section | ||||
| ``` | ||||
| [Token "outgoingwebhooktoken1"]  | ||||
| IRCChannel="#off-topic" | ||||
| MMChannel="off-topic" | ||||
| ``` | ||||
|  | ||||
| changed to | ||||
|  | ||||
| ``` | ||||
| [Channel "channelnameofchoice"]  | ||||
| IRC="#off-topic" | ||||
| Mattermost="off-topic" | ||||
| ``` | ||||
| @@ -1,37 +1,287 @@ | ||||
| #This is configuration for matterbridge. | ||||
| ################################################################### | ||||
| #IRC section | ||||
| ################################################################### | ||||
| [IRC] | ||||
| server="irc.freenode.net" | ||||
| port=6667 | ||||
| #Enable enables this bridge | ||||
| #OPTIONAL (default false) | ||||
| Enable=true | ||||
| #irc server to connect to.  | ||||
| #REQUIRED | ||||
| Server="irc.freenode.net:6667" | ||||
|  | ||||
| #Enable to use TLS connection to your irc server.  | ||||
| #OPTIONAL (default false) | ||||
| UseTLS=false | ||||
|  | ||||
| #Enable SASL (PLAIN) authentication. (freenode requires this from eg AWS hosts) | ||||
| #It uses NickServNick and NickServPassword as login and password | ||||
| #OPTIONAL (default false) | ||||
| UseSASL=false | ||||
|  | ||||
| #Enable to not verify the certificate on your irc server. i | ||||
| #e.g. when using selfsigned certificates | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
| nick="matterbot" | ||||
| channel="#matterbridge" | ||||
| UseSlackCircumfix=false | ||||
|  | ||||
| #Your nick on irc.  | ||||
| #REQUIRED | ||||
| Nick="matterbot" | ||||
|  | ||||
| #If you registered your bot with a service like Nickserv on freenode.  | ||||
| #Also being used when UseSASL=true | ||||
| #OPTIONAL | ||||
| NickServNick="nickserv" | ||||
| NickServPassword="secret" | ||||
|  | ||||
| #RemoteNickFormat defines how remote users appear on this bridge  | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||
| #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||
| #OPTIONAL (default {BRIDGE}-{NICK}) | ||||
| RemoteNickFormat="[{BRIDGE}] <{NICK}>  | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| ################################################################### | ||||
| #XMPP section | ||||
| ################################################################### | ||||
| [XMPP] | ||||
| #Enable enables this bridge | ||||
| #OPTIONAL (default false) | ||||
| Enable=true | ||||
|  | ||||
| #xmpp server to connect to.  | ||||
| #REQUIRED | ||||
| Server="jabber.example.com:5222" | ||||
|  | ||||
| #Jid | ||||
| #REQUIRED | ||||
| Jid="user@example.com" | ||||
|  | ||||
| #Password | ||||
| #REQUIRED | ||||
| Password="yourpass" | ||||
|  | ||||
| #MUC | ||||
| #REQUIRED | ||||
| Muc="conference.jabber.example.com" | ||||
|  | ||||
| #Your nick in the rooms | ||||
| #REQUIRED | ||||
| Nick="xmppbot" | ||||
|  | ||||
|  | ||||
| ################################################################### | ||||
| #mattermost section | ||||
| ################################################################### | ||||
|  | ||||
| [mattermost] | ||||
| url="http://yourdomain/hooks/yourhookkey" | ||||
| port=9999 | ||||
| showjoinpart=true | ||||
| #remove token when using multiple channels! | ||||
| token=yourtokenfrommattermost | ||||
| #Enable enables this bridge | ||||
| #OPTIONAL (default false) | ||||
| Enable=true | ||||
|  | ||||
| #### Settings for webhook matterbridge. | ||||
| #### These settings will not be used when using -plus switch which doesn't use  | ||||
| #### webhooks. | ||||
|  | ||||
| #Url is your incoming webhook url as specified in mattermost.  | ||||
| #See account settings - integrations - incoming webhooks on mattermost. | ||||
| #REQUIRED | ||||
| URL="https://yourdomain/hooks/yourhookkey" | ||||
|  | ||||
| #Address to listen on for outgoing webhook requests from mattermost. | ||||
| #See account settings - integrations - outgoing webhooks on mattermost. | ||||
| #This setting will not be used when using -plus switch which doesn't use  | ||||
| #webhooks | ||||
| #REQUIRED | ||||
| BindAddress="0.0.0.0:9999" | ||||
|  | ||||
| #Icon that will be showed in mattermost.  | ||||
| #OPTIONAL | ||||
| IconURL="http://youricon.png" | ||||
| #SkipTLSVerify=true | ||||
| #BindAddress="0.0.0.0" | ||||
|  | ||||
| #### Settings for matterbridge -plus | ||||
| #### Thse settings will only be used when using the -plus switch. | ||||
|  | ||||
| #The mattermost hostname.  | ||||
| #REQUIRED | ||||
| Server="yourmattermostserver.domain" | ||||
|  | ||||
| #Your team on mattermost.  | ||||
| #REQUIRED | ||||
| Team="yourteam" | ||||
|  | ||||
| #login/pass of your bot.  | ||||
| #Use a dedicated user for this and not your own!  | ||||
| #REQUIRED | ||||
| Login="yourlogin" | ||||
| Password="yourpass" | ||||
|  | ||||
| #Enable this to make a http connection (instead of https) to your mattermost.  | ||||
| #OPTIONAL (default false) | ||||
| NoTLS=false | ||||
|  | ||||
| #### Shared settings for matterbridge and -plus | ||||
|  | ||||
| #Enable to not verify the certificate on your mattermost server.  | ||||
| #e.g. when using selfsigned certificates | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
|  | ||||
| #Enable to show IRC joins/parts in mattermost.  | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| #Whether to prefix messages from other bridges to mattermost with the sender's nick.  | ||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||
| #mattermost server. If you set PrefixMessagesWithNick to true, each message  | ||||
| #from bridge to Mattermost will by default be prefixed by "bridge-" + nick. You can,  | ||||
| #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  | ||||
| #OPTIONAL (default false) | ||||
| PrefixMessagesWithNick=false | ||||
|  | ||||
| #RemoteNickFormat defines how remote users appear on this bridge  | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||
| #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||
| #OPTIONAL (default {BRIDGE}-{NICK}) | ||||
| RemoteNickFormat="[{BRIDGE}] <{NICK}>  | ||||
|  | ||||
| #how to format the list of IRC nicks when displayed in mattermost.  | ||||
| #Possible options are "table" and "plain" | ||||
| #OPTIONAL (default plain) | ||||
| NickFormatter=plain | ||||
| #How many nicks to list per row for formatters that support this.  | ||||
| #OPTIONAL (default 4) | ||||
| NicksPerRow=4 | ||||
| #NickServNick="nickserv" | ||||
| #NickServPassword="secret" | ||||
|  | ||||
| [general] | ||||
| GiphyAPIKey=dc6zaTOxFJmzC | ||||
| #Nicks you want to ignore. Messages from those users will not be bridged. | ||||
| #OPTIONAL  | ||||
| IgnoreNicks="mmbot spammer2" | ||||
|  | ||||
| ################################################################### | ||||
| #Gitter section | ||||
| #Best to make a dedicated gitter account for the bot. | ||||
| ################################################################### | ||||
| [Gitter] | ||||
| #Enable enables this bridge | ||||
| #OPTIONAL (default false) | ||||
| Enable=true | ||||
|  | ||||
| #Token to connect with Gitter API | ||||
| #You can get your token by going to https://developer.gitter.im/docs/welcome and SIGN IN | ||||
| #REQUIRED | ||||
| Token="Yourtokenhere" | ||||
|  | ||||
| #Nicks you want to ignore. Messages of those users will not be bridged. | ||||
| #OPTIONAL  | ||||
| IgnoreNicks="spammer1 spammer2" | ||||
|  | ||||
| #RemoteNickFormat defines how remote users appear on this bridge  | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||
| #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||
| #OPTIONAL (default {BRIDGE}-{NICK}) | ||||
| RemoteNickFormat="[{BRIDGE}] <{NICK}>  | ||||
|  | ||||
| ################################################################### | ||||
| #slack section | ||||
| ################################################################### | ||||
|  | ||||
| [slack] | ||||
| #Enable enables this bridge | ||||
| #OPTIONAL (default false) | ||||
| Enable=true | ||||
|  | ||||
| #### Settings for webhook matterbridge. | ||||
| #### These settings will not be used when useAPI is enabled | ||||
|  | ||||
| #Url is your incoming webhook url as specified in slack | ||||
| #See account settings - integrations - incoming webhooks on slack | ||||
| #REQUIRED (unless useAPI=true) | ||||
| URL="https://hooks.slack.com/services/yourhook" | ||||
|  | ||||
| #Address to listen on for outgoing webhook requests from slack | ||||
| #See account settings - integrations - outgoing webhooks on slack | ||||
| #This setting will not be used when useAPI is eanbled | ||||
| #webhooks | ||||
| #REQUIRED (unless useAPI=true) | ||||
| BindAddress="0.0.0.0:9999" | ||||
|  | ||||
| #Icon that will be showed in slack | ||||
| #OPTIONAL | ||||
| IconURL="http://youricon.png" | ||||
|  | ||||
| #### Settings for using slack API | ||||
| #OPTIONAL | ||||
| useAPI=false | ||||
|  | ||||
| #Token to connect with the Slack API | ||||
| #REQUIRED (when useAPI=true) | ||||
| Token="yourslacktoken" | ||||
|  | ||||
| #### Shared settings for webhooks and API | ||||
|  | ||||
| #Whether to prefix messages from other bridges to mattermost with the sender's nick.  | ||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||
| #slack server. If you set PrefixMessagesWithNick to true, each message  | ||||
| #from bridge to Slack will by default be prefixed by "bridge-" + nick. You can,  | ||||
| #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  | ||||
| #OPTIONAL (default false) | ||||
| PrefixMessagesWithNick=false | ||||
|  | ||||
| #RemoteNickFormat defines how remote users appear on this bridge  | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||
| #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||
| #OPTIONAL (default {BRIDGE}-{NICK}) | ||||
| RemoteNickFormat="[{BRIDGE}] <{NICK}>  | ||||
|  | ||||
| #how to format the list of IRC nicks when displayed in slack | ||||
| #Possible options are "table" and "plain" | ||||
| #OPTIONAL (default plain) | ||||
| NickFormatter=plain | ||||
| #How many nicks to list per row for formatters that support this.  | ||||
| #OPTIONAL (default 4) | ||||
| NicksPerRow=4 | ||||
|  | ||||
| #Nicks you want to ignore. Messages from those users will not be bridged. | ||||
| #OPTIONAL  | ||||
| IgnoreNicks="mmbot spammer2" | ||||
|  | ||||
| ################################################################### | ||||
| #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" | ||||
| #Choose the slack channel to send messages to. | ||||
| slack="general" | ||||
|  | ||||
| [Token "outgoingwebhooktoken2"] | ||||
| IRCChannel="#testing" | ||||
| MMChannel="testing" | ||||
| [Channel "testchannel"] | ||||
| IRC="#testing" | ||||
| mattermost="testing" | ||||
| xmpp="testing" | ||||
| gitter="user/repo" | ||||
| slack="testing" | ||||
|  | ||||
| ################################################################### | ||||
| #general | ||||
| ################################################################### | ||||
| [general] | ||||
| #request your API key on https://github.com/giphy/GiphyAPI. This is a public beta key.  | ||||
| #OPTIONAL | ||||
| GiphyApiKey="dc6zaTOxFJmzC" | ||||
|  | ||||
| #Enabling plus means you'll use the API version instead of the webhooks one | ||||
| Plus=false | ||||
|   | ||||
| @@ -2,10 +2,14 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"github.com/42wim/matterbridge-plus/bridge" | ||||
| 	"fmt" | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| var version = "0.6.1" | ||||
|  | ||||
| func init() { | ||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||
| } | ||||
| @@ -13,11 +17,25 @@ func init() { | ||||
| func main() { | ||||
| 	flagConfig := flag.String("conf", "matterbridge.conf", "config file") | ||||
| 	flagDebug := flag.Bool("debug", false, "enable debug") | ||||
| 	flagVersion := flag.Bool("version", false, "show version") | ||||
| 	flagPlus := flag.Bool("plus", false, "running using API instead of webhooks (deprecated, set Plus flag in [general] config)") | ||||
| 	flag.Parse() | ||||
| 	if *flagVersion { | ||||
| 		fmt.Println("version:", version) | ||||
| 		return | ||||
| 	} | ||||
| 	flag.Parse() | ||||
| 	if *flagDebug { | ||||
| 		log.Info("enabling debug") | ||||
| 		log.SetLevel(log.DebugLevel) | ||||
| 	} | ||||
| 	bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "legacy") | ||||
| 	select {} | ||||
| 	fmt.Println("running version", version) | ||||
| 	cfg := config.NewConfig(*flagConfig) | ||||
| 	if *flagPlus { | ||||
| 		cfg.General.Plus = true | ||||
| 	} | ||||
| 	err := bridge.NewBridge(cfg) | ||||
| 	if err != nil { | ||||
| 		log.Debugf("starting bridge failed %#v", err) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										644
									
								
								matterclient/matterclient.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										644
									
								
								matterclient/matterclient.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,644 @@ | ||||
| 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() | ||||
| 	_, 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.WsQuit { | ||||
| 			m.log.Debug("exiting WsReceiver") | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if !m.WsConnected { | ||||
| 			time.Sleep(time.Millisecond * 100) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if _, rawMsg, err = m.WsClient.ReadMessage(); err != nil { | ||||
| 			m.log.Error("error:", err) | ||||
| 			// reconnect | ||||
| 			m.Login() | ||||
| 		} | ||||
|  | ||||
| 		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, true) | ||||
| 	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) | ||||
| @@ -27,15 +27,19 @@ type OMessage struct { | ||||
|  | ||||
| // IMessage for mattermost outgoing webhook. (received from mattermost) | ||||
| type IMessage struct { | ||||
| 	BotID       string `schema:"bot_id"` | ||||
| 	BotName     string `schema:"bot_name"` | ||||
| 	Token       string `schema:"token"` | ||||
| 	TeamID      string `schema:"team_id"` | ||||
| 	TeamDomain  string `schema:"team_domain"` | ||||
| 	ChannelID   string `schema:"channel_id"` | ||||
| 	ServiceID   string `schema:"service_id"` | ||||
| 	ChannelName string `schema:"channel_name"` | ||||
| 	Timestamp   string `schema:"timestamp"` | ||||
| 	UserID      string `schema:"user_id"` | ||||
| 	UserName    string `schema:"user_name"` | ||||
| 	PostId      string `schema:"post_id"` | ||||
| 	RawText     string `schema:"raw_text"` | ||||
| 	ServiceId   string `schema:"service_id"` | ||||
| 	Text        string `schema:"text"` | ||||
| 	TriggerWord string `schema:"trigger_word"` | ||||
| } | ||||
| @@ -51,7 +55,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 +64,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 +82,8 @@ func New(url string, config Config) *Client { | ||||
| func (c *Client) StartServer() { | ||||
| 	mux := http.NewServeMux() | ||||
| 	mux.Handle("/", c) | ||||
| 	log.Printf("Listening on http://%v:%v...\n", c.BindAddress, c.Port) | ||||
| 	if err := http.ListenAndServe((c.BindAddress + strconv.Itoa(c.Port)), mux); err != nil { | ||||
| 	log.Printf("Listening on http://%v...\n", c.BindAddress) | ||||
| 	if err := http.ListenAndServe(c.BindAddress, mux); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										50
									
								
								migration.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								migration.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| # Breaking changes from 0.4 to 0.5 for matterbridge (webhooks version) | ||||
| ## IRC section | ||||
| ### Server | ||||
| Port removed, added to server | ||||
| ``` | ||||
| server="irc.freenode.net" | ||||
| port=6667 | ||||
| ``` | ||||
| changed to | ||||
| ``` | ||||
| server="irc.freenode.net:6667" | ||||
| ``` | ||||
| ### Channel | ||||
| Removed see Channels section below | ||||
|  | ||||
| ### UseSlackCircumfix=true | ||||
| Removed, can be done by using ```RemoteNickFormat="<{NICK}> "``` | ||||
|  | ||||
| ## Mattermost section | ||||
| ### BindAddress | ||||
| Port removed, added to BindAddress | ||||
|  | ||||
| ``` | ||||
| BindAddress="0.0.0.0" | ||||
| port=9999 | ||||
| ``` | ||||
|  | ||||
| changed to | ||||
|  | ||||
| ``` | ||||
| BindAddress="0.0.0.0:9999" | ||||
| ``` | ||||
|  | ||||
| ### Token | ||||
| Removed | ||||
|  | ||||
| ## Channels section | ||||
| ``` | ||||
| [Token "outgoingwebhooktoken1"]  | ||||
| IRCChannel="#off-topic" | ||||
| MMChannel="off-topic" | ||||
| ``` | ||||
|  | ||||
| changed to | ||||
|  | ||||
| ``` | ||||
| [Channel "channelnameofchoice"]  | ||||
| IRC="#off-topic" | ||||
| Mattermost="off-topic" | ||||
| ``` | ||||
							
								
								
									
										169
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										169
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -21,15 +21,17 @@ type MMhook struct { | ||||
| } | ||||
|  | ||||
| type MMapi struct { | ||||
| 	mc    *matterclient.MMClient | ||||
| 	mmMap map[string]string | ||||
| 	mc            *matterclient.MMClient | ||||
| 	mmMap         map[string]string | ||||
| 	mmIgnoreNicks []string | ||||
| } | ||||
|  | ||||
| type MMirc struct { | ||||
| 	i       *irc.Connection | ||||
| 	ircNick string | ||||
| 	ircMap  map[string]string | ||||
| 	names   map[string][]string | ||||
| 	i              *irc.Connection | ||||
| 	ircNick        string | ||||
| 	ircMap         map[string]string | ||||
| 	names          map[string][]string | ||||
| 	ircIgnoreNicks []string | ||||
| } | ||||
|  | ||||
| type MMMessage struct { | ||||
| @@ -53,6 +55,8 @@ type FancyLog struct { | ||||
|  | ||||
| var flog FancyLog | ||||
|  | ||||
| const Legacy = "legacy" | ||||
|  | ||||
| func initFLog() { | ||||
| 	flog.irc = log.WithFields(log.Fields{"module": "irc"}) | ||||
| 	flog.mm = log.WithFields(log.Fields{"module": "mattermost"}) | ||||
| @@ -66,7 +70,9 @@ func NewBridge(name string, config *Config, kind string) *Bridge { | ||||
| 	b.ircNick = b.Config.IRC.Nick | ||||
| 	b.ircMap = make(map[string]string) | ||||
| 	b.MMirc.names = make(map[string][]string) | ||||
| 	if kind == "legacy" { | ||||
| 	b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks) | ||||
| 	b.mmIgnoreNicks = strings.Fields(b.Config.Mattermost.IgnoreNicks) | ||||
| 	if kind == Legacy { | ||||
| 		if len(b.Config.Token) > 0 { | ||||
| 			for _, val := range b.Config.Token { | ||||
| 				b.ircMap[val.IRCChannel] = val.MMChannel | ||||
| @@ -87,10 +93,14 @@ func NewBridge(name string, config *Config, kind string) *Bridge { | ||||
| 		} | ||||
| 		b.mc = matterclient.New(b.Config.Mattermost.Login, b.Config.Mattermost.Password, | ||||
| 			b.Config.Mattermost.Team, b.Config.Mattermost.Server) | ||||
| 		b.mc.SkipTLSVerify = b.Config.Mattermost.SkipTLSVerify | ||||
| 		b.mc.NoTLS = b.Config.Mattermost.NoTLS | ||||
| 		flog.mm.Infof("Trying login %s (team: %s) on %s", b.Config.Mattermost.Login, b.Config.Mattermost.Team, b.Config.Mattermost.Server) | ||||
| 		err := b.mc.Login() | ||||
| 		if err != nil { | ||||
| 			flog.mm.Fatal("can not connect", err) | ||||
| 			flog.mm.Fatal("Can not connect", err) | ||||
| 		} | ||||
| 		flog.mm.Info("Login ok") | ||||
| 		b.mc.JoinChannel(b.Config.Mattermost.Channel) | ||||
| 		if len(b.Config.Channel) > 0 { | ||||
| 			for _, val := range b.Config.Channel { | ||||
| @@ -99,7 +109,9 @@ func NewBridge(name string, config *Config, kind string) *Bridge { | ||||
| 		} | ||||
| 		go b.mc.WsReceiver() | ||||
| 	} | ||||
| 	flog.irc.Info("Trying IRC connection") | ||||
| 	b.i = b.createIRC(name) | ||||
| 	flog.irc.Info("Connection succeeded") | ||||
| 	go b.handleMatter() | ||||
| 	return b | ||||
| } | ||||
| @@ -111,37 +123,51 @@ func (b *Bridge) createIRC(name string) *irc.Connection { | ||||
| 	if b.Config.IRC.Password != "" { | ||||
| 		i.Password = b.Config.IRC.Password | ||||
| 	} | ||||
| 	i.AddCallback("*", b.handleOther) | ||||
| 	i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection) | ||||
| 	i.Connect(b.Config.IRC.Server + ":" + strconv.Itoa(b.Config.IRC.Port)) | ||||
| 	return i | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleNewConnection(event *irc.Event) { | ||||
| 	flog.irc.Info("Registering callbacks") | ||||
| 	i := b.i | ||||
| 	b.ircNick = event.Arguments[0] | ||||
| 	i.AddCallback("PRIVMSG", b.handlePrivMsg) | ||||
| 	i.AddCallback("CTCP_ACTION", b.handlePrivMsg) | ||||
| 	i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames) | ||||
| 	i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames) | ||||
| 	i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime) | ||||
| 	i.AddCallback(ircm.NOTICE, b.handleNotice) | ||||
| 	i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.irc.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) }) | ||||
| 	i.AddCallback("PING", func(e *irc.Event) { | ||||
| 		i.SendRaw("PONG :" + e.Message()) | ||||
| 		flog.irc.Debugf("PING/PONG") | ||||
| 	}) | ||||
| 	if b.Config.Mattermost.ShowJoinPart { | ||||
| 		i.AddCallback("JOIN", b.handleJoinPart) | ||||
| 		i.AddCallback("PART", b.handleJoinPart) | ||||
| 	} | ||||
| 	i.AddCallback("*", b.handleOther) | ||||
| 	b.setupChannels() | ||||
| } | ||||
|  | ||||
| func (b *Bridge) setupChannels() { | ||||
| 	i := b.i | ||||
| 	flog.irc.Info("Joining ", b.Config.IRC.Channel, " as ", b.ircNick) | ||||
| 	i.Join(b.Config.IRC.Channel) | ||||
| 	if b.kind == "legacy" { | ||||
| 	if b.Config.IRC.Channel != "" { | ||||
| 		flog.irc.Infof("Joining %s as %s", b.Config.IRC.Channel, b.ircNick) | ||||
| 		i.Join(b.Config.IRC.Channel) | ||||
| 	} | ||||
| 	if b.kind == Legacy { | ||||
| 		for _, val := range b.Config.Token { | ||||
| 			flog.irc.Info("Joining ", val.IRCChannel, " as ", b.ircNick) | ||||
| 			flog.irc.Infof("Joining %s as %s", val.IRCChannel, b.ircNick) | ||||
| 			i.Join(val.IRCChannel) | ||||
| 		} | ||||
| 	} else { | ||||
| 		for _, val := range b.Config.Channel { | ||||
| 			flog.irc.Info("Joining ", val.IRC, " as ", b.ircNick) | ||||
| 			flog.irc.Infof("Joining %s as %s", val.IRC, b.ircNick) | ||||
| 			i.Join(val.IRC) | ||||
| 		} | ||||
| 	} | ||||
| 	i.AddCallback("PRIVMSG", b.handlePrivMsg) | ||||
| 	i.AddCallback("CTCP_ACTION", b.handlePrivMsg) | ||||
| 	if b.Config.Mattermost.ShowJoinPart { | ||||
| 		i.AddCallback("JOIN", b.handleJoinPart) | ||||
| 		i.AddCallback("PART", b.handleJoinPart) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleIrcBotCommand(event *irc.Event) bool { | ||||
| @@ -177,6 +203,9 @@ func (b *Bridge) ircNickFormat(nick string) string { | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handlePrivMsg(event *irc.Event) { | ||||
| 	if b.ignoreMessage(event.Nick, event.Message(), "irc") { | ||||
| 		return | ||||
| 	} | ||||
| 	if b.handleIrcBotCommand(event) { | ||||
| 		return | ||||
| 	} | ||||
| @@ -238,81 +267,21 @@ func (b *Bridge) endNames(event *irc.Event) { | ||||
| 	b.MMirc.names[channel] = nil | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleTopicWhoTime(event *irc.Event) bool { | ||||
| func (b *Bridge) handleTopicWhoTime(event *irc.Event) { | ||||
| 	parts := strings.Split(event.Arguments[2], "!") | ||||
| 	t_i, err := strconv.ParseInt(event.Arguments[3], 10, 64) | ||||
| 	t, err := strconv.ParseInt(event.Arguments[3], 10, 64) | ||||
| 	if err != nil { | ||||
| 		flog.irc.Errorf("Invalid time stamp: %s", event.Arguments[3]) | ||||
| 		return false | ||||
| 	} | ||||
| 	user := parts[0] | ||||
| 	if len(parts) > 1 { | ||||
| 		user += " [" + parts[1] + "]" | ||||
| 	} | ||||
| 	flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t_i, 0)) | ||||
| 	return true | ||||
| 	flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t, 0)) | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleOther(event *irc.Event) { | ||||
| 	flog.irc.Debugf("%#v", event) | ||||
| 	switch event.Code { | ||||
| 	case ircm.RPL_WELCOME: | ||||
| 		b.handleNewConnection(event) | ||||
| 	case ircm.RPL_ENDOFNAMES: | ||||
| 		b.endNames(event) | ||||
| 	case ircm.RPL_NAMREPLY: | ||||
| 		b.storeNames(event) | ||||
| 	case ircm.RPL_ISUPPORT: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LUSEROP: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LUSERUNKNOWN: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LUSERCHANNELS: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_MYINFO: | ||||
| 		flog.irc.Infof("%s: %s", event.Code, strings.Join(event.Arguments[1:], " ")) | ||||
| 	case ircm.RPL_YOURHOST: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_CREATED: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_STATSDLINE: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LUSERCLIENT: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LUSERME: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_LOCALUSERS: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_GLOBALUSERS: | ||||
| 		fallthrough | ||||
| 	case ircm.RPL_MOTD: | ||||
| 		flog.irc.Infof("%s: %s", event.Code, event.Message()) | ||||
| 		// flog.irc.Info(event.Message()) | ||||
| 	case ircm.RPL_TOPIC: | ||||
| 		flog.irc.Infof("%s: Topic for %s: %s", event.Code, event.Arguments[1], event.Message()) | ||||
| 	case ircm.RPL_TOPICWHOTIME: | ||||
| 		if !b.handleTopicWhoTime(event) { | ||||
| 			break | ||||
| 		} | ||||
| 	case ircm.MODE: | ||||
| 		flog.irc.Infof("%s: %s %s", event.Code, event.Arguments[1], event.Arguments[0]) | ||||
| 	case ircm.JOIN: | ||||
| 		fallthrough | ||||
| 	case ircm.PING: | ||||
| 		fallthrough | ||||
| 	case ircm.PONG: | ||||
| 		flog.irc.Infof("%s: %s", event.Code, event.Message()) | ||||
| 	case ircm.RPL_ENDOFMOTD: | ||||
| 	case ircm.RPL_MOTDSTART: | ||||
| 	case ircm.ERR_NICKNAMEINUSE: | ||||
| 		flog.irc.Warn(event.Message()) | ||||
| 	case ircm.NOTICE: | ||||
| 		b.handleNotice(event) | ||||
| 	default: | ||||
| 		flog.irc.Infof("UNKNOWN EVENT: %#v", event) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bridge) Send(nick string, message string, channel string) error { | ||||
| @@ -327,7 +296,7 @@ func (b *Bridge) SendType(nick string, message string, channel string, mtype str | ||||
| 			message = nick + " " + message | ||||
| 		} | ||||
| 	} | ||||
| 	if b.kind == "legacy" { | ||||
| 	if b.kind == Legacy { | ||||
| 		matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL} | ||||
| 		matterMessage.Channel = channel | ||||
| 		matterMessage.UserName = nick | ||||
| @@ -371,24 +340,34 @@ func (b *Bridge) handleMatterClient(mchan chan *MMMessage) { | ||||
| } | ||||
|  | ||||
| func (b *Bridge) handleMatter() { | ||||
| 	flog.mm.Infof("Choosing Mattermost connection type %s", b.kind) | ||||
| 	mchan := make(chan *MMMessage) | ||||
| 	if b.kind == "legacy" { | ||||
| 	if b.kind == Legacy { | ||||
| 		go b.handleMatterHook(mchan) | ||||
| 	} else { | ||||
| 		go b.handleMatterClient(mchan) | ||||
| 	} | ||||
| 	flog.mm.Info("Start listening for Mattermost messages") | ||||
| 	for message := range mchan { | ||||
| 		var username string | ||||
| 		if b.ignoreMessage(message.Username, message.Text, "mattermost") { | ||||
| 			continue | ||||
| 		} | ||||
| 		username = message.Username + ": " | ||||
| 		if b.Config.IRC.RemoteNickFormat != "" { | ||||
| 			username = strings.Replace(b.Config.IRC.RemoteNickFormat, "{NICK}", message.Username, -1) | ||||
| 		} else if b.Config.IRC.UseSlackCircumfix { | ||||
| 			username = "<" + message.Username + "> " | ||||
| 		} | ||||
| 		cmd := strings.Fields(message.Text)[0] | ||||
| 		cmds := strings.Fields(message.Text) | ||||
| 		// empty message | ||||
| 		if len(cmds) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		cmd := cmds[0] | ||||
| 		switch cmd { | ||||
| 		case "!users": | ||||
| 			flog.mm.Info("received !users from ", message.Username) | ||||
| 			flog.mm.Info("Received !users from ", message.Username) | ||||
| 			b.i.SendRaw("NAMES " + b.getIRCChannel(message.Channel)) | ||||
| 			continue | ||||
| 		case "!gif": | ||||
| @@ -425,7 +404,7 @@ func (b *Bridge) getMMChannel(ircChannel string) string { | ||||
| } | ||||
|  | ||||
| func (b *Bridge) getIRCChannel(channel string) string { | ||||
| 	if b.kind == "legacy" { | ||||
| 	if b.kind == Legacy { | ||||
| 		ircchannel := b.Config.IRC.Channel | ||||
| 		_, ok := b.Config.Token[channel] | ||||
| 		if ok { | ||||
| @@ -439,3 +418,17 @@ func (b *Bridge) getIRCChannel(channel string) string { | ||||
| 	} | ||||
| 	return ircchannel | ||||
| } | ||||
|  | ||||
| func (b *Bridge) ignoreMessage(nick string, message string, protocol string) bool { | ||||
| 	var ignoreNicks = b.mmIgnoreNicks | ||||
| 	if protocol == "irc" { | ||||
| 		ignoreNicks = b.ircIgnoreNicks | ||||
| 	} | ||||
| 	// should we discard messages ? | ||||
| 	for _, entry := range ignoreNicks { | ||||
| 		if nick == entry { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|   | ||||
							
								
								
									
										3
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/42wim/matterbridge-plus/bridge/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -19,6 +19,7 @@ type Config struct { | ||||
| 		NickServNick      string | ||||
| 		NickServPassword  string | ||||
| 		RemoteNickFormat  string | ||||
| 		IgnoreNicks       string | ||||
| 	} | ||||
| 	Mattermost struct { | ||||
| 		URL                    string | ||||
| @@ -37,6 +38,8 @@ type Config struct { | ||||
| 		Login                  string | ||||
| 		Password               string | ||||
| 		RemoteNickFormat       *string | ||||
| 		IgnoreNicks            string | ||||
| 		NoTLS                  bool | ||||
| 	} | ||||
| 	Token map[string]*struct { | ||||
| 		IRCChannel string | ||||
|   | ||||
							
								
								
									
										167
									
								
								vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										167
									
								
								vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,9 +1,12 @@ | ||||
| package matterclient | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"net/http" | ||||
| 	"net/http/cookiejar" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| @@ -13,11 +16,12 @@ import ( | ||||
| ) | ||||
|  | ||||
| type Credentials struct { | ||||
| 	Login  string | ||||
| 	Team   string | ||||
| 	Pass   string | ||||
| 	Server string | ||||
| 	NoTLS  bool | ||||
| 	Login         string | ||||
| 	Team          string | ||||
| 	Pass          string | ||||
| 	Server        string | ||||
| 	NoTLS         bool | ||||
| 	SkipTLSVerify bool | ||||
| } | ||||
|  | ||||
| type Message struct { | ||||
| @@ -33,6 +37,8 @@ type MMClient struct { | ||||
| 	*Credentials | ||||
| 	Client       *model.Client | ||||
| 	WsClient     *websocket.Conn | ||||
| 	WsQuit       bool | ||||
| 	WsAway       bool | ||||
| 	Channels     *model.ChannelList | ||||
| 	MoreChannels *model.ChannelList | ||||
| 	User         *model.User | ||||
| @@ -60,6 +66,9 @@ func (m *MMClient) SetLogLevel(level string) { | ||||
| } | ||||
|  | ||||
| func (m *MMClient) Login() error { | ||||
| 	if m.WsQuit { | ||||
| 		return nil | ||||
| 	} | ||||
| 	b := &backoff.Backoff{ | ||||
| 		Min:    time.Second, | ||||
| 		Max:    5 * time.Minute, | ||||
| @@ -73,12 +82,25 @@ func (m *MMClient) Login() error { | ||||
| 	} | ||||
| 	// login to mattermost | ||||
| 	m.Client = model.NewClient(uriScheme + m.Credentials.Server) | ||||
| 	m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} | ||||
| 	var myinfo *model.Result | ||||
| 	var appErr *model.AppError | ||||
| 	var logmsg = "trying login" | ||||
| 	for { | ||||
| 		m.log.Debugf(logmsg+" %s %s %s", m.Credentials.Team, m.Credentials.Login, m.Credentials.Server) | ||||
| 		myinfo, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) | ||||
| 		m.log.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server) | ||||
| 		if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) { | ||||
| 			m.log.Debugf(logmsg+" with ", model.SESSION_COOKIE_TOKEN) | ||||
| 			token := strings.Split(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN+"=") | ||||
| 			m.Client.HttpClient.Jar = m.createCookieJar(token[1]) | ||||
| 			m.Client.MockSession(token[1]) | ||||
| 			myinfo, appErr = m.Client.GetMe("") | ||||
| 			if myinfo.Data.(*model.User) == nil { | ||||
| 				m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass) | ||||
| 				return errors.New("invalid " + model.SESSION_COOKIE_TOKEN) | ||||
| 			} | ||||
| 		} else { | ||||
| 			myinfo, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) | ||||
| 		} | ||||
| 		if appErr != nil { | ||||
| 			d := b.Duration() | ||||
| 			m.log.Debug(appErr.DetailedError) | ||||
| @@ -89,7 +111,7 @@ func (m *MMClient) Login() error { | ||||
| 				} | ||||
| 				return errors.New(appErr.Message) | ||||
| 			} | ||||
| 			m.log.Debug("LOGIN: %s, reconnecting in %s", appErr, d) | ||||
| 			m.log.Debugf("LOGIN: %s, reconnecting in %s", appErr, d) | ||||
| 			time.Sleep(d) | ||||
| 			logmsg = "retrying login" | ||||
| 			continue | ||||
| @@ -98,15 +120,16 @@ func (m *MMClient) Login() error { | ||||
| 	} | ||||
| 	// reset timer | ||||
| 	b.Reset() | ||||
| 	m.User = myinfo.Data.(*model.User) | ||||
|  | ||||
| 	teamdata, _ := m.Client.GetAllTeamListings() | ||||
| 	teams := teamdata.Data.(map[string]*model.Team) | ||||
| 	for k, v := range teams { | ||||
| 	initLoad, _ := m.Client.GetInitialLoad() | ||||
| 	initData := initLoad.Data.(*model.InitialLoad) | ||||
| 	m.User = initData.User | ||||
| 	for _, v := range initData.Teams { | ||||
| 		m.log.Debugf("trying %s (id: %s)", v.Name, v.Id) | ||||
| 		if v.Name == m.Credentials.Team { | ||||
| 			m.Client.SetTeamId(k) | ||||
| 			m.Client.SetTeamId(v.Id) | ||||
| 			m.Team = v | ||||
| 			m.log.Debug("GetallTeamListings: found id ", k) | ||||
| 			m.log.Debugf("GetallTeamListings: found id %s for team %s", v.Id, v.Name) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| @@ -119,13 +142,14 @@ func (m *MMClient) Login() error { | ||||
| 	header := http.Header{} | ||||
| 	header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) | ||||
|  | ||||
| 	var WsClient *websocket.Conn | ||||
| 	m.log.Debug("WsClient: making connection") | ||||
| 	var err error | ||||
| 	for { | ||||
| 		WsClient, _, err = websocket.DefaultDialer.Dial(wsurl, header) | ||||
| 		wsDialer := &websocket.Dialer{Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} | ||||
| 		m.WsClient, _, err = wsDialer.Dial(wsurl, header) | ||||
| 		if err != nil { | ||||
| 			d := b.Duration() | ||||
| 			log.Printf("WSS: %s, reconnecting in %s", err, d) | ||||
| 			m.log.Debugf("WSS: %s, reconnecting in %s", err, d) | ||||
| 			time.Sleep(d) | ||||
| 			continue | ||||
| 		} | ||||
| @@ -133,8 +157,6 @@ func (m *MMClient) Login() error { | ||||
| 	} | ||||
| 	b.Reset() | ||||
|  | ||||
| 	m.WsClient = WsClient | ||||
|  | ||||
| 	// populating users | ||||
| 	m.UpdateUsers() | ||||
|  | ||||
| @@ -147,12 +169,19 @@ func (m *MMClient) Login() error { | ||||
| func (m *MMClient) WsReceiver() { | ||||
| 	var rmsg model.Message | ||||
| 	for { | ||||
| 		if m.WsQuit { | ||||
| 			m.log.Debug("exiting WsReceiver") | ||||
| 			return | ||||
| 		} | ||||
| 		if err := m.WsClient.ReadJSON(&rmsg); err != nil { | ||||
| 			log.Println("error:", err) | ||||
| 			m.log.Error("error:", err) | ||||
| 			// reconnect | ||||
| 			m.Login() | ||||
| 		} | ||||
| 		//log.Printf("WsReceiver: %#v", rmsg) | ||||
| 		if rmsg.Action == "ping" { | ||||
| 			m.handleWsPing() | ||||
| 			continue | ||||
| 		} | ||||
| 		msg := &Message{Raw: &rmsg, Team: m.Credentials.Team} | ||||
| 		m.parseMessage(msg) | ||||
| 		m.MessageChan <- msg | ||||
| @@ -160,6 +189,14 @@ func (m *MMClient) WsReceiver() { | ||||
|  | ||||
| } | ||||
|  | ||||
| func (m *MMClient) handleWsPing() { | ||||
| 	m.log.Debug("Ws PING") | ||||
| 	if !m.WsQuit && !m.WsAway { | ||||
| 		m.log.Debug("Ws PONG") | ||||
| 		m.WsClient.WriteMessage(websocket.PongMessage, []byte{}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *MMClient) parseMessage(rmsg *Message) { | ||||
| 	switch rmsg.Raw.Action { | ||||
| 	case model.ACTION_POSTED: | ||||
| @@ -198,7 +235,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) { | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateUsers() error { | ||||
| 	mmusers, _ := m.Client.GetProfiles(m.Client.GetTeamId(), "") | ||||
| 	mmusers, _ := m.Client.GetProfilesForDirectMessageList(m.Team.Id) | ||||
| 	m.Users = mmusers.Data.(map[string]*model.User) | ||||
| 	return nil | ||||
| } | ||||
| @@ -294,29 +331,49 @@ func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList { | ||||
| 	return res.Data.(*model.PostList) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetPublicLink(filename string) string { | ||||
| 	res, err := m.Client.GetPublicLink(filename) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return res.Data.(string) | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetPublicLinks(filenames []string) []string { | ||||
| 	var output []string | ||||
| 	for _, f := range filenames { | ||||
| 		res, err := m.Client.GetPublicLink(f) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		output = append(output, res.Data.(string)) | ||||
| 	} | ||||
| 	return output | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateChannelHeader(channelId string, header string) { | ||||
| 	data := make(map[string]string) | ||||
| 	data["channel_id"] = channelId | ||||
| 	data["channel_header"] = header | ||||
| 	log.Printf("updating channelheader %#v, %#v", channelId, header) | ||||
| 	m.log.Debugf("updating channelheader %#v, %#v", channelId, header) | ||||
| 	_, err := m.Client.UpdateChannelHeader(data) | ||||
| 	if err != nil { | ||||
| 		log.Print(err) | ||||
| 		log.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateLastViewed(channelId string) { | ||||
| 	log.Printf("posting lastview %#v", channelId) | ||||
| 	m.log.Debugf("posting lastview %#v", channelId) | ||||
| 	_, err := m.Client.UpdateLastViewedAt(channelId) | ||||
| 	if err != nil { | ||||
| 		log.Print(err) | ||||
| 		m.log.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UsernamesInChannel(channelName string) []string { | ||||
| 	ceiRes, err := m.Client.GetChannelExtraInfo(m.GetChannelId(channelName), 5000, "") | ||||
| 	if err != nil { | ||||
| 		log.Errorf("UsernamesInChannel(%s) failed: %s", channelName, err) | ||||
| 		m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelName, err) | ||||
| 		return []string{} | ||||
| 	} | ||||
| 	extra := ceiRes.Data.(*model.ChannelExtra) | ||||
| @@ -326,3 +383,59 @@ func (m *MMClient) UsernamesInChannel(channelName string) []string { | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| func (m *MMClient) createCookieJar(token string) *cookiejar.Jar { | ||||
| 	var cookies []*http.Cookie | ||||
| 	jar, _ := cookiejar.New(nil) | ||||
| 	firstCookie := &http.Cookie{ | ||||
| 		Name:   "MMAUTHTOKEN", | ||||
| 		Value:  token, | ||||
| 		Path:   "/", | ||||
| 		Domain: m.Credentials.Server, | ||||
| 	} | ||||
| 	cookies = append(cookies, firstCookie) | ||||
| 	cookieURL, _ := url.Parse("https://" + m.Credentials.Server) | ||||
| 	jar.SetCookies(cookieURL, cookies) | ||||
| 	return jar | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetOtherUserDM(channel string) *model.User { | ||||
| 	m.UpdateUsers() | ||||
| 	var rcvuser *model.User | ||||
| 	if strings.Contains(channel, "__") { | ||||
| 		rcvusers := strings.Split(channel, "__") | ||||
| 		if rcvusers[0] != m.User.Id { | ||||
| 			rcvuser = m.Users[rcvusers[0]] | ||||
| 		} else { | ||||
| 			rcvuser = m.Users[rcvusers[1]] | ||||
| 		} | ||||
| 	} | ||||
| 	return rcvuser | ||||
| } | ||||
|  | ||||
| func (m *MMClient) SendDirectMessage(toUserId string, msg string) { | ||||
| 	m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg) | ||||
| 	var channel string | ||||
| 	// We don't have a DM with this user yet. | ||||
| 	if m.GetChannelId(toUserId+"__"+m.User.Id) == "" && m.GetChannelId(m.User.Id+"__"+toUserId) == "" { | ||||
| 		// create DM channel | ||||
| 		_, err := m.Client.CreateDirectChannel(toUserId) | ||||
| 		if err != nil { | ||||
| 			m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, err) | ||||
| 		} | ||||
| 		// update our channels | ||||
| 		mmchannels, _ := m.Client.GetChannels("") | ||||
| 		m.Channels = mmchannels.Data.(*model.ChannelList) | ||||
| 	} | ||||
|  | ||||
| 	// build the channel name | ||||
| 	if toUserId > m.User.Id { | ||||
| 		channel = m.User.Id + "__" + toUserId | ||||
| 	} else { | ||||
| 		channel = toUserId + "__" + m.User.Id | ||||
| 	} | ||||
| 	// build & send the message | ||||
| 	msg = strings.Replace(msg, "\r", "", -1) | ||||
| 	post := &model.Post{ChannelId: m.GetChannelId(channel), Message: msg} | ||||
| 	m.Client.CreatePost(post) | ||||
| } | ||||
|   | ||||
							
								
								
									
										154
									
								
								vendor/github.com/42wim/matterbridge/matterhook/matterhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										154
									
								
								vendor/github.com/42wim/matterbridge/matterhook/matterhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,154 +0,0 @@ | ||||
| //Package matterhook provides interaction with mattermost incoming/outgoing webhooks | ||||
| package matterhook | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/tls" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/gorilla/schema" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| // OMessage for mattermost incoming webhook. (send to mattermost) | ||||
| type OMessage struct { | ||||
| 	Channel     string      `json:"channel,omitempty"` | ||||
| 	IconURL     string      `json:"icon_url,omitempty"` | ||||
| 	IconEmoji   string      `json:"icon_emoji,omitempty"` | ||||
| 	UserName    string      `json:"username,omitempty"` | ||||
| 	Text        string      `json:"text"` | ||||
| 	Attachments interface{} `json:"attachments,omitempty"` | ||||
| 	Type        string      `json:"type,omitempty"` | ||||
| } | ||||
|  | ||||
| // IMessage for mattermost outgoing webhook. (received from mattermost) | ||||
| type IMessage struct { | ||||
| 	Token       string `schema:"token"` | ||||
| 	TeamID      string `schema:"team_id"` | ||||
| 	TeamDomain  string `schema:"team_domain"` | ||||
| 	ChannelID   string `schema:"channel_id"` | ||||
| 	ServiceID   string `schema:"service_id"` | ||||
| 	ChannelName string `schema:"channel_name"` | ||||
| 	Timestamp   string `schema:"timestamp"` | ||||
| 	UserID      string `schema:"user_id"` | ||||
| 	UserName    string `schema:"user_name"` | ||||
| 	Text        string `schema:"text"` | ||||
| 	TriggerWord string `schema:"trigger_word"` | ||||
| } | ||||
|  | ||||
| // Client for Mattermost. | ||||
| type Client struct { | ||||
| 	Url        string // URL for incoming webhooks on mattermost. | ||||
| 	In         chan IMessage | ||||
| 	Out        chan OMessage | ||||
| 	httpclient *http.Client | ||||
| 	Config | ||||
| } | ||||
|  | ||||
| // Config for client. | ||||
| type Config struct { | ||||
| 	Port               int    // Port to listen on. | ||||
| 	BindAddress        string // Address to listen on | ||||
| 	Token              string // Only allow this token from Mattermost. (Allow everything when empty) | ||||
| 	InsecureSkipVerify bool   // disable certificate checking | ||||
| 	DisableServer      bool   // Do not start server for outgoing webhooks from Mattermost. | ||||
| } | ||||
|  | ||||
| // New Mattermost client. | ||||
| func New(url string, config Config) *Client { | ||||
| 	c := &Client{Url: url, In: make(chan IMessage), Out: make(chan OMessage), Config: config} | ||||
| 	if c.Port == 0 { | ||||
| 		c.Port = 9999 | ||||
| 	} | ||||
| 	c.BindAddress += ":" | ||||
| 	tr := &http.Transport{ | ||||
| 		TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}, | ||||
| 	} | ||||
| 	c.httpclient = &http.Client{Transport: tr} | ||||
| 	if !c.DisableServer { | ||||
| 		go c.StartServer() | ||||
| 	} | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| // StartServer starts a webserver listening for incoming mattermost POSTS. | ||||
| func (c *Client) StartServer() { | ||||
| 	mux := http.NewServeMux() | ||||
| 	mux.Handle("/", c) | ||||
| 	log.Printf("Listening on http://%v:%v...\n", c.BindAddress, c.Port) | ||||
| 	if err := http.ListenAndServe((c.BindAddress + strconv.Itoa(c.Port)), mux); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ServeHTTP implementation. | ||||
| func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	if r.Method != "POST" { | ||||
| 		log.Println("invalid " + r.Method + " connection from " + r.RemoteAddr) | ||||
| 		http.NotFound(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 	msg := IMessage{} | ||||
| 	err := r.ParseForm() | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 		http.NotFound(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 	defer r.Body.Close() | ||||
| 	decoder := schema.NewDecoder() | ||||
| 	err = decoder.Decode(&msg, r.PostForm) | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 		http.NotFound(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 	if msg.Token == "" { | ||||
| 		log.Println("no token from " + r.RemoteAddr) | ||||
| 		http.NotFound(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 	if c.Token != "" { | ||||
| 		if msg.Token != c.Token { | ||||
| 			log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr) | ||||
| 			http.NotFound(w, r) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	c.In <- msg | ||||
| } | ||||
|  | ||||
| // Receive returns an incoming message from mattermost outgoing webhooks URL. | ||||
| func (c *Client) Receive() IMessage { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case msg := <-c.In: | ||||
| 			return msg | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Send sends a msg to mattermost incoming webhooks URL. | ||||
| func (c *Client) Send(msg OMessage) error { | ||||
| 	buf, err := json.Marshal(msg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	resp, err := c.httpclient.Post(c.Url, "application/json", bytes.NewReader(buf)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	// Read entire body to completion to re-use keep-alive connections. | ||||
| 	io.Copy(ioutil.Discard, resp.Body) | ||||
|  | ||||
| 	if resp.StatusCode != 200 { | ||||
| 		return fmt.Errorf("unexpected status code: %d", resp.StatusCode) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										20
									
								
								vendor/github.com/mattermost/platform/einterfaces/account_migration.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/mattermost/platform/einterfaces/account_migration.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package einterfaces | ||||
|  | ||||
| import "github.com/mattermost/platform/model" | ||||
|  | ||||
| type AccountMigrationInterface interface { | ||||
| 	MigrateToLdap(fromAuthService string, forignUserFieldNameToMatch string) *model.AppError | ||||
| } | ||||
|  | ||||
| var theAccountMigrationInterface AccountMigrationInterface | ||||
|  | ||||
| func RegisterAccountMigrationInterface(newInterface AccountMigrationInterface) { | ||||
| 	theAccountMigrationInterface = newInterface | ||||
| } | ||||
|  | ||||
| func GetAccountMigrationInterface() AccountMigrationInterface { | ||||
| 	return theAccountMigrationInterface | ||||
| } | ||||
							
								
								
									
										32
									
								
								vendor/github.com/mattermost/platform/einterfaces/cluster.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								vendor/github.com/mattermost/platform/einterfaces/cluster.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package einterfaces | ||||
|  | ||||
| import ( | ||||
| 	"github.com/mattermost/platform/model" | ||||
| ) | ||||
|  | ||||
| type ClusterInterface interface { | ||||
| 	StartInterNodeCommunication() | ||||
| 	StopInterNodeCommunication() | ||||
| 	GetClusterInfos() []*model.ClusterInfo | ||||
| 	RemoveAllSessionsForUserId(userId string) | ||||
| 	InvalidateCacheForUser(userId string) | ||||
| 	InvalidateCacheForChannel(channelId string) | ||||
| 	Publish(event *model.WebSocketEvent) | ||||
| 	UpdateStatus(status *model.Status) | ||||
| 	GetLogs() ([]string, *model.AppError) | ||||
| 	GetClusterId() string | ||||
| 	ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError | ||||
| } | ||||
|  | ||||
| var theClusterInterface ClusterInterface | ||||
|  | ||||
| func RegisterClusterInterface(newInterface ClusterInterface) { | ||||
| 	theClusterInterface = newInterface | ||||
| } | ||||
|  | ||||
| func GetClusterInterface() ClusterInterface { | ||||
| 	return theClusterInterface | ||||
| } | ||||
							
								
								
									
										22
									
								
								vendor/github.com/mattermost/platform/einterfaces/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/mattermost/platform/einterfaces/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package einterfaces | ||||
|  | ||||
| import ( | ||||
| 	"github.com/mattermost/platform/model" | ||||
| ) | ||||
|  | ||||
| type EmojiInterface interface { | ||||
| 	CanUserCreateEmoji(string, []*model.TeamMember) bool | ||||
| } | ||||
|  | ||||
| var theEmojiInterface EmojiInterface | ||||
|  | ||||
| func RegisterEmojiInterface(newInterface EmojiInterface) { | ||||
| 	theEmojiInterface = newInterface | ||||
| } | ||||
|  | ||||
| func GetEmojiInterface() EmojiInterface { | ||||
| 	return theEmojiInterface | ||||
| } | ||||
							
								
								
									
										5
									
								
								vendor/github.com/mattermost/platform/einterfaces/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/mattermost/platform/einterfaces/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -13,6 +13,11 @@ type LdapInterface interface { | ||||
| 	CheckPassword(id string, password string) *model.AppError | ||||
| 	SwitchToLdap(userId, ldapId, ldapPassword string) *model.AppError | ||||
| 	ValidateFilter(filter string) *model.AppError | ||||
| 	Syncronize() *model.AppError | ||||
| 	StartLdapSyncJob() | ||||
| 	SyncNow() | ||||
| 	RunTest() *model.AppError | ||||
| 	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 | ||||
|   | ||||
							
								
								
									
										651
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										651
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										66
									
								
								vendor/github.com/mattermost/platform/model/cluster_info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								vendor/github.com/mattermost/platform/model/cluster_info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type ClusterInfo struct { | ||||
| 	Id                 string `json:"id"` | ||||
| 	Version            string `json:"version"` | ||||
| 	ConfigHash         string `json:"config_hash"` | ||||
| 	InterNodeUrl       string `json:"internode_url"` | ||||
| 	Hostname           string `json:"hostname"` | ||||
| 	LastSuccessfulPing int64  `json:"last_ping"` | ||||
| 	IsAlive            bool   `json:"is_alive"` | ||||
| } | ||||
|  | ||||
| func (me *ClusterInfo) ToJson() string { | ||||
| 	b, err := json.Marshal(me) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ClusterInfoFromJson(data io.Reader) *ClusterInfo { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var me ClusterInfo | ||||
| 	err := decoder.Decode(&me) | ||||
| 	if err == nil { | ||||
| 		return &me | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (me *ClusterInfo) HaveEstablishedInitialContact() bool { | ||||
| 	if me.Id != "" { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func ClusterInfosToJson(objmap []*ClusterInfo) string { | ||||
| 	if b, err := json.Marshal(objmap); err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ClusterInfosFromJson(data io.Reader) []*ClusterInfo { | ||||
| 	decoder := json.NewDecoder(data) | ||||
|  | ||||
| 	var objmap []*ClusterInfo | ||||
| 	if err := decoder.Decode(&objmap); err != nil { | ||||
| 		return make([]*ClusterInfo, 0) | ||||
| 	} else { | ||||
| 		return objmap | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										5
									
								
								vendor/github.com/mattermost/platform/model/command.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/mattermost/platform/model/command.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,11 +6,14 @@ package model | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	COMMAND_METHOD_POST = "P" | ||||
| 	COMMAND_METHOD_GET  = "G" | ||||
| 	MIN_TRIGGER_LENGTH  = 1 | ||||
| 	MAX_TRIGGER_LENGTH  = 128 | ||||
| ) | ||||
|  | ||||
| type Command struct { | ||||
| @@ -99,7 +102,7 @@ func (o *Command) IsValid() *AppError { | ||||
| 		return NewLocAppError("Command.IsValid", "model.command.is_valid.team_id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(o.Trigger) == 0 || len(o.Trigger) > 128 { | ||||
| 	if len(o.Trigger) < MIN_TRIGGER_LENGTH || len(o.Trigger) > MAX_TRIGGER_LENGTH || strings.Index(o.Trigger, "/") == 0 || strings.Contains(o.Trigger, " ") { | ||||
| 		return NewLocAppError("Command.IsValid", "model.command.is_valid.trigger.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										602
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										602
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,10 +6,12 @@ package model | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	CONN_SECURITY_NONE     = "" | ||||
| 	CONN_SECURITY_PLAIN    = "PLAIN" | ||||
| 	CONN_SECURITY_TLS      = "TLS" | ||||
| 	CONN_SECURITY_STARTTLS = "STARTTLS" | ||||
|  | ||||
| @@ -19,8 +21,12 @@ const ( | ||||
| 	DATABASE_DRIVER_MYSQL    = "mysql" | ||||
| 	DATABASE_DRIVER_POSTGRES = "postgres" | ||||
|  | ||||
| 	SERVICE_GITLAB = "gitlab" | ||||
| 	SERVICE_GOOGLE = "google" | ||||
| 	PASSWORD_MAXIMUM_LENGTH = 64 | ||||
| 	PASSWORD_MINIMUM_LENGTH = 5 | ||||
|  | ||||
| 	SERVICE_GITLAB    = "gitlab" | ||||
| 	SERVICE_GOOGLE    = "google" | ||||
| 	SERVICE_OFFICE365 = "office365" | ||||
|  | ||||
| 	WEBSERVER_MODE_REGULAR  = "regular" | ||||
| 	WEBSERVER_MODE_GZIP     = "gzip" | ||||
| @@ -32,10 +38,24 @@ 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" | ||||
|  | ||||
| 	EMAIL_BATCHING_BUFFER_SIZE = 256 | ||||
| 	EMAIL_BATCHING_INTERVAL    = 30 | ||||
|  | ||||
| 	SITENAME_MAX_LENGTH = 30 | ||||
| ) | ||||
|  | ||||
| type ServiceSettings struct { | ||||
| 	SiteURL                           *string | ||||
| 	ListenAddress                     string | ||||
| 	MaximumLoginAttempts              int | ||||
| 	SegmentDeveloperKey               string | ||||
| @@ -60,6 +80,14 @@ type ServiceSettings struct { | ||||
| 	WebsocketSecurePort               *int | ||||
| 	WebsocketPort                     *int | ||||
| 	WebserverMode                     *string | ||||
| 	EnableCustomEmoji                 *bool | ||||
| 	RestrictCustomEmojiCreation       *string | ||||
| } | ||||
|  | ||||
| type ClusterSettings struct { | ||||
| 	Enable                 *bool | ||||
| 	InterNodeListenAddress *string | ||||
| 	InterNodeUrls          []string | ||||
| } | ||||
|  | ||||
| type SSOSettings struct { | ||||
| @@ -83,19 +111,30 @@ 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 | ||||
| 	EnableDiagnostics      *bool | ||||
| } | ||||
|  | ||||
| type PasswordSettings struct { | ||||
| 	MinimumLength *int | ||||
| 	Lowercase     *bool | ||||
| 	Number        *bool | ||||
| 	Uppercase     *bool | ||||
| 	Symbol        *bool | ||||
| } | ||||
|  | ||||
| type FileSettings struct { | ||||
| 	MaxFileSize                *int64 | ||||
| 	DriverName                 string | ||||
| 	Directory                  string | ||||
| 	EnablePublicLink           bool | ||||
| 	PublicLinkSalt             string | ||||
| 	PublicLinkSalt             *string | ||||
| 	ThumbnailWidth             int | ||||
| 	ThumbnailHeight            int | ||||
| 	PreviewWidth               int | ||||
| @@ -121,6 +160,7 @@ type EmailSettings struct { | ||||
| 	RequireEmailVerification bool | ||||
| 	FeedbackName             string | ||||
| 	FeedbackEmail            string | ||||
| 	FeedbackOrganization     *string | ||||
| 	SMTPUsername             string | ||||
| 	SMTPPassword             string | ||||
| 	SMTPServer               string | ||||
| @@ -131,6 +171,9 @@ type EmailSettings struct { | ||||
| 	SendPushNotifications    *bool | ||||
| 	PushNotificationServer   *string | ||||
| 	PushNotificationContents *string | ||||
| 	EnableEmailBatching      *bool | ||||
| 	EmailBatchingBufferSize  *int | ||||
| 	EmailBatchingInterval    *int | ||||
| } | ||||
|  | ||||
| type RateLimitSettings struct { | ||||
| @@ -156,16 +199,21 @@ type SupportSettings struct { | ||||
| } | ||||
|  | ||||
| type TeamSettings struct { | ||||
| 	SiteName                  string | ||||
| 	MaxUsersPerTeam           int | ||||
| 	EnableTeamCreation        bool | ||||
| 	EnableUserCreation        bool | ||||
| 	EnableOpenServer          *bool | ||||
| 	RestrictCreationToDomains string | ||||
| 	RestrictTeamNames         *bool | ||||
| 	EnableCustomBrand         *bool | ||||
| 	CustomBrandText           *string | ||||
| 	RestrictDirectMessage     *string | ||||
| 	SiteName                         string | ||||
| 	MaxUsersPerTeam                  int | ||||
| 	EnableTeamCreation               bool | ||||
| 	EnableUserCreation               bool | ||||
| 	EnableOpenServer                 *bool | ||||
| 	RestrictCreationToDomains        string | ||||
| 	RestrictTeamNames                *bool | ||||
| 	EnableCustomBrand                *bool | ||||
| 	CustomBrandText                  *string | ||||
| 	CustomDescriptionText            *string | ||||
| 	RestrictDirectMessage            *string | ||||
| 	RestrictTeamInvite               *string | ||||
| 	RestrictPublicChannelManagement  *string | ||||
| 	RestrictPrivateChannelManagement *string | ||||
| 	UserStatusAwayTimeout            *int64 | ||||
| } | ||||
|  | ||||
| type LdapSettings struct { | ||||
| @@ -189,9 +237,13 @@ type LdapSettings struct { | ||||
| 	NicknameAttribute  *string | ||||
| 	IdAttribute        *string | ||||
|  | ||||
| 	// Syncronization | ||||
| 	SyncIntervalMinutes *int | ||||
|  | ||||
| 	// Advanced | ||||
| 	SkipCertificateVerification *bool | ||||
| 	QueryTimeout                *int | ||||
| 	MaxPageSize                 *int | ||||
|  | ||||
| 	// Customization | ||||
| 	LoginFieldName *string | ||||
| @@ -203,20 +255,63 @@ type ComplianceSettings struct { | ||||
| 	EnableDaily *bool | ||||
| } | ||||
|  | ||||
| type LocalizationSettings struct { | ||||
| 	DefaultServerLocale *string | ||||
| 	DefaultClientLocale *string | ||||
| 	AvailableLocales    *string | ||||
| } | ||||
|  | ||||
| type SamlSettings struct { | ||||
| 	// Basic | ||||
| 	Enable  *bool | ||||
| 	Verify  *bool | ||||
| 	Encrypt *bool | ||||
|  | ||||
| 	IdpUrl                      *string | ||||
| 	IdpDescriptorUrl            *string | ||||
| 	AssertionConsumerServiceURL *string | ||||
|  | ||||
| 	IdpCertificateFile    *string | ||||
| 	PublicCertificateFile *string | ||||
| 	PrivateKeyFile        *string | ||||
|  | ||||
| 	// User Mapping | ||||
| 	FirstNameAttribute *string | ||||
| 	LastNameAttribute  *string | ||||
| 	EmailAttribute     *string | ||||
| 	UsernameAttribute  *string | ||||
| 	NicknameAttribute  *string | ||||
| 	LocaleAttribute    *string | ||||
|  | ||||
| 	LoginButtonText *string | ||||
| } | ||||
|  | ||||
| type NativeAppSettings struct { | ||||
| 	AppDownloadLink        *string | ||||
| 	AndroidAppDownloadLink *string | ||||
| 	IosAppDownloadLink     *string | ||||
| } | ||||
|  | ||||
| type Config struct { | ||||
| 	ServiceSettings    ServiceSettings | ||||
| 	TeamSettings       TeamSettings | ||||
| 	SqlSettings        SqlSettings | ||||
| 	LogSettings        LogSettings | ||||
| 	FileSettings       FileSettings | ||||
| 	EmailSettings      EmailSettings | ||||
| 	RateLimitSettings  RateLimitSettings | ||||
| 	PrivacySettings    PrivacySettings | ||||
| 	SupportSettings    SupportSettings | ||||
| 	GitLabSettings     SSOSettings | ||||
| 	GoogleSettings     SSOSettings | ||||
| 	LdapSettings       LdapSettings | ||||
| 	ComplianceSettings ComplianceSettings | ||||
| 	ServiceSettings      ServiceSettings | ||||
| 	TeamSettings         TeamSettings | ||||
| 	SqlSettings          SqlSettings | ||||
| 	LogSettings          LogSettings | ||||
| 	PasswordSettings     PasswordSettings | ||||
| 	FileSettings         FileSettings | ||||
| 	EmailSettings        EmailSettings | ||||
| 	RateLimitSettings    RateLimitSettings | ||||
| 	PrivacySettings      PrivacySettings | ||||
| 	SupportSettings      SupportSettings | ||||
| 	GitLabSettings       SSOSettings | ||||
| 	GoogleSettings       SSOSettings | ||||
| 	Office365Settings    SSOSettings | ||||
| 	LdapSettings         LdapSettings | ||||
| 	ComplianceSettings   ComplianceSettings | ||||
| 	LocalizationSettings LocalizationSettings | ||||
| 	SamlSettings         SamlSettings | ||||
| 	NativeAppSettings    NativeAppSettings | ||||
| 	ClusterSettings      ClusterSettings | ||||
| } | ||||
|  | ||||
| func (o *Config) ToJson() string { | ||||
| @@ -234,6 +329,8 @@ func (o *Config) GetSSOService(service string) *SSOSettings { | ||||
| 		return &o.GitLabSettings | ||||
| 	case SERVICE_GOOGLE: | ||||
| 		return &o.GoogleSettings | ||||
| 	case SERVICE_OFFICE365: | ||||
| 		return &o.Office365Settings | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| @@ -256,8 +353,14 @@ func (o *Config) SetDefaults() { | ||||
| 		o.SqlSettings.AtRestEncryptKey = NewRandomString(32) | ||||
| 	} | ||||
|  | ||||
| 	if len(o.FileSettings.PublicLinkSalt) == 0 { | ||||
| 		o.FileSettings.PublicLinkSalt = NewRandomString(32) | ||||
| 	if o.FileSettings.MaxFileSize == nil { | ||||
| 		o.FileSettings.MaxFileSize = new(int64) | ||||
| 		*o.FileSettings.MaxFileSize = 52428800 // 50 MB | ||||
| 	} | ||||
|  | ||||
| 	if len(*o.FileSettings.PublicLinkSalt) == 0 { | ||||
| 		o.FileSettings.PublicLinkSalt = new(string) | ||||
| 		*o.FileSettings.PublicLinkSalt = NewRandomString(32) | ||||
| 	} | ||||
|  | ||||
| 	if o.FileSettings.AmazonS3LocationConstraint == nil { | ||||
| @@ -278,6 +381,11 @@ func (o *Config) SetDefaults() { | ||||
| 		o.EmailSettings.PasswordResetSalt = NewRandomString(32) | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.SiteURL == nil { | ||||
| 		o.ServiceSettings.SiteURL = new(string) | ||||
| 		*o.ServiceSettings.SiteURL = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.EnableDeveloper == nil { | ||||
| 		o.ServiceSettings.EnableDeveloper = new(bool) | ||||
| 		*o.ServiceSettings.EnableDeveloper = false | ||||
| @@ -298,6 +406,31 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.ServiceSettings.EnableMultifactorAuthentication = false | ||||
| 	} | ||||
|  | ||||
| 	if o.PasswordSettings.MinimumLength == nil { | ||||
| 		o.PasswordSettings.MinimumLength = new(int) | ||||
| 		*o.PasswordSettings.MinimumLength = PASSWORD_MINIMUM_LENGTH | ||||
| 	} | ||||
|  | ||||
| 	if o.PasswordSettings.Lowercase == nil { | ||||
| 		o.PasswordSettings.Lowercase = new(bool) | ||||
| 		*o.PasswordSettings.Lowercase = false | ||||
| 	} | ||||
|  | ||||
| 	if o.PasswordSettings.Number == nil { | ||||
| 		o.PasswordSettings.Number = new(bool) | ||||
| 		*o.PasswordSettings.Number = false | ||||
| 	} | ||||
|  | ||||
| 	if o.PasswordSettings.Uppercase == nil { | ||||
| 		o.PasswordSettings.Uppercase = new(bool) | ||||
| 		*o.PasswordSettings.Uppercase = false | ||||
| 	} | ||||
|  | ||||
| 	if o.PasswordSettings.Symbol == nil { | ||||
| 		o.PasswordSettings.Symbol = new(bool) | ||||
| 		*o.PasswordSettings.Symbol = false | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.RestrictTeamNames == nil { | ||||
| 		o.TeamSettings.RestrictTeamNames = new(bool) | ||||
| 		*o.TeamSettings.RestrictTeamNames = true | ||||
| @@ -313,6 +446,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.TeamSettings.CustomBrandText = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.CustomDescriptionText == nil { | ||||
| 		o.TeamSettings.CustomDescriptionText = new(string) | ||||
| 		*o.TeamSettings.CustomDescriptionText = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.EnableOpenServer == nil { | ||||
| 		o.TeamSettings.EnableOpenServer = new(bool) | ||||
| 		*o.TeamSettings.EnableOpenServer = false | ||||
| @@ -323,6 +461,26 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.TeamSettings.RestrictDirectMessage = DIRECT_MESSAGE_ANY | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.RestrictTeamInvite == nil { | ||||
| 		o.TeamSettings.RestrictTeamInvite = new(string) | ||||
| 		*o.TeamSettings.RestrictTeamInvite = PERMISSIONS_ALL | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.RestrictPublicChannelManagement == nil { | ||||
| 		o.TeamSettings.RestrictPublicChannelManagement = new(string) | ||||
| 		*o.TeamSettings.RestrictPublicChannelManagement = PERMISSIONS_ALL | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.RestrictPrivateChannelManagement == nil { | ||||
| 		o.TeamSettings.RestrictPrivateChannelManagement = new(string) | ||||
| 		*o.TeamSettings.RestrictPrivateChannelManagement = PERMISSIONS_ALL | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.UserStatusAwayTimeout == nil { | ||||
| 		o.TeamSettings.UserStatusAwayTimeout = new(int64) | ||||
| 		*o.TeamSettings.UserStatusAwayTimeout = 300 | ||||
| 	} | ||||
|  | ||||
| 	if o.EmailSettings.EnableSignInWithEmail == nil { | ||||
| 		o.EmailSettings.EnableSignInWithEmail = new(bool) | ||||
|  | ||||
| @@ -353,13 +511,33 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.EmailSettings.PushNotificationContents = GENERIC_NOTIFICATION | ||||
| 	} | ||||
|  | ||||
| 	if o.EmailSettings.FeedbackOrganization == nil { | ||||
| 		o.EmailSettings.FeedbackOrganization = new(string) | ||||
| 		*o.EmailSettings.FeedbackOrganization = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.EmailSettings.EnableEmailBatching == nil { | ||||
| 		o.EmailSettings.EnableEmailBatching = new(bool) | ||||
| 		*o.EmailSettings.EnableEmailBatching = false | ||||
| 	} | ||||
|  | ||||
| 	if o.EmailSettings.EmailBatchingBufferSize == nil { | ||||
| 		o.EmailSettings.EmailBatchingBufferSize = new(int) | ||||
| 		*o.EmailSettings.EmailBatchingBufferSize = EMAIL_BATCHING_BUFFER_SIZE | ||||
| 	} | ||||
|  | ||||
| 	if o.EmailSettings.EmailBatchingInterval == nil { | ||||
| 		o.EmailSettings.EmailBatchingInterval = new(int) | ||||
| 		*o.EmailSettings.EmailBatchingInterval = EMAIL_BATCHING_INTERVAL | ||||
| 	} | ||||
|  | ||||
| 	if !IsSafeLink(o.SupportSettings.TermsOfServiceLink) { | ||||
| 		o.SupportSettings.TermsOfServiceLink = nil | ||||
| 	} | ||||
|  | ||||
| 	if o.SupportSettings.TermsOfServiceLink == nil { | ||||
| 		o.SupportSettings.TermsOfServiceLink = new(string) | ||||
| 		*o.SupportSettings.TermsOfServiceLink = "/static/help/terms.html" | ||||
| 		*o.SupportSettings.TermsOfServiceLink = "https://about.mattermost.com/default-terms/" | ||||
| 	} | ||||
|  | ||||
| 	if !IsSafeLink(o.SupportSettings.PrivacyPolicyLink) { | ||||
| @@ -368,7 +546,7 @@ func (o *Config) SetDefaults() { | ||||
|  | ||||
| 	if o.SupportSettings.PrivacyPolicyLink == nil { | ||||
| 		o.SupportSettings.PrivacyPolicyLink = new(string) | ||||
| 		*o.SupportSettings.PrivacyPolicyLink = "/static/help/privacy.html" | ||||
| 		*o.SupportSettings.PrivacyPolicyLink = "" | ||||
| 	} | ||||
|  | ||||
| 	if !IsSafeLink(o.SupportSettings.AboutLink) { | ||||
| @@ -377,7 +555,7 @@ func (o *Config) SetDefaults() { | ||||
|  | ||||
| 	if o.SupportSettings.AboutLink == nil { | ||||
| 		o.SupportSettings.AboutLink = new(string) | ||||
| 		*o.SupportSettings.AboutLink = "/static/help/about.html" | ||||
| 		*o.SupportSettings.AboutLink = "" | ||||
| 	} | ||||
|  | ||||
| 	if !IsSafeLink(o.SupportSettings.HelpLink) { | ||||
| @@ -386,7 +564,7 @@ func (o *Config) SetDefaults() { | ||||
|  | ||||
| 	if o.SupportSettings.HelpLink == nil { | ||||
| 		o.SupportSettings.HelpLink = new(string) | ||||
| 		*o.SupportSettings.HelpLink = "/static/help/help.html" | ||||
| 		*o.SupportSettings.HelpLink = "" | ||||
| 	} | ||||
|  | ||||
| 	if !IsSafeLink(o.SupportSettings.ReportAProblemLink) { | ||||
| @@ -395,7 +573,7 @@ func (o *Config) SetDefaults() { | ||||
|  | ||||
| 	if o.SupportSettings.ReportAProblemLink == nil { | ||||
| 		o.SupportSettings.ReportAProblemLink = new(string) | ||||
| 		*o.SupportSettings.ReportAProblemLink = "/static/help/report_problem.html" | ||||
| 		*o.SupportSettings.ReportAProblemLink = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SupportSettings.SupportEmail == nil { | ||||
| @@ -403,24 +581,94 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.SupportSettings.SupportEmail = "feedback@mattermost.com" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.Enable == nil { | ||||
| 		o.LdapSettings.Enable = new(bool) | ||||
| 		*o.LdapSettings.Enable = false | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.LdapServer == nil { | ||||
| 		o.LdapSettings.LdapServer = new(string) | ||||
| 		*o.LdapSettings.LdapServer = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.LdapPort == nil { | ||||
| 		o.LdapSettings.LdapPort = new(int) | ||||
| 		*o.LdapSettings.LdapPort = 389 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.ConnectionSecurity == nil { | ||||
| 		o.LdapSettings.ConnectionSecurity = new(string) | ||||
| 		*o.LdapSettings.ConnectionSecurity = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.BaseDN == nil { | ||||
| 		o.LdapSettings.BaseDN = new(string) | ||||
| 		*o.LdapSettings.BaseDN = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.BindUsername == nil { | ||||
| 		o.LdapSettings.BindUsername = new(string) | ||||
| 		*o.LdapSettings.BindUsername = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.BindPassword == nil { | ||||
| 		o.LdapSettings.BindPassword = new(string) | ||||
| 		*o.LdapSettings.BindPassword = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.UserFilter == nil { | ||||
| 		o.LdapSettings.UserFilter = new(string) | ||||
| 		*o.LdapSettings.UserFilter = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.FirstNameAttribute == nil { | ||||
| 		o.LdapSettings.FirstNameAttribute = new(string) | ||||
| 		*o.LdapSettings.FirstNameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.LastNameAttribute == nil { | ||||
| 		o.LdapSettings.LastNameAttribute = new(string) | ||||
| 		*o.LdapSettings.LastNameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.EmailAttribute == nil { | ||||
| 		o.LdapSettings.EmailAttribute = new(string) | ||||
| 		*o.LdapSettings.EmailAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.UsernameAttribute == nil { | ||||
| 		o.LdapSettings.UsernameAttribute = new(string) | ||||
| 		*o.LdapSettings.UsernameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.NicknameAttribute == nil { | ||||
| 		o.LdapSettings.NicknameAttribute = new(string) | ||||
| 		*o.LdapSettings.NicknameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.IdAttribute == nil { | ||||
| 		o.LdapSettings.IdAttribute = new(string) | ||||
| 		*o.LdapSettings.IdAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.SyncIntervalMinutes == nil { | ||||
| 		o.LdapSettings.SyncIntervalMinutes = new(int) | ||||
| 		*o.LdapSettings.SyncIntervalMinutes = 60 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.SkipCertificateVerification == nil { | ||||
| 		o.LdapSettings.SkipCertificateVerification = new(bool) | ||||
| 		*o.LdapSettings.SkipCertificateVerification = false | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.QueryTimeout == nil { | ||||
| 		o.LdapSettings.QueryTimeout = new(int) | ||||
| 		*o.LdapSettings.QueryTimeout = 60 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.Enable == nil { | ||||
| 		o.LdapSettings.Enable = new(bool) | ||||
| 		*o.LdapSettings.Enable = false | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.UserFilter == nil { | ||||
| 		o.LdapSettings.UserFilter = new(string) | ||||
| 		*o.LdapSettings.UserFilter = "" | ||||
| 	if o.LdapSettings.MaxPageSize == nil { | ||||
| 		o.LdapSettings.MaxPageSize = new(int) | ||||
| 		*o.LdapSettings.MaxPageSize = 0 | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.LoginFieldName == nil { | ||||
| @@ -475,7 +723,33 @@ func (o *Config) SetDefaults() { | ||||
|  | ||||
| 	if o.ServiceSettings.WebserverMode == nil { | ||||
| 		o.ServiceSettings.WebserverMode = new(string) | ||||
| 		*o.ServiceSettings.WebserverMode = "regular" | ||||
| 		*o.ServiceSettings.WebserverMode = "gzip" | ||||
| 	} else if *o.ServiceSettings.WebserverMode == "regular" { | ||||
| 		*o.ServiceSettings.WebserverMode = "gzip" | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.EnableCustomEmoji == nil { | ||||
| 		o.ServiceSettings.EnableCustomEmoji = new(bool) | ||||
| 		*o.ServiceSettings.EnableCustomEmoji = true | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.RestrictCustomEmojiCreation == nil { | ||||
| 		o.ServiceSettings.RestrictCustomEmojiCreation = new(string) | ||||
| 		*o.ServiceSettings.RestrictCustomEmojiCreation = RESTRICT_EMOJI_CREATION_ALL | ||||
| 	} | ||||
|  | ||||
| 	if o.ClusterSettings.InterNodeListenAddress == nil { | ||||
| 		o.ClusterSettings.InterNodeListenAddress = new(string) | ||||
| 		*o.ClusterSettings.InterNodeListenAddress = ":8075" | ||||
| 	} | ||||
|  | ||||
| 	if o.ClusterSettings.Enable == nil { | ||||
| 		o.ClusterSettings.Enable = new(bool) | ||||
| 		*o.ClusterSettings.Enable = false | ||||
| 	} | ||||
|  | ||||
| 	if o.ClusterSettings.InterNodeUrls == nil { | ||||
| 		o.ClusterSettings.InterNodeUrls = []string{} | ||||
| 	} | ||||
|  | ||||
| 	if o.ComplianceSettings.Enable == nil { | ||||
| @@ -493,19 +767,119 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.ComplianceSettings.EnableDaily = false | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.ConnectionSecurity == nil { | ||||
| 		o.LdapSettings.ConnectionSecurity = new(string) | ||||
| 		*o.LdapSettings.ConnectionSecurity = "" | ||||
| 	if o.LocalizationSettings.DefaultServerLocale == nil { | ||||
| 		o.LocalizationSettings.DefaultServerLocale = new(string) | ||||
| 		*o.LocalizationSettings.DefaultServerLocale = DEFAULT_LOCALE | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.SkipCertificateVerification == nil { | ||||
| 		o.LdapSettings.SkipCertificateVerification = new(bool) | ||||
| 		*o.LdapSettings.SkipCertificateVerification = false | ||||
| 	if o.LocalizationSettings.DefaultClientLocale == nil { | ||||
| 		o.LocalizationSettings.DefaultClientLocale = new(string) | ||||
| 		*o.LocalizationSettings.DefaultClientLocale = DEFAULT_LOCALE | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.NicknameAttribute == nil { | ||||
| 		o.LdapSettings.NicknameAttribute = new(string) | ||||
| 		*o.LdapSettings.NicknameAttribute = "" | ||||
| 	if o.LocalizationSettings.AvailableLocales == nil { | ||||
| 		o.LocalizationSettings.AvailableLocales = new(string) | ||||
| 		*o.LocalizationSettings.AvailableLocales = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LogSettings.EnableDiagnostics == nil { | ||||
| 		o.LogSettings.EnableDiagnostics = new(bool) | ||||
| 		*o.LogSettings.EnableDiagnostics = true | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.Enable == nil { | ||||
| 		o.SamlSettings.Enable = new(bool) | ||||
| 		*o.SamlSettings.Enable = false | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.Verify == nil { | ||||
| 		o.SamlSettings.Verify = new(bool) | ||||
| 		*o.SamlSettings.Verify = false | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.Encrypt == nil { | ||||
| 		o.SamlSettings.Encrypt = new(bool) | ||||
| 		*o.SamlSettings.Encrypt = false | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.IdpUrl == nil { | ||||
| 		o.SamlSettings.IdpUrl = new(string) | ||||
| 		*o.SamlSettings.IdpUrl = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.IdpDescriptorUrl == nil { | ||||
| 		o.SamlSettings.IdpDescriptorUrl = new(string) | ||||
| 		*o.SamlSettings.IdpDescriptorUrl = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.IdpCertificateFile == nil { | ||||
| 		o.SamlSettings.IdpCertificateFile = new(string) | ||||
| 		*o.SamlSettings.IdpCertificateFile = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.PublicCertificateFile == nil { | ||||
| 		o.SamlSettings.PublicCertificateFile = new(string) | ||||
| 		*o.SamlSettings.PublicCertificateFile = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.PrivateKeyFile == nil { | ||||
| 		o.SamlSettings.PrivateKeyFile = new(string) | ||||
| 		*o.SamlSettings.PrivateKeyFile = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.AssertionConsumerServiceURL == nil { | ||||
| 		o.SamlSettings.AssertionConsumerServiceURL = new(string) | ||||
| 		*o.SamlSettings.AssertionConsumerServiceURL = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.LoginButtonText == nil || *o.SamlSettings.LoginButtonText == "" { | ||||
| 		o.SamlSettings.LoginButtonText = new(string) | ||||
| 		*o.SamlSettings.LoginButtonText = USER_AUTH_SERVICE_SAML_TEXT | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.FirstNameAttribute == nil { | ||||
| 		o.SamlSettings.FirstNameAttribute = new(string) | ||||
| 		*o.SamlSettings.FirstNameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.LastNameAttribute == nil { | ||||
| 		o.SamlSettings.LastNameAttribute = new(string) | ||||
| 		*o.SamlSettings.LastNameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.EmailAttribute == nil { | ||||
| 		o.SamlSettings.EmailAttribute = new(string) | ||||
| 		*o.SamlSettings.EmailAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.UsernameAttribute == nil { | ||||
| 		o.SamlSettings.UsernameAttribute = new(string) | ||||
| 		*o.SamlSettings.UsernameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.NicknameAttribute == nil { | ||||
| 		o.SamlSettings.NicknameAttribute = new(string) | ||||
| 		*o.SamlSettings.NicknameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.LocaleAttribute == nil { | ||||
| 		o.SamlSettings.LocaleAttribute = new(string) | ||||
| 		*o.SamlSettings.LocaleAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.NativeAppSettings.AppDownloadLink == nil { | ||||
| 		o.NativeAppSettings.AppDownloadLink = new(string) | ||||
| 		*o.NativeAppSettings.AppDownloadLink = "https://about.mattermost.com/downloads/" | ||||
| 	} | ||||
|  | ||||
| 	if o.NativeAppSettings.AndroidAppDownloadLink == nil { | ||||
| 		o.NativeAppSettings.AndroidAppDownloadLink = new(string) | ||||
| 		*o.NativeAppSettings.AndroidAppDownloadLink = "https://about.mattermost.com/mattermost-android-app/" | ||||
| 	} | ||||
|  | ||||
| 	if o.NativeAppSettings.IosAppDownloadLink == nil { | ||||
| 		o.NativeAppSettings.IosAppDownloadLink = new(string) | ||||
| 		*o.NativeAppSettings.IosAppDownloadLink = "https://about.mattermost.com/mattermost-ios-app/" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -515,10 +889,24 @@ func (o *Config) IsValid() *AppError { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.login_attempts.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(*o.ServiceSettings.SiteURL) != 0 { | ||||
| 		if _, err := url.ParseRequestURI(*o.ServiceSettings.SiteURL); err != nil { | ||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.site_url.app_error", nil, "") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(o.ServiceSettings.ListenAddress) == 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.ClusterSettings.Enable && *o.EmailSettings.EnableEmailBatching { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.cluster_email_batching.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(*o.ServiceSettings.SiteURL) == 0 && *o.EmailSettings.EnableEmailBatching { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.site_url_email_batching.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.MaxUsersPerTeam <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "") | ||||
| 	} | ||||
| @@ -547,6 +935,10 @@ func (o *Config) IsValid() *AppError { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_max_conn.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.FileSettings.MaxFileSize <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.max_file_size.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if !(o.FileSettings.DriverName == IMAGE_DRIVER_LOCAL || o.FileSettings.DriverName == IMAGE_DRIVER_S3) { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "") | ||||
| 	} | ||||
| @@ -575,11 +967,11 @@ func (o *Config) IsValid() *AppError { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_width.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(o.FileSettings.PublicLinkSalt) < 32 { | ||||
| 	if len(*o.FileSettings.PublicLinkSalt) < 32 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if !(o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_STARTTLS) { | ||||
| 	if !(o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_TLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_STARTTLS || o.EmailSettings.ConnectionSecurity == CONN_SECURITY_PLAIN) { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.email_security.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| @@ -591,6 +983,14 @@ func (o *Config) IsValid() *AppError { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.email_reset_salt.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.EmailSettings.EmailBatchingBufferSize <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.email_batching_buffer_size.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.EmailSettings.EmailBatchingInterval < 30 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.email_batching_interval.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if o.RateLimitSettings.MemoryStoreSize <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "") | ||||
| 	} | ||||
| @@ -603,6 +1003,86 @@ func (o *Config) IsValid() *AppError { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_security.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.LdapSettings.SyncIntervalMinutes <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_sync_interval.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.LdapSettings.MaxPageSize < 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_max_page_size.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.LdapSettings.Enable { | ||||
| 		if *o.LdapSettings.LdapServer == "" { | ||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_server", nil, "") | ||||
| 		} | ||||
|  | ||||
| 		if *o.LdapSettings.BaseDN == "" { | ||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.ldap_basedn", nil, "") | ||||
| 		} | ||||
|  | ||||
| 		if *o.LdapSettings.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 *o.SamlSettings.Verify { | ||||
| 			if len(*o.SamlSettings.AssertionConsumerServiceURL) == 0 || !IsValidHttpUrl(*o.SamlSettings.AssertionConsumerServiceURL) { | ||||
| 				return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_assertion_consumer_service_url.app_error", nil, "") | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if *o.SamlSettings.Encrypt { | ||||
| 			if len(*o.SamlSettings.PrivateKeyFile) == 0 { | ||||
| 				return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_private_key.app_error", nil, "") | ||||
| 			} | ||||
|  | ||||
| 			if len(*o.SamlSettings.PublicCertificateFile) == 0 { | ||||
| 				return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_public_cert.app_error", nil, "") | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if len(*o.SamlSettings.EmailAttribute) == 0 { | ||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.saml_email_attribute.app_error", nil, "") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if *o.PasswordSettings.MinimumLength < PASSWORD_MINIMUM_LENGTH || *o.PasswordSettings.MinimumLength > PASSWORD_MAXIMUM_LENGTH { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.password_length.app_error", map[string]interface{}{"MinLength": PASSWORD_MINIMUM_LENGTH, "MaxLength": PASSWORD_MAXIMUM_LENGTH}, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(o.TeamSettings.SiteName) > SITENAME_MAX_LENGTH { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SITENAME_MAX_LENGTH}, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -615,11 +1095,11 @@ func (o *Config) GetSanitizeOptions() map[string]bool { | ||||
| } | ||||
|  | ||||
| func (o *Config) Sanitize() { | ||||
| 	if &o.LdapSettings != nil && len(*o.LdapSettings.BindPassword) > 0 { | ||||
| 	if o.LdapSettings.BindPassword != nil && len(*o.LdapSettings.BindPassword) > 0 { | ||||
| 		*o.LdapSettings.BindPassword = FAKE_SETTING | ||||
| 	} | ||||
|  | ||||
| 	o.FileSettings.PublicLinkSalt = FAKE_SETTING | ||||
| 	*o.FileSettings.PublicLinkSalt = FAKE_SETTING | ||||
| 	if len(o.FileSettings.AmazonS3SecretAccessKey) > 0 { | ||||
| 		o.FileSettings.AmazonS3SecretAccessKey = FAKE_SETTING | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										95
									
								
								vendor/github.com/mattermost/platform/model/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								vendor/github.com/mattermost/platform/model/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type Emoji struct { | ||||
| 	Id        string `json:"id"` | ||||
| 	CreateAt  int64  `json:"create_at"` | ||||
| 	UpdateAt  int64  `json:"update_at"` | ||||
| 	DeleteAt  int64  `json:"delete_at"` | ||||
| 	CreatorId string `json:"creator_id"` | ||||
| 	Name      string `json:"name"` | ||||
| } | ||||
|  | ||||
| func (emoji *Emoji) IsValid() *AppError { | ||||
| 	if len(emoji.Id) != 26 { | ||||
| 		return NewLocAppError("Emoji.IsValid", "model.emoji.id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if emoji.CreateAt == 0 { | ||||
| 		return NewLocAppError("Emoji.IsValid", "model.emoji.create_at.app_error", nil, "id="+emoji.Id) | ||||
| 	} | ||||
|  | ||||
| 	if emoji.UpdateAt == 0 { | ||||
| 		return NewLocAppError("Emoji.IsValid", "model.emoji.update_at.app_error", nil, "id="+emoji.Id) | ||||
| 	} | ||||
|  | ||||
| 	if len(emoji.CreatorId) != 26 { | ||||
| 		return NewLocAppError("Emoji.IsValid", "model.emoji.user_id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(emoji.Name) == 0 || len(emoji.Name) > 64 { | ||||
| 		return NewLocAppError("Emoji.IsValid", "model.emoji.name.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (emoji *Emoji) PreSave() { | ||||
| 	if emoji.Id == "" { | ||||
| 		emoji.Id = NewId() | ||||
| 	} | ||||
|  | ||||
| 	emoji.CreateAt = GetMillis() | ||||
| 	emoji.UpdateAt = emoji.CreateAt | ||||
| } | ||||
|  | ||||
| func (emoji *Emoji) PreUpdate() { | ||||
| 	emoji.UpdateAt = GetMillis() | ||||
| } | ||||
|  | ||||
| func (emoji *Emoji) ToJson() string { | ||||
| 	b, err := json.Marshal(emoji) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func EmojiFromJson(data io.Reader) *Emoji { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var emoji Emoji | ||||
| 	err := decoder.Decode(&emoji) | ||||
| 	if err == nil { | ||||
| 		return &emoji | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func EmojiListToJson(emojiList []*Emoji) string { | ||||
| 	b, err := json.Marshal(emojiList) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func EmojiListFromJson(data io.Reader) []*Emoji { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var emojiList []*Emoji | ||||
| 	err := decoder.Decode(&emojiList) | ||||
| 	if err == nil { | ||||
| 		return emojiList | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										4
									
								
								vendor/github.com/mattermost/platform/model/file.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/mattermost/platform/model/file.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -8,10 +8,6 @@ import ( | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	MAX_FILE_SIZE = 50000000 // 50 MB | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	IMAGE_EXTENSIONS = [5]string{".jpg", ".jpeg", ".gif", ".bmp", ".png"} | ||||
| 	IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff"} | ||||
|   | ||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/model/gitlab/gitlab.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/gitlab/gitlab.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -47,7 +47,8 @@ func userFromGitLabUser(glu *GitLabUser) *model.User { | ||||
| 	} | ||||
| 	strings.TrimSpace(user.Email) | ||||
| 	user.Email = glu.Email | ||||
| 	*user.AuthData = strconv.FormatInt(glu.Id, 10) | ||||
| 	userId := strconv.FormatInt(glu.Id, 10) | ||||
| 	user.AuthData = &userId | ||||
| 	user.AuthService = model.USER_AUTH_SERVICE_GITLAB | ||||
|  | ||||
| 	return user | ||||
|   | ||||
							
								
								
									
										135
									
								
								vendor/github.com/mattermost/platform/model/incoming_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										135
									
								
								vendor/github.com/mattermost/platform/model/incoming_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -4,13 +4,15 @@ | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	DEFAULT_WEBHOOK_USERNAME = "webhook" | ||||
| 	DEFAULT_WEBHOOK_ICON     = "/static/images/webhook_icon.jpg" | ||||
| ) | ||||
|  | ||||
| type IncomingWebhook struct { | ||||
| @@ -125,13 +127,136 @@ func (o *IncomingWebhook) PreUpdate() { | ||||
| 	o.UpdateAt = GetMillis() | ||||
| } | ||||
|  | ||||
| func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| // escapeControlCharsFromPayload escapes control chars (\n, \t) from a byte slice. | ||||
| // Context: | ||||
| // JSON strings are not supposed to contain control characters such as \n, \t, | ||||
| // ... but some incoming webhooks might still send invalid JSON and we want to | ||||
| // try to handle that. An example invalid JSON string from an incoming webhook | ||||
| // might look like this (strings for both "text" and "fallback" attributes are | ||||
| // invalid JSON strings because they contain unescaped newlines and tabs): | ||||
| //  `{ | ||||
| //    "text": "this is a test | ||||
| //						 that contains a newline and tabs", | ||||
| //    "attachments": [ | ||||
| //      { | ||||
| //        "fallback": "Required plain-text summary of the attachment | ||||
| //										that contains a newline and tabs", | ||||
| //        "color": "#36a64f", | ||||
| //  			... | ||||
| //        "text": "Optional text that appears within the attachment | ||||
| //								 that contains a newline and tabs", | ||||
| //  			... | ||||
| //        "thumb_url": "http://example.com/path/to/thumb.png" | ||||
| //      } | ||||
| //    ] | ||||
| //  }` | ||||
| // This function will search for `"key": "value"` pairs, and escape \n, \t | ||||
| // from the value. | ||||
| func escapeControlCharsFromPayload(by []byte) []byte { | ||||
| 	// we'll search for `"text": "..."` or `"fallback": "..."`, ... | ||||
| 	keys := "text|fallback|pretext|author_name|title|value" | ||||
|  | ||||
| 	// the regexp reads like this: | ||||
| 	// (?s): this flag let . match \n (default is false) | ||||
| 	// "(keys)": we search for the keys defined above | ||||
| 	// \s*:\s*: followed by 0..n spaces/tabs, a colon then 0..n spaces/tabs | ||||
| 	// ": a double-quote | ||||
| 	// (\\"|[^"])*: any number of times the `\"` string or any char but a double-quote | ||||
| 	// ": a double-quote | ||||
| 	r := `(?s)"(` + keys + `)"\s*:\s*"(\\"|[^"])*"` | ||||
| 	re := regexp.MustCompile(r) | ||||
|  | ||||
| 	// the function that will escape \n and \t on the regexp matches | ||||
| 	repl := func(b []byte) []byte { | ||||
| 		if bytes.Contains(b, []byte("\n")) { | ||||
| 			b = bytes.Replace(b, []byte("\n"), []byte("\\n"), -1) | ||||
| 		} | ||||
| 		if bytes.Contains(b, []byte("\t")) { | ||||
| 			b = bytes.Replace(b, []byte("\t"), []byte("\\t"), -1) | ||||
| 		} | ||||
|  | ||||
| 		return b | ||||
| 	} | ||||
|  | ||||
| 	return re.ReplaceAllFunc(by, repl) | ||||
| } | ||||
|  | ||||
| func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) { | ||||
| 	decoder := json.NewDecoder(bytes.NewReader(by)) | ||||
| 	var o IncomingWebhookRequest | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 		return &o, nil | ||||
| 	} else { | ||||
| 		return nil | ||||
| 		return nil, err | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // To mention @channel via a webhook in Slack, the message should contain | ||||
| // <!channel>, as explained at the bottom of this article: | ||||
| // https://get.slack.help/hc/en-us/articles/202009646-Making-announcements | ||||
| func expandAnnouncement(text string) string { | ||||
| 	c1 := "<!channel>" | ||||
| 	c2 := "@channel" | ||||
| 	if strings.Contains(text, c1) { | ||||
| 		return strings.Replace(text, c1, c2, -1) | ||||
| 	} | ||||
| 	return text | ||||
| } | ||||
|  | ||||
| // Expand announcements in incoming webhooks from Slack. Those announcements | ||||
| // can be found in the text attribute, or in the pretext, text, title and value | ||||
| // attributes of the attachment structure. The Slack attachment structure is | ||||
| // documented here: https://api.slack.com/docs/attachments | ||||
| func expandAnnouncements(i *IncomingWebhookRequest) { | ||||
| 	i.Text = expandAnnouncement(i.Text) | ||||
|  | ||||
| 	if i.Attachments != nil { | ||||
| 		attachments := i.Attachments.([]interface{}) | ||||
| 		for _, attachment := range attachments { | ||||
| 			a := attachment.(map[string]interface{}) | ||||
|  | ||||
| 			if a["pretext"] != nil { | ||||
| 				a["pretext"] = expandAnnouncement(a["pretext"].(string)) | ||||
| 			} | ||||
|  | ||||
| 			if a["text"] != nil { | ||||
| 				a["text"] = expandAnnouncement(a["text"].(string)) | ||||
| 			} | ||||
|  | ||||
| 			if a["title"] != nil { | ||||
| 				a["title"] = expandAnnouncement(a["title"].(string)) | ||||
| 			} | ||||
|  | ||||
| 			if a["fields"] != nil { | ||||
| 				fields := a["fields"].([]interface{}) | ||||
| 				for _, field := range fields { | ||||
| 					f := field.(map[string]interface{}) | ||||
| 					if f["value"] != nil { | ||||
| 						f["value"] = expandAnnouncement(f["value"].(string)) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest { | ||||
| 	buf := new(bytes.Buffer) | ||||
| 	buf.ReadFrom(data) | ||||
| 	by := buf.Bytes() | ||||
|  | ||||
| 	// Try to decode the JSON data. Only if it fails, try to escape control | ||||
| 	// characters from the strings contained in the JSON data. | ||||
| 	o, err := decodeIncomingWebhookRequest(by) | ||||
| 	if err != nil { | ||||
| 		o, err = decodeIncomingWebhookRequest(escapeControlCharsFromPayload(by)) | ||||
| 		if err != nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	expandAnnouncements(o) | ||||
|  | ||||
| 	return o | ||||
| } | ||||
|   | ||||
							
								
								
									
										100
									
								
								vendor/github.com/mattermost/platform/model/job.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								vendor/github.com/mattermost/platform/model/job.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type TaskFunc func() | ||||
|  | ||||
| type ScheduledTask struct { | ||||
| 	Name      string        `json:"name"` | ||||
| 	Interval  time.Duration `json:"interval"` | ||||
| 	Recurring bool          `json:"recurring"` | ||||
| 	function  TaskFunc      `json:",omitempty"` | ||||
| 	timer     *time.Timer   `json:",omitempty"` | ||||
| } | ||||
|  | ||||
| var tasks = make(map[string]*ScheduledTask) | ||||
|  | ||||
| func addTask(task *ScheduledTask) { | ||||
| 	tasks[task.Name] = task | ||||
| } | ||||
|  | ||||
| func removeTaskByName(name string) { | ||||
| 	delete(tasks, name) | ||||
| } | ||||
|  | ||||
| func GetTaskByName(name string) *ScheduledTask { | ||||
| 	if task, ok := tasks[name]; ok { | ||||
| 		return task | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func GetAllTasks() *map[string]*ScheduledTask { | ||||
| 	return &tasks | ||||
| } | ||||
|  | ||||
| func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask { | ||||
| 	task := &ScheduledTask{ | ||||
| 		Name:      name, | ||||
| 		Interval:  timeToExecution, | ||||
| 		Recurring: false, | ||||
| 		function:  function, | ||||
| 	} | ||||
|  | ||||
| 	taskRunner := func() { | ||||
| 		go task.function() | ||||
| 		removeTaskByName(task.Name) | ||||
| 	} | ||||
|  | ||||
| 	task.timer = time.AfterFunc(timeToExecution, taskRunner) | ||||
|  | ||||
| 	addTask(task) | ||||
|  | ||||
| 	return task | ||||
| } | ||||
|  | ||||
| func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask { | ||||
| 	task := &ScheduledTask{ | ||||
| 		Name:      name, | ||||
| 		Interval:  interval, | ||||
| 		Recurring: true, | ||||
| 		function:  function, | ||||
| 	} | ||||
|  | ||||
| 	taskRecurer := func() { | ||||
| 		go task.function() | ||||
| 		task.timer.Reset(task.Interval) | ||||
| 	} | ||||
|  | ||||
| 	task.timer = time.AfterFunc(interval, taskRecurer) | ||||
|  | ||||
| 	addTask(task) | ||||
|  | ||||
| 	return task | ||||
| } | ||||
|  | ||||
| func (task *ScheduledTask) Cancel() { | ||||
| 	task.timer.Stop() | ||||
| 	removeTaskByName(task.Name) | ||||
| } | ||||
|  | ||||
| // Executes the task immediatly. A recurring task will be run regularally after interval. | ||||
| func (task *ScheduledTask) Execute() { | ||||
| 	task.function() | ||||
| 	task.timer.Reset(task.Interval) | ||||
| } | ||||
|  | ||||
| func (task *ScheduledTask) String() string { | ||||
| 	return fmt.Sprintf( | ||||
| 		"%s\nInterval: %s\nRecurring: %t\n", | ||||
| 		task.Name, | ||||
| 		task.Interval.String(), | ||||
| 		task.Recurring, | ||||
| 	) | ||||
| } | ||||
							
								
								
									
										1
									
								
								vendor/github.com/mattermost/platform/model/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/mattermost/platform/model/ldap.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -5,4 +5,5 @@ package model | ||||
|  | ||||
| const ( | ||||
| 	USER_AUTH_SERVICE_LDAP = "ldap" | ||||
| 	LDAP_SYNC_TASK_NAME    = "LDAP Syncronization" | ||||
| ) | ||||
|   | ||||
							
								
								
									
										62
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										62
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -32,14 +32,34 @@ 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) ToMap() map[string]interface{} { | ||||
| 	return map[string]interface{}{ | ||||
| 		"ldap":         *f.LDAP, | ||||
| 		"mfa":          *f.MFA, | ||||
| 		"google":       *f.GoogleOAuth, | ||||
| 		"office365":    *f.Office365OAuth, | ||||
| 		"compliance":   *f.Compliance, | ||||
| 		"cluster":      *f.Cluster, | ||||
| 		"custom_brand": *f.CustomBrand, | ||||
| 		"mhpns":        *f.MHPNS, | ||||
| 		"saml":         *f.SAML, | ||||
| 		"password":     *f.PasswordRequirements, | ||||
| 		"future":       *f.FutureFeatures, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (f *Features) SetDefaults() { | ||||
| @@ -63,9 +83,14 @@ func (f *Features) SetDefaults() { | ||||
| 		*f.MFA = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.GoogleSSO == nil { | ||||
| 		f.GoogleSSO = new(bool) | ||||
| 		*f.GoogleSSO = *f.FutureFeatures | ||||
| 	if f.GoogleOAuth == nil { | ||||
| 		f.GoogleOAuth = new(bool) | ||||
| 		*f.GoogleOAuth = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.Office365OAuth == nil { | ||||
| 		f.Office365OAuth = new(bool) | ||||
| 		*f.Office365OAuth = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.Compliance == nil { | ||||
| @@ -73,6 +98,11 @@ func (f *Features) SetDefaults() { | ||||
| 		*f.Compliance = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.Cluster == nil { | ||||
| 		f.Cluster = new(bool) | ||||
| 		*f.Cluster = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.CustomBrand == nil { | ||||
| 		f.CustomBrand = new(bool) | ||||
| 		*f.CustomBrand = *f.FutureFeatures | ||||
| @@ -82,6 +112,16 @@ func (f *Features) SetDefaults() { | ||||
| 		f.MHPNS = new(bool) | ||||
| 		*f.MHPNS = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.SAML == nil { | ||||
| 		f.SAML = new(bool) | ||||
| 		*f.SAML = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.PasswordRequirements == nil { | ||||
| 		f.PasswordRequirements = new(bool) | ||||
| 		*f.PasswordRequirements = *f.FutureFeatures | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (l *License) IsExpired() bool { | ||||
|   | ||||
							
								
								
									
										60
									
								
								vendor/github.com/mattermost/platform/model/message.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										60
									
								
								vendor/github.com/mattermost/platform/model/message.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,60 +0,0 @@ | ||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	ACTION_TYPING             = "typing" | ||||
| 	ACTION_POSTED             = "posted" | ||||
| 	ACTION_POST_EDITED        = "post_edited" | ||||
| 	ACTION_POST_DELETED       = "post_deleted" | ||||
| 	ACTION_CHANNEL_DELETED    = "channel_deleted" | ||||
| 	ACTION_CHANNEL_VIEWED     = "channel_viewed" | ||||
| 	ACTION_DIRECT_ADDED       = "direct_added" | ||||
| 	ACTION_NEW_USER           = "new_user" | ||||
| 	ACTION_USER_ADDED         = "user_added" | ||||
| 	ACTION_USER_REMOVED       = "user_removed" | ||||
| 	ACTION_PREFERENCE_CHANGED = "preference_changed" | ||||
| 	ACTION_EPHEMERAL_MESSAGE  = "ephemeral_message" | ||||
| ) | ||||
|  | ||||
| type Message struct { | ||||
| 	TeamId    string            `json:"team_id"` | ||||
| 	ChannelId string            `json:"channel_id"` | ||||
| 	UserId    string            `json:"user_id"` | ||||
| 	Action    string            `json:"action"` | ||||
| 	Props     map[string]string `json:"props"` | ||||
| } | ||||
|  | ||||
| func (m *Message) Add(key string, value string) { | ||||
| 	m.Props[key] = value | ||||
| } | ||||
|  | ||||
| func NewMessage(teamId string, channelId string, userId string, action string) *Message { | ||||
| 	return &Message{TeamId: teamId, ChannelId: channelId, UserId: userId, Action: action, Props: make(map[string]string)} | ||||
| } | ||||
|  | ||||
| func (o *Message) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func MessageFromJson(data io.Reader) *Message { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o Message | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										40
									
								
								vendor/github.com/mattermost/platform/model/oauth.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/mattermost/platform/model/oauth.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -25,8 +25,10 @@ type OAuthApp struct { | ||||
| 	ClientSecret string      `json:"client_secret"` | ||||
| 	Name         string      `json:"name"` | ||||
| 	Description  string      `json:"description"` | ||||
| 	IconURL      string      `json:"icon_url"` | ||||
| 	CallbackUrls StringArray `json:"callback_urls"` | ||||
| 	Homepage     string      `json:"homepage"` | ||||
| 	IsTrusted    bool        `json:"is_trusted"` | ||||
| } | ||||
|  | ||||
| // IsValid validates the app and returns an error if it isn't configured | ||||
| @@ -61,7 +63,13 @@ func (a *OAuthApp) IsValid() *AppError { | ||||
| 		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id) | ||||
| 	} | ||||
|  | ||||
| 	if len(a.Homepage) == 0 || len(a.Homepage) > 256 { | ||||
| 	for _, callback := range a.CallbackUrls { | ||||
| 		if !IsValidHttpUrl(callback) { | ||||
| 			return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) { | ||||
| 		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id) | ||||
| 	} | ||||
|  | ||||
| @@ -69,6 +77,12 @@ func (a *OAuthApp) IsValid() *AppError { | ||||
| 		return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id) | ||||
| 	} | ||||
|  | ||||
| 	if len(a.IconURL) > 0 { | ||||
| 		if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) { | ||||
| 			return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -85,10 +99,6 @@ func (a *OAuthApp) PreSave() { | ||||
|  | ||||
| 	a.CreateAt = GetMillis() | ||||
| 	a.UpdateAt = a.CreateAt | ||||
|  | ||||
| 	if len(a.ClientSecret) > 0 { | ||||
| 		a.ClientSecret = HashPassword(a.ClientSecret) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // PreUpdate should be run before updating the app in the db. | ||||
| @@ -157,3 +167,23 @@ func OAuthAppMapFromJson(data io.Reader) map[string]*OAuthApp { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func OAuthAppListToJson(l []*OAuthApp) string { | ||||
| 	b, err := json.Marshal(l) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func OAuthAppListFromJson(data io.Reader) []*OAuthApp { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o []*OAuthApp | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										67
									
								
								vendor/github.com/mattermost/platform/model/outgoing_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										67
									
								
								vendor/github.com/mattermost/platform/model/outgoing_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -7,6 +7,9 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type OutgoingWebhook struct { | ||||
| @@ -19,9 +22,51 @@ type OutgoingWebhook struct { | ||||
| 	ChannelId    string      `json:"channel_id"` | ||||
| 	TeamId       string      `json:"team_id"` | ||||
| 	TriggerWords StringArray `json:"trigger_words"` | ||||
| 	TriggerWhen  int         `json:"trigger_when"` | ||||
| 	CallbackURLs StringArray `json:"callback_urls"` | ||||
| 	DisplayName  string      `json:"display_name"` | ||||
| 	Description  string      `json:"description"` | ||||
| 	ContentType  string      `json:"content_type"` | ||||
| } | ||||
|  | ||||
| type OutgoingWebhookPayload struct { | ||||
| 	Token       string `json:"token"` | ||||
| 	TeamId      string `json:"team_id"` | ||||
| 	TeamDomain  string `json:"team_domain"` | ||||
| 	ChannelId   string `json:"channel_id"` | ||||
| 	ChannelName string `json:"channel_name"` | ||||
| 	Timestamp   int64  `json:"timestamp"` | ||||
| 	UserId      string `json:"user_id"` | ||||
| 	UserName    string `json:"user_name"` | ||||
| 	PostId      string `json:"post_id"` | ||||
| 	Text        string `json:"text"` | ||||
| 	TriggerWord string `json:"trigger_word"` | ||||
| } | ||||
|  | ||||
| func (o *OutgoingWebhookPayload) ToJSON() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *OutgoingWebhookPayload) ToFormValues() string { | ||||
| 	v := url.Values{} | ||||
| 	v.Set("token", o.Token) | ||||
| 	v.Set("team_id", o.TeamId) | ||||
| 	v.Set("team_domain", o.TeamDomain) | ||||
| 	v.Set("channel_id", o.ChannelId) | ||||
| 	v.Set("channel_name", o.ChannelName) | ||||
| 	v.Set("timestamp", strconv.FormatInt(o.Timestamp/1000, 10)) | ||||
| 	v.Set("user_id", o.UserId) | ||||
| 	v.Set("user_name", o.UserName) | ||||
| 	v.Set("post_id", o.PostId) | ||||
| 	v.Set("text", o.Text) | ||||
| 	v.Set("trigger_word", o.TriggerWord) | ||||
|  | ||||
| 	return v.Encode() | ||||
| } | ||||
|  | ||||
| func (o *OutgoingWebhook) ToJson() string { | ||||
| @@ -124,6 +169,14 @@ func (o *OutgoingWebhook) IsValid() *AppError { | ||||
| 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(o.ContentType) > 128 { | ||||
| 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if o.TriggerWhen > 1 { | ||||
| 		return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -157,3 +210,17 @@ func (o *OutgoingWebhook) HasTriggerWord(word string) bool { | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool { | ||||
| 	if len(o.TriggerWords) == 0 || len(word) == 0 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	for _, trigger := range o.TriggerWords { | ||||
| 		if strings.HasPrefix(word, trigger) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|   | ||||
							
								
								
									
										6
									
								
								vendor/github.com/mattermost/platform/model/post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/mattermost/platform/model/post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -15,6 +15,7 @@ const ( | ||||
| 	POST_SLACK_ATTACHMENT      = "slack_attachment" | ||||
| 	POST_SYSTEM_GENERIC        = "system_generic" | ||||
| 	POST_JOIN_LEAVE            = "system_join_leave" | ||||
| 	POST_ADD_REMOVE            = "system_add_remove" | ||||
| 	POST_HEADER_CHANGE         = "system_header_change" | ||||
| 	POST_CHANNEL_DELETED       = "system_channel_deleted" | ||||
| 	POST_EPHEMERAL             = "system_ephemeral" | ||||
| @@ -109,7 +110,7 @@ func (o *Post) IsValid() *AppError { | ||||
| 	} | ||||
|  | ||||
| 	// should be removed once more message types are supported | ||||
| 	if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE) { | ||||
| 	if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_ADD_REMOVE || o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE) { | ||||
| 		return NewLocAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type) | ||||
| 	} | ||||
|  | ||||
| @@ -162,9 +163,6 @@ func (o *Post) AddProp(key string, value interface{}) { | ||||
| 	o.Props[key] = value | ||||
| } | ||||
|  | ||||
| func (o *Post) PreExport() { | ||||
| } | ||||
|  | ||||
| func (o *Post) IsSystemMessage() bool { | ||||
| 	return len(o.Type) >= len(POST_SYSTEM_MESSAGE_PREFIX) && o.Type[:len(POST_SYSTEM_MESSAGE_PREFIX)] == POST_SYSTEM_MESSAGE_PREFIX | ||||
| } | ||||
|   | ||||
							
								
								
									
										60
									
								
								vendor/github.com/mattermost/platform/model/preference.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										60
									
								
								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,9 +15,28 @@ const ( | ||||
| 	PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW = "direct_channel_show" | ||||
| 	PREFERENCE_CATEGORY_TUTORIAL_STEPS      = "tutorial_step" | ||||
| 	PREFERENCE_CATEGORY_ADVANCED_SETTINGS   = "advanced_settings" | ||||
| 	PREFERENCE_CATEGORY_FLAGGED_POST        = "flagged_post" | ||||
|  | ||||
| 	PREFERENCE_CATEGORY_DISPLAY_SETTINGS   = "display_settings" | ||||
| 	PREFERENCE_NAME_COLLAPSE_SETTING       = "collapse_previews" | ||||
| 	PREFERENCE_NAME_DISPLAY_NAME_FORMAT    = "name_format" | ||||
| 	PREFERENCE_VALUE_DISPLAY_NAME_NICKNAME = "nickname_full_name" | ||||
| 	PREFERENCE_VALUE_DISPLAY_NAME_FULL     = "full_name" | ||||
| 	PREFERENCE_VALUE_DISPLAY_NAME_USERNAME = "username" | ||||
| 	PREFERENCE_DEFAULT_DISPLAY_NAME_FORMAT = PREFERENCE_VALUE_DISPLAY_NAME_USERNAME | ||||
|  | ||||
| 	PREFERENCE_CATEGORY_THEME = "theme" | ||||
| 	// the name for theme props is the team id | ||||
|  | ||||
| 	PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP = "oauth_app" | ||||
| 	// the name for oauth_app is the client_id and value is the current scope | ||||
|  | ||||
| 	PREFERENCE_CATEGORY_LAST     = "last" | ||||
| 	PREFERENCE_NAME_LAST_CHANNEL = "channel" | ||||
|  | ||||
| 	PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications" | ||||
| 	PREFERENCE_NAME_EMAIL_INTERVAL    = "email_interval" | ||||
| 	PREFERENCE_DEFAULT_EMAIL_INTERVAL = "30" // default to match the interval of the "immediate" setting (ie 30 seconds) | ||||
| ) | ||||
|  | ||||
| type Preference struct { | ||||
| @@ -54,13 +75,48 @@ func (o *Preference) IsValid() *AppError { | ||||
| 		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.category.app_error", nil, "category="+o.Category) | ||||
| 	} | ||||
|  | ||||
| 	if len(o.Name) == 0 || len(o.Name) > 32 { | ||||
| 	if len(o.Name) > 32 { | ||||
| 		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.name.app_error", nil, "name="+o.Name) | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(o.Value) > 128 { | ||||
| 	if utf8.RuneCountInString(o.Value) > 2000 { | ||||
| 		return NewLocAppError("Preference.IsValid", "model.preference.is_valid.value.app_error", nil, "value="+o.Value) | ||||
| 	} | ||||
|  | ||||
| 	if o.Category == PREFERENCE_CATEGORY_THEME { | ||||
| 		var unused map[string]string | ||||
| 		if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&unused); err != nil { | ||||
| 			return NewLocAppError("Preference.IsValid", "model.preference.is_valid.theme.app_error", nil, "value="+o.Value) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (o *Preference) PreUpdate() { | ||||
| 	if o.Category == PREFERENCE_CATEGORY_THEME { | ||||
| 		// decode the value of theme (a map of strings to string) and eliminate any invalid values | ||||
| 		var props map[string]string | ||||
| 		if err := json.NewDecoder(strings.NewReader(o.Value)).Decode(&props); err != nil { | ||||
| 			// just continue, the invalid preference value should get caught by IsValid before saving | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		colorPattern := regexp.MustCompile(`^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$`) | ||||
|  | ||||
| 		// blank out any invalid theme values | ||||
| 		for name, value := range props { | ||||
| 			if name == "image" || name == "type" || name == "codeTheme" { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			if !colorPattern.MatchString(value) { | ||||
| 				props[name] = "#ffffff" | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if b, err := json.Marshal(props); err == nil { | ||||
| 			o.Value = string(b) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										15
									
								
								vendor/github.com/mattermost/platform/model/push_notification.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/mattermost/platform/model/push_notification.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,12 +6,16 @@ package model | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	PUSH_NOTIFY_APPLE   = "apple" | ||||
| 	PUSH_NOTIFY_ANDROID = "android" | ||||
|  | ||||
| 	PUSH_TYPE_MESSAGE = "message" | ||||
| 	PUSH_TYPE_CLEAR   = "clear" | ||||
|  | ||||
| 	CATEGORY_DM = "DIRECT_MESSAGE" | ||||
|  | ||||
| 	MHPNS = "https://push.mattermost.com" | ||||
| @@ -28,6 +32,7 @@ type PushNotification struct { | ||||
| 	ContentAvailable int    `json:"cont_ava"` | ||||
| 	ChannelId        string `json:"channel_id"` | ||||
| 	ChannelName      string `json:"channel_name"` | ||||
| 	Type             string `json:"type"` | ||||
| } | ||||
|  | ||||
| func (me *PushNotification) ToJson() string { | ||||
| @@ -39,6 +44,16 @@ func (me *PushNotification) ToJson() string { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (me *PushNotification) SetDeviceIdAndPlatform(deviceId string) { | ||||
| 	if strings.HasPrefix(deviceId, PUSH_NOTIFY_APPLE+":") { | ||||
| 		me.Platform = PUSH_NOTIFY_APPLE | ||||
| 		me.DeviceId = strings.TrimPrefix(deviceId, PUSH_NOTIFY_APPLE+":") | ||||
| 	} else if strings.HasPrefix(deviceId, PUSH_NOTIFY_ANDROID+":") { | ||||
| 		me.Platform = PUSH_NOTIFY_ANDROID | ||||
| 		me.DeviceId = strings.TrimPrefix(deviceId, PUSH_NOTIFY_ANDROID+":") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func PushNotificationFromJson(data io.Reader) *PushNotification { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var me PushNotification | ||||
|   | ||||
							
								
								
									
										18
									
								
								vendor/github.com/mattermost/platform/model/saml.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/mattermost/platform/model/saml.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| const ( | ||||
| 	USER_AUTH_SERVICE_SAML      = "saml" | ||||
| 	USER_AUTH_SERVICE_SAML_TEXT = "With SAML" | ||||
| 	SAML_IDP_CERTIFICATE        = 1 | ||||
| 	SAML_PRIVATE_KEY            = 2 | ||||
| 	SAML_PUBLIC_CERT            = 3 | ||||
| ) | ||||
|  | ||||
| type SamlAuthRequest struct { | ||||
| 	Base64AuthRequest string | ||||
| 	URL               string | ||||
| 	RelayState        string | ||||
| } | ||||
							
								
								
									
										8
									
								
								vendor/github.com/mattermost/platform/model/search_params.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/mattermost/platform/model/search_params.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -4,9 +4,13 @@ | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`) | ||||
| var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`) | ||||
|  | ||||
| type SearchParams struct { | ||||
| 	Terms      string | ||||
| 	IsHashtag  bool | ||||
| @@ -91,8 +95,8 @@ func parseSearchFlags(input []string) ([]string, [][2]string) { | ||||
|  | ||||
| 		if !isFlag { | ||||
| 			// trim off surrounding punctuation (note that we leave trailing asterisks to allow wildcards) | ||||
| 			word = puncStart.ReplaceAllString(word, "") | ||||
| 			word = puncEndWildcard.ReplaceAllString(word, "") | ||||
| 			word = searchTermPuncStart.ReplaceAllString(word, "") | ||||
| 			word = searchTermPuncEnd.ReplaceAllString(word, "") | ||||
|  | ||||
| 			// and remove extra pound #s | ||||
| 			word = hashtagStart.ReplaceAllString(word, "#") | ||||
|   | ||||
							
								
								
									
										12
									
								
								vendor/github.com/mattermost/platform/model/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/mattermost/platform/model/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,6 +6,7 @@ package model | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -83,7 +84,11 @@ func (me *Session) IsExpired() bool { | ||||
| } | ||||
|  | ||||
| func (me *Session) SetExpireInDays(days int) { | ||||
| 	me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days)) | ||||
| 	if me.CreateAt == 0 { | ||||
| 		me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * int64(days)) | ||||
| 	} else { | ||||
| 		me.ExpiresAt = me.CreateAt + (1000 * 60 * 60 * 24 * int64(days)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (me *Session) AddProp(key string, value string) { | ||||
| @@ -105,6 +110,11 @@ func (me *Session) GetTeamByTeamId(teamId string) *TeamMember { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (me *Session) IsMobileApp() bool { | ||||
| 	return len(me.DeviceId) > 0 && | ||||
| 		(strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_ANDROID+":")) | ||||
| } | ||||
|  | ||||
| func SessionsToJson(o []*Session) string { | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		return "[]" | ||||
|   | ||||
							
								
								
									
										45
									
								
								vendor/github.com/mattermost/platform/model/status.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								vendor/github.com/mattermost/platform/model/status.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	STATUS_OFFLINE         = "offline" | ||||
| 	STATUS_AWAY            = "away" | ||||
| 	STATUS_ONLINE          = "online" | ||||
| 	STATUS_CACHE_SIZE      = 10000 | ||||
| 	STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds | ||||
| ) | ||||
|  | ||||
| type Status struct { | ||||
| 	UserId         string `json:"user_id"` | ||||
| 	Status         string `json:"status"` | ||||
| 	Manual         bool   `json:"manual"` | ||||
| 	LastActivityAt int64  `json:"last_activity_at"` | ||||
| 	ActiveChannel  string `json:"active_channel"` | ||||
| } | ||||
|  | ||||
| func (o *Status) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func StatusFromJson(data io.Reader) *Status { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o Status | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/model/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -224,9 +224,6 @@ func CleanTeamName(s string) string { | ||||
| 	return s | ||||
| } | ||||
|  | ||||
| func (o *Team) PreExport() { | ||||
| } | ||||
|  | ||||
| func (o *Team) Sanitize() { | ||||
| 	o.Email = "" | ||||
| 	o.AllowedDomains = "" | ||||
|   | ||||
							
								
								
									
										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 { | ||||
|   | ||||
							
								
								
									
										77
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										77
									
								
								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,18 +39,16 @@ type User struct { | ||||
| 	FirstName          string    `json:"first_name"` | ||||
| 	LastName           string    `json:"last_name"` | ||||
| 	Roles              string    `json:"roles"` | ||||
| 	LastActivityAt     int64     `json:"last_activity_at,omitempty"` | ||||
| 	LastPingAt         int64     `json:"last_ping_at,omitempty"` | ||||
| 	AllowMarketing     bool      `json:"allow_marketing,omitempty"` | ||||
| 	Props              StringMap `json:"props,omitempty"` | ||||
| 	NotifyProps        StringMap `json:"notify_props,omitempty"` | ||||
| 	ThemeProps         StringMap `json:"theme_props,omitempty"` | ||||
| 	LastPasswordUpdate int64     `json:"last_password_update,omitempty"` | ||||
| 	LastPictureUpdate  int64     `json:"last_picture_update,omitempty"` | ||||
| 	FailedAttempts     int       `json:"failed_attempts,omitempty"` | ||||
| 	Locale             string    `json:"locale"` | ||||
| 	MfaActive          bool      `json:"mfa_active,omitempty"` | ||||
| 	MfaSecret          string    `json:"mfa_secret,omitempty"` | ||||
| 	LastActivityAt     int64     `db:"-" json:"last_activity_at,omitempty"` | ||||
| } | ||||
|  | ||||
| // IsValid validates the user and returns an error if it isn't configured | ||||
| @@ -95,10 +87,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 +99,6 @@ func (u *User) IsValid() *AppError { | ||||
| 		return NewLocAppError("User.IsValid", "model.user.is_valid.auth_data_pwd.app_error", nil, "user_id="+u.Id) | ||||
| 	} | ||||
|  | ||||
| 	if len(u.ThemeProps) > 2000 { | ||||
| 		return NewLocAppError("User.IsValid", "model.user.is_valid.theme.app_error", nil, "user_id="+u.Id) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -136,7 +120,6 @@ func (u *User) PreSave() { | ||||
|  | ||||
| 	u.Username = strings.ToLower(u.Username) | ||||
| 	u.Email = strings.ToLower(u.Email) | ||||
| 	u.Locale = strings.ToLower(u.Locale) | ||||
|  | ||||
| 	u.CreateAt = GetMillis() | ||||
| 	u.UpdateAt = u.CreateAt | ||||
| @@ -166,7 +149,6 @@ func (u *User) PreSave() { | ||||
| func (u *User) PreUpdate() { | ||||
| 	u.Username = strings.ToLower(u.Username) | ||||
| 	u.Email = strings.ToLower(u.Email) | ||||
| 	u.Locale = strings.ToLower(u.Locale) | ||||
| 	u.UpdateAt = GetMillis() | ||||
|  | ||||
| 	if u.AuthData != nil && *u.AuthData == "" { | ||||
| @@ -191,10 +173,10 @@ func (u *User) PreUpdate() { | ||||
| func (u *User) SetDefaultNotifications() { | ||||
| 	u.NotifyProps = make(map[string]string) | ||||
| 	u.NotifyProps["email"] = "true" | ||||
| 	u.NotifyProps["push"] = USER_NOTIFY_MENTION | ||||
| 	u.NotifyProps["desktop"] = USER_NOTIFY_ALL | ||||
| 	u.NotifyProps["desktop_sound"] = "true" | ||||
| 	u.NotifyProps["mention_keys"] = u.Username + ",@" + u.Username | ||||
| 	u.NotifyProps["all"] = "true" | ||||
| 	u.NotifyProps["channel"] = "true" | ||||
|  | ||||
| 	if u.FirstName == "" { | ||||
| @@ -230,16 +212,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 | ||||
| @@ -262,7 +236,6 @@ func (u *User) Sanitize(options map[string]bool) { | ||||
| } | ||||
|  | ||||
| func (u *User) ClearNonProfileFields() { | ||||
| 	u.UpdateAt = 0 | ||||
| 	u.Password = "" | ||||
| 	u.AuthData = new(string) | ||||
| 	*u.AuthData = "" | ||||
| @@ -270,16 +243,20 @@ func (u *User) ClearNonProfileFields() { | ||||
| 	u.MfaActive = false | ||||
| 	u.MfaSecret = "" | ||||
| 	u.EmailVerified = false | ||||
| 	u.LastPingAt = 0 | ||||
| 	u.AllowMarketing = false | ||||
| 	u.Props = StringMap{} | ||||
| 	u.NotifyProps = StringMap{} | ||||
| 	u.ThemeProps = StringMap{} | ||||
| 	u.LastPasswordUpdate = 0 | ||||
| 	u.LastPictureUpdate = 0 | ||||
| 	u.FailedAttempts = 0 | ||||
| } | ||||
|  | ||||
| func (u *User) SanitizeProfile(options map[string]bool) { | ||||
| 	u.ClearNonProfileFields() | ||||
|  | ||||
| 	u.Sanitize(options) | ||||
| } | ||||
|  | ||||
| func (u *User) MakeNonNil() { | ||||
| 	if u.Props == nil { | ||||
| 		u.Props = make(map[string]string) | ||||
| @@ -324,6 +301,24 @@ func (u *User) GetDisplayName() string { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (u *User) GetDisplayNameForPreference(nameFormat string) string { | ||||
| 	displayName := u.Username | ||||
|  | ||||
| 	if nameFormat == PREFERENCE_VALUE_DISPLAY_NAME_NICKNAME { | ||||
| 		if u.Nickname != "" { | ||||
| 			displayName = u.Nickname | ||||
| 		} else if fullName := u.GetFullName(); fullName != "" { | ||||
| 			displayName = fullName | ||||
| 		} | ||||
| 	} else if nameFormat == PREFERENCE_VALUE_DISPLAY_NAME_FULL { | ||||
| 		if fullName := u.GetFullName(); fullName != "" { | ||||
| 			displayName = fullName | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return displayName | ||||
| } | ||||
|  | ||||
| func IsValidUserRoles(userRoles string) bool { | ||||
|  | ||||
| 	roles := strings.Split(userRoles, " ") | ||||
| @@ -349,13 +344,13 @@ func isValidRole(role string) bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // Make sure you acually want to use this function. In context.go there are functions to check permssions | ||||
| // Make sure you acually want to use this function. In context.go there are functions to check permissions | ||||
| // This function should not be used to check permissions. | ||||
| func (u *User) IsInRole(inRole string) bool { | ||||
| 	return IsInRole(u.Roles, inRole) | ||||
| } | ||||
|  | ||||
| // Make sure you acually want to use this function. In context.go there are functions to check permssions | ||||
| // Make sure you acually want to use this function. In context.go there are functions to check permissions | ||||
| // This function should not be used to check permissions. | ||||
| func IsInRole(userRoles string, inRole string) bool { | ||||
| 	roles := strings.Split(userRoles, " ") | ||||
| @@ -384,17 +379,6 @@ func (u *User) IsLDAPUser() bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (u *User) PreExport() { | ||||
| 	u.Password = "" | ||||
| 	u.AuthData = new(string) | ||||
| 	*u.AuthData = "" | ||||
| 	u.LastActivityAt = 0 | ||||
| 	u.LastPingAt = 0 | ||||
| 	u.LastPasswordUpdate = 0 | ||||
| 	u.LastPictureUpdate = 0 | ||||
| 	u.FailedAttempts = 0 | ||||
| } | ||||
|  | ||||
| // UserFromJson will decode the input and return a User | ||||
| func UserFromJson(data io.Reader) *User { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| @@ -453,6 +437,7 @@ var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`) | ||||
| var restrictedUsernames = []string{ | ||||
| 	"all", | ||||
| 	"channel", | ||||
| 	"matterbot", | ||||
| } | ||||
|  | ||||
| func IsValidUsername(s string) bool { | ||||
|   | ||||
							
								
								
									
										24
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -20,6 +20,13 @@ import ( | ||||
| 	"github.com/pborman/uuid" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz" | ||||
| 	UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||||
| 	NUMBERS           = "0123456789" | ||||
| 	SYMBOLS           = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~" | ||||
| ) | ||||
|  | ||||
| type StringInterface map[string]interface{} | ||||
| type StringMap map[string]string | ||||
| type StringArray []string | ||||
| @@ -27,12 +34,12 @@ type EncryptStringMap map[string]string | ||||
|  | ||||
| type AppError struct { | ||||
| 	Id            string                 `json:"id"` | ||||
| 	Message       string                 `json:"message"`        // Message to be display to the end user without debugging information | ||||
| 	DetailedError string                 `json:"detailed_error"` // Internal error string to help the developer | ||||
| 	RequestId     string                 `json:"request_id"`     // The RequestId that's also set in the header | ||||
| 	StatusCode    int                    `json:"status_code"`    // The http status code | ||||
| 	Where         string                 `json:"-"`              // The function where it happened in the form of Struct.Func | ||||
| 	IsOAuth       bool                   `json:"is_oauth"`       // Whether the error is OAuth specific | ||||
| 	Message       string                 `json:"message"`               // Message to be display to the end user without debugging information | ||||
| 	DetailedError string                 `json:"detailed_error"`        // Internal error string to help the developer | ||||
| 	RequestId     string                 `json:"request_id,omitempty"`  // The RequestId that's also set in the header | ||||
| 	StatusCode    int                    `json:"status_code,omitempty"` // The http status code | ||||
| 	Where         string                 `json:"-"`                     // The function where it happened in the form of Struct.Func | ||||
| 	IsOAuth       bool                   `json:"is_oauth,omitempty"`    // Whether the error is OAuth specific | ||||
| 	params        map[string]interface{} `json:"-"` | ||||
| } | ||||
|  | ||||
| @@ -315,10 +322,9 @@ func Etag(parts ...interface{}) string { | ||||
| } | ||||
|  | ||||
| var validHashtag = regexp.MustCompile(`^(#[A-Za-zäöüÄÖÜß]+[A-Za-z0-9äöüÄÖÜß_\-]*[A-Za-z0-9äöüÄÖÜß])$`) | ||||
| var puncStart = regexp.MustCompile(`^[.,()&$!\?\[\]{}':;\\<>\-+=%^*|]+`) | ||||
| var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) | ||||
| var hashtagStart = regexp.MustCompile(`^#{2,}`) | ||||
| var puncEnd = regexp.MustCompile(`[.,()&$#!\?\[\]{}':;\\<>\-+=%^*|]+$`) | ||||
| var puncEndWildcard = regexp.MustCompile(`[.,()&$#!\?\[\]{}':;\\<>\-+=%^|]+$`) | ||||
| var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) | ||||
|  | ||||
| func ParseHashtags(text string) (string, string) { | ||||
| 	words := strings.Fields(text) | ||||
|   | ||||
							
								
								
									
										5
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -13,6 +13,10 @@ import ( | ||||
| // It should be maitained in chronological order with most current | ||||
| // release at the front of the list. | ||||
| var versions = []string{ | ||||
| 	"3.4.0", | ||||
| 	"3.3.0", | ||||
| 	"3.2.0", | ||||
| 	"3.1.0", | ||||
| 	"3.0.0", | ||||
| 	"2.2.0", | ||||
| 	"2.1.0", | ||||
| @@ -33,6 +37,7 @@ var CurrentVersion string = versions[0] | ||||
| var BuildNumber string | ||||
| var BuildDate string | ||||
| var BuildHash string | ||||
| var BuildHashEnterprise string | ||||
| var BuildEnterpriseReady string | ||||
| var versionsWithoutHotFixes []string | ||||
|  | ||||
|   | ||||
							
								
								
									
										109
									
								
								vendor/github.com/mattermost/platform/model/websocket_client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								vendor/github.com/mattermost/platform/model/websocket_client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"github.com/gorilla/websocket" | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| type WebSocketClient struct { | ||||
| 	Url             string          // The location of the server like "ws://localhost:8065" | ||||
| 	ApiUrl          string          // The api location of the server like "ws://localhost:8065/api/v3" | ||||
| 	Conn            *websocket.Conn // The WebSocket connection | ||||
| 	AuthToken       string          // The token used to open the WebSocket | ||||
| 	Sequence        int64           // The ever-incrementing sequence attached to each WebSocket action | ||||
| 	EventChannel    chan *WebSocketEvent | ||||
| 	ResponseChannel chan *WebSocketResponse | ||||
| } | ||||
|  | ||||
| // NewWebSocketClient constructs a new WebSocket client with convienence | ||||
| // methods for talking to the server. | ||||
| func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) { | ||||
| 	header := http.Header{} | ||||
| 	header.Set(HEADER_AUTH, "BEARER "+authToken) | ||||
| 	conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", header) | ||||
| 	if err != nil { | ||||
| 		return nil, NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return &WebSocketClient{ | ||||
| 		url, | ||||
| 		url + API_URL_SUFFIX, | ||||
| 		conn, | ||||
| 		authToken, | ||||
| 		1, | ||||
| 		make(chan *WebSocketEvent, 100), | ||||
| 		make(chan *WebSocketResponse, 100), | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (wsc *WebSocketClient) Connect() *AppError { | ||||
| 	header := http.Header{} | ||||
| 	header.Set(HEADER_AUTH, "BEARER "+wsc.AuthToken) | ||||
|  | ||||
| 	var err error | ||||
| 	wsc.Conn, _, err = websocket.DefaultDialer.Dial(wsc.ApiUrl+"/users/websocket", header) | ||||
| 	if err != nil { | ||||
| 		return NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (wsc *WebSocketClient) Close() { | ||||
| 	wsc.Conn.Close() | ||||
| } | ||||
|  | ||||
| func (wsc *WebSocketClient) Listen() { | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			var rawMsg json.RawMessage | ||||
| 			var err error | ||||
| 			if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil { | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			var event WebSocketEvent | ||||
| 			if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { | ||||
| 				wsc.EventChannel <- &event | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			var response WebSocketResponse | ||||
| 			if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() { | ||||
| 				wsc.ResponseChannel <- &response | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
|  | ||||
| func (wsc *WebSocketClient) SendMessage(action string, data map[string]interface{}) { | ||||
| 	req := &WebSocketRequest{} | ||||
| 	req.Seq = wsc.Sequence | ||||
| 	req.Action = action | ||||
| 	req.Data = data | ||||
|  | ||||
| 	wsc.Sequence++ | ||||
|  | ||||
| 	wsc.Conn.WriteJSON(req) | ||||
| } | ||||
|  | ||||
| // UserTyping will push a user_typing event out to all connected users | ||||
| // who are in the specified channel | ||||
| func (wsc *WebSocketClient) UserTyping(channelId, parentId string) { | ||||
| 	data := map[string]interface{}{ | ||||
| 		"channel_id": channelId, | ||||
| 		"parent_id":  parentId, | ||||
| 	} | ||||
|  | ||||
| 	wsc.SendMessage("user_typing", data) | ||||
| } | ||||
|  | ||||
| // GetStatuses will return a map of string statuses using user id as the key | ||||
| func (wsc *WebSocketClient) GetStatuses() { | ||||
| 	wsc.SendMessage("get_statuses", nil) | ||||
| } | ||||
							
								
								
									
										116
									
								
								vendor/github.com/mattermost/platform/model/websocket_message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								vendor/github.com/mattermost/platform/model/websocket_message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	WEBSOCKET_EVENT_TYPING             = "typing" | ||||
| 	WEBSOCKET_EVENT_POSTED             = "posted" | ||||
| 	WEBSOCKET_EVENT_POST_EDITED        = "post_edited" | ||||
| 	WEBSOCKET_EVENT_POST_DELETED       = "post_deleted" | ||||
| 	WEBSOCKET_EVENT_CHANNEL_DELETED    = "channel_deleted" | ||||
| 	WEBSOCKET_EVENT_CHANNEL_VIEWED     = "channel_viewed" | ||||
| 	WEBSOCKET_EVENT_DIRECT_ADDED       = "direct_added" | ||||
| 	WEBSOCKET_EVENT_NEW_USER           = "new_user" | ||||
| 	WEBSOCKET_EVENT_LEAVE_TEAM         = "leave_team" | ||||
| 	WEBSOCKET_EVENT_USER_ADDED         = "user_added" | ||||
| 	WEBSOCKET_EVENT_USER_UPDATED       = "user_updated" | ||||
| 	WEBSOCKET_EVENT_USER_REMOVED       = "user_removed" | ||||
| 	WEBSOCKET_EVENT_PREFERENCE_CHANGED = "preference_changed" | ||||
| 	WEBSOCKET_EVENT_EPHEMERAL_MESSAGE  = "ephemeral_message" | ||||
| 	WEBSOCKET_EVENT_STATUS_CHANGE      = "status_change" | ||||
| 	WEBSOCKET_EVENT_HELLO              = "hello" | ||||
| ) | ||||
|  | ||||
| type WebSocketMessage interface { | ||||
| 	ToJson() string | ||||
| 	IsValid() bool | ||||
| } | ||||
|  | ||||
| type WebSocketEvent struct { | ||||
| 	TeamId    string                 `json:"team_id"` | ||||
| 	ChannelId string                 `json:"channel_id"` | ||||
| 	UserId    string                 `json:"user_id"` | ||||
| 	Event     string                 `json:"event"` | ||||
| 	Data      map[string]interface{} `json:"data"` | ||||
| } | ||||
|  | ||||
| func (m *WebSocketEvent) Add(key string, value interface{}) { | ||||
| 	m.Data[key] = value | ||||
| } | ||||
|  | ||||
| func NewWebSocketEvent(teamId string, channelId string, userId string, event string) *WebSocketEvent { | ||||
| 	return &WebSocketEvent{TeamId: teamId, ChannelId: channelId, UserId: userId, Event: event, Data: make(map[string]interface{})} | ||||
| } | ||||
|  | ||||
| func (o *WebSocketEvent) IsValid() bool { | ||||
| 	return o.Event != "" | ||||
| } | ||||
|  | ||||
| func (o *WebSocketEvent) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WebSocketEventFromJson(data io.Reader) *WebSocketEvent { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o WebSocketEvent | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type WebSocketResponse struct { | ||||
| 	Status   string                 `json:"status"` | ||||
| 	SeqReply int64                  `json:"seq_reply,omitempty"` | ||||
| 	Data     map[string]interface{} `json:"data,omitempty"` | ||||
| 	Error    *AppError              `json:"error,omitempty"` | ||||
| } | ||||
|  | ||||
| func (m *WebSocketResponse) Add(key string, value interface{}) { | ||||
| 	m.Data[key] = value | ||||
| } | ||||
|  | ||||
| func NewWebSocketResponse(status string, seqReply int64, data map[string]interface{}) *WebSocketResponse { | ||||
| 	return &WebSocketResponse{Status: status, SeqReply: seqReply, Data: data} | ||||
| } | ||||
|  | ||||
| func NewWebSocketError(seqReply int64, err *AppError) *WebSocketResponse { | ||||
| 	return &WebSocketResponse{Status: STATUS_FAIL, SeqReply: seqReply, Error: err} | ||||
| } | ||||
|  | ||||
| func (o *WebSocketResponse) IsValid() bool { | ||||
| 	return o.Status != "" | ||||
| } | ||||
|  | ||||
| func (o *WebSocketResponse) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o WebSocketResponse | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										43
									
								
								vendor/github.com/mattermost/platform/model/websocket_request.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								vendor/github.com/mattermost/platform/model/websocket_request.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
|  | ||||
| 	goi18n "github.com/nicksnyder/go-i18n/i18n" | ||||
| ) | ||||
|  | ||||
| type WebSocketRequest struct { | ||||
| 	// Client-provided fields | ||||
| 	Seq    int64                  `json:"seq"` | ||||
| 	Action string                 `json:"action"` | ||||
| 	Data   map[string]interface{} `json:"data"` | ||||
|  | ||||
| 	// Server-provided fields | ||||
| 	Session Session              `json:"-"` | ||||
| 	T       goi18n.TranslateFunc `json:"-"` | ||||
| 	Locale  string               `json:"-"` | ||||
| } | ||||
|  | ||||
| func (o *WebSocketRequest) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func WebSocketRequestFromJson(data io.Reader) *WebSocketRequest { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o WebSocketRequest | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| Copyright (c) 2009 The Go Authors. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
|  | ||||
|    * Redistributions of source code must retain the above copyright | ||||
| notice, this list of conditions and the following disclaimer. | ||||
|    * Redistributions in binary form must reproduce the above | ||||
| copyright notice, this list of conditions and the following disclaimer | ||||
| in the documentation and/or other materials provided with the | ||||
| distribution. | ||||
|    * Neither the name of Google Inc. nor the names of its | ||||
| contributors may be used to endorse or promote products derived from | ||||
| this software without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										881
									
								
								vendor/github.com/mattn/go-xmpp/xmpp.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										881
									
								
								vendor/github.com/mattn/go-xmpp/xmpp.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,881 @@ | ||||
| // Copyright 2011 The Go Authors.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // TODO(rsc): | ||||
| //	More precise error handling. | ||||
| //	Presence functionality. | ||||
| // TODO(mattn): | ||||
| //  Add proxy authentication. | ||||
|  | ||||
| // Package xmpp implements a simple Google Talk client | ||||
| // using the XMPP protocol described in RFC 3920 and RFC 3921. | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"crypto/md5" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/tls" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/binary" | ||||
| 	"encoding/xml" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math/big" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	nsStream  = "http://etherx.jabber.org/streams" | ||||
| 	nsTLS     = "urn:ietf:params:xml:ns:xmpp-tls" | ||||
| 	nsSASL    = "urn:ietf:params:xml:ns:xmpp-sasl" | ||||
| 	nsBind    = "urn:ietf:params:xml:ns:xmpp-bind" | ||||
| 	nsClient  = "jabber:client" | ||||
| 	nsSession = "urn:ietf:params:xml:ns:xmpp-session" | ||||
| ) | ||||
|  | ||||
| // Default TLS configuration options | ||||
| var DefaultConfig tls.Config | ||||
|  | ||||
| // Cookie is a unique XMPP session identifier | ||||
| type Cookie uint64 | ||||
|  | ||||
| func getCookie() Cookie { | ||||
| 	var buf [8]byte | ||||
| 	if _, err := rand.Reader.Read(buf[:]); err != nil { | ||||
| 		panic("Failed to read random bytes: " + err.Error()) | ||||
| 	} | ||||
| 	return Cookie(binary.LittleEndian.Uint64(buf[:])) | ||||
| } | ||||
|  | ||||
| // Client holds XMPP connection opitons | ||||
| type Client struct { | ||||
| 	conn   net.Conn // connection to server | ||||
| 	jid    string   // Jabber ID for our connection | ||||
| 	domain string | ||||
| 	p      *xml.Decoder | ||||
| } | ||||
|  | ||||
| func (c *Client) JID() string { | ||||
| 	return c.jid | ||||
| } | ||||
|  | ||||
| func connect(host, user, passwd string) (net.Conn, error) { | ||||
| 	addr := host | ||||
|  | ||||
| 	if strings.TrimSpace(host) == "" { | ||||
| 		a := strings.SplitN(user, "@", 2) | ||||
| 		if len(a) == 2 { | ||||
| 			addr = a[1] | ||||
| 		} | ||||
| 	} | ||||
| 	a := strings.SplitN(host, ":", 2) | ||||
| 	if len(a) == 1 { | ||||
| 		addr += ":5222" | ||||
| 	} | ||||
| 	proxy := os.Getenv("HTTP_PROXY") | ||||
| 	if proxy == "" { | ||||
| 		proxy = os.Getenv("http_proxy") | ||||
| 	} | ||||
| 	if proxy != "" { | ||||
| 		url, err := url.Parse(proxy) | ||||
| 		if err == nil { | ||||
| 			addr = url.Host | ||||
| 		} | ||||
| 	} | ||||
| 	c, err := net.Dial("tcp", addr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if proxy != "" { | ||||
| 		fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\n", host) | ||||
| 		fmt.Fprintf(c, "Host: %s\r\n", host) | ||||
| 		fmt.Fprintf(c, "\r\n") | ||||
| 		br := bufio.NewReader(c) | ||||
| 		req, _ := http.NewRequest("CONNECT", host, nil) | ||||
| 		resp, err := http.ReadResponse(br, req) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if resp.StatusCode != 200 { | ||||
| 			f := strings.SplitN(resp.Status, " ", 2) | ||||
| 			return nil, errors.New(f[1]) | ||||
| 		} | ||||
| 	} | ||||
| 	return c, nil | ||||
| } | ||||
|  | ||||
| // Options are used to specify additional options for new clients, such as a Resource. | ||||
| type Options struct { | ||||
| 	// Host specifies what host to connect to, as either "hostname" or "hostname:port" | ||||
| 	// If host is not specified, the  DNS SRV should be used to find the host from the domainpart of the JID. | ||||
| 	// Default the port to 5222. | ||||
| 	Host string | ||||
|  | ||||
| 	// User specifies what user to authenticate to the remote server. | ||||
| 	User string | ||||
|  | ||||
| 	// Password supplies the password to use for authentication with the remote server. | ||||
| 	Password string | ||||
|  | ||||
| 	// Resource specifies an XMPP client resource, like "bot", instead of accepting one | ||||
| 	// from the server.  Use "" to let the server generate one for your client. | ||||
| 	Resource string | ||||
|  | ||||
| 	// OAuthScope provides go-xmpp the required scope for OAuth2 authentication. | ||||
| 	OAuthScope string | ||||
|  | ||||
| 	// OAuthToken provides go-xmpp with the required OAuth2 token used to authenticate | ||||
| 	OAuthToken string | ||||
|  | ||||
| 	// OAuthXmlNs provides go-xmpp with the required namespaced used for OAuth2 authentication.  This is | ||||
| 	// provided to the server as the xmlns:auth attribute of the OAuth2 authentication request. | ||||
| 	OAuthXmlNs string | ||||
|  | ||||
| 	// TLS Config | ||||
| 	TLSConfig *tls.Config | ||||
|  | ||||
| 	// InsecureAllowUnencryptedAuth permits authentication over a TCP connection that has not been promoted to | ||||
| 	// TLS by STARTTLS; this could leak authentication information over the network, or permit man in the middle | ||||
| 	// attacks. | ||||
| 	InsecureAllowUnencryptedAuth bool | ||||
|  | ||||
| 	// NoTLS directs go-xmpp to not use TLS initially to contact the server; instead, a plain old unencrypted | ||||
| 	// TCP connection should be used. (Can be combined with StartTLS to support STARTTLS-based servers.) | ||||
| 	NoTLS bool | ||||
|  | ||||
| 	// StartTLS directs go-xmpp to STARTTLS if the server supports it; go-xmpp will automatically STARTTLS | ||||
| 	// if the server requires it regardless of this option. | ||||
| 	StartTLS bool | ||||
|  | ||||
| 	// Debug output | ||||
| 	Debug bool | ||||
|  | ||||
| 	// Use server sessions | ||||
| 	Session bool | ||||
|  | ||||
| 	// Presence Status | ||||
| 	Status string | ||||
|  | ||||
| 	// Status message | ||||
| 	StatusMessage string | ||||
| } | ||||
|  | ||||
| // NewClient establishes a new Client connection based on a set of Options. | ||||
| func (o Options) NewClient() (*Client, error) { | ||||
| 	host := o.Host | ||||
| 	c, err := connect(host, o.User, o.Password) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if strings.LastIndex(o.Host, ":") > 0 { | ||||
| 		host = host[:strings.LastIndex(o.Host, ":")] | ||||
| 	} | ||||
|  | ||||
| 	client := new(Client) | ||||
| 	if o.NoTLS { | ||||
| 		client.conn = c | ||||
| 	} else { | ||||
| 		var tlsconn *tls.Conn | ||||
| 		if o.TLSConfig != nil { | ||||
| 			tlsconn = tls.Client(c, o.TLSConfig) | ||||
| 		} else { | ||||
| 			DefaultConfig.ServerName = host | ||||
| 			tlsconn = tls.Client(c, &DefaultConfig) | ||||
| 		} | ||||
| 		if err = tlsconn.Handshake(); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		insecureSkipVerify := DefaultConfig.InsecureSkipVerify | ||||
| 		if o.TLSConfig != nil { | ||||
| 			insecureSkipVerify = o.TLSConfig.InsecureSkipVerify | ||||
| 		} | ||||
| 		if !insecureSkipVerify { | ||||
| 			if err = tlsconn.VerifyHostname(host); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
| 		client.conn = tlsconn | ||||
| 	} | ||||
|  | ||||
| 	if err := client.init(&o); err != nil { | ||||
| 		client.Close() | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return client, nil | ||||
| } | ||||
|  | ||||
| // NewClient creates a new connection to a host given as "hostname" or "hostname:port". | ||||
| // If host is not specified, the  DNS SRV should be used to find the host from the domainpart of the JID. | ||||
| // Default the port to 5222. | ||||
| func NewClient(host, user, passwd string, debug bool) (*Client, error) { | ||||
| 	opts := Options{ | ||||
| 		Host:     host, | ||||
| 		User:     user, | ||||
| 		Password: passwd, | ||||
| 		Debug:    debug, | ||||
| 		Session:  false, | ||||
| 	} | ||||
| 	return opts.NewClient() | ||||
| } | ||||
|  | ||||
| // NewClientNoTLS creates a new client without TLS | ||||
| func NewClientNoTLS(host, user, passwd string, debug bool) (*Client, error) { | ||||
| 	opts := Options{ | ||||
| 		Host:     host, | ||||
| 		User:     user, | ||||
| 		Password: passwd, | ||||
| 		NoTLS:    true, | ||||
| 		Debug:    debug, | ||||
| 		Session:  false, | ||||
| 	} | ||||
| 	return opts.NewClient() | ||||
| } | ||||
|  | ||||
| // Close closes the XMPP connection | ||||
| func (c *Client) Close() error { | ||||
| 	if c.conn != (*tls.Conn)(nil) { | ||||
| 		return c.conn.Close() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, authenticate, digestURI, nonceCountStr string) string { | ||||
| 	h := func(text string) []byte { | ||||
| 		h := md5.New() | ||||
| 		h.Write([]byte(text)) | ||||
| 		return h.Sum(nil) | ||||
| 	} | ||||
| 	hex := func(bytes []byte) string { | ||||
| 		return fmt.Sprintf("%x", bytes) | ||||
| 	} | ||||
| 	kd := func(secret, data string) []byte { | ||||
| 		return h(secret + ":" + data) | ||||
| 	} | ||||
|  | ||||
| 	a1 := string(h(username+":"+realm+":"+passwd)) + ":" + nonce + ":" + cnonceStr | ||||
| 	a2 := authenticate + ":" + digestURI | ||||
| 	response := hex(kd(hex(h(a1)), nonce+":"+nonceCountStr+":"+cnonceStr+":auth:"+hex(h(a2)))) | ||||
| 	return response | ||||
| } | ||||
|  | ||||
| func cnonce() string { | ||||
| 	randSize := big.NewInt(0) | ||||
| 	randSize.Lsh(big.NewInt(1), 64) | ||||
| 	cn, err := rand.Int(rand.Reader, randSize) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return fmt.Sprintf("%016x", cn) | ||||
| } | ||||
|  | ||||
| func (c *Client) init(o *Options) error { | ||||
|  | ||||
| 	var domain string | ||||
| 	var user string | ||||
| 	a := strings.SplitN(o.User, "@", 2) | ||||
| 	if len(o.User) > 0 { | ||||
| 		if len(a) != 2 { | ||||
| 			return errors.New("xmpp: invalid username (want user@domain): " + o.User) | ||||
| 		} | ||||
| 		user = a[0] | ||||
| 		domain = a[1] | ||||
| 	} // Otherwise, we'll be attempting ANONYMOUS | ||||
|  | ||||
| 	// Declare intent to be a jabber client and gather stream features. | ||||
| 	f, err := c.startStream(o, domain) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// If the server requires we STARTTLS, attempt to do so. | ||||
| 	if f, err = c.startTLSIfRequired(f, o, domain); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if o.User == "" && o.Password == "" { | ||||
| 		foundAnonymous := false | ||||
| 		for _, m := range f.Mechanisms.Mechanism { | ||||
| 			if m == "ANONYMOUS" { | ||||
| 				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='ANONYMOUS' />\n", nsSASL) | ||||
| 				foundAnonymous = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if !foundAnonymous { | ||||
| 			return fmt.Errorf("ANONYMOUS authentication is not an option and username and password were not specified") | ||||
| 		} | ||||
| 	} else { | ||||
| 		// Even digest forms of authentication are unsafe if we do not know that the host | ||||
| 		// we are talking to is the actual server, and not a man in the middle playing | ||||
| 		// proxy. | ||||
| 		if !c.IsEncrypted() && !o.InsecureAllowUnencryptedAuth { | ||||
| 			return errors.New("refusing to authenticate over unencrypted TCP connection") | ||||
| 		} | ||||
|  | ||||
| 		mechanism := "" | ||||
| 		for _, m := range f.Mechanisms.Mechanism { | ||||
| 			if m == "X-OAUTH2" && o.OAuthToken != "" && o.OAuthScope != "" { | ||||
| 				mechanism = m | ||||
| 				// Oauth authentication: send base64-encoded \x00 user \x00 token. | ||||
| 				raw := "\x00" + user + "\x00" + o.OAuthToken | ||||
| 				enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw))) | ||||
| 				base64.StdEncoding.Encode(enc, []byte(raw)) | ||||
| 				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='X-OAUTH2' auth:service='oauth2' "+ | ||||
| 					"xmlns:auth='%s'>%s</auth>\n", nsSASL, o.OAuthXmlNs, enc) | ||||
| 				break | ||||
| 			} | ||||
| 			if m == "PLAIN" { | ||||
| 				mechanism = m | ||||
| 				// Plain authentication: send base64-encoded \x00 user \x00 password. | ||||
| 				raw := "\x00" + user + "\x00" + o.Password | ||||
| 				enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw))) | ||||
| 				base64.StdEncoding.Encode(enc, []byte(raw)) | ||||
| 				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>\n", nsSASL, enc) | ||||
| 				break | ||||
| 			} | ||||
| 			if m == "DIGEST-MD5" { | ||||
| 				mechanism = m | ||||
| 				// Digest-MD5 authentication | ||||
| 				fmt.Fprintf(c.conn, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", nsSASL) | ||||
| 				var ch saslChallenge | ||||
| 				if err = c.p.DecodeElement(&ch, nil); err != nil { | ||||
| 					return errors.New("unmarshal <challenge>: " + err.Error()) | ||||
| 				} | ||||
| 				b, err := base64.StdEncoding.DecodeString(string(ch)) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				tokens := map[string]string{} | ||||
| 				for _, token := range strings.Split(string(b), ",") { | ||||
| 					kv := strings.SplitN(strings.TrimSpace(token), "=", 2) | ||||
| 					if len(kv) == 2 { | ||||
| 						if kv[1][0] == '"' && kv[1][len(kv[1])-1] == '"' { | ||||
| 							kv[1] = kv[1][1 : len(kv[1])-1] | ||||
| 						} | ||||
| 						tokens[kv[0]] = kv[1] | ||||
| 					} | ||||
| 				} | ||||
| 				realm, _ := tokens["realm"] | ||||
| 				nonce, _ := tokens["nonce"] | ||||
| 				qop, _ := tokens["qop"] | ||||
| 				charset, _ := tokens["charset"] | ||||
| 				cnonceStr := cnonce() | ||||
| 				digestURI := "xmpp/" + domain | ||||
| 				nonceCount := fmt.Sprintf("%08x", 1) | ||||
| 				digest := saslDigestResponse(user, realm, o.Password, nonce, cnonceStr, "AUTHENTICATE", digestURI, nonceCount) | ||||
| 				message := "username=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr + | ||||
| 					"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset | ||||
|  | ||||
| 				fmt.Fprintf(c.conn, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message))) | ||||
|  | ||||
| 				var rspauth saslRspAuth | ||||
| 				if err = c.p.DecodeElement(&rspauth, nil); err != nil { | ||||
| 					return errors.New("unmarshal <challenge>: " + err.Error()) | ||||
| 				} | ||||
| 				b, err = base64.StdEncoding.DecodeString(string(rspauth)) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				fmt.Fprintf(c.conn, "<response xmlns='%s'/>\n", nsSASL) | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if mechanism == "" { | ||||
| 			return fmt.Errorf("PLAIN authentication is not an option: %v", f.Mechanisms.Mechanism) | ||||
| 		} | ||||
| 	} | ||||
| 	// Next message should be either success or failure. | ||||
| 	name, val, err := next(c.p) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	switch v := val.(type) { | ||||
| 	case *saslSuccess: | ||||
| 	case *saslFailure: | ||||
| 		// v.Any is type of sub-element in failure, | ||||
| 		// which gives a description of what failed. | ||||
| 		return errors.New("auth failure: " + v.Any.Local) | ||||
| 	default: | ||||
| 		return errors.New("expected <success> or <failure>, got <" + name.Local + "> in " + name.Space) | ||||
| 	} | ||||
|  | ||||
| 	// Now that we're authenticated, we're supposed to start the stream over again. | ||||
| 	// Declare intent to be a jabber client. | ||||
| 	if f, err = c.startStream(o, domain); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Generate a unique cookie | ||||
| 	cookie := getCookie() | ||||
|  | ||||
| 	// Send IQ message asking to bind to the local user name. | ||||
| 	if o.Resource == "" { | ||||
| 		fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'></bind></iq>\n", cookie, nsBind) | ||||
| 	} else { | ||||
| 		fmt.Fprintf(c.conn, "<iq type='set' id='%x'><bind xmlns='%s'><resource>%s</resource></bind></iq>\n", cookie, nsBind, o.Resource) | ||||
| 	} | ||||
| 	var iq clientIQ | ||||
| 	if err = c.p.DecodeElement(&iq, nil); err != nil { | ||||
| 		return errors.New("unmarshal <iq>: " + err.Error()) | ||||
| 	} | ||||
| 	if &iq.Bind == nil { | ||||
| 		return errors.New("<iq> result missing <bind>") | ||||
| 	} | ||||
| 	c.jid = iq.Bind.Jid // our local id | ||||
| 	c.domain = domain | ||||
|  | ||||
| 	if o.Session { | ||||
| 		//if server support session, open it | ||||
| 		fmt.Fprintf(c.conn, "<iq to='%s' type='set' id='%x'><session xmlns='%s'/></iq>", xmlEscape(domain), cookie, nsSession) | ||||
| 	} | ||||
|  | ||||
| 	// We're connected and can now receive and send messages. | ||||
| 	fmt.Fprintf(c.conn, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", o.Status, o.StatusMessage) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // startTlsIfRequired examines the server's stream features and, if STARTTLS is required or supported, performs the TLS handshake. | ||||
| // f will be updated if the handshake completes, as the new stream's features are typically different from the original. | ||||
| func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string) (*streamFeatures, error) { | ||||
| 	// whether we start tls is a matter of opinion: the server's and the user's. | ||||
| 	switch { | ||||
| 	case f.StartTLS == nil: | ||||
| 		// the server does not support STARTTLS | ||||
| 		return f, nil | ||||
| 	case f.StartTLS.Required != nil: | ||||
| 		// the server requires STARTTLS. | ||||
| 	case !o.StartTLS: | ||||
| 		// the user wants STARTTLS and the server supports it. | ||||
| 	} | ||||
| 	var err error | ||||
|  | ||||
| 	fmt.Fprintf(c.conn, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n") | ||||
| 	var k tlsProceed | ||||
| 	if err = c.p.DecodeElement(&k, nil); err != nil { | ||||
| 		return f, errors.New("unmarshal <proceed>: " + err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	tc := o.TLSConfig | ||||
| 	if tc == nil { | ||||
| 		tc = new(tls.Config) | ||||
| 		*tc = DefaultConfig | ||||
| 		//TODO(scott): we should consider using the server's address or reverse lookup | ||||
| 		tc.ServerName = domain | ||||
| 	} | ||||
| 	t := tls.Client(c.conn, tc) | ||||
|  | ||||
| 	if err = t.Handshake(); err != nil { | ||||
| 		return f, errors.New("starttls handshake: " + err.Error()) | ||||
| 	} | ||||
| 	c.conn = t | ||||
|  | ||||
| 	// restart our declaration of XMPP stream intentions. | ||||
| 	tf, err := c.startStream(o, domain) | ||||
| 	if err != nil { | ||||
| 		return f, err | ||||
| 	} | ||||
| 	return tf, nil | ||||
| } | ||||
|  | ||||
| // startStream will start a new XML decoder for the connection, signal the start of a stream to the server and verify that the server has | ||||
| // also started the stream; if o.Debug is true, startStream will tee decoded XML data to stderr.  The features advertised by the server | ||||
| // will be returned. | ||||
| func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) { | ||||
| 	if o.Debug { | ||||
| 		c.p = xml.NewDecoder(tee{c.conn, os.Stderr}) | ||||
| 	} else { | ||||
| 		c.p = xml.NewDecoder(c.conn) | ||||
| 	} | ||||
|  | ||||
| 	_, err := fmt.Fprintf(c.conn, "<?xml version='1.0'?>\n"+ | ||||
| 		"<stream:stream to='%s' xmlns='%s'\n"+ | ||||
| 		" xmlns:stream='%s' version='1.0'>\n", | ||||
| 		xmlEscape(domain), nsClient, nsStream) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// We expect the server to start a <stream>. | ||||
| 	se, err := nextStart(c.p) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if se.Name.Space != nsStream || se.Name.Local != "stream" { | ||||
| 		return nil, fmt.Errorf("expected <stream> but got <%v> in %v", se.Name.Local, se.Name.Space) | ||||
| 	} | ||||
|  | ||||
| 	// Now we're in the stream and can use Unmarshal. | ||||
| 	// Next message should be <features> to tell us authentication options. | ||||
| 	// See section 4.6 in RFC 3920. | ||||
| 	f := new(streamFeatures) | ||||
| 	if err = c.p.DecodeElement(f, nil); err != nil { | ||||
| 		return f, errors.New("unmarshal <features>: " + err.Error()) | ||||
| 	} | ||||
| 	return f, nil | ||||
| } | ||||
|  | ||||
| // IsEncrypted will return true if the client is connected using a TLS transport, either because it used. | ||||
| // TLS to connect from the outset, or because it successfully used STARTTLS to promote a TCP connection to TLS. | ||||
| func (c *Client) IsEncrypted() bool { | ||||
| 	_, ok := c.conn.(*tls.Conn) | ||||
| 	return ok | ||||
| } | ||||
|  | ||||
| // Chat is an incoming or outgoing XMPP chat message. | ||||
| type Chat struct { | ||||
| 	Remote string | ||||
| 	Type   string | ||||
| 	Text   string | ||||
| 	Roster Roster | ||||
| 	Other  []string | ||||
| 	Stamp  time.Time | ||||
| } | ||||
|  | ||||
| type Roster []Contact | ||||
|  | ||||
| type Contact struct { | ||||
| 	Remote string | ||||
| 	Name   string | ||||
| 	Group  []string | ||||
| } | ||||
|  | ||||
| // Presence is an XMPP presence notification. | ||||
| type Presence struct { | ||||
| 	From   string | ||||
| 	To     string | ||||
| 	Type   string | ||||
| 	Show   string | ||||
| 	Status string | ||||
| } | ||||
|  | ||||
| type IQ struct { | ||||
| 	ID    string | ||||
| 	From  string | ||||
| 	To    string | ||||
| 	Type  string | ||||
| 	Query []byte | ||||
| } | ||||
|  | ||||
| // Recv waits to receive the next XMPP stanza. | ||||
| // Return type is either a presence notification or a chat message. | ||||
| func (c *Client) Recv() (stanza interface{}, err error) { | ||||
| 	for { | ||||
| 		_, val, err := next(c.p) | ||||
| 		if err != nil { | ||||
| 			return Chat{}, err | ||||
| 		} | ||||
| 		switch v := val.(type) { | ||||
| 		case *clientMessage: | ||||
| 			stamp, _ := time.Parse( | ||||
| 				"2006-01-02T15:04:05Z", | ||||
| 				v.Delay.Stamp, | ||||
| 			) | ||||
| 			chat := Chat{ | ||||
| 				Remote: v.From, | ||||
| 				Type:   v.Type, | ||||
| 				Text:   v.Body, | ||||
| 				Other:  v.Other, | ||||
| 				Stamp:  stamp, | ||||
| 			} | ||||
| 			return chat, nil | ||||
| 		case *clientQuery: | ||||
| 			var r Roster | ||||
| 			for _, item := range v.Item { | ||||
| 				r = append(r, Contact{item.Jid, item.Name, item.Group}) | ||||
| 			} | ||||
| 			return Chat{Type: "roster", Roster: r}, nil | ||||
| 		case *clientPresence: | ||||
| 			return Presence{v.From, v.To, v.Type, v.Show, v.Status}, nil | ||||
| 		case *clientIQ: | ||||
| 			return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Send sends the message wrapped inside an XMPP message stanza body. | ||||
| func (c *Client) Send(chat Chat) (n int, err error) { | ||||
| 	return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<body>%s</body></message>", | ||||
| 		xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text)) | ||||
| } | ||||
|  | ||||
| // SendOrg sends the original text without being wrapped in an XMPP message stanza. | ||||
| func (c *Client) SendOrg(org string) (n int, err error) { | ||||
| 	return fmt.Fprint(c.conn, org) | ||||
| } | ||||
|  | ||||
| func (c *Client) SendPresence(presence Presence) (n int, err error) { | ||||
| 	return fmt.Fprintf(c.conn, "<presence from='%s' to='%s'/>", xmlEscape(presence.From), xmlEscape(presence.To)) | ||||
| } | ||||
|  | ||||
| // SendHtml sends the message as HTML as defined by XEP-0071 | ||||
| func (c *Client) SendHtml(chat Chat) (n int, err error) { | ||||
| 	return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+ | ||||
| 		"<body>%s</body>"+ | ||||
| 		"<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html></message>", | ||||
| 		xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text), chat.Text) | ||||
| } | ||||
|  | ||||
| // Roster asks for the chat roster. | ||||
| func (c *Client) Roster() error { | ||||
| 	fmt.Fprintf(c.conn, "<iq from='%s' type='get' id='roster1'><query xmlns='jabber:iq:roster'/></iq>\n", xmlEscape(c.jid)) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // RFC 3920  C.1  Streams name space | ||||
| type streamFeatures struct { | ||||
| 	XMLName    xml.Name `xml:"http://etherx.jabber.org/streams features"` | ||||
| 	StartTLS   *tlsStartTLS | ||||
| 	Mechanisms saslMechanisms | ||||
| 	Bind       bindBind | ||||
| 	Session    bool | ||||
| } | ||||
|  | ||||
| type streamError struct { | ||||
| 	XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"` | ||||
| 	Any     xml.Name | ||||
| 	Text    string | ||||
| } | ||||
|  | ||||
| // RFC 3920  C.3  TLS name space | ||||
| type tlsStartTLS struct { | ||||
| 	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"` | ||||
| 	Required *string  `xml:"required"` | ||||
| } | ||||
|  | ||||
| type tlsProceed struct { | ||||
| 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"` | ||||
| } | ||||
|  | ||||
| type tlsFailure struct { | ||||
| 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls failure"` | ||||
| } | ||||
|  | ||||
| // RFC 3920  C.4  SASL name space | ||||
| type saslMechanisms struct { | ||||
| 	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"` | ||||
| 	Mechanism []string `xml:"mechanism"` | ||||
| } | ||||
|  | ||||
| type saslAuth struct { | ||||
| 	XMLName   xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"` | ||||
| 	Mechanism string   `xml:",attr"` | ||||
| } | ||||
|  | ||||
| type saslChallenge string | ||||
|  | ||||
| type saslRspAuth string | ||||
|  | ||||
| type saslResponse string | ||||
|  | ||||
| type saslAbort struct { | ||||
| 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl abort"` | ||||
| } | ||||
|  | ||||
| type saslSuccess struct { | ||||
| 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"` | ||||
| } | ||||
|  | ||||
| type saslFailure struct { | ||||
| 	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"` | ||||
| 	Any     xml.Name `xml:",any"` | ||||
| } | ||||
|  | ||||
| // RFC 3920  C.5  Resource binding name space | ||||
| type bindBind struct { | ||||
| 	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"` | ||||
| 	Resource string | ||||
| 	Jid      string `xml:"jid"` | ||||
| } | ||||
|  | ||||
| // RFC 3921  B.1  jabber:client | ||||
| type clientMessage struct { | ||||
| 	XMLName xml.Name `xml:"jabber:client message"` | ||||
| 	From    string   `xml:"from,attr"` | ||||
| 	ID      string   `xml:"id,attr"` | ||||
| 	To      string   `xml:"to,attr"` | ||||
| 	Type    string   `xml:"type,attr"` // chat, error, groupchat, headline, or normal | ||||
|  | ||||
| 	// These should technically be []clientText, but string is much more convenient. | ||||
| 	Subject string `xml:"subject"` | ||||
| 	Body    string `xml:"body"` | ||||
| 	Thread  string `xml:"thread"` | ||||
|  | ||||
| 	// Any hasn't matched element | ||||
| 	Other []string `xml:",any"` | ||||
|  | ||||
| 	Delay Delay `xml:"delay"` | ||||
| } | ||||
|  | ||||
| type Delay struct { | ||||
| 	Stamp string `xml:"stamp,attr"` | ||||
| } | ||||
|  | ||||
| type clientText struct { | ||||
| 	Lang string `xml:",attr"` | ||||
| 	Body string `xml:"chardata"` | ||||
| } | ||||
|  | ||||
| type clientPresence struct { | ||||
| 	XMLName xml.Name `xml:"jabber:client presence"` | ||||
| 	From    string   `xml:"from,attr"` | ||||
| 	ID      string   `xml:"id,attr"` | ||||
| 	To      string   `xml:"to,attr"` | ||||
| 	Type    string   `xml:"type,attr"` // error, probe, subscribe, subscribed, unavailable, unsubscribe, unsubscribed | ||||
| 	Lang    string   `xml:"lang,attr"` | ||||
|  | ||||
| 	Show     string `xml:"show"`   // away, chat, dnd, xa | ||||
| 	Status   string `xml:"status"` // sb []clientText | ||||
| 	Priority string `xml:"priority,attr"` | ||||
| 	Error    *clientError | ||||
| } | ||||
|  | ||||
| type clientIQ struct { // info/query | ||||
| 	XMLName xml.Name `xml:"jabber:client iq"` | ||||
| 	From    string   `xml:"from,attr"` | ||||
| 	ID      string   `xml:"id,attr"` | ||||
| 	To      string   `xml:"to,attr"` | ||||
| 	Type    string   `xml:"type,attr"` // error, get, result, set | ||||
| 	Query   []byte   `xml:",innerxml"` | ||||
| 	Error   clientError | ||||
| 	Bind    bindBind | ||||
| } | ||||
|  | ||||
| type clientError struct { | ||||
| 	XMLName xml.Name `xml:"jabber:client error"` | ||||
| 	Code    string   `xml:",attr"` | ||||
| 	Type    string   `xml:",attr"` | ||||
| 	Any     xml.Name | ||||
| 	Text    string | ||||
| } | ||||
|  | ||||
| type clientQuery struct { | ||||
| 	Item []rosterItem | ||||
| } | ||||
|  | ||||
| type rosterItem struct { | ||||
| 	XMLName      xml.Name `xml:"jabber:iq:roster item"` | ||||
| 	Jid          string   `xml:",attr"` | ||||
| 	Name         string   `xml:",attr"` | ||||
| 	Subscription string   `xml:",attr"` | ||||
| 	Group        []string | ||||
| } | ||||
|  | ||||
| // Scan XML token stream to find next StartElement. | ||||
| func nextStart(p *xml.Decoder) (xml.StartElement, error) { | ||||
| 	for { | ||||
| 		t, err := p.Token() | ||||
| 		if err != nil && err != io.EOF || t == nil { | ||||
| 			return xml.StartElement{}, err | ||||
| 		} | ||||
| 		switch t := t.(type) { | ||||
| 		case xml.StartElement: | ||||
| 			return t, nil | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Scan XML token stream for next element and save into val. | ||||
| // If val == nil, allocate new element based on proto map. | ||||
| // Either way, return val. | ||||
| func next(p *xml.Decoder) (xml.Name, interface{}, error) { | ||||
| 	// Read start element to find out what type we want. | ||||
| 	se, err := nextStart(p) | ||||
| 	if err != nil { | ||||
| 		return xml.Name{}, nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Put it in an interface and allocate one. | ||||
| 	var nv interface{} | ||||
| 	switch se.Name.Space + " " + se.Name.Local { | ||||
| 	case nsStream + " features": | ||||
| 		nv = &streamFeatures{} | ||||
| 	case nsStream + " error": | ||||
| 		nv = &streamError{} | ||||
| 	case nsTLS + " starttls": | ||||
| 		nv = &tlsStartTLS{} | ||||
| 	case nsTLS + " proceed": | ||||
| 		nv = &tlsProceed{} | ||||
| 	case nsTLS + " failure": | ||||
| 		nv = &tlsFailure{} | ||||
| 	case nsSASL + " mechanisms": | ||||
| 		nv = &saslMechanisms{} | ||||
| 	case nsSASL + " challenge": | ||||
| 		nv = "" | ||||
| 	case nsSASL + " response": | ||||
| 		nv = "" | ||||
| 	case nsSASL + " abort": | ||||
| 		nv = &saslAbort{} | ||||
| 	case nsSASL + " success": | ||||
| 		nv = &saslSuccess{} | ||||
| 	case nsSASL + " failure": | ||||
| 		nv = &saslFailure{} | ||||
| 	case nsBind + " bind": | ||||
| 		nv = &bindBind{} | ||||
| 	case nsClient + " message": | ||||
| 		nv = &clientMessage{} | ||||
| 	case nsClient + " presence": | ||||
| 		nv = &clientPresence{} | ||||
| 	case nsClient + " iq": | ||||
| 		nv = &clientIQ{} | ||||
| 	case nsClient + " error": | ||||
| 		nv = &clientError{} | ||||
| 	default: | ||||
| 		return xml.Name{}, nil, errors.New("unexpected XMPP message " + | ||||
| 			se.Name.Space + " <" + se.Name.Local + "/>") | ||||
| 	} | ||||
|  | ||||
| 	// Unmarshal into that storage. | ||||
| 	if err = p.DecodeElement(nv, &se); err != nil { | ||||
| 		return xml.Name{}, nil, err | ||||
| 	} | ||||
|  | ||||
| 	return se.Name, nv, err | ||||
| } | ||||
|  | ||||
| var xmlSpecial = map[byte]string{ | ||||
| 	'<':  "<", | ||||
| 	'>':  ">", | ||||
| 	'"':  """, | ||||
| 	'\'': "'", | ||||
| 	'&':  "&", | ||||
| } | ||||
|  | ||||
| func xmlEscape(s string) string { | ||||
| 	var b bytes.Buffer | ||||
| 	for i := 0; i < len(s); i++ { | ||||
| 		c := s[i] | ||||
| 		if s, ok := xmlSpecial[c]; ok { | ||||
| 			b.WriteString(s) | ||||
| 		} else { | ||||
| 			b.WriteByte(c) | ||||
| 		} | ||||
| 	} | ||||
| 	return b.String() | ||||
| } | ||||
|  | ||||
| type tee struct { | ||||
| 	r io.Reader | ||||
| 	w io.Writer | ||||
| } | ||||
|  | ||||
| func (t tee) Read(p []byte) (n int, err error) { | ||||
| 	n, err = t.r.Read(p) | ||||
| 	if n > 0 { | ||||
| 		t.w.Write(p[0:n]) | ||||
| 		t.w.Write([]byte("\n")) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										24
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_information_query.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_information_query.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| const IQTypeGet = "get" | ||||
| const IQTypeSet = "set" | ||||
| const IQTypeResult = "result" | ||||
|  | ||||
| func (c *Client) Discovery() (string, error) { | ||||
| 	const namespace = "http://jabber.org/protocol/disco#items" | ||||
| 	// use getCookie for a pseudo random id. | ||||
| 	reqID := strconv.FormatUint(uint64(getCookie()), 10) | ||||
| 	return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, namespace, "") | ||||
| } | ||||
|  | ||||
| // RawInformationQuery sends an information query request to the server. | ||||
| func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) { | ||||
| 	const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>" | ||||
| 	_, err := fmt.Fprintf(c.conn, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body) | ||||
| 	return id, err | ||||
| } | ||||
							
								
								
									
										134
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_muc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_muc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| // Copyright 2013 Flo Lauber <dev@qatfy.at>.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // TODO(flo): | ||||
| //   - support password protected MUC rooms | ||||
| //   - cleanup signatures of join/leave functions | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| 	"errors" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	nsMUC     = "http://jabber.org/protocol/muc" | ||||
| 	nsMUCUser = "http://jabber.org/protocol/muc#user" | ||||
| 	NoHistory = 0 | ||||
| 	CharHistory = 1 | ||||
| 	StanzaHistory = 2 | ||||
| 	SecondsHistory = 3 | ||||
| 	SinceHistory = 4 | ||||
| ) | ||||
|  | ||||
| // Send sends room topic wrapped inside an XMPP message stanza body. | ||||
| func (c *Client) SendTopic(chat Chat) (n int, err error) { | ||||
| 	return fmt.Fprintf(c.conn, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>", | ||||
| 		xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text)) | ||||
| } | ||||
|  | ||||
| func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) { | ||||
| 	if nick == "" { | ||||
| 		nick = c.jid | ||||
| 	} | ||||
| 	return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n"+ | ||||
| 		"<x xmlns='%s'>"+ | ||||
| 		"<history maxchars='0'/></x>\n"+ | ||||
| 		"</presence>", | ||||
| 		xmlEscape(jid), xmlEscape(nick), nsMUC) | ||||
| } | ||||
|  | ||||
| // xep-0045 7.2 | ||||
| func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_date *time.Time) (n int, err error) { | ||||
| 	if nick == "" { | ||||
| 		nick = c.jid | ||||
| 	} | ||||
| 	switch history_type { | ||||
| 	case NoHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s' />\n" + | ||||
| 			"</presence>", | ||||
| 				xmlEscape(jid), xmlEscape(nick), nsMUC) | ||||
| 	case CharHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<history maxchars='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 				xmlEscape(jid), xmlEscape(nick), nsMUC, history) | ||||
| 	case StanzaHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<history maxstanzas='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 				xmlEscape(jid), xmlEscape(nick), nsMUC, history) | ||||
| 	case SecondsHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<history seconds='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 				xmlEscape(jid), xmlEscape(nick), nsMUC, history) | ||||
| 	case SinceHistory: | ||||
| 		if history_date != nil { | ||||
| 			return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 				"<x xmlns='%s'>\n" + | ||||
| 				"<history since='%s'/></x>\n" + | ||||
| 				"</presence>", | ||||
| 					xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339)) | ||||
| 		} | ||||
| 	} | ||||
| 	return 0, errors.New("Unknown history option") | ||||
| } | ||||
|  | ||||
| // xep-0045 7.2.6 | ||||
| func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_type, history int, history_date *time.Time) (n int, err error) { | ||||
| 	if nick == "" { | ||||
| 		nick = c.jid | ||||
| 	} | ||||
| 	switch history_type { | ||||
| 	case NoHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<password>%s</password>\n"+ | ||||
| 			"</presence>", | ||||
| 				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password)) | ||||
| 	case CharHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<password>%s</password>\n"+ | ||||
| 			"<history maxchars='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) | ||||
| 	case StanzaHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<password>%s</password>\n"+ | ||||
| 			"<history maxstanzas='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) | ||||
| 	case SecondsHistory: | ||||
| 		return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 			"<x xmlns='%s'>\n" + | ||||
| 			"<password>%s</password>\n"+ | ||||
| 			"<history seconds='%d'/></x>\n"+ | ||||
| 			"</presence>", | ||||
| 				xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history) | ||||
| 	case SinceHistory: | ||||
| 		if history_date != nil { | ||||
| 			return fmt.Fprintf(c.conn, "<presence to='%s/%s'>\n" + | ||||
| 				"<x xmlns='%s'>\n" + | ||||
| 				"<password>%s</password>\n"+ | ||||
| 				"<history since='%s'/></x>\n" + | ||||
| 				"</presence>", | ||||
| 					xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339)) | ||||
| 		} | ||||
| 	} | ||||
| 	return 0, errors.New("Unknown history option") | ||||
| } | ||||
|  | ||||
| // xep-0045 7.14 | ||||
| func (c *Client) LeaveMUC(jid string) (n int, err error) { | ||||
| 	return fmt.Fprintf(c.conn, "<presence from='%s' to='%s' type='unavailable' />", | ||||
| 		c.jid, xmlEscape(jid)) | ||||
| } | ||||
							
								
								
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_ping.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_ping.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| func (c *Client) PingC2S(jid, server string) error { | ||||
| 	if jid == "" { | ||||
| 		jid = c.jid | ||||
| 	} | ||||
| 	if server == "" { | ||||
| 		server = c.domain | ||||
| 	} | ||||
| 	_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+ | ||||
| 		"<ping xmlns='urn:xmpp:ping'/>\n"+ | ||||
| 		"</iq>", | ||||
| 		xmlEscape(jid), xmlEscape(server)) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *Client) PingS2S(fromServer, toServer string) error { | ||||
| 	_, err := fmt.Fprintf(c.conn, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+ | ||||
| 		"<ping xmlns='urn:xmpp:ping'/>\n"+ | ||||
| 		"</iq>", | ||||
| 		xmlEscape(fromServer), xmlEscape(toServer)) | ||||
| 	return err | ||||
| } | ||||
							
								
								
									
										20
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_subscription.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_subscription.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| package xmpp | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| func (c *Client) ApproveSubscription(jid string) { | ||||
| 	fmt.Fprintf(c.conn, "<presence to='%s' type='subscribed'/>", | ||||
| 		xmlEscape(jid)) | ||||
| } | ||||
|  | ||||
| func (c *Client) RevokeSubscription(jid string) { | ||||
| 	fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribed'/>", | ||||
| 		xmlEscape(jid)) | ||||
| } | ||||
|  | ||||
| func (c *Client) RequestSubscription(jid string) { | ||||
| 	fmt.Fprintf(c.conn, "<presence to='%s' type='subscribe'/>", | ||||
| 		xmlEscape(jid)) | ||||
| } | ||||
							
								
								
									
										21
									
								
								vendor/github.com/mreiferson/go-httpclient/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/mreiferson/go-httpclient/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2012 Matt Reiferson | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
							
								
								
									
										237
									
								
								vendor/github.com/mreiferson/go-httpclient/httpclient.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								vendor/github.com/mreiferson/go-httpclient/httpclient.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,237 @@ | ||||
| /* | ||||
| Provides an HTTP Transport that implements the `RoundTripper` interface and | ||||
| can be used as a built in replacement for the standard library's, providing: | ||||
|  | ||||
| 	* connection timeouts | ||||
| 	* request timeouts | ||||
|  | ||||
| This is a thin wrapper around `http.Transport` that sets dial timeouts and uses | ||||
| Go's internal timer scheduler to call the Go 1.1+ `CancelRequest()` API. | ||||
| */ | ||||
| package httpclient | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // returns the current version of the package | ||||
| func Version() string { | ||||
| 	return "0.4.1" | ||||
| } | ||||
|  | ||||
| // Transport implements the RoundTripper interface and can be used as a replacement | ||||
| // for Go's built in http.Transport implementing end-to-end request timeouts. | ||||
| // | ||||
| // 	transport := &httpclient.Transport{ | ||||
| // 	    ConnectTimeout: 1*time.Second, | ||||
| // 	    ResponseHeaderTimeout: 5*time.Second, | ||||
| // 	    RequestTimeout: 10*time.Second, | ||||
| // 	} | ||||
| // 	defer transport.Close() | ||||
| // | ||||
| // 	client := &http.Client{Transport: transport} | ||||
| // 	req, _ := http.NewRequest("GET", "http://127.0.0.1/test", nil) | ||||
| // 	resp, err := client.Do(req) | ||||
| // 	if err != nil { | ||||
| // 	    return err | ||||
| // 	} | ||||
| // 	defer resp.Body.Close() | ||||
| // | ||||
| type Transport struct { | ||||
| 	// Proxy specifies a function to return a proxy for a given | ||||
| 	// *http.Request. If the function returns a non-nil error, the | ||||
| 	// request is aborted with the provided error. | ||||
| 	// If Proxy is nil or returns a nil *url.URL, no proxy is used. | ||||
| 	Proxy func(*http.Request) (*url.URL, error) | ||||
|  | ||||
| 	// Dial specifies the dial function for creating TCP | ||||
| 	// connections. This will override the Transport's ConnectTimeout and | ||||
| 	// ReadWriteTimeout settings. | ||||
| 	// If Dial is nil, a dialer is generated on demand matching the Transport's | ||||
| 	// options. | ||||
| 	Dial func(network, addr string) (net.Conn, error) | ||||
|  | ||||
| 	// TLSClientConfig specifies the TLS configuration to use with | ||||
| 	// tls.Client. If nil, the default configuration is used. | ||||
| 	TLSClientConfig *tls.Config | ||||
|  | ||||
| 	// DisableKeepAlives, if true, prevents re-use of TCP connections | ||||
| 	// between different HTTP requests. | ||||
| 	DisableKeepAlives bool | ||||
|  | ||||
| 	// DisableCompression, if true, prevents the Transport from | ||||
| 	// requesting compression with an "Accept-Encoding: gzip" | ||||
| 	// request header when the Request contains no existing | ||||
| 	// Accept-Encoding value. If the Transport requests gzip on | ||||
| 	// its own and gets a gzipped response, it's transparently | ||||
| 	// decoded in the Response.Body. However, if the user | ||||
| 	// explicitly requested gzip it is not automatically | ||||
| 	// uncompressed. | ||||
| 	DisableCompression bool | ||||
|  | ||||
| 	// MaxIdleConnsPerHost, if non-zero, controls the maximum idle | ||||
| 	// (keep-alive) to keep per-host.  If zero, | ||||
| 	// http.DefaultMaxIdleConnsPerHost is used. | ||||
| 	MaxIdleConnsPerHost int | ||||
|  | ||||
| 	// ConnectTimeout, if non-zero, is the maximum amount of time a dial will wait for | ||||
| 	// a connect to complete. | ||||
| 	ConnectTimeout time.Duration | ||||
|  | ||||
| 	// ResponseHeaderTimeout, if non-zero, specifies the amount of | ||||
| 	// time to wait for a server's response headers after fully | ||||
| 	// writing the request (including its body, if any). This | ||||
| 	// time does not include the time to read the response body. | ||||
| 	ResponseHeaderTimeout time.Duration | ||||
|  | ||||
| 	// RequestTimeout, if non-zero, specifies the amount of time for the entire | ||||
| 	// request to complete (including all of the above timeouts + entire response body). | ||||
| 	// This should never be less than the sum total of the above two timeouts. | ||||
| 	RequestTimeout time.Duration | ||||
|  | ||||
| 	// ReadWriteTimeout, if non-zero, will set a deadline for every Read and | ||||
| 	// Write operation on the request connection. | ||||
| 	ReadWriteTimeout time.Duration | ||||
|  | ||||
| 	// TCPWriteBufferSize, the size of the operating system's write | ||||
| 	// buffer associated with the connection. | ||||
| 	TCPWriteBufferSize int | ||||
|  | ||||
| 	// TCPReadBuffserSize, the size of the operating system's read | ||||
| 	// buffer associated with the connection. | ||||
| 	TCPReadBufferSize int | ||||
|  | ||||
| 	starter   sync.Once | ||||
| 	transport *http.Transport | ||||
| } | ||||
|  | ||||
| // Close cleans up the Transport, currently a no-op | ||||
| func (t *Transport) Close() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (t *Transport) lazyStart() { | ||||
| 	if t.Dial == nil { | ||||
| 		t.Dial = func(netw, addr string) (net.Conn, error) { | ||||
| 			c, err := net.DialTimeout(netw, addr, t.ConnectTimeout) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
|  | ||||
| 			if t.TCPReadBufferSize != 0 || t.TCPWriteBufferSize != 0 { | ||||
| 				if tcpCon, ok := c.(*net.TCPConn); ok { | ||||
| 					if t.TCPWriteBufferSize != 0 { | ||||
| 						if err = tcpCon.SetWriteBuffer(t.TCPWriteBufferSize); err != nil { | ||||
| 							return nil, err | ||||
| 						} | ||||
| 					} | ||||
| 					if t.TCPReadBufferSize != 0 { | ||||
| 						if err = tcpCon.SetReadBuffer(t.TCPReadBufferSize); err != nil { | ||||
| 							return nil, err | ||||
| 						} | ||||
| 					} | ||||
| 				} else { | ||||
| 					err = errors.New("Not Tcp Connection") | ||||
| 					return nil, err | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if t.ReadWriteTimeout > 0 { | ||||
| 				timeoutConn := &rwTimeoutConn{ | ||||
| 					TCPConn:   c.(*net.TCPConn), | ||||
| 					rwTimeout: t.ReadWriteTimeout, | ||||
| 				} | ||||
| 				return timeoutConn, nil | ||||
| 			} | ||||
| 			return c, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	t.transport = &http.Transport{ | ||||
| 		Dial:                  t.Dial, | ||||
| 		Proxy:                 t.Proxy, | ||||
| 		TLSClientConfig:       t.TLSClientConfig, | ||||
| 		DisableKeepAlives:     t.DisableKeepAlives, | ||||
| 		DisableCompression:    t.DisableCompression, | ||||
| 		MaxIdleConnsPerHost:   t.MaxIdleConnsPerHost, | ||||
| 		ResponseHeaderTimeout: t.ResponseHeaderTimeout, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (t *Transport) CancelRequest(req *http.Request) { | ||||
| 	t.starter.Do(t.lazyStart) | ||||
|  | ||||
| 	t.transport.CancelRequest(req) | ||||
| } | ||||
|  | ||||
| func (t *Transport) CloseIdleConnections() { | ||||
| 	t.starter.Do(t.lazyStart) | ||||
|  | ||||
| 	t.transport.CloseIdleConnections() | ||||
| } | ||||
|  | ||||
| func (t *Transport) RegisterProtocol(scheme string, rt http.RoundTripper) { | ||||
| 	t.starter.Do(t.lazyStart) | ||||
|  | ||||
| 	t.transport.RegisterProtocol(scheme, rt) | ||||
| } | ||||
|  | ||||
| func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { | ||||
| 	t.starter.Do(t.lazyStart) | ||||
|  | ||||
| 	if t.RequestTimeout > 0 { | ||||
| 		timer := time.AfterFunc(t.RequestTimeout, func() { | ||||
| 			t.transport.CancelRequest(req) | ||||
| 		}) | ||||
|  | ||||
| 		resp, err = t.transport.RoundTrip(req) | ||||
| 		if err != nil { | ||||
| 			timer.Stop() | ||||
| 		} else { | ||||
| 			resp.Body = &bodyCloseInterceptor{ReadCloser: resp.Body, timer: timer} | ||||
| 		} | ||||
| 	} else { | ||||
| 		resp, err = t.transport.RoundTrip(req) | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| type bodyCloseInterceptor struct { | ||||
| 	io.ReadCloser | ||||
| 	timer *time.Timer | ||||
| } | ||||
|  | ||||
| func (bci *bodyCloseInterceptor) Close() error { | ||||
| 	bci.timer.Stop() | ||||
| 	return bci.ReadCloser.Close() | ||||
| } | ||||
|  | ||||
| // A net.Conn that sets a deadline for every Read or Write operation | ||||
| type rwTimeoutConn struct { | ||||
| 	*net.TCPConn | ||||
| 	rwTimeout time.Duration | ||||
| } | ||||
|  | ||||
| func (c *rwTimeoutConn) Read(b []byte) (int, error) { | ||||
| 	err := c.TCPConn.SetDeadline(time.Now().Add(c.rwTimeout)) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	return c.TCPConn.Read(b) | ||||
| } | ||||
|  | ||||
| func (c *rwTimeoutConn) Write(b []byte) (int, error) { | ||||
| 	err := c.TCPConn.SetDeadline(time.Now().Add(c.rwTimeout)) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
| 	return c.TCPConn.Write(b) | ||||
| } | ||||
							
								
								
									
										17
									
								
								vendor/github.com/mrexodia/wray/examples/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/mrexodia/wray/examples/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| package main | ||||
|  | ||||
| import "github.com/pythonandchips/wray" | ||||
| import "fmt" | ||||
|  | ||||
| func main() { | ||||
| 	wray.RegisterTransports([]wray.Transport{&wray.HttpTransport{}}) | ||||
| 	client := wray.NewFayeClient("http://localhost:5000/faye") | ||||
|  | ||||
| 	fmt.Println("subscribing") | ||||
| 	client.Subscribe("/foo", false, func(message wray.Message) { | ||||
| 		fmt.Println("-------------------------------------------") | ||||
| 		fmt.Println(message.Data) | ||||
| 	}) | ||||
|  | ||||
| 	client.Listen() | ||||
| } | ||||
							
								
								
									
										15
									
								
								vendor/github.com/mrexodia/wray/examples/publish.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/mrexodia/wray/examples/publish.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| package main | ||||
|  | ||||
| import "github.com/pythonandchips/wray" | ||||
| import "fmt" | ||||
|  | ||||
| func main() { | ||||
|   wray.RegisterTransports([]wray.Transport{ &gofaye.HttpTransport{} }) | ||||
|   client := wray.NewFayeClient("http://localhost:5000/faye") | ||||
|  | ||||
|   params := map[string]interface{}{"hello": "from golang"} | ||||
|   fmt.Println("sending") | ||||
|   client.Publish("/foo", params) | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										140
									
								
								vendor/github.com/mrexodia/wray/go_faye.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								vendor/github.com/mrexodia/wray/go_faye.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| package wray | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	UNCONNECTED  = 1 | ||||
| 	CONNECTING   = 2 | ||||
| 	CONNECTED    = 3 | ||||
| 	DISCONNECTED = 4 | ||||
|  | ||||
| 	HANDSHAKE = "handshake" | ||||
| 	RETRY     = "retry" | ||||
| 	NONE      = "none" | ||||
|  | ||||
| 	CONNECTION_TIMEOUT = 60.0 | ||||
| 	DEFAULT_RETRY      = 5.0 | ||||
| 	MAX_REQUEST_SIZE   = 2048 | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	MANDATORY_CONNECTION_TYPES = []string{"long-polling"} | ||||
| 	registeredTransports       = []Transport{} | ||||
| ) | ||||
|  | ||||
| type FayeClient struct { | ||||
| 	state         int | ||||
| 	url           string | ||||
| 	subscriptions []Subscription | ||||
| 	transport     Transport | ||||
| 	clientId      string | ||||
| 	schedular     Schedular | ||||
| } | ||||
|  | ||||
| type Subscription struct { | ||||
| 	channel  string | ||||
| 	callback func(Message) | ||||
| } | ||||
|  | ||||
| type SubscriptionPromise struct { | ||||
| 	subscription Subscription | ||||
| } | ||||
|  | ||||
| func NewFayeClient(url string) *FayeClient { | ||||
| 	schedular := ChannelSchedular{} | ||||
| 	client := &FayeClient{url: url, state: UNCONNECTED, schedular: schedular} | ||||
| 	return client | ||||
| } | ||||
|  | ||||
| func (self *FayeClient) handshake() { | ||||
| 	t, err := SelectTransport(self, MANDATORY_CONNECTION_TYPES, []string{}) | ||||
| 	if err != nil { | ||||
| 		panic("No usable transports available") | ||||
| 	} | ||||
| 	self.transport = t | ||||
| 	self.transport.setUrl(self.url) | ||||
| 	self.state = CONNECTING | ||||
| 	handshakeParams := map[string]interface{}{"channel": "/meta/handshake", | ||||
| 		"version":                  "1.0", | ||||
| 		"supportedConnectionTypes": []string{"long-polling"}} | ||||
| 	response, err := self.transport.send(handshakeParams) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("Handshake failed. Retry in 10 seconds") | ||||
| 		self.state = UNCONNECTED | ||||
| 		self.schedular.wait(10*time.Second, func() { | ||||
| 			fmt.Println("retying handshake") | ||||
| 			self.handshake() | ||||
| 		}) | ||||
| 		return | ||||
| 	} | ||||
| 	self.clientId = response.clientId | ||||
| 	self.state = CONNECTED | ||||
| 	self.transport, err = SelectTransport(self, response.supportedConnectionTypes, []string{}) | ||||
| 	if err != nil { | ||||
| 		panic("Server does not support any available transports. Supported transports: " + strings.Join(response.supportedConnectionTypes, ",")) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *FayeClient) Subscribe(channel string, force bool, callback func(Message)) SubscriptionPromise { | ||||
| 	if self.state == UNCONNECTED { | ||||
| 		self.handshake() | ||||
| 	} | ||||
| 	subscriptionParams := map[string]interface{}{"channel": "/meta/subscribe", "clientId": self.clientId, "subscription": channel, "id": "1"} | ||||
| 	subscription := Subscription{channel: channel, callback: callback} | ||||
| 	//TODO: deal with subscription failures | ||||
| 	self.transport.send(subscriptionParams) | ||||
| 	self.subscriptions = append(self.subscriptions, subscription) | ||||
| 	return SubscriptionPromise{subscription} | ||||
| } | ||||
|  | ||||
| func (self *FayeClient) handleResponse(response Response) { | ||||
| 	for _, message := range response.messages { | ||||
| 		for _, subscription := range self.subscriptions { | ||||
| 			matched, _ := filepath.Match(subscription.channel, message.Channel) | ||||
| 			if matched { | ||||
| 				go subscription.callback(message) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *FayeClient) connect() { | ||||
| 	connectParams := map[string]interface{}{"channel": "/meta/connect", "clientId": self.clientId, "connectionType": self.transport.connectionType()} | ||||
| 	responseChannel := make(chan Response) | ||||
| 	go func() { | ||||
| 		response, _ := self.transport.send(connectParams) | ||||
| 		responseChannel <- response | ||||
| 	}() | ||||
| 	self.listen(responseChannel) | ||||
| } | ||||
|  | ||||
| func (self *FayeClient) listen(responseChannel chan Response) { | ||||
| 	response := <-responseChannel | ||||
| 	if response.successful == true { | ||||
| 		go self.handleResponse(response) | ||||
| 	} else { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *FayeClient) Listen() { | ||||
| 	for { | ||||
| 		self.connect() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (self *FayeClient) Publish(channel string, data map[string]interface{}) { | ||||
| 	if self.state != CONNECTED { | ||||
| 		self.handshake() | ||||
| 	} | ||||
| 	publishParams := map[string]interface{}{"channel": channel, "data": data, "clientId": self.clientId} | ||||
| 	self.transport.send(publishParams) | ||||
| } | ||||
|  | ||||
| func RegisterTransports(transports []Transport) { | ||||
| 	registeredTransports = transports | ||||
| } | ||||
							
								
								
									
										55
									
								
								vendor/github.com/mrexodia/wray/http_transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								vendor/github.com/mrexodia/wray/http_transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| package wray | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
| type HttpTransport struct { | ||||
| 	url      string | ||||
| 	SendHook func(data map[string]interface{}) | ||||
| } | ||||
|  | ||||
| func (self HttpTransport) isUsable(clientUrl string) bool { | ||||
| 	parsedUrl, err := url.Parse(clientUrl) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	if parsedUrl.Scheme == "http" || parsedUrl.Scheme == "https" { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (self HttpTransport) connectionType() string { | ||||
| 	return "long-polling" | ||||
| } | ||||
|  | ||||
| func (self HttpTransport) send(data map[string]interface{}) (Response, error) { | ||||
| 	if self.SendHook != nil { | ||||
| 		self.SendHook(data) | ||||
| 	} | ||||
| 	dataBytes, _ := json.Marshal(data) | ||||
| 	buffer := bytes.NewBuffer(dataBytes) | ||||
| 	responseData, err := http.Post(self.url, "application/json", buffer) | ||||
| 	if err != nil { | ||||
| 		return Response{}, err | ||||
| 	} | ||||
| 	if responseData.StatusCode != 200 { | ||||
| 		return Response{}, errors.New(responseData.Status) | ||||
| 	} | ||||
| 	readData, _ := ioutil.ReadAll(responseData.Body) | ||||
| 	responseData.Body.Close() | ||||
| 	var jsonData []interface{} | ||||
| 	json.Unmarshal(readData, &jsonData) | ||||
| 	response := newResponse(jsonData) | ||||
| 	return response, nil | ||||
| } | ||||
|  | ||||
| func (self *HttpTransport) setUrl(url string) { | ||||
| 	self.url = url | ||||
| } | ||||
							
								
								
									
										61
									
								
								vendor/github.com/mrexodia/wray/response.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								vendor/github.com/mrexodia/wray/response.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| package wray | ||||
|  | ||||
| type Response struct { | ||||
|   id string | ||||
|   channel string | ||||
|   successful bool | ||||
|   clientId string | ||||
|   supportedConnectionTypes []string | ||||
|   messages []Message | ||||
|   error error | ||||
| } | ||||
|  | ||||
| type Message struct { | ||||
|   Channel string | ||||
|   Id string | ||||
|   Data map[string]interface{} | ||||
| } | ||||
|  | ||||
| func newResponse(data []interface{}) Response { | ||||
|   headerData := data[0].(map[string]interface{}) | ||||
|   messagesData := data[1.:] | ||||
|   messages := parseMessages(messagesData) | ||||
|   var id string | ||||
|   if headerData["id"] != nil { | ||||
|     id = headerData["id"].(string) | ||||
|   } | ||||
|   supportedConnectionTypes := []string{} | ||||
|   if headerData["supportedConnectionTypes"] != nil { | ||||
|     d := headerData["supportedConnectionTypes"].([]interface{}) | ||||
|     for _, sct := range(d) { | ||||
|       supportedConnectionTypes = append(supportedConnectionTypes, sct.(string)) | ||||
|     } | ||||
|   } | ||||
|   var clientId string | ||||
|   if headerData["clientId"] != nil { | ||||
|     clientId = headerData["clientId"].(string) | ||||
|   } | ||||
|   return Response{id: id, | ||||
|                   clientId: clientId, | ||||
|                   channel: headerData["channel"].(string), | ||||
|                   successful: headerData["successful"].(bool), | ||||
|                   messages: messages, | ||||
|                   supportedConnectionTypes: supportedConnectionTypes} | ||||
| } | ||||
|  | ||||
| func parseMessages(data []interface{}) []Message { | ||||
|   messages := []Message{} | ||||
|   for _, messageData := range(data) { | ||||
|     m := messageData.(map[string]interface{}) | ||||
|     var id string | ||||
|     if m["id"] != nil { | ||||
|       id = m["id"].(string) | ||||
|     } | ||||
|     message := Message{Channel: m["channel"].(string), | ||||
|                        Id: id, | ||||
|                        Data: m["data"].(map[string]interface{})} | ||||
|     messages = append(messages, message) | ||||
|   } | ||||
|   return messages | ||||
| } | ||||
|  | ||||
							
								
								
									
										22
									
								
								vendor/github.com/mrexodia/wray/schedular.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/mrexodia/wray/schedular.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| package wray | ||||
|  | ||||
| import "time" | ||||
|  | ||||
| type Schedular interface { | ||||
| 	wait(time.Duration, func()) | ||||
| 	delay() time.Duration | ||||
| } | ||||
|  | ||||
| type ChannelSchedular struct { | ||||
| } | ||||
|  | ||||
| func (self ChannelSchedular) wait(delay time.Duration, callback func()) { | ||||
| 	go func() { | ||||
| 		time.Sleep(delay) | ||||
| 		callback() | ||||
| 	}() | ||||
| } | ||||
|  | ||||
| func (self ChannelSchedular) delay() time.Duration { | ||||
| 	return (1 * time.Minute) | ||||
| } | ||||
							
								
								
									
										21
									
								
								vendor/github.com/mrexodia/wray/transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/mrexodia/wray/transport.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| package wray | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| ) | ||||
|  | ||||
| type Transport interface { | ||||
| 	isUsable(string) bool | ||||
| 	connectionType() string | ||||
| 	send(map[string]interface{}) (Response, error) | ||||
| 	setUrl(string) | ||||
| } | ||||
|  | ||||
| func SelectTransport(client *FayeClient, transportTypes []string, disabled []string) (Transport, error) { | ||||
| 	for _, transport := range registeredTransports { | ||||
| 		if contains(transport.connectionType(), transportTypes) && transport.isUsable(client.url) { | ||||
| 			return transport, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, errors.New("No usable transports available") | ||||
| } | ||||
							
								
								
									
										10
									
								
								vendor/github.com/mrexodia/wray/utils.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/mrexodia/wray/utils.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| package wray | ||||
|  | ||||
| func contains(target string, slice []string) bool { | ||||
|   for _, t := range(slice) { | ||||
|     if t == target { | ||||
|       return true | ||||
|     } | ||||
|   } | ||||
|   return false | ||||
| } | ||||
							
								
								
									
										23
									
								
								vendor/github.com/nlopes/slack/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/nlopes/slack/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| Copyright (c) 2015, Norberto Lopes | ||||
| All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without modification, | ||||
| are permitted provided that the following conditions are met: | ||||
|  | ||||
| 1. Redistributions of source code must retain the above copyright notice, this | ||||
| list of conditions and the following disclaimer. | ||||
|  | ||||
| 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
| this list of conditions and the following disclaimer in the documentation and/or | ||||
| other materials provided with the distribution. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||||
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR | ||||
| ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||||
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||||
| ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										190
									
								
								vendor/github.com/nlopes/slack/admin.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								vendor/github.com/nlopes/slack/admin.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| package slack | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
| type adminResponse struct { | ||||
| 	OK    bool   `json:"ok"` | ||||
| 	Error string `json:"error"` | ||||
| } | ||||
|  | ||||
| func adminRequest(method string, teamName string, values url.Values, debug bool) (*adminResponse, error) { | ||||
| 	adminResponse := &adminResponse{} | ||||
| 	err := parseAdminResponse(method, teamName, values, adminResponse, debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if !adminResponse.OK { | ||||
| 		return nil, errors.New(adminResponse.Error) | ||||
| 	} | ||||
|  | ||||
| 	return adminResponse, nil | ||||
| } | ||||
|  | ||||
| // DisableUser disabled a user account, given a user ID | ||||
| func (api *Client) DisableUser(teamName string, uid string) error { | ||||
| 	values := url.Values{ | ||||
| 		"user":       {uid}, | ||||
| 		"token":      {api.config.token}, | ||||
| 		"set_active": {"true"}, | ||||
| 		"_attempts":  {"1"}, | ||||
| 	} | ||||
|  | ||||
| 	_, err := adminRequest("setInactive", teamName, values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to disable user with id '%s': %s", uid, err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // InviteGuest invites a user to Slack as a single-channel guest | ||||
| func (api *Client) InviteGuest( | ||||
| 	teamName string, | ||||
| 	channel string, | ||||
| 	firstName string, | ||||
| 	lastName string, | ||||
| 	emailAddress string, | ||||
| ) error { | ||||
| 	values := url.Values{ | ||||
| 		"email":            {emailAddress}, | ||||
| 		"channels":         {channel}, | ||||
| 		"first_name":       {firstName}, | ||||
| 		"last_name":        {lastName}, | ||||
| 		"ultra_restricted": {"1"}, | ||||
| 		"token":            {api.config.token}, | ||||
| 		"set_active":       {"true"}, | ||||
| 		"_attempts":        {"1"}, | ||||
| 	} | ||||
|  | ||||
| 	_, err := adminRequest("invite", teamName, values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to invite single-channel guest: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // InviteRestricted invites a user to Slack as a restricted account | ||||
| func (api *Client) InviteRestricted( | ||||
| 	teamName string, | ||||
| 	channel string, | ||||
| 	firstName string, | ||||
| 	lastName string, | ||||
| 	emailAddress string, | ||||
| ) error { | ||||
| 	values := url.Values{ | ||||
| 		"email":      {emailAddress}, | ||||
| 		"channels":   {channel}, | ||||
| 		"first_name": {firstName}, | ||||
| 		"last_name":  {lastName}, | ||||
| 		"restricted": {"1"}, | ||||
| 		"token":      {api.config.token}, | ||||
| 		"set_active": {"true"}, | ||||
| 		"_attempts":  {"1"}, | ||||
| 	} | ||||
|  | ||||
| 	_, err := adminRequest("invite", teamName, values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to restricted account: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // InviteToTeam invites a user to a Slack team | ||||
| func (api *Client) InviteToTeam( | ||||
| 	teamName string, | ||||
| 	firstName string, | ||||
| 	lastName string, | ||||
| 	emailAddress string, | ||||
| ) error { | ||||
| 	values := url.Values{ | ||||
| 		"email":      {emailAddress}, | ||||
| 		"first_name": {firstName}, | ||||
| 		"last_name":  {lastName}, | ||||
| 		"token":      {api.config.token}, | ||||
| 		"set_active": {"true"}, | ||||
| 		"_attempts":  {"1"}, | ||||
| 	} | ||||
|  | ||||
| 	_, err := adminRequest("invite", teamName, values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to invite to team: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // SetRegular enables the specified user | ||||
| func (api *Client) SetRegular(teamName string, user string) error { | ||||
| 	values := url.Values{ | ||||
| 		"user":       {user}, | ||||
| 		"token":      {api.config.token}, | ||||
| 		"set_active": {"true"}, | ||||
| 		"_attempts":  {"1"}, | ||||
| 	} | ||||
|  | ||||
| 	_, err := adminRequest("setRegular", teamName, values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to change the user (%s) to a regular user: %s", user, err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // SendSSOBindingEmail sends an SSO binding email to the specified user | ||||
| func (api *Client) SendSSOBindingEmail(teamName string, user string) error { | ||||
| 	values := url.Values{ | ||||
| 		"user":       {user}, | ||||
| 		"token":      {api.config.token}, | ||||
| 		"set_active": {"true"}, | ||||
| 		"_attempts":  {"1"}, | ||||
| 	} | ||||
|  | ||||
| 	_, err := adminRequest("sendSSOBind", teamName, values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to send SSO binding email for user (%s): %s", user, err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // SetUltraRestricted converts a user into a single-channel guest | ||||
| func (api *Client) SetUltraRestricted(teamName, uid, channel string) error { | ||||
| 	values := url.Values{ | ||||
| 		"user":       {uid}, | ||||
| 		"channel":    {channel}, | ||||
| 		"token":      {api.config.token}, | ||||
| 		"set_active": {"true"}, | ||||
| 		"_attempts":  {"1"}, | ||||
| 	} | ||||
|  | ||||
| 	_, err := adminRequest("setUltraRestricted", teamName, values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to ultra-restrict account: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // SetRestricted converts a user into a restricted account | ||||
| func (api *Client) SetRestricted(teamName, uid string) error { | ||||
| 	values := url.Values{ | ||||
| 		"user":       {uid}, | ||||
| 		"token":      {api.config.token}, | ||||
| 		"set_active": {"true"}, | ||||
| 		"_attempts":  {"1"}, | ||||
| 	} | ||||
|  | ||||
| 	_, err := adminRequest("setRestricted", teamName, values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to restrict account: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										76
									
								
								vendor/github.com/nlopes/slack/attachments.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								vendor/github.com/nlopes/slack/attachments.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| package slack | ||||
|  | ||||
| // AttachmentField contains information for an attachment field | ||||
| // An Attachment can contain multiple of these | ||||
| type AttachmentField struct { | ||||
| 	Title string `json:"title"` | ||||
| 	Value string `json:"value"` | ||||
| 	Short bool   `json:"short"` | ||||
| } | ||||
|  | ||||
| // AttachmentAction is a button to be included in the attachment. Required when | ||||
| // using message buttons and otherwise not useful. A maximum of 5 actions may be | ||||
| // provided per attachment. | ||||
| type AttachmentAction struct { | ||||
| 	Name    string              `json:"name"`              // Required. | ||||
| 	Text    string              `json:"text"`              // Required. | ||||
| 	Style   string              `json:"style,omitempty"`   // Optional. Allowed values: "default", "primary", "danger" | ||||
| 	Type    string              `json:"type"`              // Required. Must be set to "button" | ||||
| 	Value   string              `json:"value,omitempty"`   // Optional. | ||||
| 	Confirm []ConfirmationField `json:"confirm,omitempty"` // Optional. | ||||
| } | ||||
|  | ||||
| // AttachmentActionCallback is sent from Slack when a user clicks a button in an interactive message (aka AttachmentAction) | ||||
| type AttachmentActionCallback struct { | ||||
| 	Actions    []AttachmentAction `json:"actions"` | ||||
| 	CallbackID string             `json:"callback_id"` | ||||
| 	Team       Team               `json:"team"` | ||||
| 	Channel    Channel            `json:"channel"` | ||||
| 	User       User               `json:"user"` | ||||
|  | ||||
| 	OriginalMessage Message `json:"original_message"` | ||||
|  | ||||
| 	ActionTs     string `json:"action_ts"` | ||||
| 	MessageTs    string `json:"message_ts"` | ||||
| 	AttachmentID string `json:"attachment_id"` | ||||
| 	Token        string `json:"token"` | ||||
| 	ResponseURL  string `json:"response_url"` | ||||
| } | ||||
|  | ||||
| // ConfirmationField are used to ask users to confirm actions | ||||
| type ConfirmationField struct { | ||||
| 	Title       string `json:"title,omitempty"`        // Optional. | ||||
| 	Text        string `json:"text"`                   // Required. | ||||
| 	OkText      string `json:"ok_text,omitempty"`      // Optional. Defaults to "Okay" | ||||
| 	DismissText string `json:"dismiss_text,omitempty"` // Optional. Defaults to "Cancel" | ||||
| } | ||||
|  | ||||
| // Attachment contains all the information for an attachment | ||||
| type Attachment struct { | ||||
| 	Color    string `json:"color,omitempty"` | ||||
| 	Fallback string `json:"fallback"` | ||||
|  | ||||
| 	CallbackID string `json:"callback_id,omitempty"` | ||||
|  | ||||
| 	AuthorName    string `json:"author_name,omitempty"` | ||||
| 	AuthorSubname string `json:"author_subname,omitempty"` | ||||
| 	AuthorLink    string `json:"author_link,omitempty"` | ||||
| 	AuthorIcon    string `json:"author_icon,omitempty"` | ||||
|  | ||||
| 	Title     string `json:"title,omitempty"` | ||||
| 	TitleLink string `json:"title_link,omitempty"` | ||||
| 	Pretext   string `json:"pretext,omitempty"` | ||||
| 	Text      string `json:"text"` | ||||
|  | ||||
| 	ImageURL string `json:"image_url,omitempty"` | ||||
| 	ThumbURL string `json:"thumb_url,omitempty"` | ||||
|  | ||||
| 	Fields     []AttachmentField  `json:"fields,omitempty"` | ||||
| 	Actions    []AttachmentAction `json:"actions,omitempty"` | ||||
| 	MarkdownIn []string           `json:"mrkdwn_in,omitempty"` | ||||
|  | ||||
| 	Footer     string `json:"footer,omitempty"` | ||||
| 	FooterIcon string `json:"footer_icon,omitempty"` | ||||
|  | ||||
| 	Ts int64 `json:"ts,omitempty"` | ||||
| } | ||||
							
								
								
									
										57
									
								
								vendor/github.com/nlopes/slack/backoff.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								vendor/github.com/nlopes/slack/backoff.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| package slack | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"math/rand" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // This one was ripped from https://github.com/jpillora/backoff/blob/master/backoff.go | ||||
|  | ||||
| // Backoff is a time.Duration counter. It starts at Min.  After every | ||||
| // call to Duration() it is multiplied by Factor.  It is capped at | ||||
| // Max. It returns to Min on every call to Reset().  Used in | ||||
| // conjunction with the time package. | ||||
| type backoff struct { | ||||
| 	attempts int | ||||
| 	//Factor is the multiplying factor for each increment step | ||||
| 	Factor float64 | ||||
| 	//Jitter eases contention by randomizing backoff steps | ||||
| 	Jitter bool | ||||
| 	//Min and Max are the minimum and maximum values of the counter | ||||
| 	Min, Max time.Duration | ||||
| } | ||||
|  | ||||
| // Returns the current value of the counter and then multiplies it | ||||
| // Factor | ||||
| func (b *backoff) Duration() time.Duration { | ||||
| 	//Zero-values are nonsensical, so we use | ||||
| 	//them to apply defaults | ||||
| 	if b.Min == 0 { | ||||
| 		b.Min = 100 * time.Millisecond | ||||
| 	} | ||||
| 	if b.Max == 0 { | ||||
| 		b.Max = 10 * time.Second | ||||
| 	} | ||||
| 	if b.Factor == 0 { | ||||
| 		b.Factor = 2 | ||||
| 	} | ||||
| 	//calculate this duration | ||||
| 	dur := float64(b.Min) * math.Pow(b.Factor, float64(b.attempts)) | ||||
| 	if b.Jitter == true { | ||||
| 		dur = rand.Float64()*(dur-float64(b.Min)) + float64(b.Min) | ||||
| 	} | ||||
| 	//cap! | ||||
| 	if dur > float64(b.Max) { | ||||
| 		return b.Max | ||||
| 	} | ||||
| 	//bump attempts count | ||||
| 	b.attempts++ | ||||
| 	//return as a time.Duration | ||||
| 	return time.Duration(dur) | ||||
| } | ||||
|  | ||||
| //Resets the current value of the counter back to Min | ||||
| func (b *backoff) Reset() { | ||||
| 	b.attempts = 0 | ||||
| } | ||||
							
								
								
									
										261
									
								
								vendor/github.com/nlopes/slack/channels.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								vendor/github.com/nlopes/slack/channels.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,261 @@ | ||||
| package slack | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| type channelResponseFull struct { | ||||
| 	Channel      Channel   `json:"channel"` | ||||
| 	Channels     []Channel `json:"channels"` | ||||
| 	Purpose      string    `json:"purpose"` | ||||
| 	Topic        string    `json:"topic"` | ||||
| 	NotInChannel bool      `json:"not_in_channel"` | ||||
| 	History | ||||
| 	SlackResponse | ||||
| } | ||||
|  | ||||
| // Channel contains information about the channel | ||||
| type Channel struct { | ||||
| 	groupConversation | ||||
| 	IsChannel bool `json:"is_channel"` | ||||
| 	IsGeneral bool `json:"is_general"` | ||||
| 	IsMember  bool `json:"is_member"` | ||||
| } | ||||
|  | ||||
| func channelRequest(path string, values url.Values, debug bool) (*channelResponseFull, error) { | ||||
| 	response := &channelResponseFull{} | ||||
| 	err := post(path, values, response, debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !response.Ok { | ||||
| 		return nil, errors.New(response.Error) | ||||
| 	} | ||||
| 	return response, nil | ||||
| } | ||||
|  | ||||
| // ArchiveChannel archives the given channel | ||||
| func (api *Client) ArchiveChannel(channel string) error { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 	} | ||||
| 	_, err := channelRequest("channels.archive", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // UnarchiveChannel unarchives the given channel | ||||
| func (api *Client) UnarchiveChannel(channel string) error { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 	} | ||||
| 	_, err := channelRequest("channels.unarchive", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // CreateChannel creates a channel with the given name and returns a *Channel | ||||
| func (api *Client) CreateChannel(channel string) (*Channel, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 		"name":  {channel}, | ||||
| 	} | ||||
| 	response, err := channelRequest("channels.create", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.Channel, nil | ||||
| } | ||||
|  | ||||
| // GetChannelHistory retrieves the channel history | ||||
| func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (*History, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 	} | ||||
| 	if params.Latest != DEFAULT_HISTORY_LATEST { | ||||
| 		values.Add("latest", params.Latest) | ||||
| 	} | ||||
| 	if params.Oldest != DEFAULT_HISTORY_OLDEST { | ||||
| 		values.Add("oldest", params.Oldest) | ||||
| 	} | ||||
| 	if params.Count != DEFAULT_HISTORY_COUNT { | ||||
| 		values.Add("count", strconv.Itoa(params.Count)) | ||||
| 	} | ||||
| 	if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE { | ||||
| 		if params.Inclusive { | ||||
| 			values.Add("inclusive", "1") | ||||
| 		} else { | ||||
| 			values.Add("inclusive", "0") | ||||
| 		} | ||||
| 	} | ||||
| 	if params.Unreads != DEFAULT_HISTORY_UNREADS { | ||||
| 		if params.Unreads { | ||||
| 			values.Add("unreads", "1") | ||||
| 		} else { | ||||
| 			values.Add("unreads", "0") | ||||
| 		} | ||||
| 	} | ||||
| 	response, err := channelRequest("channels.history", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.History, nil | ||||
| } | ||||
|  | ||||
| // GetChannelInfo retrieves the given channel | ||||
| func (api *Client) GetChannelInfo(channel string) (*Channel, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 	} | ||||
| 	response, err := channelRequest("channels.info", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.Channel, nil | ||||
| } | ||||
|  | ||||
| // InviteUserToChannel invites a user to a given channel and returns a *Channel | ||||
| func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 		"user":    {user}, | ||||
| 	} | ||||
| 	response, err := channelRequest("channels.invite", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.Channel, nil | ||||
| } | ||||
|  | ||||
| // JoinChannel joins the currently authenticated user to a channel | ||||
| func (api *Client) JoinChannel(channel string) (*Channel, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 		"name":  {channel}, | ||||
| 	} | ||||
| 	response, err := channelRequest("channels.join", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.Channel, nil | ||||
| } | ||||
|  | ||||
| // LeaveChannel makes the authenticated user leave the given channel | ||||
| func (api *Client) LeaveChannel(channel string) (bool, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 	} | ||||
| 	response, err := channelRequest("channels.leave", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	if response.NotInChannel { | ||||
| 		return response.NotInChannel, nil | ||||
| 	} | ||||
| 	return false, nil | ||||
| } | ||||
|  | ||||
| // KickUserFromChannel kicks a user from a given channel | ||||
| func (api *Client) KickUserFromChannel(channel, user string) error { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 		"user":    {user}, | ||||
| 	} | ||||
| 	_, err := channelRequest("channels.kick", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetChannels retrieves all the channels | ||||
| func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 	} | ||||
| 	if excludeArchived { | ||||
| 		values.Add("exclude_archived", "1") | ||||
| 	} | ||||
| 	response, err := channelRequest("channels.list", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return response.Channels, nil | ||||
| } | ||||
|  | ||||
| // SetChannelReadMark sets the read mark of a given channel to a specific point | ||||
| // Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a | ||||
| // timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls | ||||
| // (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A | ||||
| // timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout. | ||||
| func (api *Client) SetChannelReadMark(channel, ts string) error { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 		"ts":      {ts}, | ||||
| 	} | ||||
| 	_, err := channelRequest("channels.mark", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // RenameChannel renames a given channel | ||||
| func (api *Client) RenameChannel(channel, name string) (*Channel, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 		"name":    {name}, | ||||
| 	} | ||||
| 	// XXX: the created entry in this call returns a string instead of a number | ||||
| 	// so I may have to do some workaround to solve it. | ||||
| 	response, err := channelRequest("channels.rename", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.Channel, nil | ||||
|  | ||||
| } | ||||
|  | ||||
| // SetChannelPurpose sets the channel purpose and returns the purpose that was | ||||
| // successfully set | ||||
| func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 		"purpose": {purpose}, | ||||
| 	} | ||||
| 	response, err := channelRequest("channels.setPurpose", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return response.Purpose, nil | ||||
| } | ||||
|  | ||||
| // SetChannelTopic sets the channel topic and returns the topic that was successfully set | ||||
| func (api *Client) SetChannelTopic(channel, topic string) (string, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 		"topic":   {topic}, | ||||
| 	} | ||||
| 	response, err := channelRequest("channels.setTopic", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return response.Topic, nil | ||||
| } | ||||
							
								
								
									
										166
									
								
								vendor/github.com/nlopes/slack/chat.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								vendor/github.com/nlopes/slack/chat.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | ||||
| package slack | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	DEFAULT_MESSAGE_USERNAME     = "" | ||||
| 	DEFAULT_MESSAGE_ASUSER       = false | ||||
| 	DEFAULT_MESSAGE_PARSE        = "" | ||||
| 	DEFAULT_MESSAGE_LINK_NAMES   = 0 | ||||
| 	DEFAULT_MESSAGE_UNFURL_LINKS = false | ||||
| 	DEFAULT_MESSAGE_UNFURL_MEDIA = true | ||||
| 	DEFAULT_MESSAGE_ICON_URL     = "" | ||||
| 	DEFAULT_MESSAGE_ICON_EMOJI   = "" | ||||
| 	DEFAULT_MESSAGE_MARKDOWN     = true | ||||
| 	DEFAULT_MESSAGE_ESCAPE_TEXT  = true | ||||
| ) | ||||
|  | ||||
| type chatResponseFull struct { | ||||
| 	Channel   string `json:"channel"` | ||||
| 	Timestamp string `json:"ts"` | ||||
| 	Text      string `json:"text"` | ||||
| 	SlackResponse | ||||
| } | ||||
|  | ||||
| // PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request | ||||
| type PostMessageParameters struct { | ||||
| 	Text        string | ||||
| 	Username    string | ||||
| 	AsUser      bool | ||||
| 	Parse       string | ||||
| 	LinkNames   int | ||||
| 	Attachments []Attachment | ||||
| 	UnfurlLinks bool | ||||
| 	UnfurlMedia bool | ||||
| 	IconURL     string | ||||
| 	IconEmoji   string | ||||
| 	Markdown    bool `json:"mrkdwn,omitempty"` | ||||
| 	EscapeText  bool | ||||
| } | ||||
|  | ||||
| // NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set | ||||
| func NewPostMessageParameters() PostMessageParameters { | ||||
| 	return PostMessageParameters{ | ||||
| 		Username:    DEFAULT_MESSAGE_USERNAME, | ||||
| 		AsUser:      DEFAULT_MESSAGE_ASUSER, | ||||
| 		Parse:       DEFAULT_MESSAGE_PARSE, | ||||
| 		LinkNames:   DEFAULT_MESSAGE_LINK_NAMES, | ||||
| 		Attachments: nil, | ||||
| 		UnfurlLinks: DEFAULT_MESSAGE_UNFURL_LINKS, | ||||
| 		UnfurlMedia: DEFAULT_MESSAGE_UNFURL_MEDIA, | ||||
| 		IconURL:     DEFAULT_MESSAGE_ICON_URL, | ||||
| 		IconEmoji:   DEFAULT_MESSAGE_ICON_EMOJI, | ||||
| 		Markdown:    DEFAULT_MESSAGE_MARKDOWN, | ||||
| 		EscapeText:  DEFAULT_MESSAGE_ESCAPE_TEXT, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func chatRequest(path string, values url.Values, debug bool) (*chatResponseFull, error) { | ||||
| 	response := &chatResponseFull{} | ||||
| 	err := post(path, values, response, debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !response.Ok { | ||||
| 		return nil, errors.New(response.Error) | ||||
| 	} | ||||
| 	return response, nil | ||||
| } | ||||
|  | ||||
| // DeleteMessage deletes a message in a channel | ||||
| func (api *Client) DeleteMessage(channel, messageTimestamp string) (string, string, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 		"ts":      {messageTimestamp}, | ||||
| 	} | ||||
| 	response, err := chatRequest("chat.delete", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
| 	return response.Channel, response.Timestamp, nil | ||||
| } | ||||
|  | ||||
| func escapeMessage(message string) string { | ||||
| 	replacer := strings.NewReplacer("&", "&", "<", "<", ">", ">") | ||||
| 	return replacer.Replace(message) | ||||
| } | ||||
|  | ||||
| // PostMessage sends a message to a channel. | ||||
| // Message is escaped by default according to https://api.slack.com/docs/formatting | ||||
| // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message. | ||||
| func (api *Client) PostMessage(channel, text string, params PostMessageParameters) (string, string, error) { | ||||
| 	if params.EscapeText { | ||||
| 		text = escapeMessage(text) | ||||
| 	} | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 		"text":    {text}, | ||||
| 	} | ||||
| 	if params.Username != DEFAULT_MESSAGE_USERNAME { | ||||
| 		values.Set("username", string(params.Username)) | ||||
| 	} | ||||
| 	if params.AsUser != DEFAULT_MESSAGE_ASUSER { | ||||
| 		values.Set("as_user", "true") | ||||
| 	} | ||||
| 	if params.Parse != DEFAULT_MESSAGE_PARSE { | ||||
| 		values.Set("parse", string(params.Parse)) | ||||
| 	} | ||||
| 	if params.LinkNames != DEFAULT_MESSAGE_LINK_NAMES { | ||||
| 		values.Set("link_names", "1") | ||||
| 	} | ||||
| 	if params.Attachments != nil { | ||||
| 		attachments, err := json.Marshal(params.Attachments) | ||||
| 		if err != nil { | ||||
| 			return "", "", err | ||||
| 		} | ||||
| 		values.Set("attachments", string(attachments)) | ||||
| 	} | ||||
| 	if params.UnfurlLinks != DEFAULT_MESSAGE_UNFURL_LINKS { | ||||
| 		values.Set("unfurl_links", "true") | ||||
| 	} | ||||
| 	// I want to send a message with explicit `as_user` `true` and `unfurl_links` `false` in request. | ||||
| 	// Because setting `as_user` to `true` will change the default value for `unfurl_links` to `true` on Slack API side. | ||||
| 	if params.AsUser != DEFAULT_MESSAGE_ASUSER && params.UnfurlLinks == DEFAULT_MESSAGE_UNFURL_LINKS { | ||||
| 		values.Set("unfurl_links", "false") | ||||
| 	} | ||||
| 	if params.UnfurlMedia != DEFAULT_MESSAGE_UNFURL_MEDIA { | ||||
| 		values.Set("unfurl_media", "false") | ||||
| 	} | ||||
| 	if params.IconURL != DEFAULT_MESSAGE_ICON_URL { | ||||
| 		values.Set("icon_url", params.IconURL) | ||||
| 	} | ||||
| 	if params.IconEmoji != DEFAULT_MESSAGE_ICON_EMOJI { | ||||
| 		values.Set("icon_emoji", params.IconEmoji) | ||||
| 	} | ||||
| 	if params.Markdown != DEFAULT_MESSAGE_MARKDOWN { | ||||
| 		values.Set("mrkdwn", "false") | ||||
| 	} | ||||
|  | ||||
| 	response, err := chatRequest("chat.postMessage", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
| 	return response.Channel, response.Timestamp, nil | ||||
| } | ||||
|  | ||||
| // UpdateMessage updates a message in a channel | ||||
| func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 		"text":    {escapeMessage(text)}, | ||||
| 		"ts":      {timestamp}, | ||||
| 	} | ||||
| 	response, err := chatRequest("chat.update", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return "", "", "", err | ||||
| 	} | ||||
| 	return response.Channel, response.Timestamp, response.Text, nil | ||||
| } | ||||
							
								
								
									
										10
									
								
								vendor/github.com/nlopes/slack/comment.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/nlopes/slack/comment.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| package slack | ||||
|  | ||||
| // Comment contains all the information relative to a comment | ||||
| type Comment struct { | ||||
| 	ID        string   `json:"id,omitempty"` | ||||
| 	Created   JSONTime `json:"created,omitempty"` | ||||
| 	Timestamp JSONTime `json:"timestamp,omitempty"` | ||||
| 	User      string   `json:"user,omitempty"` | ||||
| 	Comment   string   `json:"comment,omitempty"` | ||||
| } | ||||
							
								
								
									
										38
									
								
								vendor/github.com/nlopes/slack/conversation.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/nlopes/slack/conversation.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| package slack | ||||
|  | ||||
| // Conversation is the foundation for IM and BaseGroupConversation | ||||
| type conversation struct { | ||||
| 	ID                 string   `json:"id"` | ||||
| 	Created            JSONTime `json:"created"` | ||||
| 	IsOpen             bool     `json:"is_open"` | ||||
| 	LastRead           string   `json:"last_read,omitempty"` | ||||
| 	Latest             *Message `json:"latest,omitempty"` | ||||
| 	UnreadCount        int      `json:"unread_count,omitempty"` | ||||
| 	UnreadCountDisplay int      `json:"unread_count_display,omitempty"` | ||||
| } | ||||
|  | ||||
| // GroupConversation is the foundation for Group and Channel | ||||
| type groupConversation struct { | ||||
| 	conversation | ||||
| 	Name       string   `json:"name"` | ||||
| 	Creator    string   `json:"creator"` | ||||
| 	IsArchived bool     `json:"is_archived"` | ||||
| 	Members    []string `json:"members"` | ||||
| 	NumMembers int      `json:"num_members,omitempty"` | ||||
| 	Topic      Topic    `json:"topic"` | ||||
| 	Purpose    Purpose  `json:"purpose"` | ||||
| } | ||||
|  | ||||
| // Topic contains information about the topic | ||||
| type Topic struct { | ||||
| 	Value   string   `json:"value"` | ||||
| 	Creator string   `json:"creator"` | ||||
| 	LastSet JSONTime `json:"last_set"` | ||||
| } | ||||
|  | ||||
| // Purpose contains information about the purpose | ||||
| type Purpose struct { | ||||
| 	Value   string   `json:"value"` | ||||
| 	Creator string   `json:"creator"` | ||||
| 	LastSet JSONTime `json:"last_set"` | ||||
| } | ||||
							
								
								
									
										123
									
								
								vendor/github.com/nlopes/slack/dnd.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								vendor/github.com/nlopes/slack/dnd.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| package slack | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type SnoozeDebug struct { | ||||
| 	SnoozeEndDate string `json:"snooze_end_date"` | ||||
| } | ||||
|  | ||||
| type SnoozeInfo struct { | ||||
| 	SnoozeEnabled   bool        `json:"snooze_enabled,omitempty"` | ||||
| 	SnoozeEndTime   int         `json:"snooze_endtime,omitempty"` | ||||
| 	SnoozeRemaining int         `json:"snooze_remaining,omitempty"` | ||||
| 	SnoozeDebug     SnoozeDebug `json:"snooze_debug,omitempty"` | ||||
| } | ||||
|  | ||||
| type DNDStatus struct { | ||||
| 	Enabled            bool `json:"dnd_enabled"` | ||||
| 	NextStartTimestamp int  `json:"next_dnd_start_ts"` | ||||
| 	NextEndTimestamp   int  `json:"next_dnd_end_ts"` | ||||
| 	SnoozeInfo | ||||
| } | ||||
|  | ||||
| type dndResponseFull struct { | ||||
| 	DNDStatus | ||||
| 	SlackResponse | ||||
| } | ||||
|  | ||||
| type dndTeamInfoResponse struct { | ||||
| 	Users map[string]DNDStatus `json:"users"` | ||||
| 	SlackResponse | ||||
| } | ||||
|  | ||||
| func dndRequest(path string, values url.Values, debug bool) (*dndResponseFull, error) { | ||||
| 	response := &dndResponseFull{} | ||||
| 	err := post(path, values, response, debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !response.Ok { | ||||
| 		return nil, errors.New(response.Error) | ||||
| 	} | ||||
| 	return response, nil | ||||
| } | ||||
|  | ||||
| // EndDND ends the user's scheduled Do Not Disturb session | ||||
| func (api *Client) EndDND() error { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 	} | ||||
|  | ||||
| 	response := &SlackResponse{} | ||||
| 	if err := post("dnd.endDnd", values, response, api.debug); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if !response.Ok { | ||||
| 		return errors.New(response.Error) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // EndSnooze ends the current user's snooze mode | ||||
| func (api *Client) EndSnooze() (*DNDStatus, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 	} | ||||
|  | ||||
| 	response, err := dndRequest("dnd.endSnooze", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.DNDStatus, nil | ||||
| } | ||||
|  | ||||
| // GetDNDInfo provides information about a user's current Do Not Disturb settings. | ||||
| func (api *Client) GetDNDInfo(user *string) (*DNDStatus, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 	} | ||||
| 	if user != nil { | ||||
| 		values.Set("user", *user) | ||||
| 	} | ||||
| 	response, err := dndRequest("dnd.info", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.DNDStatus, nil | ||||
| } | ||||
|  | ||||
| // GetDNDTeamInfo provides information about a user's current Do Not Disturb settings. | ||||
| func (api *Client) GetDNDTeamInfo(users []string) (map[string]DNDStatus, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 		"users": {strings.Join(users, ",")}, | ||||
| 	} | ||||
| 	response := &dndTeamInfoResponse{} | ||||
| 	if err := post("dnd.teamInfo", values, response, api.debug); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !response.Ok { | ||||
| 		return nil, errors.New(response.Error) | ||||
| 	} | ||||
| 	return response.Users, nil | ||||
| } | ||||
|  | ||||
| // SetSnooze adjusts the snooze duration for a user's Do Not Disturb | ||||
| // settings. If a snooze session is not already active for the user, invoking | ||||
| // this method will begin one for the specified duration. | ||||
| func (api *Client) SetSnooze(minutes int) (*DNDStatus, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":       {api.config.token}, | ||||
| 		"num_minutes": {strconv.Itoa(minutes)}, | ||||
| 	} | ||||
| 	response, err := dndRequest("dnd.setSnooze", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.DNDStatus, nil | ||||
| } | ||||
							
								
								
									
										27
									
								
								vendor/github.com/nlopes/slack/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/nlopes/slack/emoji.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| package slack | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
| type emojiResponseFull struct { | ||||
| 	Emoji map[string]string `json:"emoji"` | ||||
| 	SlackResponse | ||||
| } | ||||
|  | ||||
| // GetEmoji retrieves all the emojis | ||||
| func (api *Client) GetEmoji() (map[string]string, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 	} | ||||
| 	response := &emojiResponseFull{} | ||||
| 	err := post("emoji.list", values, response, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !response.Ok { | ||||
| 		return nil, errors.New(response.Error) | ||||
| 	} | ||||
| 	return response.Emoji, nil | ||||
| } | ||||
							
								
								
									
										19
									
								
								vendor/github.com/nlopes/slack/examples/channels/channels.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/nlopes/slack/examples/channels/channels.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/nlopes/slack" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	api := slack.New("YOUR_TOKEN_HERE") | ||||
| 	channels, err := api.GetChannels(false) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("%s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	for _, channel := range channels { | ||||
| 		fmt.Println(channel.ID) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										30
									
								
								vendor/github.com/nlopes/slack/examples/files/files.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/nlopes/slack/examples/files/files.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/nlopes/slack" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	api := slack.New("YOUR_TOKEN_HERE") | ||||
| 	params := slack.FileUploadParameters{ | ||||
| 		Title: "Batman Example", | ||||
| 		//Filetype: "txt", | ||||
| 		File: "example.txt", | ||||
| 		//Content:  "Nan Nan Nan Nan Nan Nan Nan Nan Batman", | ||||
| 	} | ||||
| 	file, err := api.UploadFile(params) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("%s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Printf("Name: %s, URL: %s\n", file.Name, file.URL) | ||||
|  | ||||
| 	err = api.DeleteFile(file.ID) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("%s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Printf("File %s deleted successfully.\n", file.Name) | ||||
| } | ||||
							
								
								
									
										22
									
								
								vendor/github.com/nlopes/slack/examples/groups/groups.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/nlopes/slack/examples/groups/groups.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/nlopes/slack" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	api := slack.New("YOUR_TOKEN_HERE") | ||||
| 	// If you set debugging, it will log all requests to the console | ||||
| 	// Useful when encountering issues | ||||
| 	// api.SetDebug(true) | ||||
| 	groups, err := api.GetGroups(false) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("%s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	for _, group := range groups { | ||||
| 		fmt.Printf("ID: %s, Name: %s\n", group.ID, group.Name) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										32
									
								
								vendor/github.com/nlopes/slack/examples/messages/messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								vendor/github.com/nlopes/slack/examples/messages/messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/nlopes/slack" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	api := slack.New("YOUR_TOKEN_HERE") | ||||
| 	params := slack.PostMessageParameters{} | ||||
| 	attachment := slack.Attachment{ | ||||
| 		Pretext: "some pretext", | ||||
| 		Text:    "some text", | ||||
| 		// Uncomment the following part to send a field too | ||||
| 		/* | ||||
| 			Fields: []slack.AttachmentField{ | ||||
| 				slack.AttachmentField{ | ||||
| 					Title: "a", | ||||
| 					Value: "no", | ||||
| 				}, | ||||
| 			}, | ||||
| 		*/ | ||||
| 	} | ||||
| 	params.Attachments = []slack.Attachment{attachment} | ||||
| 	channelID, timestamp, err := api.PostMessage("CHANNEL_ID", "Some text", params) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("%s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Printf("Message successfully sent to channel %s at %s", channelID, timestamp) | ||||
| } | ||||
							
								
								
									
										123
									
								
								vendor/github.com/nlopes/slack/examples/pins/pins.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								vendor/github.com/nlopes/slack/examples/pins/pins.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/nlopes/slack" | ||||
| ) | ||||
|  | ||||
| /* | ||||
|    WARNING: This example is destructive in the sense that it create a channel called testpinning | ||||
| */ | ||||
| func main() { | ||||
| 	var ( | ||||
| 		apiToken string | ||||
| 		debug    bool | ||||
| 	) | ||||
|  | ||||
| 	flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token") | ||||
| 	flag.BoolVar(&debug, "debug", false, "Show JSON output") | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	api := slack.New(apiToken) | ||||
| 	if debug { | ||||
| 		api.SetDebug(true) | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		postAsUserName  string | ||||
| 		postAsUserID    string | ||||
| 		postToChannelID string | ||||
| 	) | ||||
|  | ||||
| 	// Find the user to post as. | ||||
| 	authTest, err := api.AuthTest() | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error getting channels: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	channelName := "testpinning" | ||||
|  | ||||
| 	// Post as the authenticated user. | ||||
| 	postAsUserName = authTest.User | ||||
| 	postAsUserID = authTest.UserID | ||||
|  | ||||
| 	// Create a temporary channel | ||||
| 	channel, err := api.CreateChannel(channelName) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		// If the channel exists, that means we just need to unarchive it | ||||
| 		if err.Error() == "name_taken" { | ||||
| 			err = nil | ||||
| 			channels, err := api.GetChannels(false) | ||||
| 			if err != nil { | ||||
| 				fmt.Println("Could not retrieve channels") | ||||
| 				return | ||||
| 			} | ||||
| 			for _, archivedChannel := range channels { | ||||
| 				if archivedChannel.Name == channelName { | ||||
| 					if archivedChannel.IsArchived { | ||||
| 						err = api.UnarchiveChannel(archivedChannel.ID) | ||||
| 						if err != nil { | ||||
| 							fmt.Printf("Could not unarchive %s: %s\n", archivedChannel.ID, err) | ||||
| 							return | ||||
| 						} | ||||
| 					} | ||||
| 					channel = &archivedChannel | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("Error setting test channel for pinning: %s\n", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	postToChannelID = channel.ID | ||||
|  | ||||
| 	fmt.Printf("Posting as %s (%s) in channel %s\n", postAsUserName, postAsUserID, postToChannelID) | ||||
|  | ||||
| 	// Post a message. | ||||
| 	postParams := slack.PostMessageParameters{} | ||||
| 	channelID, timestamp, err := api.PostMessage(postToChannelID, "Is this any good?", postParams) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error posting message: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Grab a reference to the message. | ||||
| 	msgRef := slack.NewRefToMessage(channelID, timestamp) | ||||
|  | ||||
| 	// Add message pin to channel | ||||
| 	if err := api.AddPin(channelID, msgRef); err != nil { | ||||
| 		fmt.Printf("Error adding pin: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// List all of the users pins. | ||||
| 	listPins, _, err := api.ListPins(channelID) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error listing pins: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Printf("\n") | ||||
| 	fmt.Printf("All pins by %s...\n", authTest.User) | ||||
| 	for _, item := range listPins { | ||||
| 		fmt.Printf(" > Item type: %s\n", item.Type) | ||||
| 	} | ||||
|  | ||||
| 	// Remove the pin. | ||||
| 	err = api.RemovePin(channelID, msgRef) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error remove pin: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if err = api.ArchiveChannel(channelID); err != nil { | ||||
| 		fmt.Printf("Error archiving channel: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										126
									
								
								vendor/github.com/nlopes/slack/examples/reactions/reactions.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								vendor/github.com/nlopes/slack/examples/reactions/reactions.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/nlopes/slack" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	var ( | ||||
| 		apiToken string | ||||
| 		debug    bool | ||||
| 	) | ||||
|  | ||||
| 	flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token") | ||||
| 	flag.BoolVar(&debug, "debug", false, "Show JSON output") | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	api := slack.New(apiToken) | ||||
| 	if debug { | ||||
| 		api.SetDebug(true) | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		postAsUserName  string | ||||
| 		postAsUserID    string | ||||
| 		postToUserName  string | ||||
| 		postToUserID    string | ||||
| 		postToChannelID string | ||||
| 	) | ||||
|  | ||||
| 	// Find the user to post as. | ||||
| 	authTest, err := api.AuthTest() | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error getting channels: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Post as the authenticated user. | ||||
| 	postAsUserName = authTest.User | ||||
| 	postAsUserID = authTest.UserID | ||||
|  | ||||
| 	// Posting to DM with self causes a conversation with slackbot. | ||||
| 	postToUserName = authTest.User | ||||
| 	postToUserID = authTest.UserID | ||||
|  | ||||
| 	// Find the channel. | ||||
| 	_, _, chanID, err := api.OpenIMChannel(postToUserID) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error opening IM: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	postToChannelID = chanID | ||||
|  | ||||
| 	fmt.Printf("Posting as %s (%s) in DM with %s (%s), channel %s\n", postAsUserName, postAsUserID, postToUserName, postToUserID, postToChannelID) | ||||
|  | ||||
| 	// Post a message. | ||||
| 	postParams := slack.PostMessageParameters{} | ||||
| 	channelID, timestamp, err := api.PostMessage(postToChannelID, "Is this any good?", postParams) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error posting message: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Grab a reference to the message. | ||||
| 	msgRef := slack.NewRefToMessage(channelID, timestamp) | ||||
|  | ||||
| 	// React with :+1: | ||||
| 	if err := api.AddReaction("+1", msgRef); err != nil { | ||||
| 		fmt.Printf("Error adding reaction: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// React with :-1: | ||||
| 	if err := api.AddReaction("cry", msgRef); err != nil { | ||||
| 		fmt.Printf("Error adding reaction: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Get all reactions on the message. | ||||
| 	msgReactions, err := api.GetReactions(msgRef, slack.NewGetReactionsParameters()) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error getting reactions: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Printf("\n") | ||||
| 	fmt.Printf("%d reactions to message...\n", len(msgReactions)) | ||||
| 	for _, r := range msgReactions { | ||||
| 		fmt.Printf("  %d users say %s\n", r.Count, r.Name) | ||||
| 	} | ||||
|  | ||||
| 	// List all of the users reactions. | ||||
| 	listReactions, _, err := api.ListReactions(slack.NewListReactionsParameters()) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error listing reactions: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Printf("\n") | ||||
| 	fmt.Printf("All reactions by %s...\n", authTest.User) | ||||
| 	for _, item := range listReactions { | ||||
| 		fmt.Printf("%d on a %s...\n", len(item.Reactions), item.Type) | ||||
| 		for _, r := range item.Reactions { | ||||
| 			fmt.Printf("  %s (along with %d others)\n", r.Name, r.Count-1) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Remove the :cry: reaction. | ||||
| 	err = api.RemoveReaction("cry", msgRef) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error remove reaction: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Get all reactions on the message. | ||||
| 	msgReactions, err = api.GetReactions(msgRef, slack.NewGetReactionsParameters()) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error getting reactions: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Printf("\n") | ||||
| 	fmt.Printf("%d reactions to message after removing cry...\n", len(msgReactions)) | ||||
| 	for _, r := range msgReactions { | ||||
| 		fmt.Printf("  %d users say %s\n", r.Count, r.Name) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										46
									
								
								vendor/github.com/nlopes/slack/examples/stars/stars.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								vendor/github.com/nlopes/slack/examples/stars/stars.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/nlopes/slack" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	var ( | ||||
| 		apiToken string | ||||
| 		debug    bool | ||||
| 	) | ||||
|  | ||||
| 	flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token") | ||||
| 	flag.BoolVar(&debug, "debug", false, "Show JSON output") | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	api := slack.New(apiToken) | ||||
| 	if debug { | ||||
| 		api.SetDebug(true) | ||||
| 	} | ||||
|  | ||||
| 	// Get all stars for the usr. | ||||
| 	params := slack.NewStarsParameters() | ||||
| 	starredItems, _, err := api.GetStarred(params) | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("Error getting stars: %s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	for _, s := range starredItems { | ||||
| 		var desc string | ||||
| 		switch s.Type { | ||||
| 		case slack.TYPE_MESSAGE: | ||||
| 			desc = s.Message.Text | ||||
| 		case slack.TYPE_FILE: | ||||
| 			desc = s.File.Name | ||||
| 		case slack.TYPE_FILE_COMMENT: | ||||
| 			desc = s.File.Name + " - " + s.Comment.Comment | ||||
| 		case slack.TYPE_CHANNEL, slack.TYPE_IM, slack.TYPE_GROUP: | ||||
| 			desc = s.Channel | ||||
| 		} | ||||
| 		fmt.Printf("Starred %s: %s\n", s.Type, desc) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										17
									
								
								vendor/github.com/nlopes/slack/examples/users/users.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/nlopes/slack/examples/users/users.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/nlopes/slack" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	api := slack.New("YOUR_TOKEN_HERE") | ||||
| 	user, err := api.GetUserInfo("U023BECGF") | ||||
| 	if err != nil { | ||||
| 		fmt.Printf("%s\n", err) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Printf("ID: %s, Fullname: %s, Email: %s\n", user.ID, user.Profile.RealName, user.Profile.Email) | ||||
| } | ||||
							
								
								
									
										58
									
								
								vendor/github.com/nlopes/slack/examples/websocket/websocket.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								vendor/github.com/nlopes/slack/examples/websocket/websocket.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/nlopes/slack" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	api := slack.New("YOUR TOKEN HERE") | ||||
| 	logger := log.New(os.Stdout, "slack-bot: ", log.Lshortfile|log.LstdFlags) | ||||
| 	slack.SetLogger(logger) | ||||
| 	api.SetDebug(true) | ||||
|  | ||||
| 	rtm := api.NewRTM() | ||||
| 	go rtm.ManageConnection() | ||||
|  | ||||
| Loop: | ||||
| 	for { | ||||
| 		select { | ||||
| 		case msg := <-rtm.IncomingEvents: | ||||
| 			fmt.Print("Event Received: ") | ||||
| 			switch ev := msg.Data.(type) { | ||||
| 			case *slack.HelloEvent: | ||||
| 				// Ignore hello | ||||
|  | ||||
| 			case *slack.ConnectedEvent: | ||||
| 				fmt.Println("Infos:", ev.Info) | ||||
| 				fmt.Println("Connection counter:", ev.ConnectionCount) | ||||
| 				// Replace #general with your Channel ID | ||||
| 				rtm.SendMessage(rtm.NewOutgoingMessage("Hello world", "#general")) | ||||
|  | ||||
| 			case *slack.MessageEvent: | ||||
| 				fmt.Printf("Message: %v\n", ev) | ||||
|  | ||||
| 			case *slack.PresenceChangeEvent: | ||||
| 				fmt.Printf("Presence Change: %v\n", ev) | ||||
|  | ||||
| 			case *slack.LatencyReport: | ||||
| 				fmt.Printf("Current latency: %v\n", ev.Value) | ||||
|  | ||||
| 			case *slack.RTMError: | ||||
| 				fmt.Printf("Error: %s\n", ev.Error()) | ||||
|  | ||||
| 			case *slack.InvalidAuthEvent: | ||||
| 				fmt.Printf("Invalid credentials") | ||||
| 				break Loop | ||||
|  | ||||
| 			default: | ||||
|  | ||||
| 				// Ignore other events.. | ||||
| 				// fmt.Printf("Unexpected: %v\n", msg.Data) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										274
									
								
								vendor/github.com/nlopes/slack/files.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								vendor/github.com/nlopes/slack/files.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,274 @@ | ||||
| package slack | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// Add here the defaults in the siten | ||||
| 	DEFAULT_FILES_USER    = "" | ||||
| 	DEFAULT_FILES_CHANNEL = "" | ||||
| 	DEFAULT_FILES_TS_FROM = 0 | ||||
| 	DEFAULT_FILES_TS_TO   = -1 | ||||
| 	DEFAULT_FILES_TYPES   = "all" | ||||
| 	DEFAULT_FILES_COUNT   = 100 | ||||
| 	DEFAULT_FILES_PAGE    = 1 | ||||
| ) | ||||
|  | ||||
| // File contains all the information for a file | ||||
| type File struct { | ||||
| 	ID        string   `json:"id"` | ||||
| 	Created   JSONTime `json:"created"` | ||||
| 	Timestamp JSONTime `json:"timestamp"` | ||||
|  | ||||
| 	Name              string `json:"name"` | ||||
| 	Title             string `json:"title"` | ||||
| 	Mimetype          string `json:"mimetype"` | ||||
| 	ImageExifRotation int    `json:"image_exif_rotation"` | ||||
| 	Filetype          string `json:"filetype"` | ||||
| 	PrettyType        string `json:"pretty_type"` | ||||
| 	User              string `json:"user"` | ||||
|  | ||||
| 	Mode         string `json:"mode"` | ||||
| 	Editable     bool   `json:"editable"` | ||||
| 	IsExternal   bool   `json:"is_external"` | ||||
| 	ExternalType string `json:"external_type"` | ||||
|  | ||||
| 	Size int `json:"size"` | ||||
|  | ||||
| 	URL                string `json:"url"`          // Deprecated - never set | ||||
| 	URLDownload        string `json:"url_download"` // Deprecated - never set | ||||
| 	URLPrivate         string `json:"url_private"` | ||||
| 	URLPrivateDownload string `json:"url_private_download"` | ||||
|  | ||||
| 	OriginalH   int    `json:"original_h"` | ||||
| 	OriginalW   int    `json:"original_w"` | ||||
| 	Thumb64     string `json:"thumb_64"` | ||||
| 	Thumb80     string `json:"thumb_80"` | ||||
| 	Thumb160    string `json:"thumb_160"` | ||||
| 	Thumb360    string `json:"thumb_360"` | ||||
| 	Thumb360Gif string `json:"thumb_360_gif"` | ||||
| 	Thumb360W   int    `json:"thumb_360_w"` | ||||
| 	Thumb360H   int    `json:"thumb_360_h"` | ||||
| 	Thumb480    string `json:"thumb_480"` | ||||
| 	Thumb480W   int    `json:"thumb_480_w"` | ||||
| 	Thumb480H   int    `json:"thumb_480_h"` | ||||
| 	Thumb720    string `json:"thumb_720"` | ||||
| 	Thumb720W   int    `json:"thumb_720_w"` | ||||
| 	Thumb720H   int    `json:"thumb_720_h"` | ||||
| 	Thumb960    string `json:"thumb_960"` | ||||
| 	Thumb960W   int    `json:"thumb_960_w"` | ||||
| 	Thumb960H   int    `json:"thumb_960_h"` | ||||
| 	Thumb1024   string `json:"thumb_1024"` | ||||
| 	Thumb1024W  int    `json:"thumb_1024_w"` | ||||
| 	Thumb1024H  int    `json:"thumb_1024_h"` | ||||
|  | ||||
| 	Permalink       string `json:"permalink"` | ||||
| 	PermalinkPublic string `json:"permalink_public"` | ||||
|  | ||||
| 	EditLink         string `json:"edit_link"` | ||||
| 	Preview          string `json:"preview"` | ||||
| 	PreviewHighlight string `json:"preview_highlight"` | ||||
| 	Lines            int    `json:"lines"` | ||||
| 	LinesMore        int    `json:"lines_more"` | ||||
|  | ||||
| 	IsPublic        bool     `json:"is_public"` | ||||
| 	PublicURLShared bool     `json:"public_url_shared"` | ||||
| 	Channels        []string `json:"channels"` | ||||
| 	Groups          []string `json:"groups"` | ||||
| 	IMs             []string `json:"ims"` | ||||
| 	InitialComment  Comment  `json:"initial_comment"` | ||||
| 	CommentsCount   int      `json:"comments_count"` | ||||
| 	NumStars        int      `json:"num_stars"` | ||||
| 	IsStarred       bool     `json:"is_starred"` | ||||
| } | ||||
|  | ||||
| // FileUploadParameters contains all the parameters necessary (including the optional ones) for an UploadFile() request | ||||
| type FileUploadParameters struct { | ||||
| 	File           string | ||||
| 	Content        string | ||||
| 	Filetype       string | ||||
| 	Filename       string | ||||
| 	Title          string | ||||
| 	InitialComment string | ||||
| 	Channels       []string | ||||
| } | ||||
|  | ||||
| // GetFilesParameters contains all the parameters necessary (including the optional ones) for a GetFiles() request | ||||
| type GetFilesParameters struct { | ||||
| 	User          string | ||||
| 	Channel       string | ||||
| 	TimestampFrom JSONTime | ||||
| 	TimestampTo   JSONTime | ||||
| 	Types         string | ||||
| 	Count         int | ||||
| 	Page          int | ||||
| } | ||||
|  | ||||
| type fileResponseFull struct { | ||||
| 	File     `json:"file"` | ||||
| 	Paging   `json:"paging"` | ||||
| 	Comments []Comment `json:"comments"` | ||||
| 	Files    []File    `json:"files"` | ||||
|  | ||||
| 	SlackResponse | ||||
| } | ||||
|  | ||||
| // NewGetFilesParameters provides an instance of GetFilesParameters with all the sane default values set | ||||
| func NewGetFilesParameters() GetFilesParameters { | ||||
| 	return GetFilesParameters{ | ||||
| 		User:          DEFAULT_FILES_USER, | ||||
| 		Channel:       DEFAULT_FILES_CHANNEL, | ||||
| 		TimestampFrom: DEFAULT_FILES_TS_FROM, | ||||
| 		TimestampTo:   DEFAULT_FILES_TS_TO, | ||||
| 		Types:         DEFAULT_FILES_TYPES, | ||||
| 		Count:         DEFAULT_FILES_COUNT, | ||||
| 		Page:          DEFAULT_FILES_PAGE, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func fileRequest(path string, values url.Values, debug bool) (*fileResponseFull, error) { | ||||
| 	response := &fileResponseFull{} | ||||
| 	err := post(path, values, response, debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !response.Ok { | ||||
| 		return nil, errors.New(response.Error) | ||||
| 	} | ||||
| 	return response, nil | ||||
| } | ||||
|  | ||||
| // GetFileInfo retrieves a file and related comments | ||||
| func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 		"file":  {fileID}, | ||||
| 		"count": {strconv.Itoa(count)}, | ||||
| 		"page":  {strconv.Itoa(page)}, | ||||
| 	} | ||||
| 	response, err := fileRequest("files.info", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, nil, err | ||||
| 	} | ||||
| 	return &response.File, response.Comments, &response.Paging, nil | ||||
| } | ||||
|  | ||||
| // GetFiles retrieves all files according to the parameters given | ||||
| func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 	} | ||||
| 	if params.User != DEFAULT_FILES_USER { | ||||
| 		values.Add("user", params.User) | ||||
| 	} | ||||
| 	if params.Channel != DEFAULT_FILES_CHANNEL { | ||||
| 		values.Add("channel", params.Channel) | ||||
| 	} | ||||
| 	// XXX: this is broken. fix it with a proper unix timestamp | ||||
| 	if params.TimestampFrom != DEFAULT_FILES_TS_FROM { | ||||
| 		values.Add("ts_from", params.TimestampFrom.String()) | ||||
| 	} | ||||
| 	if params.TimestampTo != DEFAULT_FILES_TS_TO { | ||||
| 		values.Add("ts_to", params.TimestampTo.String()) | ||||
| 	} | ||||
| 	if params.Types != DEFAULT_FILES_TYPES { | ||||
| 		values.Add("types", params.Types) | ||||
| 	} | ||||
| 	if params.Count != DEFAULT_FILES_COUNT { | ||||
| 		values.Add("count", strconv.Itoa(params.Count)) | ||||
| 	} | ||||
| 	if params.Page != DEFAULT_FILES_PAGE { | ||||
| 		values.Add("page", strconv.Itoa(params.Page)) | ||||
| 	} | ||||
| 	response, err := fileRequest("files.list", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return response.Files, &response.Paging, nil | ||||
| } | ||||
|  | ||||
| // UploadFile uploads a file | ||||
| func (api *Client) UploadFile(params FileUploadParameters) (file *File, err error) { | ||||
| 	// Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More | ||||
| 	// investigation needed, but for now this will do. | ||||
| 	_, err = api.AuthTest() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	response := &fileResponseFull{} | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 	} | ||||
| 	if params.Filetype != "" { | ||||
| 		values.Add("filetype", params.Filetype) | ||||
| 	} | ||||
| 	if params.Filename != "" { | ||||
| 		values.Add("filename", params.Filename) | ||||
| 	} | ||||
| 	if params.Title != "" { | ||||
| 		values.Add("title", params.Title) | ||||
| 	} | ||||
| 	if params.InitialComment != "" { | ||||
| 		values.Add("initial_comment", params.InitialComment) | ||||
| 	} | ||||
| 	if len(params.Channels) != 0 { | ||||
| 		values.Add("channels", strings.Join(params.Channels, ",")) | ||||
| 	} | ||||
| 	if params.Content != "" { | ||||
| 		values.Add("content", params.Content) | ||||
| 		err = post("files.upload", values, response, api.debug) | ||||
| 	} else if params.File != "" { | ||||
| 		err = postWithMultipartResponse("files.upload", params.File, values, response, api.debug) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !response.Ok { | ||||
| 		return nil, errors.New(response.Error) | ||||
| 	} | ||||
| 	return &response.File, nil | ||||
| } | ||||
|  | ||||
| // DeleteFile deletes a file | ||||
| func (api *Client) DeleteFile(fileID string) error { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 		"file":  {fileID}, | ||||
| 	} | ||||
| 	_, err := fileRequest("files.delete", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
|  | ||||
| } | ||||
|  | ||||
| // RevokeFilePublicURL disables public/external sharing for a file | ||||
| func (api *Client) RevokeFilePublicURL(fileID string) (*File, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 		"file":  {fileID}, | ||||
| 	} | ||||
| 	response, err := fileRequest("files.revokePublicURL", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.File, nil | ||||
| } | ||||
|  | ||||
| // ShareFilePublicURL enabled public/external sharing for a file | ||||
| func (api *Client) ShareFilePublicURL(fileID string) (*File, []Comment, *Paging, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 		"file":  {fileID}, | ||||
| 	} | ||||
| 	response, err := fileRequest("files.sharedPublicURL", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, nil, err | ||||
| 	} | ||||
| 	return &response.File, response.Comments, &response.Paging, nil | ||||
| } | ||||
							
								
								
									
										293
									
								
								vendor/github.com/nlopes/slack/groups.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								vendor/github.com/nlopes/slack/groups.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,293 @@ | ||||
| package slack | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| // Group contains all the information for a group | ||||
| type Group struct { | ||||
| 	groupConversation | ||||
| 	IsGroup bool `json:"is_group"` | ||||
| } | ||||
|  | ||||
| type groupResponseFull struct { | ||||
| 	Group          Group   `json:"group"` | ||||
| 	Groups         []Group `json:"groups"` | ||||
| 	Purpose        string  `json:"purpose"` | ||||
| 	Topic          string  `json:"topic"` | ||||
| 	NotInGroup     bool    `json:"not_in_group"` | ||||
| 	NoOp           bool    `json:"no_op"` | ||||
| 	AlreadyClosed  bool    `json:"already_closed"` | ||||
| 	AlreadyOpen    bool    `json:"already_open"` | ||||
| 	AlreadyInGroup bool    `json:"already_in_group"` | ||||
| 	Channel        Channel `json:"channel"` | ||||
| 	History | ||||
| 	SlackResponse | ||||
| } | ||||
|  | ||||
| func groupRequest(path string, values url.Values, debug bool) (*groupResponseFull, error) { | ||||
| 	response := &groupResponseFull{} | ||||
| 	err := post(path, values, response, debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !response.Ok { | ||||
| 		return nil, errors.New(response.Error) | ||||
| 	} | ||||
| 	return response, nil | ||||
| } | ||||
|  | ||||
| // ArchiveGroup archives a private group | ||||
| func (api *Client) ArchiveGroup(group string) error { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 	} | ||||
| 	_, err := groupRequest("groups.archive", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // UnarchiveGroup unarchives a private group | ||||
| func (api *Client) UnarchiveGroup(group string) error { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 	} | ||||
| 	_, err := groupRequest("groups.unarchive", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // CreateGroup creates a private group | ||||
| func (api *Client) CreateGroup(group string) (*Group, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 		"name":  {group}, | ||||
| 	} | ||||
| 	response, err := groupRequest("groups.create", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.Group, nil | ||||
| } | ||||
|  | ||||
| // CreateChildGroup creates a new private group archiving the old one | ||||
| // This method takes an existing private group and performs the following steps: | ||||
| //   1. Renames the existing group (from "example" to "example-archived"). | ||||
| //   2. Archives the existing group. | ||||
| //   3. Creates a new group with the name of the existing group. | ||||
| //   4. Adds all members of the existing group to the new group. | ||||
| func (api *Client) CreateChildGroup(group string) (*Group, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 	} | ||||
| 	response, err := groupRequest("groups.createChild", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.Group, nil | ||||
| } | ||||
|  | ||||
| // CloseGroup closes a private group | ||||
| func (api *Client) CloseGroup(group string) (bool, bool, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 	} | ||||
| 	response, err := imRequest("groups.close", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return false, false, err | ||||
| 	} | ||||
| 	return response.NoOp, response.AlreadyClosed, nil | ||||
| } | ||||
|  | ||||
| // GetGroupHistory fetches all the history for a private group | ||||
| func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*History, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 	} | ||||
| 	if params.Latest != DEFAULT_HISTORY_LATEST { | ||||
| 		values.Add("latest", params.Latest) | ||||
| 	} | ||||
| 	if params.Oldest != DEFAULT_HISTORY_OLDEST { | ||||
| 		values.Add("oldest", params.Oldest) | ||||
| 	} | ||||
| 	if params.Count != DEFAULT_HISTORY_COUNT { | ||||
| 		values.Add("count", strconv.Itoa(params.Count)) | ||||
| 	} | ||||
| 	if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE { | ||||
| 		if params.Inclusive { | ||||
| 			values.Add("inclusive", "1") | ||||
| 		} else { | ||||
| 			values.Add("inclusive", "0") | ||||
| 		} | ||||
| 	} | ||||
| 	if params.Unreads != DEFAULT_HISTORY_UNREADS { | ||||
| 		if params.Unreads { | ||||
| 			values.Add("unreads", "1") | ||||
| 		} else { | ||||
| 			values.Add("unreads", "0") | ||||
| 		} | ||||
| 	} | ||||
| 	response, err := groupRequest("groups.history", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.History, nil | ||||
| } | ||||
|  | ||||
| // InviteUserToGroup invites a specific user to a private group | ||||
| func (api *Client) InviteUserToGroup(group, user string) (*Group, bool, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 		"user":    {user}, | ||||
| 	} | ||||
| 	response, err := groupRequest("groups.invite", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, false, err | ||||
| 	} | ||||
| 	return &response.Group, response.AlreadyInGroup, nil | ||||
| } | ||||
|  | ||||
| // LeaveGroup makes authenticated user leave the group | ||||
| func (api *Client) LeaveGroup(group string) error { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 	} | ||||
| 	_, err := groupRequest("groups.leave", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // KickUserFromGroup kicks a user from a group | ||||
| func (api *Client) KickUserFromGroup(group, user string) error { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 		"user":    {user}, | ||||
| 	} | ||||
| 	_, err := groupRequest("groups.kick", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetGroups retrieves all groups | ||||
| func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 	} | ||||
| 	if excludeArchived { | ||||
| 		values.Add("exclude_archived", "1") | ||||
| 	} | ||||
| 	response, err := groupRequest("groups.list", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return response.Groups, nil | ||||
| } | ||||
|  | ||||
| // GetGroupInfo retrieves the given group | ||||
| func (api *Client) GetGroupInfo(group string) (*Group, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 	} | ||||
| 	response, err := groupRequest("groups.info", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.Group, nil | ||||
| } | ||||
|  | ||||
| // SetGroupReadMark sets the read mark on a private group | ||||
| // Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a | ||||
| // timer before making the call. In this way, any further updates needed during the timeout will not generate extra | ||||
| // calls (just one per channel). This is useful for when reading scroll-back history, or following a busy live | ||||
| // channel. A timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout. | ||||
| func (api *Client) SetGroupReadMark(group, ts string) error { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 		"ts":      {ts}, | ||||
| 	} | ||||
| 	_, err := groupRequest("groups.mark", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // OpenGroup opens a private group | ||||
| func (api *Client) OpenGroup(group string) (bool, bool, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 	} | ||||
| 	response, err := groupRequest("groups.open", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return false, false, err | ||||
| 	} | ||||
| 	return response.NoOp, response.AlreadyOpen, nil | ||||
| } | ||||
|  | ||||
| // RenameGroup renames a group | ||||
| // XXX: They return a channel, not a group. What is this crap? :( | ||||
| // Inconsistent api it seems. | ||||
| func (api *Client) RenameGroup(group, name string) (*Channel, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 		"name":    {name}, | ||||
| 	} | ||||
| 	// XXX: the created entry in this call returns a string instead of a number | ||||
| 	// so I may have to do some workaround to solve it. | ||||
| 	response, err := groupRequest("groups.rename", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.Channel, nil | ||||
|  | ||||
| } | ||||
|  | ||||
| // SetGroupPurpose sets the group purpose | ||||
| func (api *Client) SetGroupPurpose(group, purpose string) (string, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 		"purpose": {purpose}, | ||||
| 	} | ||||
| 	response, err := groupRequest("groups.setPurpose", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return response.Purpose, nil | ||||
| } | ||||
|  | ||||
| // SetGroupTopic sets the group topic | ||||
| func (api *Client) SetGroupTopic(group, topic string) (string, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {group}, | ||||
| 		"topic":   {topic}, | ||||
| 	} | ||||
| 	response, err := groupRequest("groups.setTopic", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return response.Topic, nil | ||||
| } | ||||
							
								
								
									
										36
									
								
								vendor/github.com/nlopes/slack/history.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								vendor/github.com/nlopes/slack/history.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| package slack | ||||
|  | ||||
| const ( | ||||
| 	DEFAULT_HISTORY_LATEST    = "" | ||||
| 	DEFAULT_HISTORY_OLDEST    = "0" | ||||
| 	DEFAULT_HISTORY_COUNT     = 100 | ||||
| 	DEFAULT_HISTORY_INCLUSIVE = false | ||||
| 	DEFAULT_HISTORY_UNREADS   = false | ||||
| ) | ||||
|  | ||||
| // HistoryParameters contains all the necessary information to help in the retrieval of history for Channels/Groups/DMs | ||||
| type HistoryParameters struct { | ||||
| 	Latest    string | ||||
| 	Oldest    string | ||||
| 	Count     int | ||||
| 	Inclusive bool | ||||
| 	Unreads   bool | ||||
| } | ||||
|  | ||||
| // History contains message history information needed to navigate a Channel / Group / DM history | ||||
| type History struct { | ||||
| 	Latest   string    `json:"latest"` | ||||
| 	Messages []Message `json:"messages"` | ||||
| 	HasMore  bool      `json:"has_more"` | ||||
| } | ||||
|  | ||||
| // NewHistoryParameters provides an instance of HistoryParameters with all the sane default values set | ||||
| func NewHistoryParameters() HistoryParameters { | ||||
| 	return HistoryParameters{ | ||||
| 		Latest:    DEFAULT_HISTORY_LATEST, | ||||
| 		Oldest:    DEFAULT_HISTORY_OLDEST, | ||||
| 		Count:     DEFAULT_HISTORY_COUNT, | ||||
| 		Inclusive: DEFAULT_HISTORY_INCLUSIVE, | ||||
| 		Unreads:   DEFAULT_HISTORY_UNREADS, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										130
									
								
								vendor/github.com/nlopes/slack/im.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								vendor/github.com/nlopes/slack/im.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| package slack | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| type imChannel struct { | ||||
| 	ID string `json:"id"` | ||||
| } | ||||
|  | ||||
| type imResponseFull struct { | ||||
| 	NoOp          bool      `json:"no_op"` | ||||
| 	AlreadyClosed bool      `json:"already_closed"` | ||||
| 	AlreadyOpen   bool      `json:"already_open"` | ||||
| 	Channel       imChannel `json:"channel"` | ||||
| 	IMs           []IM      `json:"ims"` | ||||
| 	History | ||||
| 	SlackResponse | ||||
| } | ||||
|  | ||||
| // IM contains information related to the Direct Message channel | ||||
| type IM struct { | ||||
| 	conversation | ||||
| 	IsIM          bool   `json:"is_im"` | ||||
| 	User          string `json:"user"` | ||||
| 	IsUserDeleted bool   `json:"is_user_deleted"` | ||||
| } | ||||
|  | ||||
| func imRequest(path string, values url.Values, debug bool) (*imResponseFull, error) { | ||||
| 	response := &imResponseFull{} | ||||
| 	err := post(path, values, response, debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !response.Ok { | ||||
| 		return nil, errors.New(response.Error) | ||||
| 	} | ||||
| 	return response, nil | ||||
| } | ||||
|  | ||||
| // CloseIMChannel closes the direct message channel | ||||
| func (api *Client) CloseIMChannel(channel string) (bool, bool, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 	} | ||||
| 	response, err := imRequest("im.close", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return false, false, err | ||||
| 	} | ||||
| 	return response.NoOp, response.AlreadyClosed, nil | ||||
| } | ||||
|  | ||||
| // OpenIMChannel opens a direct message channel to the user provided as argument | ||||
| // Returns some status and the channel ID | ||||
| func (api *Client) OpenIMChannel(user string) (bool, bool, string, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 		"user":  {user}, | ||||
| 	} | ||||
| 	response, err := imRequest("im.open", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return false, false, "", err | ||||
| 	} | ||||
| 	return response.NoOp, response.AlreadyOpen, response.Channel.ID, nil | ||||
| } | ||||
|  | ||||
| // MarkIMChannel sets the read mark of a direct message channel to a specific point | ||||
| func (api *Client) MarkIMChannel(channel, ts string) (err error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 		"ts":      {ts}, | ||||
| 	} | ||||
| 	_, err = imRequest("im.mark", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // GetIMHistory retrieves the direct message channel history | ||||
| func (api *Client) GetIMHistory(channel string, params HistoryParameters) (*History, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token":   {api.config.token}, | ||||
| 		"channel": {channel}, | ||||
| 	} | ||||
| 	if params.Latest != DEFAULT_HISTORY_LATEST { | ||||
| 		values.Add("latest", params.Latest) | ||||
| 	} | ||||
| 	if params.Oldest != DEFAULT_HISTORY_OLDEST { | ||||
| 		values.Add("oldest", params.Oldest) | ||||
| 	} | ||||
| 	if params.Count != DEFAULT_HISTORY_COUNT { | ||||
| 		values.Add("count", strconv.Itoa(params.Count)) | ||||
| 	} | ||||
| 	if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE { | ||||
| 		if params.Inclusive { | ||||
| 			values.Add("inclusive", "1") | ||||
| 		} else { | ||||
| 			values.Add("inclusive", "0") | ||||
| 		} | ||||
| 	} | ||||
| 	if params.Unreads != DEFAULT_HISTORY_UNREADS { | ||||
| 		if params.Unreads { | ||||
| 			values.Add("unreads", "1") | ||||
| 		} else { | ||||
| 			values.Add("unreads", "0") | ||||
| 		} | ||||
| 	} | ||||
| 	response, err := imRequest("im.history", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &response.History, nil | ||||
| } | ||||
|  | ||||
| // GetIMChannels returns the list of direct message channels | ||||
| func (api *Client) GetIMChannels() ([]IM, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 	} | ||||
| 	response, err := imRequest("im.list", values, api.debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return response.IMs, nil | ||||
| } | ||||
							
								
								
									
										206
									
								
								vendor/github.com/nlopes/slack/info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								vendor/github.com/nlopes/slack/info.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | ||||
| package slack | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // UserPrefs needs to be implemented | ||||
| type UserPrefs struct { | ||||
| 	// "highlight_words":"", | ||||
| 	// "user_colors":"", | ||||
| 	// "color_names_in_list":true, | ||||
| 	// "growls_enabled":true, | ||||
| 	// "tz":"Europe\/London", | ||||
| 	// "push_dm_alert":true, | ||||
| 	// "push_mention_alert":true, | ||||
| 	// "push_everything":true, | ||||
| 	// "push_idle_wait":2, | ||||
| 	// "push_sound":"b2.mp3", | ||||
| 	// "push_loud_channels":"", | ||||
| 	// "push_mention_channels":"", | ||||
| 	// "push_loud_channels_set":"", | ||||
| 	// "email_alerts":"instant", | ||||
| 	// "email_alerts_sleep_until":0, | ||||
| 	// "email_misc":false, | ||||
| 	// "email_weekly":true, | ||||
| 	// "welcome_message_hidden":false, | ||||
| 	// "all_channels_loud":true, | ||||
| 	// "loud_channels":"", | ||||
| 	// "never_channels":"", | ||||
| 	// "loud_channels_set":"", | ||||
| 	// "show_member_presence":true, | ||||
| 	// "search_sort":"timestamp", | ||||
| 	// "expand_inline_imgs":true, | ||||
| 	// "expand_internal_inline_imgs":true, | ||||
| 	// "expand_snippets":false, | ||||
| 	// "posts_formatting_guide":true, | ||||
| 	// "seen_welcome_2":true, | ||||
| 	// "seen_ssb_prompt":false, | ||||
| 	// "search_only_my_channels":false, | ||||
| 	// "emoji_mode":"default", | ||||
| 	// "has_invited":true, | ||||
| 	// "has_uploaded":false, | ||||
| 	// "has_created_channel":true, | ||||
| 	// "search_exclude_channels":"", | ||||
| 	// "messages_theme":"default", | ||||
| 	// "webapp_spellcheck":true, | ||||
| 	// "no_joined_overlays":false, | ||||
| 	// "no_created_overlays":true, | ||||
| 	// "dropbox_enabled":false, | ||||
| 	// "seen_user_menu_tip_card":true, | ||||
| 	// "seen_team_menu_tip_card":true, | ||||
| 	// "seen_channel_menu_tip_card":true, | ||||
| 	// "seen_message_input_tip_card":true, | ||||
| 	// "seen_channels_tip_card":true, | ||||
| 	// "seen_domain_invite_reminder":false, | ||||
| 	// "seen_member_invite_reminder":false, | ||||
| 	// "seen_flexpane_tip_card":true, | ||||
| 	// "seen_search_input_tip_card":true, | ||||
| 	// "mute_sounds":false, | ||||
| 	// "arrow_history":false, | ||||
| 	// "tab_ui_return_selects":true, | ||||
| 	// "obey_inline_img_limit":true, | ||||
| 	// "new_msg_snd":"knock_brush.mp3", | ||||
| 	// "collapsible":false, | ||||
| 	// "collapsible_by_click":true, | ||||
| 	// "require_at":false, | ||||
| 	// "mac_ssb_bounce":"", | ||||
| 	// "mac_ssb_bullet":true, | ||||
| 	// "win_ssb_bullet":true, | ||||
| 	// "expand_non_media_attachments":true, | ||||
| 	// "show_typing":true, | ||||
| 	// "pagekeys_handled":true, | ||||
| 	// "last_snippet_type":"", | ||||
| 	// "display_real_names_override":0, | ||||
| 	// "time24":false, | ||||
| 	// "enter_is_special_in_tbt":false, | ||||
| 	// "graphic_emoticons":false, | ||||
| 	// "convert_emoticons":true, | ||||
| 	// "autoplay_chat_sounds":true, | ||||
| 	// "ss_emojis":true, | ||||
| 	// "sidebar_behavior":"", | ||||
| 	// "mark_msgs_read_immediately":true, | ||||
| 	// "start_scroll_at_oldest":true, | ||||
| 	// "snippet_editor_wrap_long_lines":false, | ||||
| 	// "ls_disabled":false, | ||||
| 	// "sidebar_theme":"default", | ||||
| 	// "sidebar_theme_custom_values":"", | ||||
| 	// "f_key_search":false, | ||||
| 	// "k_key_omnibox":true, | ||||
| 	// "speak_growls":false, | ||||
| 	// "mac_speak_voice":"com.apple.speech.synthesis.voice.Alex", | ||||
| 	// "mac_speak_speed":250, | ||||
| 	// "comma_key_prefs":false, | ||||
| 	// "at_channel_suppressed_channels":"", | ||||
| 	// "push_at_channel_suppressed_channels":"", | ||||
| 	// "prompted_for_email_disabling":false, | ||||
| 	// "full_text_extracts":false, | ||||
| 	// "no_text_in_notifications":false, | ||||
| 	// "muted_channels":"", | ||||
| 	// "no_macssb1_banner":false, | ||||
| 	// "privacy_policy_seen":true, | ||||
| 	// "search_exclude_bots":false, | ||||
| 	// "fuzzy_matching":false | ||||
| } | ||||
|  | ||||
| // UserDetails contains user details coming in the initial response from StartRTM | ||||
| type UserDetails struct { | ||||
| 	ID             string    `json:"id"` | ||||
| 	Name           string    `json:"name"` | ||||
| 	Created        JSONTime  `json:"created"` | ||||
| 	ManualPresence string    `json:"manual_presence"` | ||||
| 	Prefs          UserPrefs `json:"prefs"` | ||||
| } | ||||
|  | ||||
| // JSONTime exists so that we can have a String method converting the date | ||||
| type JSONTime int64 | ||||
|  | ||||
| // String converts the unix timestamp into a string | ||||
| func (t JSONTime) String() string { | ||||
| 	tm := t.Time() | ||||
| 	return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2")) | ||||
| } | ||||
|  | ||||
| // Time returns a `time.Time` representation of this value. | ||||
| func (t JSONTime) Time() time.Time { | ||||
| 	return time.Unix(int64(t), 0) | ||||
| } | ||||
|  | ||||
| // Team contains details about a team | ||||
| type Team struct { | ||||
| 	ID     string `json:"id"` | ||||
| 	Name   string `json:"name"` | ||||
| 	Domain string `json:"domain"` | ||||
| } | ||||
|  | ||||
| // Icons XXX: needs further investigation | ||||
| type Icons struct { | ||||
| 	Image48 string `json:"image_48"` | ||||
| } | ||||
|  | ||||
| // Bot contains information about a bot | ||||
| type Bot struct { | ||||
| 	ID      string `json:"id"` | ||||
| 	Name    string `json:"name"` | ||||
| 	Deleted bool   `json:"deleted"` | ||||
| 	Icons   Icons  `json:"icons"` | ||||
| } | ||||
|  | ||||
| // Info contains various details about Users, Channels, Bots and the authenticated user. | ||||
| // It is returned by StartRTM or included in the "ConnectedEvent" RTM event. | ||||
| type Info struct { | ||||
| 	URL      string       `json:"url,omitempty"` | ||||
| 	User     *UserDetails `json:"self,omitempty"` | ||||
| 	Team     *Team        `json:"team,omitempty"` | ||||
| 	Users    []User       `json:"users,omitempty"` | ||||
| 	Channels []Channel    `json:"channels,omitempty"` | ||||
| 	Groups   []Group      `json:"groups,omitempty"` | ||||
| 	Bots     []Bot        `json:"bots,omitempty"` | ||||
| 	IMs      []IM         `json:"ims,omitempty"` | ||||
| } | ||||
|  | ||||
| type infoResponseFull struct { | ||||
| 	Info | ||||
| 	WebResponse | ||||
| } | ||||
|  | ||||
| // GetBotByID returns a bot given a bot id | ||||
| func (info Info) GetBotByID(botID string) *Bot { | ||||
| 	for _, bot := range info.Bots { | ||||
| 		if bot.ID == botID { | ||||
| 			return &bot | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetUserByID returns a user given a user id | ||||
| func (info Info) GetUserByID(userID string) *User { | ||||
| 	for _, user := range info.Users { | ||||
| 		if user.ID == userID { | ||||
| 			return &user | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetChannelByID returns a channel given a channel id | ||||
| func (info Info) GetChannelByID(channelID string) *Channel { | ||||
| 	for _, channel := range info.Channels { | ||||
| 		if channel.ID == channelID { | ||||
| 			return &channel | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetGroupByID returns a group given a group id | ||||
| func (info Info) GetGroupByID(groupID string) *Group { | ||||
| 	for _, group := range info.Groups { | ||||
| 		if group.ID == groupID { | ||||
| 			return &group | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										75
									
								
								vendor/github.com/nlopes/slack/item.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								vendor/github.com/nlopes/slack/item.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| package slack | ||||
|  | ||||
| const ( | ||||
| 	TYPE_MESSAGE      = "message" | ||||
| 	TYPE_FILE         = "file" | ||||
| 	TYPE_FILE_COMMENT = "file_comment" | ||||
| 	TYPE_CHANNEL      = "channel" | ||||
| 	TYPE_IM           = "im" | ||||
| 	TYPE_GROUP        = "group" | ||||
| ) | ||||
|  | ||||
| // Item is any type of slack message - message, file, or file comment. | ||||
| type Item struct { | ||||
| 	Type      string   `json:"type"` | ||||
| 	Channel   string   `json:"channel,omitempty"` | ||||
| 	Message   *Message `json:"message,omitempty"` | ||||
| 	File      *File    `json:"file,omitempty"` | ||||
| 	Comment   *Comment `json:"comment,omitempty"` | ||||
| 	Timestamp string   `json:"ts,omitempty"` | ||||
| } | ||||
|  | ||||
| // NewMessageItem turns a message on a channel into a typed message struct. | ||||
| func NewMessageItem(ch string, m *Message) Item { | ||||
| 	return Item{Type: TYPE_MESSAGE, Channel: ch, Message: m} | ||||
| } | ||||
|  | ||||
| // NewFileItem turns a file into a typed file struct. | ||||
| func NewFileItem(f *File) Item { | ||||
| 	return Item{Type: TYPE_FILE, File: f} | ||||
| } | ||||
|  | ||||
| // NewFileCommentItem turns a file and comment into a typed file_comment struct. | ||||
| func NewFileCommentItem(f *File, c *Comment) Item { | ||||
| 	return Item{Type: TYPE_FILE_COMMENT, File: f, Comment: c} | ||||
| } | ||||
|  | ||||
| // NewChannelItem turns a channel id into a typed channel struct. | ||||
| func NewChannelItem(ch string) Item { | ||||
| 	return Item{Type: TYPE_CHANNEL, Channel: ch} | ||||
| } | ||||
|  | ||||
| // NewIMItem turns a channel id into a typed im struct. | ||||
| func NewIMItem(ch string) Item { | ||||
| 	return Item{Type: TYPE_IM, Channel: ch} | ||||
| } | ||||
|  | ||||
| // NewGroupItem turns a channel id into a typed group struct. | ||||
| func NewGroupItem(ch string) Item { | ||||
| 	return Item{Type: TYPE_GROUP, Channel: ch} | ||||
| } | ||||
|  | ||||
| // ItemRef is a reference to a message of any type. One of FileID, | ||||
| // CommentId, or the combination of ChannelId and Timestamp must be | ||||
| // specified. | ||||
| type ItemRef struct { | ||||
| 	Channel   string `json:"channel"` | ||||
| 	Timestamp string `json:"timestamp"` | ||||
| 	File      string `json:"file"` | ||||
| 	Comment   string `json:"file_comment"` | ||||
| } | ||||
|  | ||||
| // NewRefToMessage initializes a reference to to a message. | ||||
| func NewRefToMessage(channel, timestamp string) ItemRef { | ||||
| 	return ItemRef{Channel: channel, Timestamp: timestamp} | ||||
| } | ||||
|  | ||||
| // NewRefToFile initializes a reference to a file. | ||||
| func NewRefToFile(file string) ItemRef { | ||||
| 	return ItemRef{File: file} | ||||
| } | ||||
|  | ||||
| // NewRefToComment initializes a reference to a file comment. | ||||
| func NewRefToComment(comment string) ItemRef { | ||||
| 	return ItemRef{Comment: comment} | ||||
| } | ||||
							
								
								
									
										30
									
								
								vendor/github.com/nlopes/slack/messageID.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/nlopes/slack/messageID.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| package slack | ||||
|  | ||||
| import "sync" | ||||
|  | ||||
| // IDGenerator provides an interface for generating integer ID values. | ||||
| type IDGenerator interface { | ||||
| 	Next() int | ||||
| } | ||||
|  | ||||
| // NewSafeID returns a new instance of an IDGenerator which is safe for | ||||
| // concurrent use by multiple goroutines. | ||||
| func NewSafeID(startID int) IDGenerator { | ||||
| 	return &safeID{ | ||||
| 		nextID: startID, | ||||
| 		mutex:  &sync.Mutex{}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type safeID struct { | ||||
| 	nextID int | ||||
| 	mutex  *sync.Mutex | ||||
| } | ||||
|  | ||||
| func (s *safeID) Next() int { | ||||
| 	s.mutex.Lock() | ||||
| 	defer s.mutex.Unlock() | ||||
| 	id := s.nextID | ||||
| 	s.nextID++ | ||||
| 	return id | ||||
| } | ||||
							
								
								
									
										131
									
								
								vendor/github.com/nlopes/slack/messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								vendor/github.com/nlopes/slack/messages.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | ||||
| package slack | ||||
|  | ||||
| // OutgoingMessage is used for the realtime API, and seems incomplete. | ||||
| type OutgoingMessage struct { | ||||
| 	ID      int    `json:"id"` | ||||
| 	Channel string `json:"channel,omitempty"` | ||||
| 	Text    string `json:"text,omitempty"` | ||||
| 	Type    string `json:"type,omitempty"` | ||||
| } | ||||
|  | ||||
| // Message is an auxiliary type to allow us to have a message containing sub messages | ||||
| type Message struct { | ||||
| 	Msg | ||||
| 	SubMessage *Msg `json:"message,omitempty"` | ||||
| } | ||||
|  | ||||
| // Msg contains information about a slack message | ||||
| type Msg struct { | ||||
| 	// Basic Message | ||||
| 	Type        string       `json:"type,omitempty"` | ||||
| 	Channel     string       `json:"channel,omitempty"` | ||||
| 	User        string       `json:"user,omitempty"` | ||||
| 	Text        string       `json:"text,omitempty"` | ||||
| 	Timestamp   string       `json:"ts,omitempty"` | ||||
| 	IsStarred   bool         `json:"is_starred,omitempty"` | ||||
| 	PinnedTo    []string     `json:"pinned_to, omitempty"` | ||||
| 	Attachments []Attachment `json:"attachments,omitempty"` | ||||
| 	Edited      *Edited      `json:"edited,omitempty"` | ||||
|  | ||||
| 	// Message Subtypes | ||||
| 	SubType string `json:"subtype,omitempty"` | ||||
|  | ||||
| 	// Hidden Subtypes | ||||
| 	Hidden           bool   `json:"hidden,omitempty"`     // message_changed, message_deleted, unpinned_item | ||||
| 	DeletedTimestamp string `json:"deleted_ts,omitempty"` // message_deleted | ||||
| 	EventTimestamp   string `json:"event_ts,omitempty"` | ||||
|  | ||||
| 	// bot_message (https://api.slack.com/events/message/bot_message) | ||||
| 	BotID    string `json:"bot_id,omitempty"` | ||||
| 	Username string `json:"username,omitempty"` | ||||
| 	Icons    *Icon  `json:"icons,omitempty"` | ||||
|  | ||||
| 	// channel_join, group_join | ||||
| 	Inviter string `json:"inviter,omitempty"` | ||||
|  | ||||
| 	// channel_topic, group_topic | ||||
| 	Topic string `json:"topic,omitempty"` | ||||
|  | ||||
| 	// channel_purpose, group_purpose | ||||
| 	Purpose string `json:"purpose,omitempty"` | ||||
|  | ||||
| 	// channel_name, group_name | ||||
| 	Name    string `json:"name,omitempty"` | ||||
| 	OldName string `json:"old_name,omitempty"` | ||||
|  | ||||
| 	// channel_archive, group_archive | ||||
| 	Members []string `json:"members,omitempty"` | ||||
|  | ||||
| 	// file_share, file_comment, file_mention | ||||
| 	File *File `json:"file,omitempty"` | ||||
|  | ||||
| 	// file_share | ||||
| 	Upload bool `json:"upload,omitempty"` | ||||
|  | ||||
| 	// file_comment | ||||
| 	Comment *Comment `json:"comment,omitempty"` | ||||
|  | ||||
| 	// pinned_item | ||||
| 	ItemType string `json:"item_type,omitempty"` | ||||
|  | ||||
| 	// https://api.slack.com/rtm | ||||
| 	ReplyTo int    `json:"reply_to,omitempty"` | ||||
| 	Team    string `json:"team,omitempty"` | ||||
|  | ||||
| 	// reactions | ||||
| 	Reactions []ItemReaction `json:"reactions,omitempty"` | ||||
| } | ||||
|  | ||||
| // Icon is used for bot messages | ||||
| type Icon struct { | ||||
| 	IconURL   string `json:"icon_url,omitempty"` | ||||
| 	IconEmoji string `json:"icon_emoji,omitempty"` | ||||
| } | ||||
|  | ||||
| // Edited indicates that a message has been edited. | ||||
| type Edited struct { | ||||
| 	User      string `json:"user,omitempty"` | ||||
| 	Timestamp string `json:"ts,omitempty"` | ||||
| } | ||||
|  | ||||
| // Event contains the event type | ||||
| type Event struct { | ||||
| 	Type string `json:"type,omitempty"` | ||||
| } | ||||
|  | ||||
| // Ping contains information about a Ping Event | ||||
| type Ping struct { | ||||
| 	ID   int    `json:"id"` | ||||
| 	Type string `json:"type"` | ||||
| } | ||||
|  | ||||
| // Pong contains information about a Pong Event | ||||
| type Pong struct { | ||||
| 	Type    string `json:"type"` | ||||
| 	ReplyTo int    `json:"reply_to"` | ||||
| } | ||||
|  | ||||
| // NewOutgoingMessage prepares an OutgoingMessage that the user can | ||||
| // use to send a message. Use this function to properly set the | ||||
| // messageID. | ||||
| func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage { | ||||
| 	id := rtm.idGen.Next() | ||||
| 	return &OutgoingMessage{ | ||||
| 		ID:      id, | ||||
| 		Type:    "message", | ||||
| 		Channel: channel, | ||||
| 		Text:    text, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewTypingMessage prepares an OutgoingMessage that the user can | ||||
| // use to send as a typing indicator. Use this function to properly set the | ||||
| // messageID. | ||||
| func (rtm *RTM) NewTypingMessage(channel string) *OutgoingMessage { | ||||
| 	id := rtm.idGen.Next() | ||||
| 	return &OutgoingMessage{ | ||||
| 		ID:      id, | ||||
| 		Type:    "typing", | ||||
| 		Channel: channel, | ||||
| 	} | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user