forked from lug/matterbridge
		
	Compare commits
	
		
			49 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f8a6e65bfd | ||
|   | 6df6c5d615 | ||
|   | 93114b7682 | ||
|   | 9987ac3f13 | ||
|   | 01a32b2154 | ||
|   | b3c3142bb2 | ||
|   | 77f1a959c3 | ||
|   | e3dda0e812 | ||
|   | 38103d36b4 | ||
|   | 7685fe1724 | ||
|   | 01afe03a3f | ||
|   | 7fbbf89c58 | ||
|   | 84d259d8b3 | ||
|   | 8b47670a74 | ||
|   | 7f5dc1d461 | ||
|   | 43e765f4f9 | ||
|   | adec73f542 | ||
|   | fee159541f | ||
|   | d81e6bf6ce | ||
|   | 70c93d970c | ||
|   | 4960273832 | ||
|   | 6c018ee6fe | ||
|   | 4ef32103ca | ||
|   | e4ec27c5e2 | ||
|   | 20c04f7977 | ||
|   | 571f50d734 | ||
|   | 780ea6f7c0 | ||
|   | 4279906f6e | ||
|   | 2e54b97fc2 | ||
|   | e1641b2c2e | ||
|   | e0e1e4be80 | ||
|   | d5845ce900 | ||
|   | 85f2cde4c3 | ||
|   | cef64e01b3 | ||
|   | 94ea775232 | ||
|   | 2e4b7fac11 | ||
|   | 2867ec459a | ||
|   | cd18d89894 | ||
|   | 449ed31e25 | ||
|   | 1f36904588 | ||
|   | f7495dd0c3 | ||
|   | a11f77835d | ||
|   | af1ad82c8e | ||
|   | 4976338677 | ||
|   | 99d130d1ed | ||
|   | 4fb0544b0e | ||
|   | 0b4ac61435 | ||
|   | 1d5cd1d7c4 | ||
|   | 14830d9f1c | 
							
								
								
									
										20
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| Please answer the following questions.  | ||||
|  | ||||
| ### Which version of matterbridge are you using? | ||||
| run ```matterbridge -version``` | ||||
|  | ||||
| ### If you're having problems with mattermost please specify mattermost version.  | ||||
|  | ||||
|  | ||||
| ### Please describe the expected behavior. | ||||
|  | ||||
|  | ||||
| ### Please describe the actual behavior.  | ||||
| #### Use logs from running ```matterbridge -debug``` if possible. | ||||
|  | ||||
|  | ||||
| ### Any steps to reproduce the behavior? | ||||
|  | ||||
|  | ||||
| ### Please add your configuration file  | ||||
| #### (be sure to exclude or anonymize private data (tokens/passwords)) | ||||
							
								
								
									
										17
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,7 +1,9 @@ | ||||
| # matterbridge | ||||
| Simple bridge between mattermost, IRC, XMPP, Gitter, Slack and Discord | ||||
|  | ||||
|  | ||||
| * Relays public channel messages between multiple mattermost, IRC, XMPP, Gitter, Slack and Discord. Pick and mix. | ||||
| Simple bridge between mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat and Hipchat(via xmpp). | ||||
|  | ||||
| * Relays public channel messages between multiple mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat and Hipchat (via xmpp). Pick and mix. | ||||
| * Supports multiple channels. | ||||
| * Matterbridge can also work with private groups on your mattermost. | ||||
| * Allow for bridging the same bridges, which means you can eg bridge between multiple mattermosts. | ||||
| @@ -10,6 +12,7 @@ Simple bridge between mattermost, IRC, XMPP, Gitter, Slack and Discord | ||||
| Look at [matterbridge.toml.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample) for documentation and an example. | ||||
| Look at [matterbridge.toml.simple] (https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.simple) for a simple example. | ||||
|  | ||||
|  | ||||
| ## Changelog | ||||
| Since v0.7.0 the configuration has changed. More details in [changelog.md] (https://github.com/42wim/matterbridge/blob/master/changelog.md) | ||||
|  | ||||
| @@ -21,6 +24,9 @@ Accounts to one of the supported bridges | ||||
| * [Gitter] (https://gitter.im) | ||||
| * [Slack] (https://slack.com) | ||||
| * [Discord] (https://discordapp.com) | ||||
| * [Telegram] (https://telegram.org) | ||||
| * [Hipchat] (https://www.hipchat.com) | ||||
| * [Rocket.chat] (https://rocket.chat) | ||||
|  | ||||
| ## Docker | ||||
| Create your matterbridge.toml file locally eg in ```/tmp/matterbridge.toml``` | ||||
| @@ -30,16 +36,13 @@ docker run -ti -v /tmp/matterbridge.toml:/matterbridge.toml 42wim/matterbridge | ||||
|  | ||||
| ## binaries | ||||
| Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/) | ||||
| * For use with mattermost 3.5.0+ [v0.8.1](https://github.com/42wim/matterircd/releases/tag/v0.8.1) | ||||
| * For use with mattermost 3.5.x - 3.6.0 [v0.9.1](https://github.com/42wim/matterircd/releases/tag/v0.9.1) | ||||
| * For use with mattermost 3.3.0 - 3.4.0 [v0.7.1](https://github.com/42wim/matterircd/releases/tag/v0.7.1) | ||||
| * For use with mattermost 3.0.0 - 3.2.0 [v0.5.0](https://github.com/42wim/matterircd/releases/tag/v0.5.0) (not maintained anymore) | ||||
|  | ||||
| ## Compatibility | ||||
| ### Mattermost  | ||||
| * Matterbridge v0.8.1 works with mattermost 3.5.0+ [3.5.0 release](https://github.com/mattermost/platform/releases/tag/v3.5.0) | ||||
| * Matterbridge v0.9.1 works with mattermost 3.5.x - 3.6.0 [3.6.0 release](https://github.com/mattermost/platform/releases/tag/v3.6.0) | ||||
| * Matterbridge v0.7.1 works with mattermost 3.3.0 - 3.4.0 [3.4.0 release](https://github.com/mattermost/platform/releases/tag/v3.4.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. | ||||
|   | ||||
| @@ -6,40 +6,63 @@ import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/gitter" | ||||
| 	"github.com/42wim/matterbridge/bridge/irc" | ||||
| 	"github.com/42wim/matterbridge/bridge/mattermost" | ||||
| 	"github.com/42wim/matterbridge/bridge/rocketchat" | ||||
| 	"github.com/42wim/matterbridge/bridge/slack" | ||||
| 	"github.com/42wim/matterbridge/bridge/telegram" | ||||
| 	"github.com/42wim/matterbridge/bridge/xmpp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type Bridge interface { | ||||
| type Bridger interface { | ||||
| 	Send(msg config.Message) error | ||||
| 	Name() string | ||||
| 	Connect() error | ||||
| 	FullOrigin() string | ||||
| 	Origin() string | ||||
| 	Protocol() string | ||||
| 	JoinChannel(channel string) error | ||||
| } | ||||
|  | ||||
| func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) Bridge { | ||||
| type Bridge struct { | ||||
| 	Config config.Protocol | ||||
| 	Bridger | ||||
| 	Name     string | ||||
| 	Account  string | ||||
| 	Protocol string | ||||
| } | ||||
|  | ||||
| func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Bridge { | ||||
| 	b := new(Bridge) | ||||
| 	accInfo := strings.Split(bridge.Account, ".") | ||||
| 	protocol := accInfo[0] | ||||
| 	name := accInfo[1] | ||||
| 	b.Name = name | ||||
| 	b.Protocol = protocol | ||||
| 	b.Account = bridge.Account | ||||
|  | ||||
| 	// override config from environment | ||||
| 	config.OverrideCfgFromEnv(cfg, protocol, name) | ||||
| 	switch protocol { | ||||
| 	case "mattermost": | ||||
| 		return bmattermost.New(cfg.Mattermost[name], name, c) | ||||
| 		b.Config = cfg.Mattermost[name] | ||||
| 		b.Bridger = bmattermost.New(cfg.Mattermost[name], bridge.Account, c) | ||||
| 	case "irc": | ||||
| 		return birc.New(cfg.IRC[name], name, c) | ||||
| 		b.Config = cfg.IRC[name] | ||||
| 		b.Bridger = birc.New(cfg.IRC[name], bridge.Account, c) | ||||
| 	case "gitter": | ||||
| 		return bgitter.New(cfg.Gitter[name], name, c) | ||||
| 		b.Config = cfg.Gitter[name] | ||||
| 		b.Bridger = bgitter.New(cfg.Gitter[name], bridge.Account, c) | ||||
| 	case "slack": | ||||
| 		return bslack.New(cfg.Slack[name], name, c) | ||||
| 		b.Config = cfg.Slack[name] | ||||
| 		b.Bridger = bslack.New(cfg.Slack[name], bridge.Account, c) | ||||
| 	case "xmpp": | ||||
| 		return bxmpp.New(cfg.Xmpp[name], name, c) | ||||
| 		b.Config = cfg.Xmpp[name] | ||||
| 		b.Bridger = bxmpp.New(cfg.Xmpp[name], bridge.Account, c) | ||||
| 	case "discord": | ||||
| 		return bdiscord.New(cfg.Discord[name], name, c) | ||||
| 		b.Config = cfg.Discord[name] | ||||
| 		b.Bridger = bdiscord.New(cfg.Discord[name], bridge.Account, c) | ||||
| 	case "telegram": | ||||
| 		b.Config = cfg.Telegram[name] | ||||
| 		b.Bridger = btelegram.New(cfg.Telegram[name], bridge.Account, c) | ||||
| 	case "rocketchat": | ||||
| 		b.Config = cfg.Rocketchat[name] | ||||
| 		b.Bridger = brocketchat.New(cfg.Rocketchat[name], bridge.Account, c) | ||||
| 	} | ||||
| 	return nil | ||||
| 	return b | ||||
| } | ||||
|   | ||||
| @@ -8,14 +8,17 @@ import ( | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	EVENT_JOIN_LEAVE = "join_leave" | ||||
| ) | ||||
|  | ||||
| type Message struct { | ||||
| 	Text       string | ||||
| 	Channel    string | ||||
| 	Username   string | ||||
| 	Origin     string | ||||
| 	FullOrigin string | ||||
| 	Protocol   string | ||||
| 	Avatar     string | ||||
| 	Text     string | ||||
| 	Channel  string | ||||
| 	Username string | ||||
| 	Avatar   string | ||||
| 	Account  string | ||||
| 	Event    string | ||||
| } | ||||
|  | ||||
| type Protocol struct { | ||||
| @@ -49,9 +52,14 @@ type Protocol struct { | ||||
| 	UseTLS                 bool   // IRC | ||||
| } | ||||
|  | ||||
| type ChannelOptions struct { | ||||
| 	Key string // irc | ||||
| } | ||||
|  | ||||
| type Bridge struct { | ||||
| 	Account string | ||||
| 	Channel string | ||||
| 	Options ChannelOptions | ||||
| } | ||||
|  | ||||
| type Gateway struct { | ||||
| @@ -59,6 +67,7 @@ type Gateway struct { | ||||
| 	Enable bool | ||||
| 	In     []Bridge | ||||
| 	Out    []Bridge | ||||
| 	InOut  []Bridge | ||||
| } | ||||
|  | ||||
| type SameChannelGateway struct { | ||||
| @@ -75,6 +84,9 @@ type Config struct { | ||||
| 	Gitter             map[string]Protocol | ||||
| 	Xmpp               map[string]Protocol | ||||
| 	Discord            map[string]Protocol | ||||
| 	Telegram           map[string]Protocol | ||||
| 	Rocketchat         map[string]Protocol | ||||
| 	General            Protocol | ||||
| 	Gateway            []Gateway | ||||
| 	SameChannelGateway []SameChannelGateway | ||||
| } | ||||
| @@ -126,16 +138,11 @@ func OverrideCfgFromEnv(cfg *Config, protocol string, account string) { | ||||
|  | ||||
| func GetIconURL(msg *Message, cfg *Protocol) string { | ||||
| 	iconURL := cfg.IconURL | ||||
| 	info := strings.Split(msg.Account, ".") | ||||
| 	protocol := info[0] | ||||
| 	name := info[1] | ||||
| 	iconURL = strings.Replace(iconURL, "{NICK}", msg.Username, -1) | ||||
| 	iconURL = strings.Replace(iconURL, "{BRIDGE}", msg.Origin, -1) | ||||
| 	iconURL = strings.Replace(iconURL, "{PROTOCOL}", msg.Protocol, -1) | ||||
| 	iconURL = strings.Replace(iconURL, "{BRIDGE}", name, -1) | ||||
| 	iconURL = strings.Replace(iconURL, "{PROTOCOL}", protocol, -1) | ||||
| 	return iconURL | ||||
| } | ||||
|  | ||||
| func GetNick(msg *Message, cfg *Protocol) string { | ||||
| 	nick := cfg.RemoteNickFormat | ||||
| 	nick = strings.Replace(nick, "{NICK}", msg.Username, -1) | ||||
| 	nick = strings.Replace(nick, "{BRIDGE}", msg.Origin, -1) | ||||
| 	nick = strings.Replace(nick, "{PROTOCOL}", msg.Protocol, -1) | ||||
| 	return nick | ||||
| } | ||||
|   | ||||
| @@ -11,8 +11,7 @@ type bdiscord struct { | ||||
| 	c            *discordgo.Session | ||||
| 	Config       *config.Protocol | ||||
| 	Remote       chan config.Message | ||||
| 	protocol     string | ||||
| 	origin       string | ||||
| 	Account      string | ||||
| 	Channels     []*discordgo.Channel | ||||
| 	Nick         string | ||||
| 	UseChannelID bool | ||||
| @@ -25,18 +24,20 @@ func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *bdiscord { | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *bdiscord { | ||||
| 	b := &bdiscord{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.protocol = protocol | ||||
| 	b.origin = origin | ||||
| 	b.Account = account | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Info("Connecting") | ||||
| 	if !strings.HasPrefix(b.Config.Token, "Bot ") { | ||||
| 		b.Config.Token = "Bot " + b.Config.Token | ||||
| 	} | ||||
| 	b.c, err = discordgo.New(b.Config.Token) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| @@ -72,10 +73,6 @@ func (b *bdiscord) Connect() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) FullOrigin() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) JoinChannel(channel string) error { | ||||
| 	idcheck := strings.Split(channel, "ID:") | ||||
| 	if len(idcheck) > 1 { | ||||
| @@ -84,18 +81,6 @@ func (b *bdiscord) JoinChannel(channel string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Name() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Protocol() string { | ||||
| 	return b.protocol | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Origin() string { | ||||
| 	return b.origin | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) Send(msg config.Message) error { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	channelID := b.getChannelID(msg.Channel) | ||||
| @@ -103,8 +88,7 @@ func (b *bdiscord) Send(msg config.Message) error { | ||||
| 		flog.Errorf("Could not find channelID for %v", msg.Channel) | ||||
| 		return nil | ||||
| 	} | ||||
| 	nick := config.GetNick(&msg, b.Config) | ||||
| 	b.c.ChannelMessageSend(channelID, nick+msg.Text) | ||||
| 	b.c.ChannelMessageSend(channelID, msg.Username+msg.Text) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -121,13 +105,13 @@ func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat | ||||
| 	if m.Content == "" { | ||||
| 		return | ||||
| 	} | ||||
| 	flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.FullOrigin()) | ||||
| 	flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.Account) | ||||
| 	channelName := b.getChannelName(m.ChannelID) | ||||
| 	if b.UseChannelID { | ||||
| 		channelName = "ID:" + m.ChannelID | ||||
| 	} | ||||
| 	b.Remote <- config.Message{Username: m.Author.Username, Text: m.ContentWithMentionsReplaced(), Channel: channelName, | ||||
| 		Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin(), Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg"} | ||||
| 		Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg"} | ||||
| } | ||||
|  | ||||
| func (b *bdiscord) getChannelID(name string) string { | ||||
|   | ||||
| @@ -8,13 +8,12 @@ import ( | ||||
| ) | ||||
|  | ||||
| type Bgitter struct { | ||||
| 	c        *gitter.Gitter | ||||
| 	Config   *config.Protocol | ||||
| 	Remote   chan config.Message | ||||
| 	protocol string | ||||
| 	origin   string | ||||
| 	Users    []gitter.User | ||||
| 	Rooms    []gitter.Room | ||||
| 	c       *gitter.Gitter | ||||
| 	Config  *config.Protocol | ||||
| 	Remote  chan config.Message | ||||
| 	Account string | ||||
| 	Users   []gitter.User | ||||
| 	Rooms   []gitter.Room | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| @@ -24,12 +23,11 @@ func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *Bgitter { | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Bgitter { | ||||
| 	b := &Bgitter{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.protocol = protocol | ||||
| 	b.origin = origin | ||||
| 	b.Account = account | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| @@ -47,10 +45,6 @@ func (b *Bgitter) Connect() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) FullOrigin() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) JoinChannel(channel string) error { | ||||
| 	room := channel | ||||
| 	roomID := b.getRoomID(room) | ||||
| @@ -71,15 +65,14 @@ func (b *Bgitter) JoinChannel(channel string) error { | ||||
| 	go b.c.Listen(stream) | ||||
|  | ||||
| 	go func(stream *gitter.Stream, room string) { | ||||
| 		for { | ||||
| 			event := <-stream.Event | ||||
| 		for event := range 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, "") { | ||||
| 					flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.FullOrigin()) | ||||
| 					flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account) | ||||
| 					b.Remote <- config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room, | ||||
| 						Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin(), Avatar: b.getAvatar(ev.Message.From.Username)} | ||||
| 						Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username)} | ||||
| 				} | ||||
| 			case *gitter.GitterConnectionClosed: | ||||
| 				flog.Errorf("connection with gitter closed for room %s", room) | ||||
| @@ -89,18 +82,6 @@ func (b *Bgitter) JoinChannel(channel string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) Name() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) Protocol() string { | ||||
| 	return b.protocol | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) Origin() string { | ||||
| 	return b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) Send(msg config.Message) error { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	roomID := b.getRoomID(msg.Channel) | ||||
| @@ -108,9 +89,8 @@ func (b *Bgitter) Send(msg config.Message) error { | ||||
| 		flog.Errorf("Could not find roomID for %v", msg.Channel) | ||||
| 		return nil | ||||
| 	} | ||||
| 	nick := config.GetNick(&msg, b.Config) | ||||
| 	// add ZWSP because gitter echoes our own messages | ||||
| 	return b.c.SendMessage(roomID, nick+msg.Text+" ") | ||||
| 	return b.c.SendMessage(roomID, msg.Username+msg.Text+" ") | ||||
| } | ||||
|  | ||||
| func (b *Bgitter) getRoomID(channel string) string { | ||||
|   | ||||
| @@ -19,11 +19,10 @@ type Birc struct { | ||||
| 	Nick      string | ||||
| 	names     map[string][]string | ||||
| 	Config    *config.Protocol | ||||
| 	origin    string | ||||
| 	protocol  string | ||||
| 	Remote    chan config.Message | ||||
| 	connected chan struct{} | ||||
| 	Local     chan config.Message // local queue for flood control | ||||
| 	Account   string | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| @@ -33,14 +32,13 @@ func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *Birc { | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Birc { | ||||
| 	b := &Birc{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Nick = b.Config.Nick | ||||
| 	b.Remote = c | ||||
| 	b.names = make(map[string][]string) | ||||
| 	b.origin = origin | ||||
| 	b.protocol = protocol | ||||
| 	b.Account = account | ||||
| 	b.connected = make(chan struct{}) | ||||
| 	if b.Config.MessageDelay == 0 { | ||||
| 		b.Config.MessageDelay = 1300 | ||||
| @@ -93,43 +91,26 @@ func (b *Birc) Connect() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Birc) FullOrigin() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *Birc) JoinChannel(channel string) error { | ||||
| 	b.i.Join(channel) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Birc) Name() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *Birc) Protocol() string { | ||||
| 	return b.protocol | ||||
| } | ||||
|  | ||||
| func (b *Birc) Origin() string { | ||||
| 	return b.origin | ||||
| } | ||||
|  | ||||
| func (b *Birc) Send(msg config.Message) error { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	if msg.FullOrigin == b.FullOrigin() { | ||||
| 	if msg.Account == b.Account { | ||||
| 		return nil | ||||
| 	} | ||||
| 	if strings.HasPrefix(msg.Text, "!") { | ||||
| 		b.Command(&msg) | ||||
| 		return nil | ||||
| 	} | ||||
| 	nick := config.GetNick(&msg, b.Config) | ||||
| 	for _, text := range strings.Split(msg.Text, "\n") { | ||||
| 		if len(b.Local) < b.Config.MessageQueue { | ||||
| 			if len(b.Local) == b.Config.MessageQueue-1 { | ||||
| 				text = text + " <message clipped>" | ||||
| 			} | ||||
| 			b.Local <- config.Message{Text: text, Username: nick, Channel: msg.Channel} | ||||
| 			b.Local <- config.Message{Text: text, Username: msg.Username, Channel: msg.Channel} | ||||
| 		} else { | ||||
| 			flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local)) | ||||
| 		} | ||||
| @@ -153,12 +134,12 @@ func (b *Birc) endNames(event *irc.Event) { | ||||
| 	continued := false | ||||
| 	for len(b.names[channel]) > maxNamesPerPost { | ||||
| 		b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost], continued), | ||||
| 			Channel: channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} | ||||
| 			Channel: channel, Account: b.Account} | ||||
| 		b.names[channel] = b.names[channel][maxNamesPerPost:] | ||||
| 		continued = true | ||||
| 	} | ||||
| 	b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel], continued), Channel: channel, | ||||
| 		Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} | ||||
| 	b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel], continued), | ||||
| 		Channel: channel, Account: b.Account} | ||||
| 	b.names[channel] = nil | ||||
| 	b.i.ClearCallback(ircm.RPL_NAMREPLY) | ||||
| 	b.i.ClearCallback(ircm.RPL_ENDOFNAMES) | ||||
| @@ -177,11 +158,24 @@ func (b *Birc) handleNewConnection(event *irc.Event) { | ||||
| 		i.SendRaw("PONG :" + e.Message()) | ||||
| 		flog.Debugf("PING/PONG") | ||||
| 	}) | ||||
| 	i.AddCallback("JOIN", b.handleJoinPart) | ||||
| 	i.AddCallback("PART", b.handleJoinPart) | ||||
| 	i.AddCallback("QUIT", b.handleJoinPart) | ||||
| 	i.AddCallback("*", b.handleOther) | ||||
| 	// we are now fully connected | ||||
| 	b.connected <- struct{}{} | ||||
| } | ||||
|  | ||||
| func (b *Birc) handleJoinPart(event *irc.Event) { | ||||
| 	flog.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account) | ||||
| 	channel := event.Arguments[0] | ||||
| 	if event.Code == "QUIT" { | ||||
| 		channel = "" | ||||
| 	} | ||||
| 	b.Remote <- config.Message{Username: "system", Text: event.Nick + " " + strings.ToLower(event.Code) + "s", Channel: channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE} | ||||
| 	flog.Debugf("handle %#v", event) | ||||
| } | ||||
|  | ||||
| func (b *Birc) handleNotice(event *irc.Event) { | ||||
| 	if strings.Contains(event.Message(), "This nickname is registered") && event.Nick == b.Config.NickServNick { | ||||
| 		b.i.Privmsg(b.Config.NickServNick, "IDENTIFY "+b.Config.NickServPassword) | ||||
| @@ -216,8 +210,8 @@ func (b *Birc) handlePrivMsg(event *irc.Event) { | ||||
| 	// strip IRC colors | ||||
| 	re := regexp.MustCompile(`[[:cntrl:]](\d+,|)\d+`) | ||||
| 	msg = re.ReplaceAllString(msg, "") | ||||
| 	flog.Debugf("Sending message from %s on %s to gateway", event.Arguments[0], b.FullOrigin()) | ||||
| 	b.Remote <- config.Message{Username: event.Nick, Text: msg, Channel: event.Arguments[0], Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} | ||||
| 	flog.Debugf("Sending message from %s on %s to gateway", event.Arguments[0], b.Account) | ||||
| 	b.Remote <- config.Message{Username: event.Nick, Text: msg, Channel: event.Arguments[0], Account: b.Account} | ||||
| } | ||||
|  | ||||
| func (b *Birc) handleTopicWhoTime(event *irc.Event) { | ||||
|   | ||||
| @@ -26,12 +26,11 @@ type MMMessage struct { | ||||
| type Bmattermost struct { | ||||
| 	MMhook | ||||
| 	MMapi | ||||
| 	Config   *config.Protocol | ||||
| 	Remote   chan config.Message | ||||
| 	name     string | ||||
| 	origin   string | ||||
| 	protocol string | ||||
| 	TeamId   string | ||||
| 	Config  *config.Protocol | ||||
| 	Remote  chan config.Message | ||||
| 	name    string | ||||
| 	TeamId  string | ||||
| 	Account string | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| @@ -41,13 +40,11 @@ func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *Bmattermost { | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Bmattermost { | ||||
| 	b := &Bmattermost{} | ||||
| 	b.Config = &cfg | ||||
| 	b.origin = origin | ||||
| 	b.Remote = c | ||||
| 	b.protocol = "mattermost" | ||||
| 	b.name = cfg.Name | ||||
| 	b.Account = account | ||||
| 	b.mmMap = make(map[string]string) | ||||
| 	return b | ||||
| } | ||||
| @@ -80,10 +77,6 @@ func (b *Bmattermost) Connect() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) FullOrigin() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) JoinChannel(channel string) error { | ||||
| 	// we can only join channels using the API | ||||
| 	if b.Config.UseAPI { | ||||
| @@ -92,21 +85,9 @@ func (b *Bmattermost) JoinChannel(channel string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) Name() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) Origin() string { | ||||
| 	return b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) Protocol() string { | ||||
| 	return b.protocol | ||||
| } | ||||
|  | ||||
| func (b *Bmattermost) Send(msg config.Message) error { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	nick := config.GetNick(&msg, b.Config) | ||||
| 	nick := msg.Username | ||||
| 	message := msg.Text | ||||
| 	channel := msg.Channel | ||||
|  | ||||
| @@ -144,8 +125,8 @@ func (b *Bmattermost) handleMatter() { | ||||
| 		go b.handleMatterHook(mchan) | ||||
| 	} | ||||
| 	for message := range mchan { | ||||
| 		flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.FullOrigin()) | ||||
| 		b.Remote <- config.Message{Text: message.Text, Username: message.Username, Channel: message.Channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} | ||||
| 		flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account) | ||||
| 		b.Remote <- config.Message{Text: message.Text, Username: message.Username, Channel: message.Channel, Account: b.Account} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -153,14 +134,14 @@ func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) { | ||||
| 	for message := range b.mc.MessageChan { | ||||
| 		// do not post our own messages back to irc | ||||
| 		// only listen to message from our team | ||||
| 		if message.Raw.Event == "posted" && b.mc.User.Username != message.Username && message.Raw.TeamId == b.TeamId { | ||||
| 		if message.Raw.Event == "posted" && b.mc.User.Username != message.Username && message.Raw.Data["team_id"].(string) == b.TeamId { | ||||
| 			flog.Debugf("Receiving from matterclient %#v", message) | ||||
| 			m := &MMMessage{} | ||||
| 			m.Username = message.Username | ||||
| 			m.Channel = message.Channel | ||||
| 			m.Text = message.Text | ||||
| 			if len(message.Post.Filenames) > 0 { | ||||
| 				for _, link := range b.mc.GetPublicLinks(message.Post.Filenames) { | ||||
| 			if len(message.Post.FileIds) > 0 { | ||||
| 				for _, link := range b.mc.GetPublicLinks(message.Post.FileIds) { | ||||
| 					m.Text = m.Text + "\n" + link | ||||
| 				} | ||||
| 			} | ||||
|   | ||||
							
								
								
									
										82
									
								
								bridge/rocketchat/rocketchat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								bridge/rocketchat/rocketchat.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| package brocketchat | ||||
|  | ||||
| import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	"github.com/42wim/matterbridge/hook/rockethook" | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| type MMhook struct { | ||||
| 	mh *matterhook.Client | ||||
| 	rh *rockethook.Client | ||||
| } | ||||
|  | ||||
| type Brocketchat struct { | ||||
| 	MMhook | ||||
| 	Config  *config.Protocol | ||||
| 	Remote  chan config.Message | ||||
| 	name    string | ||||
| 	Account string | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "rocketchat" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Brocketchat { | ||||
| 	b := &Brocketchat{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.Account = account | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *Brocketchat) Command(cmd string) string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *Brocketchat) Connect() error { | ||||
| 	flog.Info("Connecting webhooks") | ||||
| 	b.mh = matterhook.New(b.Config.URL, | ||||
| 		matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, | ||||
| 			DisableServer: true}) | ||||
| 	b.rh = rockethook.New(b.Config.URL, rockethook.Config{BindAddress: b.Config.BindAddress}) | ||||
| 	go b.handleRocketHook() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Brocketchat) JoinChannel(channel string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Brocketchat) Send(msg config.Message) error { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} | ||||
| 	matterMessage.Channel = msg.Channel | ||||
| 	matterMessage.UserName = msg.Username | ||||
| 	matterMessage.Type = "" | ||||
| 	matterMessage.Text = msg.Text | ||||
| 	err := b.mh.Send(matterMessage) | ||||
| 	if err != nil { | ||||
| 		flog.Info(err) | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Brocketchat) handleRocketHook() { | ||||
| 	for { | ||||
| 		message := b.rh.Receive() | ||||
| 		flog.Debugf("Receiving from rockethook %#v", message) | ||||
| 		// do not loop | ||||
| 		if message.UserName == b.Config.Nick { | ||||
| 			continue | ||||
| 		} | ||||
| 		flog.Debugf("Sending message from %s on %s to gateway", message.UserName, b.Account) | ||||
| 		b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account} | ||||
| 	} | ||||
| } | ||||
| @@ -6,6 +6,7 @@ import ( | ||||
| 	"github.com/42wim/matterbridge/matterhook" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/nlopes/slack" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| @@ -25,8 +26,7 @@ type Bslack struct { | ||||
| 	Plus     bool | ||||
| 	Remote   chan config.Message | ||||
| 	Users    []slack.User | ||||
| 	protocol string | ||||
| 	origin   string | ||||
| 	Account  string | ||||
| 	si       *slack.Info | ||||
| 	channels []slack.Channel | ||||
| } | ||||
| @@ -38,12 +38,11 @@ func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *Bslack { | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Bslack { | ||||
| 	b := &Bslack{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.protocol = protocol | ||||
| 	b.origin = origin | ||||
| 	b.Account = account | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| @@ -66,10 +65,6 @@ func (b *Bslack) Connect() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bslack) FullOrigin() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bslack) JoinChannel(channel string) error { | ||||
| 	// we can only join channels using the API | ||||
| 	if b.Config.UseAPI { | ||||
| @@ -81,24 +76,12 @@ func (b *Bslack) JoinChannel(channel string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Name() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Protocol() string { | ||||
| 	return b.protocol | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Origin() string { | ||||
| 	return b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bslack) Send(msg config.Message) error { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	if msg.FullOrigin == b.FullOrigin() { | ||||
| 	if msg.Account == b.Account { | ||||
| 		return nil | ||||
| 	} | ||||
| 	nick := config.GetNick(&msg, b.Config) | ||||
| 	nick := msg.Username | ||||
| 	message := msg.Text | ||||
| 	channel := msg.Channel | ||||
| 	if b.Config.PrefixMessagesWithNick { | ||||
| @@ -154,14 +137,14 @@ func (b *Bslack) getAvatar(user string) string { | ||||
|  | ||||
| func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) { | ||||
| 	if b.channels == nil { | ||||
| 		return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.FullOrigin(), name) | ||||
| 		return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.Account, name) | ||||
| 	} | ||||
| 	for _, channel := range b.channels { | ||||
| 		if channel.Name == name { | ||||
| 			return &channel, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, fmt.Errorf("%s: channel %s not found", b.FullOrigin(), name) | ||||
| 	return nil, fmt.Errorf("%s: channel %s not found", b.Account, name) | ||||
| } | ||||
|  | ||||
| func (b *Bslack) handleSlack() { | ||||
| @@ -176,13 +159,13 @@ func (b *Bslack) handleSlack() { | ||||
| 	flog.Debug("Start listening for Slack messages") | ||||
| 	for message := range mchan { | ||||
| 		// do not send messages from ourself | ||||
| 		if message.Username == b.si.User.Name { | ||||
| 		if b.Config.UseAPI && message.Username == b.si.User.Name { | ||||
| 			continue | ||||
| 		} | ||||
| 		texts := strings.Split(message.Text, "\n") | ||||
| 		for _, text := range texts { | ||||
| 			flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.FullOrigin()) | ||||
| 			b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin(), Avatar: b.getAvatar(message.Username)} | ||||
| 			flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account) | ||||
| 			b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username)} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -209,11 +192,14 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) { | ||||
| 				m.Channel = channel.Name | ||||
| 				m.Text = ev.Text | ||||
| 				m.Raw = ev | ||||
| 				m.Text = b.replaceMention(m.Text) | ||||
| 				mchan <- m | ||||
| 			} | ||||
| 			count++ | ||||
| 		case *slack.OutgoingErrorEvent: | ||||
| 			flog.Debugf("%#v", ev.Error()) | ||||
| 		case *slack.ChannelJoinedEvent: | ||||
| 			b.Users, _ = b.sc.GetUsers() | ||||
| 		case *slack.ConnectedEvent: | ||||
| 			b.channels = ev.Info.Channels | ||||
| 			b.si = ev.Info | ||||
| @@ -232,7 +218,26 @@ func (b *Bslack) handleMatterHook(mchan chan *MMMessage) { | ||||
| 		m := &MMMessage{} | ||||
| 		m.Username = message.UserName | ||||
| 		m.Text = message.Text | ||||
| 		m.Text = b.replaceMention(m.Text) | ||||
| 		m.Channel = message.ChannelName | ||||
| 		mchan <- m | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bslack) userName(id string) string { | ||||
| 	for _, u := range b.Users { | ||||
| 		if u.ID == id { | ||||
| 			return u.Name | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (b *Bslack) replaceMention(text string) string { | ||||
| 	results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1) | ||||
| 	for _, r := range results { | ||||
| 		text = strings.Replace(text, "<@"+r[1]+">", "@"+b.userName(r[1]), -1) | ||||
|  | ||||
| 	} | ||||
| 	return text | ||||
| } | ||||
|   | ||||
							
								
								
									
										134
									
								
								bridge/telegram/telegram.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								bridge/telegram/telegram.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,134 @@ | ||||
| package btelegram | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"html" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/go-telegram-bot-api/telegram-bot-api" | ||||
| 	"github.com/russross/blackfriday" | ||||
| ) | ||||
|  | ||||
| type Btelegram struct { | ||||
| 	c       *tgbotapi.BotAPI | ||||
| 	Config  *config.Protocol | ||||
| 	Remote  chan config.Message | ||||
| 	Account string | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| var protocol = "telegram" | ||||
|  | ||||
| func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram { | ||||
| 	b := &Btelegram{} | ||||
| 	b.Config = &cfg | ||||
| 	b.Remote = c | ||||
| 	b.Account = account | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) Connect() error { | ||||
| 	var err error | ||||
| 	flog.Info("Connecting") | ||||
| 	b.c, err = tgbotapi.NewBotAPI(b.Config.Token) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0)) | ||||
| 	if err != nil { | ||||
| 		flog.Debugf("%#v", err) | ||||
| 		return err | ||||
| 	} | ||||
| 	flog.Info("Connection succeeded") | ||||
| 	go b.handleRecv(updates) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) JoinChannel(channel string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type customHtml struct { | ||||
| 	blackfriday.Renderer | ||||
| } | ||||
|  | ||||
| func (options *customHtml) Paragraph(out *bytes.Buffer, text func() bool) { | ||||
| 	marker := out.Len() | ||||
|  | ||||
| 	if !text() { | ||||
| 		out.Truncate(marker) | ||||
| 		return | ||||
| 	} | ||||
| 	out.WriteString("\n") | ||||
| } | ||||
|  | ||||
| func (options *customHtml) BlockCode(out *bytes.Buffer, text []byte, lang string) { | ||||
| 	out.WriteString("<pre>") | ||||
|  | ||||
| 	out.WriteString(html.EscapeString(string(text))) | ||||
| 	out.WriteString("</pre>\n") | ||||
| } | ||||
|  | ||||
| func (options *customHtml) Header(out *bytes.Buffer, text func() bool, level int, id string) { | ||||
| 	options.Paragraph(out, text) | ||||
| } | ||||
|  | ||||
| func (options *customHtml) HRule(out *bytes.Buffer) { | ||||
| 	out.WriteByte('\n') | ||||
| } | ||||
|  | ||||
| func (options *customHtml) BlockQuote(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("> ") | ||||
| 	out.Write(text) | ||||
| 	out.WriteByte('\n') | ||||
| } | ||||
|  | ||||
| func (options *customHtml) List(out *bytes.Buffer, text func() bool, flags int) { | ||||
| 	options.Paragraph(out, text) | ||||
| } | ||||
|  | ||||
| func (options *customHtml) ListItem(out *bytes.Buffer, text []byte, flags int) { | ||||
| 	out.WriteString("- ") | ||||
| 	out.Write(text) | ||||
| 	out.WriteByte('\n') | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) Send(msg config.Message) error { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	chatid, err := strconv.ParseInt(msg.Channel, 10, 64) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	parsed := blackfriday.Markdown([]byte(msg.Text), | ||||
| 		&customHtml{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")}, | ||||
| 		blackfriday.EXTENSION_NO_INTRA_EMPHASIS| | ||||
| 			blackfriday.EXTENSION_FENCED_CODE| | ||||
| 			blackfriday.EXTENSION_AUTOLINK| | ||||
| 			blackfriday.EXTENSION_SPACE_HEADERS| | ||||
| 			blackfriday.EXTENSION_HEADER_IDS| | ||||
| 			blackfriday.EXTENSION_BACKSLASH_LINE_BREAK| | ||||
| 			blackfriday.EXTENSION_DEFINITION_LISTS) | ||||
|  | ||||
| 	m := tgbotapi.NewMessage(chatid, msg.Username+string(parsed)) | ||||
| 	m.ParseMode = "HTML" | ||||
| 	_, err = b.c.Send(m) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| 	for update := range updates { | ||||
| 		if update.Message == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		flog.Debugf("Sending message from %s on %s to gateway", update.Message.From.UserName, b.Account) | ||||
| 		b.Remote <- config.Message{Username: update.Message.From.UserName, Text: update.Message.Text, Channel: strconv.FormatInt(update.Message.Chat.ID, 10), Account: b.Account} | ||||
| 	} | ||||
| } | ||||
| @@ -4,18 +4,18 @@ import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/mattn/go-xmpp" | ||||
| 	"crypto/tls" | ||||
|  | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type Bxmpp struct { | ||||
| 	xc       *xmpp.Client | ||||
| 	xmppMap  map[string]string | ||||
| 	Config   *config.Protocol | ||||
| 	origin   string | ||||
| 	protocol string | ||||
| 	Remote   chan config.Message | ||||
| 	xc      *xmpp.Client | ||||
| 	xmppMap map[string]string | ||||
| 	Config  *config.Protocol | ||||
| 	Remote  chan config.Message | ||||
| 	Account string | ||||
| } | ||||
|  | ||||
| var flog *log.Entry | ||||
| @@ -25,12 +25,11 @@ func init() { | ||||
| 	flog = log.WithFields(log.Fields{"module": protocol}) | ||||
| } | ||||
|  | ||||
| func New(cfg config.Protocol, origin string, c chan config.Message) *Bxmpp { | ||||
| func New(cfg config.Protocol, account string, c chan config.Message) *Bxmpp { | ||||
| 	b := &Bxmpp{} | ||||
| 	b.xmppMap = make(map[string]string) | ||||
| 	b.Config = &cfg | ||||
| 	b.protocol = protocol | ||||
| 	b.origin = origin | ||||
| 	b.Account = account | ||||
| 	b.Remote = c | ||||
| 	return b | ||||
| } | ||||
| @@ -48,41 +47,28 @@ func (b *Bxmpp) Connect() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) FullOrigin() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) JoinChannel(channel string) error { | ||||
| 	b.xc.JoinMUCNoHistory(channel+"@"+b.Config.Muc, b.Config.Nick) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) Name() string { | ||||
| 	return b.protocol + "." + b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) Protocol() string { | ||||
| 	return b.protocol | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) Origin() string { | ||||
| 	return b.origin | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) Send(msg config.Message) error { | ||||
| 	flog.Debugf("Receiving %#v", msg) | ||||
| 	nick := config.GetNick(&msg, b.Config) | ||||
| 	b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: nick + msg.Text}) | ||||
| 	b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text}) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { | ||||
| 	tc := new(tls.Config) | ||||
| 	tc.InsecureSkipVerify = b.Config.SkipTLSVerify | ||||
| 	options := xmpp.Options{ | ||||
| 		Host:     b.Config.Server, | ||||
| 		User:     b.Config.Jid, | ||||
| 		Password: b.Config.Password, | ||||
| 		NoTLS:    true, | ||||
| 		StartTLS: true, | ||||
| 		TLSConfig: tc, | ||||
|  | ||||
| 		//StartTLS:      false, | ||||
| 		Debug:                        true, | ||||
| 		Session:                      true, | ||||
| @@ -97,19 +83,27 @@ func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { | ||||
| 	return b.xc, err | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) xmppKeepAlive() { | ||||
| func (b *Bxmpp) xmppKeepAlive() chan bool { | ||||
| 	done := make(chan bool) | ||||
| 	go func() { | ||||
| 		ticker := time.NewTicker(90 * time.Second) | ||||
| 		defer ticker.Stop() | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-ticker.C: | ||||
| 				b.xc.Send(xmpp.Chat{}) | ||||
| 				b.xc.PingC2S("", "") | ||||
| 			case <-done: | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| 	return done | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) handleXmpp() error { | ||||
| 	done := b.xmppKeepAlive() | ||||
| 	defer close(done) | ||||
| 	nodelay := time.Time{} | ||||
| 	for { | ||||
| 		m, err := b.xc.Recv() | ||||
| 		if err != nil { | ||||
| @@ -127,9 +121,9 @@ func (b *Bxmpp) handleXmpp() error { | ||||
| 				if len(s) == 2 { | ||||
| 					nick = s[1] | ||||
| 				} | ||||
| 				if nick != b.Config.Nick { | ||||
| 					flog.Debugf("Sending message from %s on %s to gateway", nick, b.FullOrigin()) | ||||
| 					b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} | ||||
| 				if nick != b.Config.Nick && v.Stamp == nodelay && v.Text != "" { | ||||
| 					flog.Debugf("Sending message from %s on %s to gateway", nick, b.Account) | ||||
| 					b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Account: b.Account} | ||||
| 				} | ||||
| 			} | ||||
| 		case xmpp.Presence: | ||||
|   | ||||
							
								
								
									
										32
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,3 +1,34 @@ | ||||
| # v0.9.1 | ||||
| ## New features | ||||
| * Rocket.Chat: New protocol support added (https://rocket.chat) | ||||
| * irc: add channel key support #27 (see matterbrige.toml.sample for example) | ||||
| * xmpp: add SkipTLSVerify #106 | ||||
|  | ||||
| ## Bugfix | ||||
| * general: Exit when a bridge fails to start | ||||
| * mattermost: Check errors only on first connect. Keep retrying after first connection succeeds. #95 | ||||
| * telegram: fix missing username #102 | ||||
| * slack: do not use API functions in webhook (slack) #110 | ||||
|  | ||||
| # v0.9.0 | ||||
| ## New features | ||||
| * Telegram: New protocol support added (https://telegram.org) | ||||
| * Hipchat: Add sample config to connect to hipchat via xmpp | ||||
| * discord: add "Bot " tag to discord tokens automatically | ||||
| * slack: Add support for dynamic Iconurl #43 | ||||
| * general: Add ```gateway.inout``` config option for bidirectional bridges #85 | ||||
| * general: Add ```[general]``` section so that ```RemoteNickFormat``` can be set globally | ||||
|  | ||||
| ## Bugfix | ||||
| * general: when using samechannelgateway NickFormat get doubled by the NICK #77 | ||||
| * general: fix ShowJoinPart for messages from irc bridge #72 | ||||
| * gitter: fix high cpu usage #89 | ||||
| * irc: fix !users command #78 | ||||
| * xmpp: fix keepalive | ||||
| * xmpp: do not relay delayed/empty messages | ||||
| * slack: Replace id-mentions to usernames #86  | ||||
| * mattermost: fix public links not working (API changes) | ||||
|  | ||||
| # v0.8.1 | ||||
| ## Bugfix | ||||
| * general: when using samechannelgateway NickFormat get doubled by the NICK #77 | ||||
| @@ -47,6 +78,7 @@ See matterbridge.toml.sample for an example | ||||
| # v0.6.1 | ||||
| ## New features | ||||
| * Slack support added.  See matterbridge.conf.sample for more information | ||||
|  | ||||
| ## Bugfix | ||||
| * Fix 100% CPU bug on incorrect closed connections | ||||
|  | ||||
|   | ||||
| @@ -11,55 +11,74 @@ import ( | ||||
|  | ||||
| type Gateway struct { | ||||
| 	*config.Config | ||||
| 	MyConfig    *config.Gateway | ||||
| 	Bridges     []bridge.Bridge | ||||
| 	ChannelsOut map[string][]string | ||||
| 	ChannelsIn  map[string][]string | ||||
| 	ignoreNicks map[string][]string | ||||
| 	Name        string | ||||
| 	MyConfig *config.Gateway | ||||
| 	//Bridges     []*bridge.Bridge | ||||
| 	Bridges        map[string]*bridge.Bridge | ||||
| 	ChannelsOut    map[string][]string | ||||
| 	ChannelsIn     map[string][]string | ||||
| 	ignoreNicks    map[string][]string | ||||
| 	ChannelOptions map[string]config.ChannelOptions | ||||
| 	Name           string | ||||
| 	Message        chan config.Message | ||||
| } | ||||
|  | ||||
| func New(cfg *config.Config, gateway *config.Gateway) error { | ||||
| 	c := make(chan config.Message) | ||||
| func New(cfg *config.Config, gateway *config.Gateway) *Gateway { | ||||
| 	gw := &Gateway{} | ||||
| 	gw.Name = gateway.Name | ||||
| 	gw.Config = cfg | ||||
| 	gw.MyConfig = gateway | ||||
| 	exists := make(map[string]bool) | ||||
| 	for _, br := range append(gateway.In, gateway.Out...) { | ||||
| 		if exists[br.Account] { | ||||
| 			continue | ||||
| 		} | ||||
| 		log.Infof("Starting bridge: %s channel: %s", br.Account, br.Channel) | ||||
| 		gw.Bridges = append(gw.Bridges, bridge.New(cfg, &br, c)) | ||||
| 		exists[br.Account] = true | ||||
| 	} | ||||
| 	gw.mapChannels() | ||||
| 	//TODO fix mapIgnores | ||||
| 	//gw.mapIgnores() | ||||
| 	exists = make(map[string]bool) | ||||
| 	gw.Message = make(chan config.Message) | ||||
| 	gw.Bridges = make(map[string]*bridge.Bridge) | ||||
| 	return gw | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) AddBridge(cfg *config.Bridge) error { | ||||
| 	for _, br := range gw.Bridges { | ||||
| 		err := br.Connect() | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("Bridge %s failed to start: %v", br.FullOrigin(), err) | ||||
| 		} | ||||
| 		for _, channel := range append(gw.ChannelsOut[br.FullOrigin()], gw.ChannelsIn[br.FullOrigin()]...) { | ||||
| 			if exists[br.FullOrigin()+channel] { | ||||
| 				continue | ||||
| 			} | ||||
| 			log.Infof("%s: joining %s", br.FullOrigin(), channel) | ||||
| 			br.JoinChannel(channel) | ||||
| 			exists[br.FullOrigin()+channel] = true | ||||
| 		if br.Account == cfg.Account { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	log.Infof("Starting bridge: %s ", cfg.Account) | ||||
| 	br := bridge.New(gw.Config, cfg, gw.Message) | ||||
| 	gw.Bridges[cfg.Account] = br | ||||
| 	err := br.Connect() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Bridge %s failed to start: %v", br.Account, err) | ||||
| 	} | ||||
| 	exists := make(map[string]bool) | ||||
| 	for _, channel := range append(gw.ChannelsOut[br.Account], gw.ChannelsIn[br.Account]...) { | ||||
| 		if !exists[br.Account+channel] { | ||||
| 			mychannel := channel | ||||
| 			log.Infof("%s: joining %s", br.Account, channel) | ||||
| 			if br.Protocol == "irc" && gw.ChannelOptions[br.Account+channel].Key != "" { | ||||
| 				log.Debugf("using key %s for channel %s", gw.ChannelOptions[br.Account+channel].Key, channel) | ||||
| 				mychannel = mychannel + " " + gw.ChannelOptions[br.Account+channel].Key | ||||
| 			} | ||||
| 			br.JoinChannel(mychannel) | ||||
| 			exists[br.Account+channel] = true | ||||
| 		} | ||||
| 	} | ||||
| 	gw.handleReceive(c) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) handleReceive(c chan config.Message) { | ||||
| func (gw *Gateway) Start() error { | ||||
| 	gw.mapChannels() | ||||
| 	for _, br := range append(gw.MyConfig.In, append(gw.MyConfig.InOut, gw.MyConfig.Out...)...) { | ||||
| 		err := gw.AddBridge(&br) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	//TODO fix mapIgnores | ||||
| 	//gw.mapIgnores() | ||||
| 	go gw.handleReceive() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) handleReceive() { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case msg := <-c: | ||||
| 		case msg := <-gw.Message: | ||||
| 			for _, br := range gw.Bridges { | ||||
| 				gw.handleMessage(msg, br) | ||||
| 			} | ||||
| @@ -68,17 +87,26 @@ func (gw *Gateway) handleReceive(c chan config.Message) { | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) mapChannels() error { | ||||
| 	options := make(map[string]config.ChannelOptions) | ||||
| 	m := make(map[string][]string) | ||||
| 	for _, br := range gw.MyConfig.Out { | ||||
| 		m[br.Account] = append(m[br.Account], br.Channel) | ||||
| 		options[br.Account+br.Channel] = br.Options | ||||
| 	} | ||||
| 	gw.ChannelsOut = m | ||||
| 	m = nil | ||||
| 	m = make(map[string][]string) | ||||
| 	for _, br := range gw.MyConfig.In { | ||||
| 		m[br.Account] = append(m[br.Account], br.Channel) | ||||
| 		options[br.Account+br.Channel] = br.Options | ||||
| 	} | ||||
| 	gw.ChannelsIn = m | ||||
| 	for _, br := range gw.MyConfig.InOut { | ||||
| 		gw.ChannelsIn[br.Account] = append(gw.ChannelsIn[br.Account], br.Channel) | ||||
| 		gw.ChannelsOut[br.Account] = append(gw.ChannelsOut[br.Account], br.Channel) | ||||
| 		options[br.Account+br.Channel] = br.Options | ||||
| 	} | ||||
| 	gw.ChannelOptions = options | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -92,7 +120,11 @@ func (gw *Gateway) mapIgnores() { | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string { | ||||
| 	channels := gw.ChannelsIn[msg.FullOrigin] | ||||
| 	channels := gw.ChannelsIn[msg.Account] | ||||
| 	// broadcast to every out channel (irc QUIT) | ||||
| 	if msg.Event == config.EVENT_JOIN_LEAVE && msg.Channel == "" { | ||||
| 		return gw.ChannelsOut[dest] | ||||
| 	} | ||||
| 	for _, channel := range channels { | ||||
| 		if channel == msg.Channel { | ||||
| 			return gw.ChannelsOut[dest] | ||||
| @@ -101,15 +133,19 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string { | ||||
| 	return []string{} | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) handleMessage(msg config.Message, dest bridge.Bridge) { | ||||
| func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) { | ||||
| 	if gw.ignoreMessage(&msg) { | ||||
| 		return | ||||
| 	} | ||||
| 	// only relay join/part when configged | ||||
| 	if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart { | ||||
| 		return | ||||
| 	} | ||||
| 	originchannel := msg.Channel | ||||
| 	channels := gw.getDestChannel(&msg, dest.FullOrigin()) | ||||
| 	channels := gw.getDestChannel(&msg, dest.Account) | ||||
| 	for _, channel := range channels { | ||||
| 		// do not send the message to the bridge we come from if also the channel is the same | ||||
| 		if msg.FullOrigin == dest.FullOrigin() && channel == originchannel { | ||||
| 		if msg.Account == dest.Account && channel == originchannel { | ||||
| 			continue | ||||
| 		} | ||||
| 		msg.Channel = channel | ||||
| @@ -117,7 +153,8 @@ func (gw *Gateway) handleMessage(msg config.Message, dest bridge.Bridge) { | ||||
| 			log.Debug("empty channel") | ||||
| 			return | ||||
| 		} | ||||
| 		log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.FullOrigin, originchannel, dest.FullOrigin(), channel) | ||||
| 		log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel) | ||||
| 		gw.modifyUsername(&msg, dest) | ||||
| 		err := dest.Send(msg) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(err) | ||||
| @@ -127,7 +164,7 @@ func (gw *Gateway) handleMessage(msg config.Message, dest bridge.Bridge) { | ||||
|  | ||||
| func (gw *Gateway) ignoreMessage(msg *config.Message) bool { | ||||
| 	// should we discard messages ? | ||||
| 	for _, entry := range gw.ignoreNicks[msg.FullOrigin] { | ||||
| 	for _, entry := range gw.ignoreNicks[msg.Account] { | ||||
| 		if msg.Username == entry { | ||||
| 			return true | ||||
| 		} | ||||
| @@ -135,17 +172,29 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) modifyMessage(msg *config.Message, dest bridge.Bridge) { | ||||
| func (gw *Gateway) modifyMessage(msg *config.Message, dest *bridge.Bridge) { | ||||
| 	val := reflect.ValueOf(gw.Config).Elem() | ||||
| 	for i := 0; i < val.NumField(); i++ { | ||||
| 		typeField := val.Type().Field(i) | ||||
| 		// look for the protocol map (both lowercase) | ||||
| 		if strings.ToLower(typeField.Name) == dest.Protocol() { | ||||
| 		if strings.ToLower(typeField.Name) == dest.Protocol { | ||||
| 			// get the Protocol struct from the map | ||||
| 			protoCfg := val.Field(i).MapIndex(reflect.ValueOf(dest.Origin())) | ||||
| 			protoCfg := val.Field(i).MapIndex(reflect.ValueOf(dest.Name)) | ||||
| 			//config.SetNickFormat(msg, protoCfg.Interface().(config.Protocol)) | ||||
| 			val.Field(i).SetMapIndex(reflect.ValueOf(dest.Origin()), protoCfg) | ||||
| 			val.Field(i).SetMapIndex(reflect.ValueOf(dest.Name), protoCfg) | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) { | ||||
| 	br := gw.Bridges[msg.Account] | ||||
| 	nick := gw.Config.General.RemoteNickFormat | ||||
| 	if nick == "" { | ||||
| 		nick = dest.Config.RemoteNickFormat | ||||
| 	} | ||||
| 	nick = strings.Replace(nick, "{NICK}", msg.Username, -1) | ||||
| 	nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1) | ||||
| 	nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1) | ||||
| 	msg.Username = nick | ||||
| } | ||||
|   | ||||
| @@ -4,12 +4,13 @@ import ( | ||||
| 	"github.com/42wim/matterbridge/bridge" | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type SameChannelGateway struct { | ||||
| 	*config.Config | ||||
| 	MyConfig    *config.SameChannelGateway | ||||
| 	Bridges     []bridge.Bridge | ||||
| 	Bridges     map[string]*bridge.Bridge | ||||
| 	Channels    []string | ||||
| 	ignoreNicks map[string][]string | ||||
| 	Name        string | ||||
| @@ -18,6 +19,7 @@ type SameChannelGateway struct { | ||||
| func New(cfg *config.Config, gateway *config.SameChannelGateway) error { | ||||
| 	c := make(chan config.Message) | ||||
| 	gw := &SameChannelGateway{} | ||||
| 	gw.Bridges = make(map[string]*bridge.Bridge) | ||||
| 	gw.Name = gateway.Name | ||||
| 	gw.Config = cfg | ||||
| 	gw.MyConfig = gateway | ||||
| @@ -25,15 +27,15 @@ func New(cfg *config.Config, gateway *config.SameChannelGateway) error { | ||||
| 	for _, account := range gateway.Accounts { | ||||
| 		br := config.Bridge{Account: account} | ||||
| 		log.Infof("Starting bridge: %s", account) | ||||
| 		gw.Bridges = append(gw.Bridges, bridge.New(cfg, &br, c)) | ||||
| 		gw.Bridges[account] = bridge.New(cfg, &br, c) | ||||
| 	} | ||||
| 	for _, br := range gw.Bridges { | ||||
| 		err := br.Connect() | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("Bridge %s failed to start: %v", br.FullOrigin(), err) | ||||
| 			log.Fatalf("Bridge %s failed to start: %v", br.Account, err) | ||||
| 		} | ||||
| 		for _, channel := range gw.Channels { | ||||
| 			log.Infof("%s: joining %s", br.FullOrigin(), channel) | ||||
| 			log.Infof("%s: joining %s", br.Account, channel) | ||||
| 			br.JoinChannel(channel) | ||||
| 		} | ||||
| 	} | ||||
| @@ -52,22 +54,35 @@ func (gw *SameChannelGateway) handleReceive(c chan config.Message) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (gw *SameChannelGateway) handleMessage(msg config.Message, dest bridge.Bridge) { | ||||
| func (gw *SameChannelGateway) handleMessage(msg config.Message, dest *bridge.Bridge) { | ||||
| 	// is this a configured channel | ||||
| 	if !gw.validChannel(msg.Channel) { | ||||
| 		return | ||||
| 	} | ||||
| 	// do not send the message to the bridge we come from if also the channel is the same | ||||
| 	if msg.FullOrigin == dest.FullOrigin() { | ||||
| 	if msg.Account == dest.Account { | ||||
| 		return | ||||
| 	} | ||||
| 	log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.FullOrigin, msg.Channel, dest.FullOrigin(), msg.Channel) | ||||
| 	gw.modifyUsername(&msg, dest) | ||||
| 	log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, msg.Channel, dest.Account, msg.Channel) | ||||
| 	err := dest.Send(msg) | ||||
| 	if err != nil { | ||||
| 		log.Error(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (gw *SameChannelGateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) { | ||||
| 	br := gw.Bridges[msg.Account] | ||||
| 	nick := gw.Config.General.RemoteNickFormat | ||||
| 	if nick == "" { | ||||
| 		nick = dest.Config.RemoteNickFormat | ||||
| 	} | ||||
| 	nick = strings.Replace(nick, "{NICK}", msg.Username, -1) | ||||
| 	nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1) | ||||
| 	nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1) | ||||
| 	msg.Username = nick | ||||
| } | ||||
|  | ||||
| func (gw *SameChannelGateway) validChannel(channel string) bool { | ||||
| 	for _, c := range gw.Channels { | ||||
| 		if c == channel { | ||||
|   | ||||
							
								
								
									
										108
									
								
								hook/rockethook/rockethook.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								hook/rockethook/rockethook.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| package rockethook | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"encoding/json" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| // Message for rocketchat outgoing webhook. | ||||
| type Message struct { | ||||
| 	Token       string `json:"token"` | ||||
| 	ChannelID   string `json:"channel_id"` | ||||
| 	ChannelName string `json:"channel_name"` | ||||
| 	Timestamp   string `json:"timestamp"` | ||||
| 	UserID      string `json:"user_id"` | ||||
| 	UserName    string `json:"user_name"` | ||||
| 	Text        string `json:"text"` | ||||
| } | ||||
|  | ||||
| // Client for Rocketchat. | ||||
| type Client struct { | ||||
| 	In         chan Message | ||||
| 	httpclient *http.Client | ||||
| 	Config | ||||
| } | ||||
|  | ||||
| // Config for client. | ||||
| type Config struct { | ||||
| 	BindAddress        string // Address to listen on | ||||
| 	Token              string // Only allow this token from Rocketchat. (Allow everything when empty) | ||||
| 	InsecureSkipVerify bool   // disable certificate checking | ||||
| } | ||||
|  | ||||
| // New Rocketchat client. | ||||
| func New(url string, config Config) *Client { | ||||
| 	c := &Client{In: make(chan Message), Config: config} | ||||
| 	tr := &http.Transport{ | ||||
| 		TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}, | ||||
| 	} | ||||
| 	c.httpclient = &http.Client{Transport: tr} | ||||
| 	_, _, err := net.SplitHostPort(c.BindAddress) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("incorrect bindaddress %s", c.BindAddress) | ||||
| 	} | ||||
| 	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...\n", c.BindAddress) | ||||
| 	if err := http.ListenAndServe(c.BindAddress, 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 := Message{} | ||||
| 	body, err := ioutil.ReadAll(r.Body) | ||||
| 	log.Println(string(body)) | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 		http.NotFound(w, r) | ||||
| 		return | ||||
| 	} | ||||
| 	defer r.Body.Close() | ||||
| 	err = json.Unmarshal(body, &msg) | ||||
| 	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 | ||||
| 	} | ||||
| 	msg.ChannelName = "#" + msg.ChannelName | ||||
| 	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() Message { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case msg := <-c.In: | ||||
| 			return msg | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,287 +0,0 @@ | ||||
| #This is configuration for matterbridge. | ||||
| ################################################################### | ||||
| #IRC section | ||||
| ################################################################### | ||||
| [IRC] | ||||
| #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 | ||||
|  | ||||
| #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] | ||||
| #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" | ||||
|  | ||||
| #### Settings for matterbridge -plus | ||||
| #### Thse settings will only be used when using the -plus switch. | ||||
|  | ||||
| #The mattermost hostname.  | ||||
| #REQUIRED | ||||
| Server="yourmattermostserver.domain" | ||||
|  | ||||
| #Your team on mattermost.  | ||||
| #REQUIRED | ||||
| Team="yourteam" | ||||
|  | ||||
| #login/pass of your bot.  | ||||
| #Use a dedicated user for this and not your own!  | ||||
| #REQUIRED | ||||
| Login="yourlogin" | ||||
| Password="yourpass" | ||||
|  | ||||
| #Enable this to make a http connection (instead of https) to your mattermost.  | ||||
| #OPTIONAL (default false) | ||||
| NoTLS=false | ||||
|  | ||||
| #### Shared settings for matterbridge and -plus | ||||
|  | ||||
| #Enable to not verify the certificate on your mattermost server.  | ||||
| #e.g. when using selfsigned certificates | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
|  | ||||
| #Enable to show IRC joins/parts in mattermost.  | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| #Whether to prefix messages from other bridges to mattermost with the sender's nick.  | ||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||
| #mattermost server. If you set PrefixMessagesWithNick to true, each message  | ||||
| #from bridge to Mattermost will by default be prefixed by "bridge-" + nick. You can,  | ||||
| #however, modify how the messages appear, by setting (and modifying) RemoteNickFormat  | ||||
| #OPTIONAL (default false) | ||||
| PrefixMessagesWithNick=false | ||||
|  | ||||
| #RemoteNickFormat defines how remote users appear on this bridge  | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||
| #The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge | ||||
| #OPTIONAL (default {BRIDGE}-{NICK}) | ||||
| RemoteNickFormat="[{BRIDGE}] <{NICK}> " | ||||
|  | ||||
| #how to format the list of IRC nicks when displayed in mattermost.  | ||||
| #Possible options are "table" and "plain" | ||||
| #OPTIONAL (default plain) | ||||
| NickFormatter=plain | ||||
| #How many nicks to list per row for formatters that support this.  | ||||
| #OPTIONAL (default 4) | ||||
| NicksPerRow=4 | ||||
|  | ||||
| #Nicks you want to ignore. Messages from those users will not be bridged. | ||||
| #OPTIONAL  | ||||
| IgnoreNicks="mmbot spammer2" | ||||
|  | ||||
| ################################################################### | ||||
| #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 | ||||
| ################################################################### | ||||
| #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" | ||||
|  | ||||
| [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 | ||||
| @@ -9,7 +9,7 @@ import ( | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| var version = "0.7.1" | ||||
| var version = "0.9.1" | ||||
|  | ||||
| func init() { | ||||
| 	log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) | ||||
| @@ -39,7 +39,7 @@ func main() { | ||||
| 		go func(gw config.SameChannelGateway) { | ||||
| 			err := samechannelgateway.New(cfg, &gw) | ||||
| 			if err != nil { | ||||
| 				log.Debugf("starting gateway failed %#v", err) | ||||
| 				log.Fatalf("starting gateway failed %#v", err) | ||||
| 			} | ||||
| 		}(gw) | ||||
| 	} | ||||
| @@ -49,12 +49,11 @@ func main() { | ||||
| 			continue | ||||
| 		} | ||||
| 		fmt.Printf("starting gateway %#v\n", gw.Name) | ||||
| 		go func(gw config.Gateway) { | ||||
| 			err := gateway.New(cfg, &gw) | ||||
| 			if err != nil { | ||||
| 				log.Debugf("starting gateway failed %#v", err) | ||||
| 			} | ||||
| 		}(gw) | ||||
| 		g := gateway.New(cfg, &gw) | ||||
| 		err := g.Start() | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("starting gateway failed %#v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	select {} | ||||
| } | ||||
|   | ||||
| @@ -13,6 +13,10 @@ | ||||
| #REQUIRED | ||||
| Server="irc.freenode.net:6667" | ||||
|  | ||||
| #Password for irc server (if necessary) | ||||
| #OPTIONAL (default "") | ||||
| Password="" | ||||
|  | ||||
| #Enable to use TLS connection to your irc server.  | ||||
| #OPTIONAL (default false) | ||||
| UseTLS=false | ||||
| @@ -37,18 +41,6 @@ Nick="matterbot" | ||||
| 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 | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| #Flood control | ||||
| #Delay in milliseconds between each message send to the IRC server | ||||
| #OPTIONAL (default 1300) | ||||
| @@ -60,6 +52,22 @@ MessageDelay=1300 | ||||
| #OPTIONAL (default 30) | ||||
| MessageQueue=30 | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| #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 | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges (only from irc-bridge at the moment) | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| ################################################################### | ||||
| #XMPP section | ||||
| ################################################################### | ||||
| @@ -89,6 +97,70 @@ Muc="conference.jabber.example.com" | ||||
| #REQUIRED | ||||
| Nick="xmppbot" | ||||
|  | ||||
| #Enable to not verify the certificate on your xmpp server. | ||||
| #e.g. when using selfsigned certificates | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| #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 | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges (only from irc-bridge at the moment) | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
|  | ||||
| ################################################################### | ||||
| #hipchat section | ||||
| ################################################################### | ||||
| #Go to https://www.hipchat.com/account/xmpp this will show you the necessary data | ||||
| #to fill in the section below | ||||
| [xmpp.hipchat] | ||||
| #xmpp server to connect to.  | ||||
| #REQUIRED | ||||
| Server="chat.hipchat.com:5222" | ||||
|  | ||||
| #Jabber ID | ||||
| #REQUIRED | ||||
| Jid="12345_12345@chat.hipchat.com" | ||||
|  | ||||
| #Password (your hipchat password) | ||||
| #REQUIRED | ||||
| Password="yourpass" | ||||
|  | ||||
| #Conference (MUC) domain | ||||
| #REQUIRED | ||||
| Muc="conf.hipchat.com" | ||||
|  | ||||
| #Room nickname | ||||
| #REQUIRED | ||||
| Nick="yourlogin" | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #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 | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}/{BRIDGE}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges (only from irc-bridge at the moment) | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
|  | ||||
| ################################################################### | ||||
| #mattermost section | ||||
| @@ -150,9 +222,13 @@ NoTLS=false | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
|  | ||||
| #Enable to show IRC joins/parts in mattermost.  | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
| #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 | ||||
|  | ||||
| #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  | ||||
| @@ -162,6 +238,11 @@ ShowJoinPart=false | ||||
| #OPTIONAL (default false) | ||||
| PrefixMessagesWithNick=false | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| #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 | ||||
| @@ -169,17 +250,9 @@ PrefixMessagesWithNick=false | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #how to format the list of IRC nicks when displayed in mattermost.  | ||||
| #Possible options are "table" and "plain" | ||||
| #OPTIONAL (default plain) | ||||
| NickFormatter="plain" | ||||
| #How many nicks to list per row for formatters that support this.  | ||||
| #OPTIONAL (default 4) | ||||
| NicksPerRow=4 | ||||
|  | ||||
| #Nicks you want to ignore. Messages from those users will not be bridged. | ||||
| #OPTIONAL  | ||||
| IgnoreNicks="mmbot spammer2" | ||||
| #Enable to show users joins/parts from other bridges (only from irc-bridge at the moment) | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| ################################################################### | ||||
| #Gitter section | ||||
| @@ -197,9 +270,10 @@ IgnoreNicks="mmbot spammer2" | ||||
| #REQUIRED | ||||
| Token="Yourtokenhere" | ||||
|  | ||||
| #Nicks you want to ignore. Messages of those users will not be bridged. | ||||
| #OPTIONAL  | ||||
| IgnoreNicks="spammer1 spammer2" | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| #RemoteNickFormat defines how remote users appear on this bridge  | ||||
| #The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. | ||||
| @@ -208,6 +282,10 @@ IgnoreNicks="spammer1 spammer2" | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges (only from irc-bridge at the moment) | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| ################################################################### | ||||
| #slack section | ||||
| ################################################################### | ||||
| @@ -237,6 +315,8 @@ BindAddress="0.0.0.0:9999" | ||||
| useAPI=false | ||||
|  | ||||
| #Token to connect with the Slack API | ||||
| #You'll have to use a test/api-token using a dedicated user and not a bot token. | ||||
| #See https://github.com/42wim/matterbridge/issues/75 for more info. | ||||
| #REQUIRED (when useAPI=true) | ||||
| Token="yourslacktoken" | ||||
|  | ||||
| @@ -249,21 +329,6 @@ Token="yourslacktoken" | ||||
| #OPTIONAL | ||||
| IconURL="https://robohash.org/{NICK}.png?size=48x48" | ||||
|  | ||||
| #Whether to prefix messages from other bridges to mattermost with RemoteNickFormat | ||||
| #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 | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #how to format the list of IRC nicks when displayed in slack | ||||
| #Possible options are "table" and "plain" | ||||
| #OPTIONAL (default plain) | ||||
| @@ -272,9 +337,29 @@ NickFormatter="plain" | ||||
| #OPTIONAL (default 4) | ||||
| NicksPerRow=4 | ||||
|  | ||||
| #Nicks you want to ignore. Messages from those users will not be bridged. | ||||
| #OPTIONAL  | ||||
| IgnoreNicks="mmbot spammer2" | ||||
| #Whether to prefix messages from other bridges to mattermost with RemoteNickFormat | ||||
| #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 | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| #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 | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges (only from irc-bridge at the moment) | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| ################################################################### | ||||
| #discord section | ||||
| @@ -288,15 +373,44 @@ IgnoreNicks="mmbot spammer2" | ||||
| #Token to connect with Discord API | ||||
| #You can get your token by following the instructions on  | ||||
| #https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token | ||||
| #The "Bot" tag needs to be added before the token | ||||
| #REQUIRED | ||||
| Token="Bot Yourtokenhere" | ||||
| Token="Yourtokenhere" | ||||
|  | ||||
| #REQUIRED | ||||
| Server="yourservername" | ||||
|  | ||||
| #Nicks you want to ignore. Messages of those users will not be bridged. | ||||
| #OPTIONAL  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| #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 | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges (only from irc-bridge at the moment) | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
| ################################################################### | ||||
| #telegram section | ||||
| ################################################################### | ||||
| [telegram] | ||||
|  | ||||
| #You can configure multiple servers "[telegram.name]" or "[telegram.name2]" | ||||
| #In this example we use [telegram.secure] | ||||
| #REQUIRED | ||||
| [telegram.secure] | ||||
| #Token to connect with telegram API | ||||
| #REQUIRED | ||||
| Token="Yourtokenhere" | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="spammer1 spammer2" | ||||
|  | ||||
| #RemoteNickFormat defines how remote users appear on this bridge  | ||||
| @@ -306,6 +420,79 @@ IgnoreNicks="spammer1 spammer2" | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges (only from irc-bridge at the moment) | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
|  | ||||
| ################################################################### | ||||
| #rocketchat section | ||||
| ################################################################### | ||||
| [rocketchat] | ||||
| #You can configure multiple servers "[rocketchat.name]" or "[rocketchat.name2]" | ||||
| #In this example we use [rocketchat.work] | ||||
| #REQUIRED | ||||
|  | ||||
| [rocketchat.rockme] | ||||
| #Url is your incoming webhook url as specified in rocketchat | ||||
| #Read #https://rocket.chat/docs/administrator-guides/integrations/#how-to-create-a-new-incoming-webhook | ||||
| #See administration - integrations - new integration - incoming webhook | ||||
| #REQUIRED | ||||
| URL="https://yourdomain/hooks/yourhookkey" | ||||
|  | ||||
| #Address to listen on for outgoing webhook requests from rocketchat. | ||||
| #See administration - integrations - new integration - outgoing webhook | ||||
| #REQUIRED  | ||||
| BindAddress="0.0.0.0:9999" | ||||
|  | ||||
| #Your nick/username as specified in your incoming webhook "Post as" setting | ||||
| #REQUIRED | ||||
| Nick="matterbot" | ||||
|  | ||||
| #Enable this to make a http connection (instead of https) to your rocketchat | ||||
| #OPTIONAL (default false) | ||||
| NoTLS=false | ||||
|  | ||||
| #Enable to not verify the certificate on your rocketchat server.  | ||||
| #e.g. when using selfsigned certificates | ||||
| #OPTIONAL (default false) | ||||
| SkipTLSVerify=true | ||||
|  | ||||
| #Whether to prefix messages from other bridges to rocketchat with the sender's nick.  | ||||
| #Useful if username overrides for incoming webhooks isn't enabled on the  | ||||
| #rocketchat server. If you set PrefixMessagesWithNick to true, each message  | ||||
| #from bridge to rocketchat will by default be prefixed by the RemoteNickFormat setting. i | ||||
| #OPTIONAL (default false) | ||||
| PrefixMessagesWithNick=false | ||||
|  | ||||
| #Nicks you want to ignore.  | ||||
| #Messages from those users will not be sent to other bridges. | ||||
| #OPTIONAL | ||||
| IgnoreNicks="ircspammer1 ircspammer2" | ||||
|  | ||||
| #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 | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| #Enable to show users joins/parts from other bridges (only from irc-bridge at the moment) | ||||
| #OPTIONAL (default false) | ||||
| ShowJoinPart=false | ||||
|  | ||||
|  | ||||
| ################################################################### | ||||
| #General configuration | ||||
| ################################################################### | ||||
| #Settings here override specific settings for each protocol | ||||
| [general] | ||||
| #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 | ||||
| #The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
| ################################################################### | ||||
| #Gateway configuration | ||||
| @@ -318,7 +505,7 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
| #from [[gateway.in]] to. | ||||
| # | ||||
| #Most of the time [[gateway.in]] and [[gateway.out]] are the same if you  | ||||
| #want bidirectional bridging. | ||||
| #want bidirectional bridging. You can then use [[gateway.inout]] | ||||
| # | ||||
|  | ||||
| [[gateway]] | ||||
| @@ -346,21 +533,38 @@ enable=true | ||||
|     #discord    - channel (without the #) | ||||
|     #           - ID:123456789 (where 123456789 is the channel ID)  | ||||
|     #               (https://github.com/42wim/matterbridge/issues/57) | ||||
|     #telegram   - chatid (a large negative number, eg -123456789) | ||||
|     #hipchat    - id_channel (see https://www.hipchat.com/account/xmpp for the correct channel) | ||||
|     #rocketchat - #channel (# is required) | ||||
|     #REQUIRED | ||||
|     channel="#testing" | ||||
|  | ||||
|     [[gateway.in]] | ||||
|     account="mattermost.work" | ||||
|     channel="off-topic" | ||||
|         #OPTIONAL - only used for IRC protocol at the moment | ||||
|         [gateway.in.options] | ||||
|         #OPTIONAL - your irc channel key | ||||
|         key="yourkey" | ||||
|  | ||||
|  | ||||
|     #[[gateway.out]] specifies the account and channels we will sent messages to. | ||||
|     [[gateway.out]] | ||||
|     account="irc.freenode" | ||||
|     channel="#testing" | ||||
|  | ||||
|     [[gateway.out]] | ||||
|         #OPTIONAL - only used for IRC protocol at the moment | ||||
|         [gateway.out.options] | ||||
|         #OPTIONAL - your irc channel key | ||||
|         key="yourkey" | ||||
|  | ||||
|     #[[gateway.inout]] can be used when then channel will be used to receive from  | ||||
|     #and send messages to | ||||
|     [[gateway.inout]] | ||||
|     account="mattermost.work" | ||||
|     channel="off-topic" | ||||
|  | ||||
|         #OPTIONAL - only used for IRC protocol at the moment | ||||
|         [gateway.inout.options] | ||||
|         #OPTIONAL - your irc channel key | ||||
|         key="yourkey" | ||||
|  | ||||
| #If you want to do a 1:1 mapping between protocols where the channelnames are the same | ||||
| #e.g. slack and mattermost you can use the samechannelgateway configuration | ||||
|   | ||||
| @@ -19,14 +19,14 @@ enable=true | ||||
|     account="irc.freenode" | ||||
|     channel="#testing" | ||||
|  | ||||
|     [[gateway.in]] | ||||
|     account="mattermost.work" | ||||
|     channel="off-topic" | ||||
|  | ||||
|     [[gateway.out]] | ||||
|     account="irc.freenode" | ||||
|     channel="#testing" | ||||
|  | ||||
|     [[gateway.in]] | ||||
|     account="mattermost.work" | ||||
|     channel="off-topic" | ||||
|      | ||||
|     [[gateway.out]] | ||||
|     account="mattermost.work" | ||||
|     channel="off-topic" | ||||
|   | ||||
| @@ -80,6 +80,11 @@ func (m *MMClient) SetLogLevel(level string) { | ||||
| } | ||||
|  | ||||
| func (m *MMClient) Login() error { | ||||
| 	// check if this is a first connect or a reconnection | ||||
| 	firstConnection := true | ||||
| 	if m.WsConnected == true { | ||||
| 		firstConnection = false | ||||
| 	} | ||||
| 	m.WsConnected = false | ||||
| 	if m.WsQuit { | ||||
| 		return nil | ||||
| @@ -125,11 +130,7 @@ func (m *MMClient) Login() error { | ||||
| 		if appErr != nil { | ||||
| 			d := b.Duration() | ||||
| 			m.log.Debug(appErr.DetailedError) | ||||
| 			//TODO more generic fix needed | ||||
| 			if !strings.Contains(appErr.DetailedError, "connection refused") && | ||||
| 				!strings.Contains(appErr.DetailedError, "invalid character") && | ||||
| 				!strings.Contains(appErr.DetailedError, "connection reset by peer") && | ||||
| 				!strings.Contains(appErr.DetailedError, "connection timed out") { | ||||
| 			if firstConnection { | ||||
| 				if appErr.Message == "" { | ||||
| 					return errors.New(appErr.DetailedError) | ||||
| 				} | ||||
| @@ -264,7 +265,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) { | ||||
| 	} | ||||
| 	rmsg.Username = m.GetUser(data.UserId).Username | ||||
| 	rmsg.Channel = m.GetChannelName(data.ChannelId) | ||||
| 	rmsg.Team = m.GetTeamName(rmsg.Raw.TeamId) | ||||
| 	rmsg.Team = m.GetTeamName(rmsg.Raw.Data["team_id"].(string)) | ||||
| 	// direct message | ||||
| 	if rmsg.Raw.Data["channel_type"] == "D" { | ||||
| 		rmsg.Channel = m.GetUser(data.UserId).Username | ||||
| @@ -275,7 +276,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) { | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UpdateUsers() error { | ||||
| 	mmusers, err := m.Client.GetProfilesForDirectMessageList(m.Team.Id) | ||||
| 	mmusers, err := m.Client.GetProfiles(0, 50000, "") | ||||
| 	if err != nil { | ||||
| 		return errors.New(err.DetailedError) | ||||
| 	} | ||||
| @@ -305,7 +306,7 @@ 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...) { | ||||
| 		for _, channel := range append(*t.Channels, *t.MoreChannels...) { | ||||
| 			if channel.Id == channelId { | ||||
| 				return channel.Name | ||||
| 			} | ||||
| @@ -322,7 +323,7 @@ func (m *MMClient) GetChannelId(name string, teamId string) string { | ||||
| 	} | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		if t.Id == teamId { | ||||
| 			for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) { | ||||
| 			for _, channel := range append(*t.Channels, *t.MoreChannels...) { | ||||
| 				if channel.Name == name { | ||||
| 					return channel.Id | ||||
| 				} | ||||
| @@ -336,7 +337,7 @@ 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...) { | ||||
| 		for _, channel := range append(*t.Channels, *t.MoreChannels...) { | ||||
| 			if channel.Id == channelId { | ||||
| 				return channel.Header | ||||
| 			} | ||||
| @@ -354,7 +355,7 @@ func (m *MMClient) PostMessage(channelId string, text string) { | ||||
| func (m *MMClient) JoinChannel(channelId string) error { | ||||
| 	m.RLock() | ||||
| 	defer m.RUnlock() | ||||
| 	for _, c := range m.Team.Channels.Channels { | ||||
| 	for _, c := range *m.Team.Channels { | ||||
| 		if c.Id == channelId { | ||||
| 			m.log.Debug("Not joining ", channelId, " already joined.") | ||||
| 			return nil | ||||
| @@ -397,7 +398,7 @@ func (m *MMClient) GetPublicLink(filename string) string { | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return res.Data.(string) | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetPublicLinks(filenames []string) []string { | ||||
| @@ -407,7 +408,7 @@ func (m *MMClient) GetPublicLinks(filenames []string) []string { | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		output = append(output, res.Data.(string)) | ||||
| 		output = append(output, res) | ||||
| 	} | ||||
| 	return output | ||||
| } | ||||
| @@ -432,15 +433,17 @@ func (m *MMClient) UpdateLastViewed(channelId string) { | ||||
| } | ||||
|  | ||||
| func (m *MMClient) UsernamesInChannel(channelId string) []string { | ||||
| 	ceiRes, err := m.Client.GetChannelExtraInfo(channelId, 5000, "") | ||||
| 	res, err := m.Client.GetMyChannelMembers() | ||||
| 	if err != nil { | ||||
| 		m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, err) | ||||
| 		return []string{} | ||||
| 	} | ||||
| 	extra := ceiRes.Data.(*model.ChannelExtra) | ||||
| 	members := res.Data.(*model.ChannelMembers) | ||||
| 	result := []string{} | ||||
| 	for _, member := range extra.Members { | ||||
| 		result = append(result, member.Username) | ||||
| 	for _, channel := range *members { | ||||
| 		if channel.ChannelId == channelId { | ||||
| 			result = append(result, m.GetUser(channel.UserId).Username) | ||||
| 		} | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
| @@ -500,10 +503,10 @@ func (m *MMClient) GetChannels() []*model.Channel { | ||||
| 	defer m.RUnlock() | ||||
| 	var channels []*model.Channel | ||||
| 	// our primary team channels first | ||||
| 	channels = append(channels, m.Team.Channels.Channels...) | ||||
| 	channels = append(channels, *m.Team.Channels...) | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		if t.Id != m.Team.Id { | ||||
| 			channels = append(channels, t.Channels.Channels...) | ||||
| 			channels = append(channels, *t.Channels...) | ||||
| 		} | ||||
| 	} | ||||
| 	return channels | ||||
| @@ -515,7 +518,7 @@ func (m *MMClient) GetMoreChannels() []*model.Channel { | ||||
| 	defer m.RUnlock() | ||||
| 	var channels []*model.Channel | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		channels = append(channels, t.MoreChannels.Channels...) | ||||
| 		channels = append(channels, *t.MoreChannels...) | ||||
| 	} | ||||
| 	return channels | ||||
| } | ||||
| @@ -526,8 +529,8 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string { | ||||
| 	defer m.RUnlock() | ||||
| 	var channels []*model.Channel | ||||
| 	for _, t := range m.OtherTeams { | ||||
| 		channels = append(channels, t.Channels.Channels...) | ||||
| 		channels = append(channels, t.MoreChannels.Channels...) | ||||
| 		channels = append(channels, *t.Channels...) | ||||
| 		channels = append(channels, *t.MoreChannels...) | ||||
| 		for _, c := range channels { | ||||
| 			if c.Id == channelId { | ||||
| 				return t.Id | ||||
| @@ -540,12 +543,12 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string { | ||||
| 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 | ||||
| 		} | ||||
| 	res, err := m.Client.GetChannel(channelId, "") | ||||
| 	if err != nil { | ||||
| 		return model.GetMillis() | ||||
| 	} | ||||
| 	return 0 | ||||
| 	data := res.Data.(*model.ChannelData) | ||||
| 	return data.Member.LastViewedAt | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetUsers() map[string]*model.User { | ||||
| @@ -579,6 +582,27 @@ func (m *MMClient) GetStatus(userId string) string { | ||||
| 	return "offline" | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetStatuses() map[string]string { | ||||
| 	var ok bool | ||||
| 	statuses := make(map[string]string) | ||||
| 	res, err := m.Client.GetStatuses() | ||||
| 	if err != nil { | ||||
| 		return statuses | ||||
| 	} | ||||
| 	if statuses, ok = res.Data.(map[string]string); ok { | ||||
| 		for userId, status := range statuses { | ||||
| 			statuses[userId] = "offline" | ||||
| 			if status == model.STATUS_AWAY { | ||||
| 				statuses[userId] = "away" | ||||
| 			} | ||||
| 			if status == model.STATUS_ONLINE { | ||||
| 				statuses[userId] = "online" | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return statuses | ||||
| } | ||||
|  | ||||
| func (m *MMClient) GetTeamId() string { | ||||
| 	return m.Team.Id | ||||
| } | ||||
| @@ -619,11 +643,20 @@ func (m *MMClient) initUser() error { | ||||
| 	//m.log.Debug("initUser(): loading all team data") | ||||
| 	for _, v := range initData.Teams { | ||||
| 		m.Client.SetTeamId(v.Id) | ||||
| 		mmusers, _ := m.Client.GetProfiles(v.Id, "") | ||||
| 		mmusers, err := m.Client.GetProfiles(0, 50000, "") | ||||
| 		if err != nil { | ||||
| 			return errors.New(err.DetailedError) | ||||
| 		} | ||||
| 		t := &Team{Team: v, Users: mmusers.Data.(map[string]*model.User), Id: v.Id} | ||||
| 		mmchannels, _ := m.Client.GetChannels("") | ||||
| 		mmchannels, err := m.Client.GetChannels("") | ||||
| 		if err != nil { | ||||
| 			return errors.New(err.DetailedError) | ||||
| 		} | ||||
| 		t.Channels = mmchannels.Data.(*model.ChannelList) | ||||
| 		mmchannels, _ = m.Client.GetMoreChannels("") | ||||
| 		mmchannels, err = m.Client.GetMoreChannels("") | ||||
| 		if err != nil { | ||||
| 			return errors.New(err.DetailedError) | ||||
| 		} | ||||
| 		t.MoreChannels = mmchannels.Data.(*model.ChannelList) | ||||
| 		m.OtherTeams = append(m.OtherTeams, t) | ||||
| 		if v.Name == m.Credentials.Team { | ||||
|   | ||||
							
								
								
									
										21
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/LICENSE.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/LICENSE.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2015 Syfaro | ||||
|  | ||||
| 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. | ||||
							
								
								
									
										650
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										650
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,650 @@ | ||||
| // Package tgbotapi has functions and types used for interacting with | ||||
| // the Telegram Bot API. | ||||
| package tgbotapi | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/technoweenie/multipartstreamer" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // BotAPI allows you to interact with the Telegram Bot API. | ||||
| type BotAPI struct { | ||||
| 	Token  string       `json:"token"` | ||||
| 	Debug  bool         `json:"debug"` | ||||
| 	Self   User         `json:"-"` | ||||
| 	Client *http.Client `json:"-"` | ||||
| } | ||||
|  | ||||
| // NewBotAPI creates a new BotAPI instance. | ||||
| // | ||||
| // It requires a token, provided by @BotFather on Telegram. | ||||
| func NewBotAPI(token string) (*BotAPI, error) { | ||||
| 	return NewBotAPIWithClient(token, &http.Client{}) | ||||
| } | ||||
|  | ||||
| // NewBotAPIWithClient creates a new BotAPI instance | ||||
| // and allows you to pass a http.Client. | ||||
| // | ||||
| // It requires a token, provided by @BotFather on Telegram. | ||||
| func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) { | ||||
| 	bot := &BotAPI{ | ||||
| 		Token:  token, | ||||
| 		Client: client, | ||||
| 	} | ||||
|  | ||||
| 	self, err := bot.GetMe() | ||||
| 	if err != nil { | ||||
| 		return &BotAPI{}, err | ||||
| 	} | ||||
|  | ||||
| 	bot.Self = self | ||||
|  | ||||
| 	return bot, nil | ||||
| } | ||||
|  | ||||
| // MakeRequest makes a request to a specific endpoint with our token. | ||||
| func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) { | ||||
| 	method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint) | ||||
|  | ||||
| 	resp, err := bot.Client.PostForm(method, params) | ||||
| 	if err != nil { | ||||
| 		return APIResponse{}, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	if resp.StatusCode == http.StatusForbidden { | ||||
| 		return APIResponse{}, errors.New(ErrAPIForbidden) | ||||
| 	} | ||||
|  | ||||
| 	bytes, err := ioutil.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return APIResponse{}, err | ||||
| 	} | ||||
|  | ||||
| 	if bot.Debug { | ||||
| 		log.Println(endpoint, string(bytes)) | ||||
| 	} | ||||
|  | ||||
| 	var apiResp APIResponse | ||||
| 	json.Unmarshal(bytes, &apiResp) | ||||
|  | ||||
| 	if !apiResp.Ok { | ||||
| 		return APIResponse{}, errors.New(apiResp.Description) | ||||
| 	} | ||||
|  | ||||
| 	return apiResp, nil | ||||
| } | ||||
|  | ||||
| // makeMessageRequest makes a request to a method that returns a Message. | ||||
| func (bot *BotAPI) makeMessageRequest(endpoint string, params url.Values) (Message, error) { | ||||
| 	resp, err := bot.MakeRequest(endpoint, params) | ||||
| 	if err != nil { | ||||
| 		return Message{}, err | ||||
| 	} | ||||
|  | ||||
| 	var message Message | ||||
| 	json.Unmarshal(resp.Result, &message) | ||||
|  | ||||
| 	bot.debugLog(endpoint, params, message) | ||||
|  | ||||
| 	return message, nil | ||||
| } | ||||
|  | ||||
| // UploadFile makes a request to the API with a file. | ||||
| // | ||||
| // Requires the parameter to hold the file not be in the params. | ||||
| // File should be a string to a file path, a FileBytes struct, | ||||
| // or a FileReader struct. | ||||
| // | ||||
| // Note that if your FileReader has a size set to -1, it will read | ||||
| // the file into memory to calculate a size. | ||||
| func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) { | ||||
| 	ms := multipartstreamer.New() | ||||
| 	ms.WriteFields(params) | ||||
|  | ||||
| 	switch f := file.(type) { | ||||
| 	case string: | ||||
| 		fileHandle, err := os.Open(f) | ||||
| 		if err != nil { | ||||
| 			return APIResponse{}, err | ||||
| 		} | ||||
| 		defer fileHandle.Close() | ||||
|  | ||||
| 		fi, err := os.Stat(f) | ||||
| 		if err != nil { | ||||
| 			return APIResponse{}, err | ||||
| 		} | ||||
|  | ||||
| 		ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle) | ||||
| 	case FileBytes: | ||||
| 		buf := bytes.NewBuffer(f.Bytes) | ||||
| 		ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf) | ||||
| 	case FileReader: | ||||
| 		if f.Size != -1 { | ||||
| 			ms.WriteReader(fieldname, f.Name, f.Size, f.Reader) | ||||
|  | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		data, err := ioutil.ReadAll(f.Reader) | ||||
| 		if err != nil { | ||||
| 			return APIResponse{}, err | ||||
| 		} | ||||
|  | ||||
| 		buf := bytes.NewBuffer(data) | ||||
|  | ||||
| 		ms.WriteReader(fieldname, f.Name, int64(len(data)), buf) | ||||
| 	default: | ||||
| 		return APIResponse{}, errors.New(ErrBadFileType) | ||||
| 	} | ||||
|  | ||||
| 	method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint) | ||||
|  | ||||
| 	req, err := http.NewRequest("POST", method, nil) | ||||
| 	if err != nil { | ||||
| 		return APIResponse{}, err | ||||
| 	} | ||||
|  | ||||
| 	ms.SetupRequest(req) | ||||
|  | ||||
| 	res, err := bot.Client.Do(req) | ||||
| 	if err != nil { | ||||
| 		return APIResponse{}, err | ||||
| 	} | ||||
| 	defer res.Body.Close() | ||||
|  | ||||
| 	bytes, err := ioutil.ReadAll(res.Body) | ||||
| 	if err != nil { | ||||
| 		return APIResponse{}, err | ||||
| 	} | ||||
|  | ||||
| 	if bot.Debug { | ||||
| 		log.Println(string(bytes)) | ||||
| 	} | ||||
|  | ||||
| 	var apiResp APIResponse | ||||
| 	json.Unmarshal(bytes, &apiResp) | ||||
|  | ||||
| 	if !apiResp.Ok { | ||||
| 		return APIResponse{}, errors.New(apiResp.Description) | ||||
| 	} | ||||
|  | ||||
| 	return apiResp, nil | ||||
| } | ||||
|  | ||||
| // GetFileDirectURL returns direct URL to file | ||||
| // | ||||
| // It requires the FileID. | ||||
| func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) { | ||||
| 	file, err := bot.GetFile(FileConfig{fileID}) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	return file.Link(bot.Token), nil | ||||
| } | ||||
|  | ||||
| // GetMe fetches the currently authenticated bot. | ||||
| // | ||||
| // This method is called upon creation to validate the token, | ||||
| // and so you may get this data from BotAPI.Self without the need for | ||||
| // another request. | ||||
| func (bot *BotAPI) GetMe() (User, error) { | ||||
| 	resp, err := bot.MakeRequest("getMe", nil) | ||||
| 	if err != nil { | ||||
| 		return User{}, err | ||||
| 	} | ||||
|  | ||||
| 	var user User | ||||
| 	json.Unmarshal(resp.Result, &user) | ||||
|  | ||||
| 	bot.debugLog("getMe", nil, user) | ||||
|  | ||||
| 	return user, nil | ||||
| } | ||||
|  | ||||
| // IsMessageToMe returns true if message directed to this bot. | ||||
| // | ||||
| // It requires the Message. | ||||
| func (bot *BotAPI) IsMessageToMe(message Message) bool { | ||||
| 	return strings.Contains(message.Text, "@"+bot.Self.UserName) | ||||
| } | ||||
|  | ||||
| // Send will send a Chattable item to Telegram. | ||||
| // | ||||
| // It requires the Chattable to send. | ||||
| func (bot *BotAPI) Send(c Chattable) (Message, error) { | ||||
| 	switch c.(type) { | ||||
| 	case Fileable: | ||||
| 		return bot.sendFile(c.(Fileable)) | ||||
| 	default: | ||||
| 		return bot.sendChattable(c) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // debugLog checks if the bot is currently running in debug mode, and if | ||||
| // so will display information about the request and response in the | ||||
| // debug log. | ||||
| func (bot *BotAPI) debugLog(context string, v url.Values, message interface{}) { | ||||
| 	if bot.Debug { | ||||
| 		log.Printf("%s req : %+v\n", context, v) | ||||
| 		log.Printf("%s resp: %+v\n", context, message) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // sendExisting will send a Message with an existing file to Telegram. | ||||
| func (bot *BotAPI) sendExisting(method string, config Fileable) (Message, error) { | ||||
| 	v, err := config.values() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return Message{}, err | ||||
| 	} | ||||
|  | ||||
| 	message, err := bot.makeMessageRequest(method, v) | ||||
| 	if err != nil { | ||||
| 		return Message{}, err | ||||
| 	} | ||||
|  | ||||
| 	return message, nil | ||||
| } | ||||
|  | ||||
| // uploadAndSend will send a Message with a new file to Telegram. | ||||
| func (bot *BotAPI) uploadAndSend(method string, config Fileable) (Message, error) { | ||||
| 	params, err := config.params() | ||||
| 	if err != nil { | ||||
| 		return Message{}, err | ||||
| 	} | ||||
|  | ||||
| 	file := config.getFile() | ||||
|  | ||||
| 	resp, err := bot.UploadFile(method, params, config.name(), file) | ||||
| 	if err != nil { | ||||
| 		return Message{}, err | ||||
| 	} | ||||
|  | ||||
| 	var message Message | ||||
| 	json.Unmarshal(resp.Result, &message) | ||||
|  | ||||
| 	bot.debugLog(method, nil, message) | ||||
|  | ||||
| 	return message, nil | ||||
| } | ||||
|  | ||||
| // sendFile determines if the file is using an existing file or uploading | ||||
| // a new file, then sends it as needed. | ||||
| func (bot *BotAPI) sendFile(config Fileable) (Message, error) { | ||||
| 	if config.useExistingFile() { | ||||
| 		return bot.sendExisting(config.method(), config) | ||||
| 	} | ||||
|  | ||||
| 	return bot.uploadAndSend(config.method(), config) | ||||
| } | ||||
|  | ||||
| // sendChattable sends a Chattable. | ||||
| func (bot *BotAPI) sendChattable(config Chattable) (Message, error) { | ||||
| 	v, err := config.values() | ||||
| 	if err != nil { | ||||
| 		return Message{}, err | ||||
| 	} | ||||
|  | ||||
| 	message, err := bot.makeMessageRequest(config.method(), v) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return Message{}, err | ||||
| 	} | ||||
|  | ||||
| 	return message, nil | ||||
| } | ||||
|  | ||||
| // GetUserProfilePhotos gets a user's profile photos. | ||||
| // | ||||
| // It requires UserID. | ||||
| // Offset and Limit are optional. | ||||
| func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) { | ||||
| 	v := url.Values{} | ||||
| 	v.Add("user_id", strconv.Itoa(config.UserID)) | ||||
| 	if config.Offset != 0 { | ||||
| 		v.Add("offset", strconv.Itoa(config.Offset)) | ||||
| 	} | ||||
| 	if config.Limit != 0 { | ||||
| 		v.Add("limit", strconv.Itoa(config.Limit)) | ||||
| 	} | ||||
|  | ||||
| 	resp, err := bot.MakeRequest("getUserProfilePhotos", v) | ||||
| 	if err != nil { | ||||
| 		return UserProfilePhotos{}, err | ||||
| 	} | ||||
|  | ||||
| 	var profilePhotos UserProfilePhotos | ||||
| 	json.Unmarshal(resp.Result, &profilePhotos) | ||||
|  | ||||
| 	bot.debugLog("GetUserProfilePhoto", v, profilePhotos) | ||||
|  | ||||
| 	return profilePhotos, nil | ||||
| } | ||||
|  | ||||
| // GetFile returns a File which can download a file from Telegram. | ||||
| // | ||||
| // Requires FileID. | ||||
| func (bot *BotAPI) GetFile(config FileConfig) (File, error) { | ||||
| 	v := url.Values{} | ||||
| 	v.Add("file_id", config.FileID) | ||||
|  | ||||
| 	resp, err := bot.MakeRequest("getFile", v) | ||||
| 	if err != nil { | ||||
| 		return File{}, err | ||||
| 	} | ||||
|  | ||||
| 	var file File | ||||
| 	json.Unmarshal(resp.Result, &file) | ||||
|  | ||||
| 	bot.debugLog("GetFile", v, file) | ||||
|  | ||||
| 	return file, nil | ||||
| } | ||||
|  | ||||
| // GetUpdates fetches updates. | ||||
| // If a WebHook is set, this will not return any data! | ||||
| // | ||||
| // Offset, Limit, and Timeout are optional. | ||||
| // To avoid stale items, set Offset to one higher than the previous item. | ||||
| // Set Timeout to a large number to reduce requests so you can get updates | ||||
| // instantly instead of having to wait between requests. | ||||
| func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) { | ||||
| 	v := url.Values{} | ||||
| 	if config.Offset != 0 { | ||||
| 		v.Add("offset", strconv.Itoa(config.Offset)) | ||||
| 	} | ||||
| 	if config.Limit > 0 { | ||||
| 		v.Add("limit", strconv.Itoa(config.Limit)) | ||||
| 	} | ||||
| 	if config.Timeout > 0 { | ||||
| 		v.Add("timeout", strconv.Itoa(config.Timeout)) | ||||
| 	} | ||||
|  | ||||
| 	resp, err := bot.MakeRequest("getUpdates", v) | ||||
| 	if err != nil { | ||||
| 		return []Update{}, err | ||||
| 	} | ||||
|  | ||||
| 	var updates []Update | ||||
| 	json.Unmarshal(resp.Result, &updates) | ||||
|  | ||||
| 	bot.debugLog("getUpdates", v, updates) | ||||
|  | ||||
| 	return updates, nil | ||||
| } | ||||
|  | ||||
| // RemoveWebhook unsets the webhook. | ||||
| func (bot *BotAPI) RemoveWebhook() (APIResponse, error) { | ||||
| 	return bot.MakeRequest("setWebhook", url.Values{}) | ||||
| } | ||||
|  | ||||
| // SetWebhook sets a webhook. | ||||
| // | ||||
| // If this is set, GetUpdates will not get any data! | ||||
| // | ||||
| // If you do not have a legitimate TLS certificate, you need to include | ||||
| // your self signed certificate with the config. | ||||
| func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) { | ||||
| 	if config.Certificate == nil { | ||||
| 		v := url.Values{} | ||||
| 		v.Add("url", config.URL.String()) | ||||
|  | ||||
| 		return bot.MakeRequest("setWebhook", v) | ||||
| 	} | ||||
|  | ||||
| 	params := make(map[string]string) | ||||
| 	params["url"] = config.URL.String() | ||||
|  | ||||
| 	resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate) | ||||
| 	if err != nil { | ||||
| 		return APIResponse{}, err | ||||
| 	} | ||||
|  | ||||
| 	var apiResp APIResponse | ||||
| 	json.Unmarshal(resp.Result, &apiResp) | ||||
|  | ||||
| 	if bot.Debug { | ||||
| 		log.Printf("setWebhook resp: %+v\n", apiResp) | ||||
| 	} | ||||
|  | ||||
| 	return apiResp, nil | ||||
| } | ||||
|  | ||||
| // GetUpdatesChan starts and returns a channel for getting updates. | ||||
| func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (<-chan Update, error) { | ||||
| 	updatesChan := make(chan Update, 100) | ||||
|  | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			updates, err := bot.GetUpdates(config) | ||||
| 			if err != nil { | ||||
| 				log.Println(err) | ||||
| 				log.Println("Failed to get updates, retrying in 3 seconds...") | ||||
| 				time.Sleep(time.Second * 3) | ||||
|  | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			for _, update := range updates { | ||||
| 				if update.UpdateID >= config.Offset { | ||||
| 					config.Offset = update.UpdateID + 1 | ||||
| 					updatesChan <- update | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	return updatesChan, nil | ||||
| } | ||||
|  | ||||
| // ListenForWebhook registers a http handler for a webhook. | ||||
| func (bot *BotAPI) ListenForWebhook(pattern string) <-chan Update { | ||||
| 	updatesChan := make(chan Update, 100) | ||||
|  | ||||
| 	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { | ||||
| 		bytes, _ := ioutil.ReadAll(r.Body) | ||||
|  | ||||
| 		var update Update | ||||
| 		json.Unmarshal(bytes, &update) | ||||
|  | ||||
| 		updatesChan <- update | ||||
| 	}) | ||||
|  | ||||
| 	return updatesChan | ||||
| } | ||||
|  | ||||
| // AnswerInlineQuery sends a response to an inline query. | ||||
| // | ||||
| // Note that you must respond to an inline query within 30 seconds. | ||||
| func (bot *BotAPI) AnswerInlineQuery(config InlineConfig) (APIResponse, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	v.Add("inline_query_id", config.InlineQueryID) | ||||
| 	v.Add("cache_time", strconv.Itoa(config.CacheTime)) | ||||
| 	v.Add("is_personal", strconv.FormatBool(config.IsPersonal)) | ||||
| 	v.Add("next_offset", config.NextOffset) | ||||
| 	data, err := json.Marshal(config.Results) | ||||
| 	if err != nil { | ||||
| 		return APIResponse{}, err | ||||
| 	} | ||||
| 	v.Add("results", string(data)) | ||||
| 	v.Add("switch_pm_text", config.SwitchPMText) | ||||
| 	v.Add("switch_pm_parameter", config.SwitchPMParameter) | ||||
|  | ||||
| 	bot.debugLog("answerInlineQuery", v, nil) | ||||
|  | ||||
| 	return bot.MakeRequest("answerInlineQuery", v) | ||||
| } | ||||
|  | ||||
| // AnswerCallbackQuery sends a response to an inline query callback. | ||||
| func (bot *BotAPI) AnswerCallbackQuery(config CallbackConfig) (APIResponse, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	v.Add("callback_query_id", config.CallbackQueryID) | ||||
| 	v.Add("text", config.Text) | ||||
| 	v.Add("show_alert", strconv.FormatBool(config.ShowAlert)) | ||||
|  | ||||
| 	bot.debugLog("answerCallbackQuery", v, nil) | ||||
|  | ||||
| 	return bot.MakeRequest("answerCallbackQuery", v) | ||||
| } | ||||
|  | ||||
| // KickChatMember kicks a user from a chat. Note that this only will work | ||||
| // in supergroups, and requires the bot to be an admin. Also note they | ||||
| // will be unable to rejoin until they are unbanned. | ||||
| func (bot *BotAPI) KickChatMember(config ChatMemberConfig) (APIResponse, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	if config.SuperGroupUsername == "" { | ||||
| 		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) | ||||
| 	} else { | ||||
| 		v.Add("chat_id", config.SuperGroupUsername) | ||||
| 	} | ||||
| 	v.Add("user_id", strconv.Itoa(config.UserID)) | ||||
|  | ||||
| 	bot.debugLog("kickChatMember", v, nil) | ||||
|  | ||||
| 	return bot.MakeRequest("kickChatMember", v) | ||||
| } | ||||
|  | ||||
| // LeaveChat makes the bot leave the chat. | ||||
| func (bot *BotAPI) LeaveChat(config ChatConfig) (APIResponse, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	if config.SuperGroupUsername == "" { | ||||
| 		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) | ||||
| 	} else { | ||||
| 		v.Add("chat_id", config.SuperGroupUsername) | ||||
| 	} | ||||
|  | ||||
| 	bot.debugLog("leaveChat", v, nil) | ||||
|  | ||||
| 	return bot.MakeRequest("leaveChat", v) | ||||
| } | ||||
|  | ||||
| // GetChat gets information about a chat. | ||||
| func (bot *BotAPI) GetChat(config ChatConfig) (Chat, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	if config.SuperGroupUsername == "" { | ||||
| 		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) | ||||
| 	} else { | ||||
| 		v.Add("chat_id", config.SuperGroupUsername) | ||||
| 	} | ||||
|  | ||||
| 	resp, err := bot.MakeRequest("getChat", v) | ||||
| 	if err != nil { | ||||
| 		return Chat{}, err | ||||
| 	} | ||||
|  | ||||
| 	var chat Chat | ||||
| 	err = json.Unmarshal(resp.Result, &chat) | ||||
|  | ||||
| 	bot.debugLog("getChat", v, chat) | ||||
|  | ||||
| 	return chat, err | ||||
| } | ||||
|  | ||||
| // GetChatAdministrators gets a list of administrators in the chat. | ||||
| // | ||||
| // If none have been appointed, only the creator will be returned. | ||||
| // Bots are not shown, even if they are an administrator. | ||||
| func (bot *BotAPI) GetChatAdministrators(config ChatConfig) ([]ChatMember, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	if config.SuperGroupUsername == "" { | ||||
| 		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) | ||||
| 	} else { | ||||
| 		v.Add("chat_id", config.SuperGroupUsername) | ||||
| 	} | ||||
|  | ||||
| 	resp, err := bot.MakeRequest("getChatAdministrators", v) | ||||
| 	if err != nil { | ||||
| 		return []ChatMember{}, err | ||||
| 	} | ||||
|  | ||||
| 	var members []ChatMember | ||||
| 	err = json.Unmarshal(resp.Result, &members) | ||||
|  | ||||
| 	bot.debugLog("getChatAdministrators", v, members) | ||||
|  | ||||
| 	return members, err | ||||
| } | ||||
|  | ||||
| // GetChatMembersCount gets the number of users in a chat. | ||||
| func (bot *BotAPI) GetChatMembersCount(config ChatConfig) (int, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	if config.SuperGroupUsername == "" { | ||||
| 		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) | ||||
| 	} else { | ||||
| 		v.Add("chat_id", config.SuperGroupUsername) | ||||
| 	} | ||||
|  | ||||
| 	resp, err := bot.MakeRequest("getChatMembersCount", v) | ||||
| 	if err != nil { | ||||
| 		return -1, err | ||||
| 	} | ||||
|  | ||||
| 	var count int | ||||
| 	err = json.Unmarshal(resp.Result, &count) | ||||
|  | ||||
| 	bot.debugLog("getChatMembersCount", v, count) | ||||
|  | ||||
| 	return count, err | ||||
| } | ||||
|  | ||||
| // GetChatMember gets a specific chat member. | ||||
| func (bot *BotAPI) GetChatMember(config ChatConfigWithUser) (ChatMember, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	if config.SuperGroupUsername == "" { | ||||
| 		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) | ||||
| 	} else { | ||||
| 		v.Add("chat_id", config.SuperGroupUsername) | ||||
| 	} | ||||
| 	v.Add("user_id", strconv.Itoa(config.UserID)) | ||||
|  | ||||
| 	resp, err := bot.MakeRequest("getChatMember", v) | ||||
| 	if err != nil { | ||||
| 		return ChatMember{}, err | ||||
| 	} | ||||
|  | ||||
| 	var member ChatMember | ||||
| 	err = json.Unmarshal(resp.Result, &member) | ||||
|  | ||||
| 	bot.debugLog("getChatMember", v, member) | ||||
|  | ||||
| 	return member, err | ||||
| } | ||||
|  | ||||
| // UnbanChatMember unbans a user from a chat. Note that this only will work | ||||
| // in supergroups, and requires the bot to be an admin. | ||||
| func (bot *BotAPI) UnbanChatMember(config ChatMemberConfig) (APIResponse, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	if config.SuperGroupUsername == "" { | ||||
| 		v.Add("chat_id", strconv.FormatInt(config.ChatID, 10)) | ||||
| 	} else { | ||||
| 		v.Add("chat_id", config.SuperGroupUsername) | ||||
| 	} | ||||
| 	v.Add("user_id", strconv.Itoa(config.UserID)) | ||||
|  | ||||
| 	bot.debugLog("unbanChatMember", v, nil) | ||||
|  | ||||
| 	return bot.MakeRequest("unbanChatMember", v) | ||||
| } | ||||
							
								
								
									
										694
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										694
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,694 @@ | ||||
| package tgbotapi | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| // Telegram constants | ||||
| const ( | ||||
| 	// APIEndpoint is the endpoint for all API methods, | ||||
| 	// with formatting for Sprintf. | ||||
| 	APIEndpoint = "https://api.telegram.org/bot%s/%s" | ||||
| 	// FileEndpoint is the endpoint for downloading a file from Telegram. | ||||
| 	FileEndpoint = "https://api.telegram.org/file/bot%s/%s" | ||||
| ) | ||||
|  | ||||
| // Constant values for ChatActions | ||||
| const ( | ||||
| 	ChatTyping         = "typing" | ||||
| 	ChatUploadPhoto    = "upload_photo" | ||||
| 	ChatRecordVideo    = "record_video" | ||||
| 	ChatUploadVideo    = "upload_video" | ||||
| 	ChatRecordAudio    = "record_audio" | ||||
| 	ChatUploadAudio    = "upload_audio" | ||||
| 	ChatUploadDocument = "upload_document" | ||||
| 	ChatFindLocation   = "find_location" | ||||
| ) | ||||
|  | ||||
| // API errors | ||||
| const ( | ||||
| 	// ErrAPIForbidden happens when a token is bad | ||||
| 	ErrAPIForbidden = "forbidden" | ||||
| ) | ||||
|  | ||||
| // Constant values for ParseMode in MessageConfig | ||||
| const ( | ||||
| 	ModeMarkdown = "Markdown" | ||||
| 	ModeHTML     = "HTML" | ||||
| ) | ||||
|  | ||||
| // Library errors | ||||
| const ( | ||||
| 	// ErrBadFileType happens when you pass an unknown type | ||||
| 	ErrBadFileType = "bad file type" | ||||
| 	ErrBadURL      = "bad or empty url" | ||||
| ) | ||||
|  | ||||
| // Chattable is any config type that can be sent. | ||||
| type Chattable interface { | ||||
| 	values() (url.Values, error) | ||||
| 	method() string | ||||
| } | ||||
|  | ||||
| // Fileable is any config type that can be sent that includes a file. | ||||
| type Fileable interface { | ||||
| 	Chattable | ||||
| 	params() (map[string]string, error) | ||||
| 	name() string | ||||
| 	getFile() interface{} | ||||
| 	useExistingFile() bool | ||||
| } | ||||
|  | ||||
| // BaseChat is base type for all chat config types. | ||||
| type BaseChat struct { | ||||
| 	ChatID              int64 // required | ||||
| 	ChannelUsername     string | ||||
| 	ReplyToMessageID    int | ||||
| 	ReplyMarkup         interface{} | ||||
| 	DisableNotification bool | ||||
| } | ||||
|  | ||||
| // values returns url.Values representation of BaseChat | ||||
| func (chat *BaseChat) values() (url.Values, error) { | ||||
| 	v := url.Values{} | ||||
| 	if chat.ChannelUsername != "" { | ||||
| 		v.Add("chat_id", chat.ChannelUsername) | ||||
| 	} else { | ||||
| 		v.Add("chat_id", strconv.FormatInt(chat.ChatID, 10)) | ||||
| 	} | ||||
|  | ||||
| 	if chat.ReplyToMessageID != 0 { | ||||
| 		v.Add("reply_to_message_id", strconv.Itoa(chat.ReplyToMessageID)) | ||||
| 	} | ||||
|  | ||||
| 	if chat.ReplyMarkup != nil { | ||||
| 		data, err := json.Marshal(chat.ReplyMarkup) | ||||
| 		if err != nil { | ||||
| 			return v, err | ||||
| 		} | ||||
|  | ||||
| 		v.Add("reply_markup", string(data)) | ||||
| 	} | ||||
|  | ||||
| 	v.Add("disable_notification", strconv.FormatBool(chat.DisableNotification)) | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // BaseFile is a base type for all file config types. | ||||
| type BaseFile struct { | ||||
| 	BaseChat | ||||
| 	File        interface{} | ||||
| 	FileID      string | ||||
| 	UseExisting bool | ||||
| 	MimeType    string | ||||
| 	FileSize    int | ||||
| } | ||||
|  | ||||
| // params returns a map[string]string representation of BaseFile. | ||||
| func (file BaseFile) params() (map[string]string, error) { | ||||
| 	params := make(map[string]string) | ||||
|  | ||||
| 	if file.ChannelUsername != "" { | ||||
| 		params["chat_id"] = file.ChannelUsername | ||||
| 	} else { | ||||
| 		params["chat_id"] = strconv.FormatInt(file.ChatID, 10) | ||||
| 	} | ||||
|  | ||||
| 	if file.ReplyToMessageID != 0 { | ||||
| 		params["reply_to_message_id"] = strconv.Itoa(file.ReplyToMessageID) | ||||
| 	} | ||||
|  | ||||
| 	if file.ReplyMarkup != nil { | ||||
| 		data, err := json.Marshal(file.ReplyMarkup) | ||||
| 		if err != nil { | ||||
| 			return params, err | ||||
| 		} | ||||
|  | ||||
| 		params["reply_markup"] = string(data) | ||||
| 	} | ||||
|  | ||||
| 	if file.MimeType != "" { | ||||
| 		params["mime_type"] = file.MimeType | ||||
| 	} | ||||
|  | ||||
| 	if file.FileSize > 0 { | ||||
| 		params["file_size"] = strconv.Itoa(file.FileSize) | ||||
| 	} | ||||
|  | ||||
| 	params["disable_notification"] = strconv.FormatBool(file.DisableNotification) | ||||
|  | ||||
| 	return params, nil | ||||
| } | ||||
|  | ||||
| // getFile returns the file. | ||||
| func (file BaseFile) getFile() interface{} { | ||||
| 	return file.File | ||||
| } | ||||
|  | ||||
| // useExistingFile returns if the BaseFile has already been uploaded. | ||||
| func (file BaseFile) useExistingFile() bool { | ||||
| 	return file.UseExisting | ||||
| } | ||||
|  | ||||
| // BaseEdit is base type of all chat edits. | ||||
| type BaseEdit struct { | ||||
| 	ChatID          int64 | ||||
| 	ChannelUsername string | ||||
| 	MessageID       int | ||||
| 	InlineMessageID string | ||||
| 	ReplyMarkup     *InlineKeyboardMarkup | ||||
| } | ||||
|  | ||||
| func (edit BaseEdit) values() (url.Values, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	if edit.InlineMessageID == "" { | ||||
| 		if edit.ChannelUsername != "" { | ||||
| 			v.Add("chat_id", edit.ChannelUsername) | ||||
| 		} else { | ||||
| 			v.Add("chat_id", strconv.FormatInt(edit.ChatID, 10)) | ||||
| 		} | ||||
| 		v.Add("message_id", strconv.Itoa(edit.MessageID)) | ||||
| 	} else { | ||||
| 		v.Add("inline_message_id", edit.InlineMessageID) | ||||
| 	} | ||||
|  | ||||
| 	if edit.ReplyMarkup != nil { | ||||
| 		data, err := json.Marshal(edit.ReplyMarkup) | ||||
| 		if err != nil { | ||||
| 			return v, err | ||||
| 		} | ||||
| 		v.Add("reply_markup", string(data)) | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // MessageConfig contains information about a SendMessage request. | ||||
| type MessageConfig struct { | ||||
| 	BaseChat | ||||
| 	Text                  string | ||||
| 	ParseMode             string | ||||
| 	DisableWebPagePreview bool | ||||
| } | ||||
|  | ||||
| // values returns a url.Values representation of MessageConfig. | ||||
| func (config MessageConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v.Add("text", config.Text) | ||||
| 	v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) | ||||
| 	if config.ParseMode != "" { | ||||
| 		v.Add("parse_mode", config.ParseMode) | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // method returns Telegram API method name for sending Message. | ||||
| func (config MessageConfig) method() string { | ||||
| 	return "sendMessage" | ||||
| } | ||||
|  | ||||
| // ForwardConfig contains information about a ForwardMessage request. | ||||
| type ForwardConfig struct { | ||||
| 	BaseChat | ||||
| 	FromChatID          int64 // required | ||||
| 	FromChannelUsername string | ||||
| 	MessageID           int // required | ||||
| } | ||||
|  | ||||
| // values returns a url.Values representation of ForwardConfig. | ||||
| func (config ForwardConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v.Add("from_chat_id", strconv.FormatInt(config.FromChatID, 10)) | ||||
| 	v.Add("message_id", strconv.Itoa(config.MessageID)) | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // method returns Telegram API method name for sending Forward. | ||||
| func (config ForwardConfig) method() string { | ||||
| 	return "forwardMessage" | ||||
| } | ||||
|  | ||||
| // PhotoConfig contains information about a SendPhoto request. | ||||
| type PhotoConfig struct { | ||||
| 	BaseFile | ||||
| 	Caption string | ||||
| } | ||||
|  | ||||
| // Params returns a map[string]string representation of PhotoConfig. | ||||
| func (config PhotoConfig) params() (map[string]string, error) { | ||||
| 	params, _ := config.BaseFile.params() | ||||
|  | ||||
| 	if config.Caption != "" { | ||||
| 		params["caption"] = config.Caption | ||||
| 	} | ||||
|  | ||||
| 	return params, nil | ||||
| } | ||||
|  | ||||
| // Values returns a url.Values representation of PhotoConfig. | ||||
| func (config PhotoConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
| 	if config.Caption != "" { | ||||
| 		v.Add("caption", config.Caption) | ||||
| 	} | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // name returns the field name for the Photo. | ||||
| func (config PhotoConfig) name() string { | ||||
| 	return "photo" | ||||
| } | ||||
|  | ||||
| // method returns Telegram API method name for sending Photo. | ||||
| func (config PhotoConfig) method() string { | ||||
| 	return "sendPhoto" | ||||
| } | ||||
|  | ||||
| // AudioConfig contains information about a SendAudio request. | ||||
| type AudioConfig struct { | ||||
| 	BaseFile | ||||
| 	Duration  int | ||||
| 	Performer string | ||||
| 	Title     string | ||||
| } | ||||
|  | ||||
| // values returns a url.Values representation of AudioConfig. | ||||
| func (config AudioConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
| 	if config.Duration != 0 { | ||||
| 		v.Add("duration", strconv.Itoa(config.Duration)) | ||||
| 	} | ||||
|  | ||||
| 	if config.Performer != "" { | ||||
| 		v.Add("performer", config.Performer) | ||||
| 	} | ||||
| 	if config.Title != "" { | ||||
| 		v.Add("title", config.Title) | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // params returns a map[string]string representation of AudioConfig. | ||||
| func (config AudioConfig) params() (map[string]string, error) { | ||||
| 	params, _ := config.BaseFile.params() | ||||
|  | ||||
| 	if config.Duration != 0 { | ||||
| 		params["duration"] = strconv.Itoa(config.Duration) | ||||
| 	} | ||||
|  | ||||
| 	if config.Performer != "" { | ||||
| 		params["performer"] = config.Performer | ||||
| 	} | ||||
| 	if config.Title != "" { | ||||
| 		params["title"] = config.Title | ||||
| 	} | ||||
|  | ||||
| 	return params, nil | ||||
| } | ||||
|  | ||||
| // name returns the field name for the Audio. | ||||
| func (config AudioConfig) name() string { | ||||
| 	return "audio" | ||||
| } | ||||
|  | ||||
| // method returns Telegram API method name for sending Audio. | ||||
| func (config AudioConfig) method() string { | ||||
| 	return "sendAudio" | ||||
| } | ||||
|  | ||||
| // DocumentConfig contains information about a SendDocument request. | ||||
| type DocumentConfig struct { | ||||
| 	BaseFile | ||||
| } | ||||
|  | ||||
| // values returns a url.Values representation of DocumentConfig. | ||||
| func (config DocumentConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // params returns a map[string]string representation of DocumentConfig. | ||||
| func (config DocumentConfig) params() (map[string]string, error) { | ||||
| 	params, _ := config.BaseFile.params() | ||||
|  | ||||
| 	return params, nil | ||||
| } | ||||
|  | ||||
| // name returns the field name for the Document. | ||||
| func (config DocumentConfig) name() string { | ||||
| 	return "document" | ||||
| } | ||||
|  | ||||
| // method returns Telegram API method name for sending Document. | ||||
| func (config DocumentConfig) method() string { | ||||
| 	return "sendDocument" | ||||
| } | ||||
|  | ||||
| // StickerConfig contains information about a SendSticker request. | ||||
| type StickerConfig struct { | ||||
| 	BaseFile | ||||
| } | ||||
|  | ||||
| // values returns a url.Values representation of StickerConfig. | ||||
| func (config StickerConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // params returns a map[string]string representation of StickerConfig. | ||||
| func (config StickerConfig) params() (map[string]string, error) { | ||||
| 	params, _ := config.BaseFile.params() | ||||
|  | ||||
| 	return params, nil | ||||
| } | ||||
|  | ||||
| // name returns the field name for the Sticker. | ||||
| func (config StickerConfig) name() string { | ||||
| 	return "sticker" | ||||
| } | ||||
|  | ||||
| // method returns Telegram API method name for sending Sticker. | ||||
| func (config StickerConfig) method() string { | ||||
| 	return "sendSticker" | ||||
| } | ||||
|  | ||||
| // VideoConfig contains information about a SendVideo request. | ||||
| type VideoConfig struct { | ||||
| 	BaseFile | ||||
| 	Duration int | ||||
| 	Caption  string | ||||
| } | ||||
|  | ||||
| // values returns a url.Values representation of VideoConfig. | ||||
| func (config VideoConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
| 	if config.Duration != 0 { | ||||
| 		v.Add("duration", strconv.Itoa(config.Duration)) | ||||
| 	} | ||||
| 	if config.Caption != "" { | ||||
| 		v.Add("caption", config.Caption) | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // params returns a map[string]string representation of VideoConfig. | ||||
| func (config VideoConfig) params() (map[string]string, error) { | ||||
| 	params, _ := config.BaseFile.params() | ||||
|  | ||||
| 	return params, nil | ||||
| } | ||||
|  | ||||
| // name returns the field name for the Video. | ||||
| func (config VideoConfig) name() string { | ||||
| 	return "video" | ||||
| } | ||||
|  | ||||
| // method returns Telegram API method name for sending Video. | ||||
| func (config VideoConfig) method() string { | ||||
| 	return "sendVideo" | ||||
| } | ||||
|  | ||||
| // VoiceConfig contains information about a SendVoice request. | ||||
| type VoiceConfig struct { | ||||
| 	BaseFile | ||||
| 	Duration int | ||||
| } | ||||
|  | ||||
| // values returns a url.Values representation of VoiceConfig. | ||||
| func (config VoiceConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
| 	if config.Duration != 0 { | ||||
| 		v.Add("duration", strconv.Itoa(config.Duration)) | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // params returns a map[string]string representation of VoiceConfig. | ||||
| func (config VoiceConfig) params() (map[string]string, error) { | ||||
| 	params, _ := config.BaseFile.params() | ||||
|  | ||||
| 	if config.Duration != 0 { | ||||
| 		params["duration"] = strconv.Itoa(config.Duration) | ||||
| 	} | ||||
|  | ||||
| 	return params, nil | ||||
| } | ||||
|  | ||||
| // name returns the field name for the Voice. | ||||
| func (config VoiceConfig) name() string { | ||||
| 	return "voice" | ||||
| } | ||||
|  | ||||
| // method returns Telegram API method name for sending Voice. | ||||
| func (config VoiceConfig) method() string { | ||||
| 	return "sendVoice" | ||||
| } | ||||
|  | ||||
| // LocationConfig contains information about a SendLocation request. | ||||
| type LocationConfig struct { | ||||
| 	BaseChat | ||||
| 	Latitude  float64 // required | ||||
| 	Longitude float64 // required | ||||
| } | ||||
|  | ||||
| // values returns a url.Values representation of LocationConfig. | ||||
| func (config LocationConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
|  | ||||
| 	v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) | ||||
| 	v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // method returns Telegram API method name for sending Location. | ||||
| func (config LocationConfig) method() string { | ||||
| 	return "sendLocation" | ||||
| } | ||||
|  | ||||
| // VenueConfig contains information about a SendVenue request. | ||||
| type VenueConfig struct { | ||||
| 	BaseChat | ||||
| 	Latitude     float64 // required | ||||
| 	Longitude    float64 // required | ||||
| 	Title        string  // required | ||||
| 	Address      string  // required | ||||
| 	FoursquareID string | ||||
| } | ||||
|  | ||||
| func (config VenueConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
|  | ||||
| 	v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) | ||||
| 	v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) | ||||
| 	v.Add("title", config.Title) | ||||
| 	v.Add("address", config.Address) | ||||
| 	if config.FoursquareID != "" { | ||||
| 		v.Add("foursquare_id", config.FoursquareID) | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| func (config VenueConfig) method() string { | ||||
| 	return "sendVenue" | ||||
| } | ||||
|  | ||||
| // ContactConfig allows you to send a contact. | ||||
| type ContactConfig struct { | ||||
| 	BaseChat | ||||
| 	PhoneNumber string | ||||
| 	FirstName   string | ||||
| 	LastName    string | ||||
| } | ||||
|  | ||||
| func (config ContactConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
|  | ||||
| 	v.Add("phone_number", config.PhoneNumber) | ||||
| 	v.Add("first_name", config.FirstName) | ||||
| 	v.Add("last_name", config.LastName) | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| func (config ContactConfig) method() string { | ||||
| 	return "sendContact" | ||||
| } | ||||
|  | ||||
| // ChatActionConfig contains information about a SendChatAction request. | ||||
| type ChatActionConfig struct { | ||||
| 	BaseChat | ||||
| 	Action string // required | ||||
| } | ||||
|  | ||||
| // values returns a url.Values representation of ChatActionConfig. | ||||
| func (config ChatActionConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v.Add("action", config.Action) | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // method returns Telegram API method name for sending ChatAction. | ||||
| func (config ChatActionConfig) method() string { | ||||
| 	return "sendChatAction" | ||||
| } | ||||
|  | ||||
| // EditMessageTextConfig allows you to modify the text in a message. | ||||
| type EditMessageTextConfig struct { | ||||
| 	BaseEdit | ||||
| 	Text                  string | ||||
| 	ParseMode             string | ||||
| 	DisableWebPagePreview bool | ||||
| } | ||||
|  | ||||
| func (config EditMessageTextConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseEdit.values() | ||||
|  | ||||
| 	v.Add("text", config.Text) | ||||
| 	v.Add("parse_mode", config.ParseMode) | ||||
| 	v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| func (config EditMessageTextConfig) method() string { | ||||
| 	return "editMessageText" | ||||
| } | ||||
|  | ||||
| // EditMessageCaptionConfig allows you to modify the caption of a message. | ||||
| type EditMessageCaptionConfig struct { | ||||
| 	BaseEdit | ||||
| 	Caption string | ||||
| } | ||||
|  | ||||
| func (config EditMessageCaptionConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseEdit.values() | ||||
|  | ||||
| 	v.Add("caption", config.Caption) | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| func (config EditMessageCaptionConfig) method() string { | ||||
| 	return "editMessageCaption" | ||||
| } | ||||
|  | ||||
| // EditMessageReplyMarkupConfig allows you to modify the reply markup | ||||
| // of a message. | ||||
| type EditMessageReplyMarkupConfig struct { | ||||
| 	BaseEdit | ||||
| } | ||||
|  | ||||
| func (config EditMessageReplyMarkupConfig) values() (url.Values, error) { | ||||
| 	return config.BaseEdit.values() | ||||
| } | ||||
|  | ||||
| func (config EditMessageReplyMarkupConfig) method() string { | ||||
| 	return "editMessageReplyMarkup" | ||||
| } | ||||
|  | ||||
| // UserProfilePhotosConfig contains information about a | ||||
| // GetUserProfilePhotos request. | ||||
| type UserProfilePhotosConfig struct { | ||||
| 	UserID int | ||||
| 	Offset int | ||||
| 	Limit  int | ||||
| } | ||||
|  | ||||
| // FileConfig has information about a file hosted on Telegram. | ||||
| type FileConfig struct { | ||||
| 	FileID string | ||||
| } | ||||
|  | ||||
| // UpdateConfig contains information about a GetUpdates request. | ||||
| type UpdateConfig struct { | ||||
| 	Offset  int | ||||
| 	Limit   int | ||||
| 	Timeout int | ||||
| } | ||||
|  | ||||
| // WebhookConfig contains information about a SetWebhook request. | ||||
| type WebhookConfig struct { | ||||
| 	URL         *url.URL | ||||
| 	Certificate interface{} | ||||
| } | ||||
|  | ||||
| // FileBytes contains information about a set of bytes to upload | ||||
| // as a File. | ||||
| type FileBytes struct { | ||||
| 	Name  string | ||||
| 	Bytes []byte | ||||
| } | ||||
|  | ||||
| // FileReader contains information about a reader to upload as a File. | ||||
| // If Size is -1, it will read the entire Reader into memory to | ||||
| // calculate a Size. | ||||
| type FileReader struct { | ||||
| 	Name   string | ||||
| 	Reader io.Reader | ||||
| 	Size   int64 | ||||
| } | ||||
|  | ||||
| // InlineConfig contains information on making an InlineQuery response. | ||||
| type InlineConfig struct { | ||||
| 	InlineQueryID     string        `json:"inline_query_id"` | ||||
| 	Results           []interface{} `json:"results"` | ||||
| 	CacheTime         int           `json:"cache_time"` | ||||
| 	IsPersonal        bool          `json:"is_personal"` | ||||
| 	NextOffset        string        `json:"next_offset"` | ||||
| 	SwitchPMText      string        `json:"switch_pm_text"` | ||||
| 	SwitchPMParameter string        `json:"switch_pm_parameter"` | ||||
| } | ||||
|  | ||||
| // CallbackConfig contains information on making a CallbackQuery response. | ||||
| type CallbackConfig struct { | ||||
| 	CallbackQueryID string `json:"callback_query_id"` | ||||
| 	Text            string `json:"text"` | ||||
| 	ShowAlert       bool   `json:"show_alert"` | ||||
| } | ||||
|  | ||||
| // ChatMemberConfig contains information about a user in a chat for use | ||||
| // with administrative functions such as kicking or unbanning a user. | ||||
| type ChatMemberConfig struct { | ||||
| 	ChatID             int64 | ||||
| 	SuperGroupUsername string | ||||
| 	UserID             int | ||||
| } | ||||
|  | ||||
| // ChatConfig contains information about getting information on a chat. | ||||
| type ChatConfig struct { | ||||
| 	ChatID             int64 | ||||
| 	SuperGroupUsername string | ||||
| } | ||||
|  | ||||
| // ChatConfigWithUser contains information about getting information on | ||||
| // a specific user within a chat. | ||||
| type ChatConfigWithUser struct { | ||||
| 	ChatID             int64 | ||||
| 	SuperGroupUsername string | ||||
| 	UserID             int | ||||
| } | ||||
							
								
								
									
										598
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										598
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,598 @@ | ||||
| package tgbotapi | ||||
|  | ||||
| import ( | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
| // NewMessage creates a new Message. | ||||
| // | ||||
| // chatID is where to send it, text is the message text. | ||||
| func NewMessage(chatID int64, text string) MessageConfig { | ||||
| 	return MessageConfig{ | ||||
| 		BaseChat: BaseChat{ | ||||
| 			ChatID:           chatID, | ||||
| 			ReplyToMessageID: 0, | ||||
| 		}, | ||||
| 		Text: text, | ||||
| 		DisableWebPagePreview: false, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewMessageToChannel creates a new Message that is sent to a channel | ||||
| // by username. | ||||
| // username is the username of the channel, text is the message text. | ||||
| func NewMessageToChannel(username string, text string) MessageConfig { | ||||
| 	return MessageConfig{ | ||||
| 		BaseChat: BaseChat{ | ||||
| 			ChannelUsername: username, | ||||
| 		}, | ||||
| 		Text: text, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewForward creates a new forward. | ||||
| // | ||||
| // chatID is where to send it, fromChatID is the source chat, | ||||
| // and messageID is the ID of the original message. | ||||
| func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig { | ||||
| 	return ForwardConfig{ | ||||
| 		BaseChat:   BaseChat{ChatID: chatID}, | ||||
| 		FromChatID: fromChatID, | ||||
| 		MessageID:  messageID, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewPhotoUpload creates a new photo uploader. | ||||
| // | ||||
| // chatID is where to send it, file is a string path to the file, | ||||
| // FileReader, or FileBytes. | ||||
| // | ||||
| // Note that you must send animated GIFs as a document. | ||||
| func NewPhotoUpload(chatID int64, file interface{}) PhotoConfig { | ||||
| 	return PhotoConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			File:        file, | ||||
| 			UseExisting: false, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewPhotoShare shares an existing photo. | ||||
| // You may use this to reshare an existing photo without reuploading it. | ||||
| // | ||||
| // chatID is where to send it, fileID is the ID of the file | ||||
| // already uploaded. | ||||
| func NewPhotoShare(chatID int64, fileID string) PhotoConfig { | ||||
| 	return PhotoConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			FileID:      fileID, | ||||
| 			UseExisting: true, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewAudioUpload creates a new audio uploader. | ||||
| // | ||||
| // chatID is where to send it, file is a string path to the file, | ||||
| // FileReader, or FileBytes. | ||||
| func NewAudioUpload(chatID int64, file interface{}) AudioConfig { | ||||
| 	return AudioConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			File:        file, | ||||
| 			UseExisting: false, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewAudioShare shares an existing audio file. | ||||
| // You may use this to reshare an existing audio file without | ||||
| // reuploading it. | ||||
| // | ||||
| // chatID is where to send it, fileID is the ID of the audio | ||||
| // already uploaded. | ||||
| func NewAudioShare(chatID int64, fileID string) AudioConfig { | ||||
| 	return AudioConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			FileID:      fileID, | ||||
| 			UseExisting: true, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewDocumentUpload creates a new document uploader. | ||||
| // | ||||
| // chatID is where to send it, file is a string path to the file, | ||||
| // FileReader, or FileBytes. | ||||
| func NewDocumentUpload(chatID int64, file interface{}) DocumentConfig { | ||||
| 	return DocumentConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			File:        file, | ||||
| 			UseExisting: false, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewDocumentShare shares an existing document. | ||||
| // You may use this to reshare an existing document without | ||||
| // reuploading it. | ||||
| // | ||||
| // chatID is where to send it, fileID is the ID of the document | ||||
| // already uploaded. | ||||
| func NewDocumentShare(chatID int64, fileID string) DocumentConfig { | ||||
| 	return DocumentConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			FileID:      fileID, | ||||
| 			UseExisting: true, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewStickerUpload creates a new sticker uploader. | ||||
| // | ||||
| // chatID is where to send it, file is a string path to the file, | ||||
| // FileReader, or FileBytes. | ||||
| func NewStickerUpload(chatID int64, file interface{}) StickerConfig { | ||||
| 	return StickerConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			File:        file, | ||||
| 			UseExisting: false, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewStickerShare shares an existing sticker. | ||||
| // You may use this to reshare an existing sticker without | ||||
| // reuploading it. | ||||
| // | ||||
| // chatID is where to send it, fileID is the ID of the sticker | ||||
| // already uploaded. | ||||
| func NewStickerShare(chatID int64, fileID string) StickerConfig { | ||||
| 	return StickerConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			FileID:      fileID, | ||||
| 			UseExisting: true, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewVideoUpload creates a new video uploader. | ||||
| // | ||||
| // chatID is where to send it, file is a string path to the file, | ||||
| // FileReader, or FileBytes. | ||||
| func NewVideoUpload(chatID int64, file interface{}) VideoConfig { | ||||
| 	return VideoConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			File:        file, | ||||
| 			UseExisting: false, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewVideoShare shares an existing video. | ||||
| // You may use this to reshare an existing video without reuploading it. | ||||
| // | ||||
| // chatID is where to send it, fileID is the ID of the video | ||||
| // already uploaded. | ||||
| func NewVideoShare(chatID int64, fileID string) VideoConfig { | ||||
| 	return VideoConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			FileID:      fileID, | ||||
| 			UseExisting: true, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewVoiceUpload creates a new voice uploader. | ||||
| // | ||||
| // chatID is where to send it, file is a string path to the file, | ||||
| // FileReader, or FileBytes. | ||||
| func NewVoiceUpload(chatID int64, file interface{}) VoiceConfig { | ||||
| 	return VoiceConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			File:        file, | ||||
| 			UseExisting: false, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewVoiceShare shares an existing voice. | ||||
| // You may use this to reshare an existing voice without reuploading it. | ||||
| // | ||||
| // chatID is where to send it, fileID is the ID of the video | ||||
| // already uploaded. | ||||
| func NewVoiceShare(chatID int64, fileID string) VoiceConfig { | ||||
| 	return VoiceConfig{ | ||||
| 		BaseFile: BaseFile{ | ||||
| 			BaseChat:    BaseChat{ChatID: chatID}, | ||||
| 			FileID:      fileID, | ||||
| 			UseExisting: true, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewContact allows you to send a shared contact. | ||||
| func NewContact(chatID int64, phoneNumber, firstName string) ContactConfig { | ||||
| 	return ContactConfig{ | ||||
| 		BaseChat: BaseChat{ | ||||
| 			ChatID: chatID, | ||||
| 		}, | ||||
| 		PhoneNumber: phoneNumber, | ||||
| 		FirstName:   firstName, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewLocation shares your location. | ||||
| // | ||||
| // chatID is where to send it, latitude and longitude are coordinates. | ||||
| func NewLocation(chatID int64, latitude float64, longitude float64) LocationConfig { | ||||
| 	return LocationConfig{ | ||||
| 		BaseChat: BaseChat{ | ||||
| 			ChatID: chatID, | ||||
| 		}, | ||||
| 		Latitude:  latitude, | ||||
| 		Longitude: longitude, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewVenue allows you to send a venue and its location. | ||||
| func NewVenue(chatID int64, title, address string, latitude, longitude float64) VenueConfig { | ||||
| 	return VenueConfig{ | ||||
| 		BaseChat: BaseChat{ | ||||
| 			ChatID: chatID, | ||||
| 		}, | ||||
| 		Title:     title, | ||||
| 		Address:   address, | ||||
| 		Latitude:  latitude, | ||||
| 		Longitude: longitude, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewChatAction sets a chat action. | ||||
| // Actions last for 5 seconds, or until your next action. | ||||
| // | ||||
| // chatID is where to send it, action should be set via Chat constants. | ||||
| func NewChatAction(chatID int64, action string) ChatActionConfig { | ||||
| 	return ChatActionConfig{ | ||||
| 		BaseChat: BaseChat{ChatID: chatID}, | ||||
| 		Action:   action, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewUserProfilePhotos gets user profile photos. | ||||
| // | ||||
| // userID is the ID of the user you wish to get profile photos from. | ||||
| func NewUserProfilePhotos(userID int) UserProfilePhotosConfig { | ||||
| 	return UserProfilePhotosConfig{ | ||||
| 		UserID: userID, | ||||
| 		Offset: 0, | ||||
| 		Limit:  0, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewUpdate gets updates since the last Offset. | ||||
| // | ||||
| // offset is the last Update ID to include. | ||||
| // You likely want to set this to the last Update ID plus 1. | ||||
| func NewUpdate(offset int) UpdateConfig { | ||||
| 	return UpdateConfig{ | ||||
| 		Offset:  offset, | ||||
| 		Limit:   0, | ||||
| 		Timeout: 0, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewWebhook creates a new webhook. | ||||
| // | ||||
| // link is the url parsable link you wish to get the updates. | ||||
| func NewWebhook(link string) WebhookConfig { | ||||
| 	u, _ := url.Parse(link) | ||||
|  | ||||
| 	return WebhookConfig{ | ||||
| 		URL: u, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewWebhookWithCert creates a new webhook with a certificate. | ||||
| // | ||||
| // link is the url you wish to get webhooks, | ||||
| // file contains a string to a file, FileReader, or FileBytes. | ||||
| func NewWebhookWithCert(link string, file interface{}) WebhookConfig { | ||||
| 	u, _ := url.Parse(link) | ||||
|  | ||||
| 	return WebhookConfig{ | ||||
| 		URL:         u, | ||||
| 		Certificate: file, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultArticle creates a new inline query article. | ||||
| func NewInlineQueryResultArticle(id, title, messageText string) InlineQueryResultArticle { | ||||
| 	return InlineQueryResultArticle{ | ||||
| 		Type:  "article", | ||||
| 		ID:    id, | ||||
| 		Title: title, | ||||
| 		InputMessageContent: InputTextMessageContent{ | ||||
| 			Text: messageText, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultArticleMarkdown creates a new inline query article with Markdown parsing. | ||||
| func NewInlineQueryResultArticleMarkdown(id, title, messageText string) InlineQueryResultArticle { | ||||
| 	return InlineQueryResultArticle{ | ||||
| 		Type:  "article", | ||||
| 		ID:    id, | ||||
| 		Title: title, | ||||
| 		InputMessageContent: InputTextMessageContent{ | ||||
| 			Text:      messageText, | ||||
| 			ParseMode: "Markdown", | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultArticleHTML creates a new inline query article with HTML parsing. | ||||
| func NewInlineQueryResultArticleHTML(id, title, messageText string) InlineQueryResultArticle { | ||||
| 	return InlineQueryResultArticle{ | ||||
| 		Type:  "article", | ||||
| 		ID:    id, | ||||
| 		Title: title, | ||||
| 		InputMessageContent: InputTextMessageContent{ | ||||
| 			Text:      messageText, | ||||
| 			ParseMode: "HTML", | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultGIF creates a new inline query GIF. | ||||
| func NewInlineQueryResultGIF(id, url string) InlineQueryResultGIF { | ||||
| 	return InlineQueryResultGIF{ | ||||
| 		Type: "gif", | ||||
| 		ID:   id, | ||||
| 		URL:  url, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultMPEG4GIF creates a new inline query MPEG4 GIF. | ||||
| func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF { | ||||
| 	return InlineQueryResultMPEG4GIF{ | ||||
| 		Type: "mpeg4_gif", | ||||
| 		ID:   id, | ||||
| 		URL:  url, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultPhoto creates a new inline query photo. | ||||
| func NewInlineQueryResultPhoto(id, url string) InlineQueryResultPhoto { | ||||
| 	return InlineQueryResultPhoto{ | ||||
| 		Type: "photo", | ||||
| 		ID:   id, | ||||
| 		URL:  url, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultPhotoWithThumb creates a new inline query photo. | ||||
| func NewInlineQueryResultPhotoWithThumb(id, url, thumb string) InlineQueryResultPhoto { | ||||
| 	return InlineQueryResultPhoto{ | ||||
| 		Type:     "photo", | ||||
| 		ID:       id, | ||||
| 		URL:      url, | ||||
| 		ThumbURL: thumb, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultVideo creates a new inline query video. | ||||
| func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo { | ||||
| 	return InlineQueryResultVideo{ | ||||
| 		Type: "video", | ||||
| 		ID:   id, | ||||
| 		URL:  url, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultAudio creates a new inline query audio. | ||||
| func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio { | ||||
| 	return InlineQueryResultAudio{ | ||||
| 		Type:  "audio", | ||||
| 		ID:    id, | ||||
| 		URL:   url, | ||||
| 		Title: title, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultVoice creates a new inline query voice. | ||||
| func NewInlineQueryResultVoice(id, url, title string) InlineQueryResultVoice { | ||||
| 	return InlineQueryResultVoice{ | ||||
| 		Type:  "voice", | ||||
| 		ID:    id, | ||||
| 		URL:   url, | ||||
| 		Title: title, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultDocument creates a new inline query document. | ||||
| func NewInlineQueryResultDocument(id, url, title, mimeType string) InlineQueryResultDocument { | ||||
| 	return InlineQueryResultDocument{ | ||||
| 		Type:     "document", | ||||
| 		ID:       id, | ||||
| 		URL:      url, | ||||
| 		Title:    title, | ||||
| 		MimeType: mimeType, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultLocation creates a new inline query location. | ||||
| func NewInlineQueryResultLocation(id, title string, latitude, longitude float64) InlineQueryResultLocation { | ||||
| 	return InlineQueryResultLocation{ | ||||
| 		Type:      "location", | ||||
| 		ID:        id, | ||||
| 		Title:     title, | ||||
| 		Latitude:  latitude, | ||||
| 		Longitude: longitude, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewEditMessageText allows you to edit the text of a message. | ||||
| func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTextConfig { | ||||
| 	return EditMessageTextConfig{ | ||||
| 		BaseEdit: BaseEdit{ | ||||
| 			ChatID:    chatID, | ||||
| 			MessageID: messageID, | ||||
| 		}, | ||||
| 		Text: text, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewEditMessageCaption allows you to edit the caption of a message. | ||||
| func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig { | ||||
| 	return EditMessageCaptionConfig{ | ||||
| 		BaseEdit: BaseEdit{ | ||||
| 			ChatID:    chatID, | ||||
| 			MessageID: messageID, | ||||
| 		}, | ||||
| 		Caption: caption, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewEditMessageReplyMarkup allows you to edit the inline | ||||
| // keyboard markup. | ||||
| func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKeyboardMarkup) EditMessageReplyMarkupConfig { | ||||
| 	return EditMessageReplyMarkupConfig{ | ||||
| 		BaseEdit: BaseEdit{ | ||||
| 			ChatID:      chatID, | ||||
| 			MessageID:   messageID, | ||||
| 			ReplyMarkup: &replyMarkup, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewHideKeyboard hides the keyboard, with the option for being selective | ||||
| // or hiding for everyone. | ||||
| func NewHideKeyboard(selective bool) ReplyKeyboardHide { | ||||
| 	return ReplyKeyboardHide{ | ||||
| 		HideKeyboard: true, | ||||
| 		Selective:    selective, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewKeyboardButton creates a regular keyboard button. | ||||
| func NewKeyboardButton(text string) KeyboardButton { | ||||
| 	return KeyboardButton{ | ||||
| 		Text: text, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewKeyboardButtonContact creates a keyboard button that requests | ||||
| // user contact information upon click. | ||||
| func NewKeyboardButtonContact(text string) KeyboardButton { | ||||
| 	return KeyboardButton{ | ||||
| 		Text:           text, | ||||
| 		RequestContact: true, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewKeyboardButtonLocation creates a keyboard button that requests | ||||
| // user location information upon click. | ||||
| func NewKeyboardButtonLocation(text string) KeyboardButton { | ||||
| 	return KeyboardButton{ | ||||
| 		Text:            text, | ||||
| 		RequestLocation: true, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewKeyboardButtonRow creates a row of keyboard buttons. | ||||
| func NewKeyboardButtonRow(buttons ...KeyboardButton) []KeyboardButton { | ||||
| 	var row []KeyboardButton | ||||
|  | ||||
| 	row = append(row, buttons...) | ||||
|  | ||||
| 	return row | ||||
| } | ||||
|  | ||||
| // NewReplyKeyboard creates a new regular keyboard with sane defaults. | ||||
| func NewReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup { | ||||
| 	var keyboard [][]KeyboardButton | ||||
|  | ||||
| 	keyboard = append(keyboard, rows...) | ||||
|  | ||||
| 	return ReplyKeyboardMarkup{ | ||||
| 		ResizeKeyboard: true, | ||||
| 		Keyboard:       keyboard, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineKeyboardButtonData creates an inline keyboard button with text | ||||
| // and data for a callback. | ||||
| func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton { | ||||
| 	return InlineKeyboardButton{ | ||||
| 		Text:         text, | ||||
| 		CallbackData: &data, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineKeyboardButtonURL creates an inline keyboard button with text | ||||
| // which goes to a URL. | ||||
| func NewInlineKeyboardButtonURL(text, url string) InlineKeyboardButton { | ||||
| 	return InlineKeyboardButton{ | ||||
| 		Text: text, | ||||
| 		URL:  &url, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineKeyboardButtonSwitch creates an inline keyboard button with | ||||
| // text which allows the user to switch to a chat or return to a chat. | ||||
| func NewInlineKeyboardButtonSwitch(text, sw string) InlineKeyboardButton { | ||||
| 	return InlineKeyboardButton{ | ||||
| 		Text:              text, | ||||
| 		SwitchInlineQuery: &sw, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineKeyboardRow creates an inline keyboard row with buttons. | ||||
| func NewInlineKeyboardRow(buttons ...InlineKeyboardButton) []InlineKeyboardButton { | ||||
| 	var row []InlineKeyboardButton | ||||
|  | ||||
| 	row = append(row, buttons...) | ||||
|  | ||||
| 	return row | ||||
| } | ||||
|  | ||||
| // NewInlineKeyboardMarkup creates a new inline keyboard. | ||||
| func NewInlineKeyboardMarkup(rows ...[]InlineKeyboardButton) InlineKeyboardMarkup { | ||||
| 	var keyboard [][]InlineKeyboardButton | ||||
|  | ||||
| 	keyboard = append(keyboard, rows...) | ||||
|  | ||||
| 	return InlineKeyboardMarkup{ | ||||
| 		InlineKeyboard: keyboard, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewCallback creates a new callback message. | ||||
| func NewCallback(id, text string) CallbackConfig { | ||||
| 	return CallbackConfig{ | ||||
| 		CallbackQueryID: id, | ||||
| 		Text:            text, | ||||
| 		ShowAlert:       false, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewCallbackWithAlert creates a new callback message that alerts | ||||
| // the user. | ||||
| func NewCallbackWithAlert(id, text string) CallbackConfig { | ||||
| 	return CallbackConfig{ | ||||
| 		CallbackQueryID: id, | ||||
| 		Text:            text, | ||||
| 		ShowAlert:       true, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										550
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										550
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,550 @@ | ||||
| package tgbotapi | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // APIResponse is a response from the Telegram API with the result | ||||
| // stored raw. | ||||
| type APIResponse struct { | ||||
| 	Ok          bool            `json:"ok"` | ||||
| 	Result      json.RawMessage `json:"result"` | ||||
| 	ErrorCode   int             `json:"error_code"` | ||||
| 	Description string          `json:"description"` | ||||
| } | ||||
|  | ||||
| // Update is an update response, from GetUpdates. | ||||
| type Update struct { | ||||
| 	UpdateID           int                 `json:"update_id"` | ||||
| 	Message            *Message            `json:"message"` | ||||
| 	EditedMessage      *Message            `json:"edited_message"` | ||||
| 	InlineQuery        *InlineQuery        `json:"inline_query"` | ||||
| 	ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"` | ||||
| 	CallbackQuery      *CallbackQuery      `json:"callback_query"` | ||||
| } | ||||
|  | ||||
| // User is a user on Telegram. | ||||
| type User struct { | ||||
| 	ID        int    `json:"id"` | ||||
| 	FirstName string `json:"first_name"` | ||||
| 	LastName  string `json:"last_name"` // optional | ||||
| 	UserName  string `json:"username"`  // optional | ||||
| } | ||||
|  | ||||
| // String displays a simple text version of a user. | ||||
| // | ||||
| // It is normally a user's username, but falls back to a first/last | ||||
| // name as available. | ||||
| func (u *User) String() string { | ||||
| 	if u.UserName != "" { | ||||
| 		return u.UserName | ||||
| 	} | ||||
|  | ||||
| 	name := u.FirstName | ||||
| 	if u.LastName != "" { | ||||
| 		name += " " + u.LastName | ||||
| 	} | ||||
|  | ||||
| 	return name | ||||
| } | ||||
|  | ||||
| // GroupChat is a group chat. | ||||
| type GroupChat struct { | ||||
| 	ID    int    `json:"id"` | ||||
| 	Title string `json:"title"` | ||||
| } | ||||
|  | ||||
| // Chat contains information about the place a message was sent. | ||||
| type Chat struct { | ||||
| 	ID        int64  `json:"id"` | ||||
| 	Type      string `json:"type"` | ||||
| 	Title     string `json:"title"`      // optional | ||||
| 	UserName  string `json:"username"`   // optional | ||||
| 	FirstName string `json:"first_name"` // optional | ||||
| 	LastName  string `json:"last_name"`  // optional | ||||
| } | ||||
|  | ||||
| // IsPrivate returns if the Chat is a private conversation. | ||||
| func (c Chat) IsPrivate() bool { | ||||
| 	return c.Type == "private" | ||||
| } | ||||
|  | ||||
| // IsGroup returns if the Chat is a group. | ||||
| func (c Chat) IsGroup() bool { | ||||
| 	return c.Type == "group" | ||||
| } | ||||
|  | ||||
| // IsSuperGroup returns if the Chat is a supergroup. | ||||
| func (c Chat) IsSuperGroup() bool { | ||||
| 	return c.Type == "supergroup" | ||||
| } | ||||
|  | ||||
| // IsChannel returns if the Chat is a channel. | ||||
| func (c Chat) IsChannel() bool { | ||||
| 	return c.Type == "channel" | ||||
| } | ||||
|  | ||||
| // ChatConfig returns a ChatConfig struct for chat related methods. | ||||
| func (c Chat) ChatConfig() ChatConfig { | ||||
| 	return ChatConfig{ChatID: c.ID} | ||||
| } | ||||
|  | ||||
| // Message is returned by almost every request, and contains data about | ||||
| // almost anything. | ||||
| type Message struct { | ||||
| 	MessageID             int              `json:"message_id"` | ||||
| 	From                  *User            `json:"from"` // optional | ||||
| 	Date                  int              `json:"date"` | ||||
| 	Chat                  *Chat            `json:"chat"` | ||||
| 	ForwardFrom           *User            `json:"forward_from"`            // optional | ||||
| 	ForwardFromChat       *Chat            `json:"forward_from_chat"`       // optional | ||||
| 	ForwardDate           int              `json:"forward_date"`            // optional | ||||
| 	ReplyToMessage        *Message         `json:"reply_to_message"`        // optional | ||||
| 	EditDate              int              `json:"edit_date"`               // optional | ||||
| 	Text                  string           `json:"text"`                    // optional | ||||
| 	Entities              *[]MessageEntity `json:"entities"`                // optional | ||||
| 	Audio                 *Audio           `json:"audio"`                   // optional | ||||
| 	Document              *Document        `json:"document"`                // optional | ||||
| 	Photo                 *[]PhotoSize     `json:"photo"`                   // optional | ||||
| 	Sticker               *Sticker         `json:"sticker"`                 // optional | ||||
| 	Video                 *Video           `json:"video"`                   // optional | ||||
| 	Voice                 *Voice           `json:"voice"`                   // optional | ||||
| 	Caption               string           `json:"caption"`                 // optional | ||||
| 	Contact               *Contact         `json:"contact"`                 // optional | ||||
| 	Location              *Location        `json:"location"`                // optional | ||||
| 	Venue                 *Venue           `json:"venue"`                   // optional | ||||
| 	NewChatMember         *User            `json:"new_chat_member"`         // optional | ||||
| 	LeftChatMember        *User            `json:"left_chat_member"`        // optional | ||||
| 	NewChatTitle          string           `json:"new_chat_title"`          // optional | ||||
| 	NewChatPhoto          *[]PhotoSize     `json:"new_chat_photo"`          // optional | ||||
| 	DeleteChatPhoto       bool             `json:"delete_chat_photo"`       // optional | ||||
| 	GroupChatCreated      bool             `json:"group_chat_created"`      // optional | ||||
| 	SuperGroupChatCreated bool             `json:"supergroup_chat_created"` // optional | ||||
| 	ChannelChatCreated    bool             `json:"channel_chat_created"`    // optional | ||||
| 	MigrateToChatID       int64            `json:"migrate_to_chat_id"`      // optional | ||||
| 	MigrateFromChatID     int64            `json:"migrate_from_chat_id"`    // optional | ||||
| 	PinnedMessage         *Message         `json:"pinned_message"`          // optional | ||||
| } | ||||
|  | ||||
| // Time converts the message timestamp into a Time. | ||||
| func (m *Message) Time() time.Time { | ||||
| 	return time.Unix(int64(m.Date), 0) | ||||
| } | ||||
|  | ||||
| // IsCommand returns true if message starts with '/'. | ||||
| func (m *Message) IsCommand() bool { | ||||
| 	return m.Text != "" && m.Text[0] == '/' | ||||
| } | ||||
|  | ||||
| // Command checks if the message was a command and if it was, returns the | ||||
| // command. If the Message was not a command, it returns an empty string. | ||||
| // | ||||
| // If the command contains the at bot syntax, it removes the bot name. | ||||
| func (m *Message) Command() string { | ||||
| 	if !m.IsCommand() { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	command := strings.SplitN(m.Text, " ", 2)[0][1:] | ||||
|  | ||||
| 	if i := strings.Index(command, "@"); i != -1 { | ||||
| 		command = command[:i] | ||||
| 	} | ||||
|  | ||||
| 	return command | ||||
| } | ||||
|  | ||||
| // CommandArguments checks if the message was a command and if it was, | ||||
| // returns all text after the command name. If the Message was not a | ||||
| // command, it returns an empty string. | ||||
| func (m *Message) CommandArguments() string { | ||||
| 	if !m.IsCommand() { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	split := strings.SplitN(m.Text, " ", 2) | ||||
| 	if len(split) != 2 { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return strings.SplitN(m.Text, " ", 2)[1] | ||||
| } | ||||
|  | ||||
| // MessageEntity contains information about data in a Message. | ||||
| type MessageEntity struct { | ||||
| 	Type   string `json:"type"` | ||||
| 	Offset int    `json:"offset"` | ||||
| 	Length int    `json:"length"` | ||||
| 	URL    string `json:"url"`  // optional | ||||
| 	User   *User  `json:"user"` // optional | ||||
| } | ||||
|  | ||||
| // ParseURL attempts to parse a URL contained within a MessageEntity. | ||||
| func (entity MessageEntity) ParseURL() (*url.URL, error) { | ||||
| 	if entity.URL == "" { | ||||
| 		return nil, errors.New(ErrBadURL) | ||||
| 	} | ||||
|  | ||||
| 	return url.Parse(entity.URL) | ||||
| } | ||||
|  | ||||
| // PhotoSize contains information about photos. | ||||
| type PhotoSize struct { | ||||
| 	FileID   string `json:"file_id"` | ||||
| 	Width    int    `json:"width"` | ||||
| 	Height   int    `json:"height"` | ||||
| 	FileSize int    `json:"file_size"` // optional | ||||
| } | ||||
|  | ||||
| // Audio contains information about audio. | ||||
| type Audio struct { | ||||
| 	FileID    string `json:"file_id"` | ||||
| 	Duration  int    `json:"duration"` | ||||
| 	Performer string `json:"performer"` // optional | ||||
| 	Title     string `json:"title"`     // optional | ||||
| 	MimeType  string `json:"mime_type"` // optional | ||||
| 	FileSize  int    `json:"file_size"` // optional | ||||
| } | ||||
|  | ||||
| // Document contains information about a document. | ||||
| type Document struct { | ||||
| 	FileID    string     `json:"file_id"` | ||||
| 	Thumbnail *PhotoSize `json:"thumb"`     // optional | ||||
| 	FileName  string     `json:"file_name"` // optional | ||||
| 	MimeType  string     `json:"mime_type"` // optional | ||||
| 	FileSize  int        `json:"file_size"` // optional | ||||
| } | ||||
|  | ||||
| // Sticker contains information about a sticker. | ||||
| type Sticker struct { | ||||
| 	FileID    string     `json:"file_id"` | ||||
| 	Width     int        `json:"width"` | ||||
| 	Height    int        `json:"height"` | ||||
| 	Thumbnail *PhotoSize `json:"thumb"`     // optional | ||||
| 	Emoji     string     `json:"emoji"`     // optional | ||||
| 	FileSize  int        `json:"file_size"` // optional | ||||
| } | ||||
|  | ||||
| // Video contains information about a video. | ||||
| type Video struct { | ||||
| 	FileID    string     `json:"file_id"` | ||||
| 	Width     int        `json:"width"` | ||||
| 	Height    int        `json:"height"` | ||||
| 	Duration  int        `json:"duration"` | ||||
| 	Thumbnail *PhotoSize `json:"thumb"`     // optional | ||||
| 	MimeType  string     `json:"mime_type"` // optional | ||||
| 	FileSize  int        `json:"file_size"` // optional | ||||
| } | ||||
|  | ||||
| // Voice contains information about a voice. | ||||
| type Voice struct { | ||||
| 	FileID   string `json:"file_id"` | ||||
| 	Duration int    `json:"duration"` | ||||
| 	MimeType string `json:"mime_type"` // optional | ||||
| 	FileSize int    `json:"file_size"` // optional | ||||
| } | ||||
|  | ||||
| // Contact contains information about a contact. | ||||
| // | ||||
| // Note that LastName and UserID may be empty. | ||||
| type Contact struct { | ||||
| 	PhoneNumber string `json:"phone_number"` | ||||
| 	FirstName   string `json:"first_name"` | ||||
| 	LastName    string `json:"last_name"` // optional | ||||
| 	UserID      int    `json:"user_id"`   // optional | ||||
| } | ||||
|  | ||||
| // Location contains information about a place. | ||||
| type Location struct { | ||||
| 	Longitude float64 `json:"longitude"` | ||||
| 	Latitude  float64 `json:"latitude"` | ||||
| } | ||||
|  | ||||
| // Venue contains information about a venue, including its Location. | ||||
| type Venue struct { | ||||
| 	Location     Location `json:"location"` | ||||
| 	Title        string   `json:"title"` | ||||
| 	Address      string   `json:"address"` | ||||
| 	FoursquareID string   `json:"foursquare_id"` // optional | ||||
| } | ||||
|  | ||||
| // UserProfilePhotos contains a set of user profile photos. | ||||
| type UserProfilePhotos struct { | ||||
| 	TotalCount int           `json:"total_count"` | ||||
| 	Photos     [][]PhotoSize `json:"photos"` | ||||
| } | ||||
|  | ||||
| // File contains information about a file to download from Telegram. | ||||
| type File struct { | ||||
| 	FileID   string `json:"file_id"` | ||||
| 	FileSize int    `json:"file_size"` // optional | ||||
| 	FilePath string `json:"file_path"` // optional | ||||
| } | ||||
|  | ||||
| // Link returns a full path to the download URL for a File. | ||||
| // | ||||
| // It requires the Bot Token to create the link. | ||||
| func (f *File) Link(token string) string { | ||||
| 	return fmt.Sprintf(FileEndpoint, token, f.FilePath) | ||||
| } | ||||
|  | ||||
| // ReplyKeyboardMarkup allows the Bot to set a custom keyboard. | ||||
| type ReplyKeyboardMarkup struct { | ||||
| 	Keyboard        [][]KeyboardButton `json:"keyboard"` | ||||
| 	ResizeKeyboard  bool               `json:"resize_keyboard"`   // optional | ||||
| 	OneTimeKeyboard bool               `json:"one_time_keyboard"` // optional | ||||
| 	Selective       bool               `json:"selective"`         // optional | ||||
| } | ||||
|  | ||||
| // KeyboardButton is a button within a custom keyboard. | ||||
| type KeyboardButton struct { | ||||
| 	Text            string `json:"text"` | ||||
| 	RequestContact  bool   `json:"request_contact"` | ||||
| 	RequestLocation bool   `json:"request_location"` | ||||
| } | ||||
|  | ||||
| // ReplyKeyboardHide allows the Bot to hide a custom keyboard. | ||||
| type ReplyKeyboardHide struct { | ||||
| 	HideKeyboard bool `json:"hide_keyboard"` | ||||
| 	Selective    bool `json:"selective"` // optional | ||||
| } | ||||
|  | ||||
| // InlineKeyboardMarkup is a custom keyboard presented for an inline bot. | ||||
| type InlineKeyboardMarkup struct { | ||||
| 	InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"` | ||||
| } | ||||
|  | ||||
| // InlineKeyboardButton is a button within a custom keyboard for | ||||
| // inline query responses. | ||||
| // | ||||
| // Note that some values are references as even an empty string | ||||
| // will change behavior. | ||||
| type InlineKeyboardButton struct { | ||||
| 	Text              string  `json:"text"` | ||||
| 	URL               *string `json:"url,omitempty"`                 // optional | ||||
| 	CallbackData      *string `json:"callback_data,omitempty"`       // optional | ||||
| 	SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional | ||||
| } | ||||
|  | ||||
| // CallbackQuery is data sent when a keyboard button with callback data | ||||
| // is clicked. | ||||
| type CallbackQuery struct { | ||||
| 	ID              string   `json:"id"` | ||||
| 	From            *User    `json:"from"` | ||||
| 	Message         *Message `json:"message"`           // optional | ||||
| 	InlineMessageID string   `json:"inline_message_id"` // optional | ||||
| 	Data            string   `json:"data"`              // optional | ||||
| } | ||||
|  | ||||
| // ForceReply allows the Bot to have users directly reply to it without | ||||
| // additional interaction. | ||||
| type ForceReply struct { | ||||
| 	ForceReply bool `json:"force_reply"` | ||||
| 	Selective  bool `json:"selective"` // optional | ||||
| } | ||||
|  | ||||
| // ChatMember is information about a member in a chat. | ||||
| type ChatMember struct { | ||||
| 	User   *User  `json:"user"` | ||||
| 	Status string `json:"status"` | ||||
| } | ||||
|  | ||||
| // IsCreator returns if the ChatMember was the creator of the chat. | ||||
| func (chat ChatMember) IsCreator() bool { return chat.Status == "creator" } | ||||
|  | ||||
| // IsAdministrator returns if the ChatMember is a chat administrator. | ||||
| func (chat ChatMember) IsAdministrator() bool { return chat.Status == "administrator" } | ||||
|  | ||||
| // IsMember returns if the ChatMember is a current member of the chat. | ||||
| func (chat ChatMember) IsMember() bool { return chat.Status == "member" } | ||||
|  | ||||
| // HasLeft returns if the ChatMember left the chat. | ||||
| func (chat ChatMember) HasLeft() bool { return chat.Status == "left" } | ||||
|  | ||||
| // WasKicked returns if the ChatMember was kicked from the chat. | ||||
| func (chat ChatMember) WasKicked() bool { return chat.Status == "kicked" } | ||||
|  | ||||
| // InlineQuery is a Query from Telegram for an inline request. | ||||
| type InlineQuery struct { | ||||
| 	ID       string    `json:"id"` | ||||
| 	From     *User     `json:"from"` | ||||
| 	Location *Location `json:"location"` // optional | ||||
| 	Query    string    `json:"query"` | ||||
| 	Offset   string    `json:"offset"` | ||||
| } | ||||
|  | ||||
| // InlineQueryResultArticle is an inline query response article. | ||||
| type InlineQueryResultArticle struct { | ||||
| 	Type                string                `json:"type"`                            // required | ||||
| 	ID                  string                `json:"id"`                              // required | ||||
| 	Title               string                `json:"title"`                           // required | ||||
| 	InputMessageContent interface{}           `json:"input_message_content,omitempty"` // required | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| 	URL                 string                `json:"url"` | ||||
| 	HideURL             bool                  `json:"hide_url"` | ||||
| 	Description         string                `json:"description"` | ||||
| 	ThumbURL            string                `json:"thumb_url"` | ||||
| 	ThumbWidth          int                   `json:"thumb_width"` | ||||
| 	ThumbHeight         int                   `json:"thumb_height"` | ||||
| } | ||||
|  | ||||
| // InlineQueryResultPhoto is an inline query response photo. | ||||
| type InlineQueryResultPhoto struct { | ||||
| 	Type                string                `json:"type"`      // required | ||||
| 	ID                  string                `json:"id"`        // required | ||||
| 	URL                 string                `json:"photo_url"` // required | ||||
| 	MimeType            string                `json:"mime_type"` | ||||
| 	Width               int                   `json:"photo_width"` | ||||
| 	Height              int                   `json:"photo_height"` | ||||
| 	ThumbURL            string                `json:"thumb_url"` | ||||
| 	Title               string                `json:"title"` | ||||
| 	Description         string                `json:"description"` | ||||
| 	Caption             string                `json:"caption"` | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| 	InputMessageContent interface{}           `json:"input_message_content,omitempty"` | ||||
| } | ||||
|  | ||||
| // InlineQueryResultGIF is an inline query response GIF. | ||||
| type InlineQueryResultGIF struct { | ||||
| 	Type                string                `json:"type"`    // required | ||||
| 	ID                  string                `json:"id"`      // required | ||||
| 	URL                 string                `json:"gif_url"` // required | ||||
| 	Width               int                   `json:"gif_width"` | ||||
| 	Height              int                   `json:"gif_height"` | ||||
| 	ThumbURL            string                `json:"thumb_url"` | ||||
| 	Title               string                `json:"title"` | ||||
| 	Caption             string                `json:"caption"` | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| 	InputMessageContent interface{}           `json:"input_message_content,omitempty"` | ||||
| } | ||||
|  | ||||
| // InlineQueryResultMPEG4GIF is an inline query response MPEG4 GIF. | ||||
| type InlineQueryResultMPEG4GIF struct { | ||||
| 	Type                string                `json:"type"`      // required | ||||
| 	ID                  string                `json:"id"`        // required | ||||
| 	URL                 string                `json:"mpeg4_url"` // required | ||||
| 	Width               int                   `json:"mpeg4_width"` | ||||
| 	Height              int                   `json:"mpeg4_height"` | ||||
| 	ThumbURL            string                `json:"thumb_url"` | ||||
| 	Title               string                `json:"title"` | ||||
| 	Caption             string                `json:"caption"` | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| 	InputMessageContent interface{}           `json:"input_message_content,omitempty"` | ||||
| } | ||||
|  | ||||
| // InlineQueryResultVideo is an inline query response video. | ||||
| type InlineQueryResultVideo struct { | ||||
| 	Type                string                `json:"type"`      // required | ||||
| 	ID                  string                `json:"id"`        // required | ||||
| 	URL                 string                `json:"video_url"` // required | ||||
| 	MimeType            string                `json:"mime_type"` // required | ||||
| 	ThumbURL            string                `json:"thumb_url"` | ||||
| 	Title               string                `json:"title"` | ||||
| 	Caption             string                `json:"caption"` | ||||
| 	Width               int                   `json:"video_width"` | ||||
| 	Height              int                   `json:"video_height"` | ||||
| 	Duration            int                   `json:"video_duration"` | ||||
| 	Description         string                `json:"description"` | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| 	InputMessageContent interface{}           `json:"input_message_content,omitempty"` | ||||
| } | ||||
|  | ||||
| // InlineQueryResultAudio is an inline query response audio. | ||||
| type InlineQueryResultAudio struct { | ||||
| 	Type                string                `json:"type"`      // required | ||||
| 	ID                  string                `json:"id"`        // required | ||||
| 	URL                 string                `json:"audio_url"` // required | ||||
| 	Title               string                `json:"title"`     // required | ||||
| 	Performer           string                `json:"performer"` | ||||
| 	Duration            int                   `json:"audio_duration"` | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| 	InputMessageContent interface{}           `json:"input_message_content,omitempty"` | ||||
| } | ||||
|  | ||||
| // InlineQueryResultVoice is an inline query response voice. | ||||
| type InlineQueryResultVoice struct { | ||||
| 	Type                string                `json:"type"`      // required | ||||
| 	ID                  string                `json:"id"`        // required | ||||
| 	URL                 string                `json:"voice_url"` // required | ||||
| 	Title               string                `json:"title"`     // required | ||||
| 	Duration            int                   `json:"voice_duration"` | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| 	InputMessageContent interface{}           `json:"input_message_content,omitempty"` | ||||
| } | ||||
|  | ||||
| // InlineQueryResultDocument is an inline query response document. | ||||
| type InlineQueryResultDocument struct { | ||||
| 	Type                string                `json:"type"`  // required | ||||
| 	ID                  string                `json:"id"`    // required | ||||
| 	Title               string                `json:"title"` // required | ||||
| 	Caption             string                `json:"caption"` | ||||
| 	URL                 string                `json:"document_url"` // required | ||||
| 	MimeType            string                `json:"mime_type"`    // required | ||||
| 	Description         string                `json:"description"` | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| 	InputMessageContent interface{}           `json:"input_message_content,omitempty"` | ||||
| 	ThumbURL            string                `json:"thumb_url"` | ||||
| 	ThumbWidth          int                   `json:"thumb_width"` | ||||
| 	ThumbHeight         int                   `json:"thumb_height"` | ||||
| } | ||||
|  | ||||
| // InlineQueryResultLocation is an inline query response location. | ||||
| type InlineQueryResultLocation struct { | ||||
| 	Type                string                `json:"type"`      // required | ||||
| 	ID                  string                `json:"id"`        // required | ||||
| 	Latitude            float64               `json:"latitude"`  // required | ||||
| 	Longitude           float64               `json:"longitude"` // required | ||||
| 	Title               string                `json:"title"`     // required | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| 	InputMessageContent interface{}           `json:"input_message_content,omitempty"` | ||||
| 	ThumbURL            string                `json:"thumb_url"` | ||||
| 	ThumbWidth          int                   `json:"thumb_width"` | ||||
| 	ThumbHeight         int                   `json:"thumb_height"` | ||||
| } | ||||
|  | ||||
| // ChosenInlineResult is an inline query result chosen by a User | ||||
| type ChosenInlineResult struct { | ||||
| 	ResultID        string    `json:"result_id"` | ||||
| 	From            *User     `json:"from"` | ||||
| 	Location        *Location `json:"location"` | ||||
| 	InlineMessageID string    `json:"inline_message_id"` | ||||
| 	Query           string    `json:"query"` | ||||
| } | ||||
|  | ||||
| // InputTextMessageContent contains text for displaying | ||||
| // as an inline query result. | ||||
| type InputTextMessageContent struct { | ||||
| 	Text                  string `json:"message_text"` | ||||
| 	ParseMode             string `json:"parse_mode"` | ||||
| 	DisableWebPagePreview bool   `json:"disable_web_page_preview"` | ||||
| } | ||||
|  | ||||
| // InputLocationMessageContent contains a location for displaying | ||||
| // as an inline query result. | ||||
| type InputLocationMessageContent struct { | ||||
| 	Latitude  float64 `json:"latitude"` | ||||
| 	Longitude float64 `json:"longitude"` | ||||
| } | ||||
|  | ||||
| // InputVenueMessageContent contains a venue for displaying | ||||
| // as an inline query result. | ||||
| type InputVenueMessageContent struct { | ||||
| 	Latitude     float64 `json:"latitude"` | ||||
| 	Longitude    float64 `json:"longitude"` | ||||
| 	Title        string  `json:"title"` | ||||
| 	Address      string  `json:"address"` | ||||
| 	FoursquareID string  `json:"foursquare_id"` | ||||
| } | ||||
|  | ||||
| // InputContactMessageContent contains a contact for displaying | ||||
| // as an inline query result. | ||||
| type InputContactMessageContent struct { | ||||
| 	PhoneNumber string `json:"phone_number"` | ||||
| 	FirstName   string `json:"first_name"` | ||||
| 	LastName    string `json:"last_name"` | ||||
| } | ||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/einterfaces/cluster.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/einterfaces/cluster.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -11,14 +11,17 @@ type ClusterInterface interface { | ||||
| 	StartInterNodeCommunication() | ||||
| 	StopInterNodeCommunication() | ||||
| 	GetClusterInfos() []*model.ClusterInfo | ||||
| 	GetClusterStats() ([]*model.ClusterStats, *model.AppError) | ||||
| 	RemoveAllSessionsForUserId(userId string) | ||||
| 	InvalidateCacheForUser(userId string) | ||||
| 	InvalidateCacheForChannel(channelId string) | ||||
| 	InvalidateCacheForChannelPosts(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 | ||||
| 	InvalidateAllCaches() *model.AppError | ||||
| } | ||||
|  | ||||
| var theClusterInterface ClusterInterface | ||||
|   | ||||
							
								
								
									
										41
									
								
								vendor/github.com/mattermost/platform/einterfaces/metrics.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								vendor/github.com/mattermost/platform/einterfaces/metrics.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package einterfaces | ||||
|  | ||||
| type MetricsInterface interface { | ||||
| 	StartServer() | ||||
| 	StopServer() | ||||
|  | ||||
| 	IncrementPostCreate() | ||||
| 	IncrementPostSentEmail() | ||||
| 	IncrementPostSentPush() | ||||
| 	IncrementPostBroadcast() | ||||
| 	IncrementPostFileAttachment(count int) | ||||
|  | ||||
| 	IncrementHttpRequest() | ||||
| 	IncrementHttpError() | ||||
| 	ObserveHttpRequestDuration(elapsed float64) | ||||
|  | ||||
| 	IncrementLogin() | ||||
| 	IncrementLoginFail() | ||||
|  | ||||
| 	IncrementEtagHitCounter(route string) | ||||
| 	IncrementEtagMissCounter(route string) | ||||
|  | ||||
| 	IncrementMemCacheHitCounter(cacheName string) | ||||
| 	IncrementMemCacheMissCounter(cacheName string) | ||||
|  | ||||
| 	AddMemCacheHitCounter(cacheName string, amount float64) | ||||
| 	AddMemCacheMissCounter(cacheName string, amount float64) | ||||
| } | ||||
|  | ||||
| var theMetricsInterface MetricsInterface | ||||
|  | ||||
| func RegisterMetricsInterface(newInterface MetricsInterface) { | ||||
| 	theMetricsInterface = newInterface | ||||
| } | ||||
|  | ||||
| func GetMetricsInterface() MetricsInterface { | ||||
| 	return theMetricsInterface | ||||
| } | ||||
							
								
								
									
										2
									
								
								vendor/github.com/mattermost/platform/einterfaces/mfa.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/mattermost/platform/einterfaces/mfa.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -8,7 +8,7 @@ import ( | ||||
| ) | ||||
|  | ||||
| type MfaInterface interface { | ||||
| 	GenerateQrCode(user *model.User) ([]byte, *model.AppError) | ||||
| 	GenerateSecret(user *model.User) (string, []byte, *model.AppError) | ||||
| 	Activate(user *model.User, token string) *model.AppError | ||||
| 	Deactivate(userId string) *model.AppError | ||||
| 	ValidateToken(secret, token string) (bool, *model.AppError) | ||||
|   | ||||
							
								
								
									
										372
									
								
								vendor/github.com/mattermost/platform/model/authorization.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								vendor/github.com/mattermost/platform/model/authorization.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,372 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| type Permission struct { | ||||
| 	Id          string `json:"id"` | ||||
| 	Name        string `json:"name"` | ||||
| 	Description string `json:"description"` | ||||
| } | ||||
|  | ||||
| type Role struct { | ||||
| 	Id          string   `json:"id"` | ||||
| 	Name        string   `json:"name"` | ||||
| 	Description string   `json:"description"` | ||||
| 	Permissions []string `json:"permissions"` | ||||
| } | ||||
|  | ||||
| var PERMISSION_INVITE_USER *Permission | ||||
| var PERMISSION_ADD_USER_TO_TEAM *Permission | ||||
| var PERMISSION_USE_SLASH_COMMANDS *Permission | ||||
| var PERMISSION_MANAGE_SLASH_COMMANDS *Permission | ||||
| var PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS *Permission | ||||
| var PERMISSION_CREATE_PUBLIC_CHANNEL *Permission | ||||
| var PERMISSION_CREATE_PRIVATE_CHANNEL *Permission | ||||
| var PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS *Permission | ||||
| var PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS *Permission | ||||
| var PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE *Permission | ||||
| var PERMISSION_MANAGE_ROLES *Permission | ||||
| var PERMISSION_CREATE_DIRECT_CHANNEL *Permission | ||||
| var PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES *Permission | ||||
| var PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES *Permission | ||||
| var PERMISSION_LIST_TEAM_CHANNELS *Permission | ||||
| var PERMISSION_JOIN_PUBLIC_CHANNELS *Permission | ||||
| var PERMISSION_DELETE_PUBLIC_CHANNEL *Permission | ||||
| var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission | ||||
| var PERMISSION_EDIT_OTHER_USERS *Permission | ||||
| var PERMISSION_READ_CHANNEL *Permission | ||||
| var PERMISSION_PERMANENT_DELETE_USER *Permission | ||||
| var PERMISSION_UPLOAD_FILE *Permission | ||||
| var PERMISSION_GET_PUBLIC_LINK *Permission | ||||
| var PERMISSION_MANAGE_WEBHOOKS *Permission | ||||
| var PERMISSION_MANAGE_OTHERS_WEBHOOKS *Permission | ||||
| var PERMISSION_MANAGE_OAUTH *Permission | ||||
| var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission | ||||
| var PERMISSION_CREATE_POST *Permission | ||||
| var PERMISSION_EDIT_POST *Permission | ||||
| var PERMISSION_EDIT_OTHERS_POSTS *Permission | ||||
| var PERMISSION_REMOVE_USER_FROM_TEAM *Permission | ||||
| var PERMISSION_MANAGE_TEAM *Permission | ||||
| var PERMISSION_IMPORT_TEAM *Permission | ||||
|  | ||||
| // General permission that encompases all system admin functions | ||||
| // in the future this could be broken up to allow access to some | ||||
| // admin functions but not others | ||||
| var PERMISSION_MANAGE_SYSTEM *Permission | ||||
|  | ||||
| var ROLE_SYSTEM_USER *Role | ||||
| var ROLE_SYSTEM_ADMIN *Role | ||||
|  | ||||
| var ROLE_TEAM_USER *Role | ||||
| var ROLE_TEAM_ADMIN *Role | ||||
|  | ||||
| var ROLE_CHANNEL_USER *Role | ||||
| var ROLE_CHANNEL_ADMIN *Role | ||||
| var ROLE_CHANNEL_GUEST *Role | ||||
|  | ||||
| var BuiltInRoles map[string]*Role | ||||
|  | ||||
| func InitalizePermissions() { | ||||
| 	PERMISSION_INVITE_USER = &Permission{ | ||||
| 		"invite_user", | ||||
| 		"authentication.permissions.team_invite_user.name", | ||||
| 		"authentication.permissions.team_invite_user.description", | ||||
| 	} | ||||
| 	PERMISSION_ADD_USER_TO_TEAM = &Permission{ | ||||
| 		"add_user_to_team", | ||||
| 		"authentication.permissions.add_user_to_team.name", | ||||
| 		"authentication.permissions.add_user_to_team.description", | ||||
| 	} | ||||
| 	PERMISSION_USE_SLASH_COMMANDS = &Permission{ | ||||
| 		"use_slash_commands", | ||||
| 		"authentication.permissions.team_use_slash_commands.name", | ||||
| 		"authentication.permissions.team_use_slash_commands.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_SLASH_COMMANDS = &Permission{ | ||||
| 		"manage_slash_commands", | ||||
| 		"authentication.permissions.manage_slash_commands.name", | ||||
| 		"authentication.permissions.manage_slash_commands.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS = &Permission{ | ||||
| 		"manage_others_slash_commands", | ||||
| 		"authentication.permissions.manage_others_slash_commands.name", | ||||
| 		"authentication.permissions.manage_others_slash_commands.description", | ||||
| 	} | ||||
| 	PERMISSION_CREATE_PUBLIC_CHANNEL = &Permission{ | ||||
| 		"create_public_channel", | ||||
| 		"authentication.permissions.create_public_channel.name", | ||||
| 		"authentication.permissions.create_public_channel.description", | ||||
| 	} | ||||
| 	PERMISSION_CREATE_PRIVATE_CHANNEL = &Permission{ | ||||
| 		"create_private_channel", | ||||
| 		"authentication.permissions.create_private_channel.name", | ||||
| 		"authentication.permissions.create_private_channel.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS = &Permission{ | ||||
| 		"manage_public_channel_members", | ||||
| 		"authentication.permissions.manage_public_channel_members.name", | ||||
| 		"authentication.permissions.manage_public_channel_members.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS = &Permission{ | ||||
| 		"manage_private_channel_members", | ||||
| 		"authentication.permissions.manage_private_channel_members.name", | ||||
| 		"authentication.permissions.manage_private_channel_members.description", | ||||
| 	} | ||||
| 	PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE = &Permission{ | ||||
| 		"assign_system_admin_role", | ||||
| 		"authentication.permissions.assign_system_admin_role.name", | ||||
| 		"authentication.permissions.assign_system_admin_role.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_ROLES = &Permission{ | ||||
| 		"manage_roles", | ||||
| 		"authentication.permissions.manage_roles.name", | ||||
| 		"authentication.permissions.manage_roles.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_SYSTEM = &Permission{ | ||||
| 		"manage_system", | ||||
| 		"authentication.permissions.manage_system.name", | ||||
| 		"authentication.permissions.manage_system.description", | ||||
| 	} | ||||
| 	PERMISSION_CREATE_DIRECT_CHANNEL = &Permission{ | ||||
| 		"create_direct_channel", | ||||
| 		"authentication.permissions.create_direct_channel.name", | ||||
| 		"authentication.permissions.create_direct_channel.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES = &Permission{ | ||||
| 		"manage__publicchannel_properties", | ||||
| 		"authentication.permissions.manage_public_channel_properties.name", | ||||
| 		"authentication.permissions.manage_public_channel_properties.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES = &Permission{ | ||||
| 		"manage_private_channel_properties", | ||||
| 		"authentication.permissions.manage_private_channel_properties.name", | ||||
| 		"authentication.permissions.manage_private_channel_properties.description", | ||||
| 	} | ||||
| 	PERMISSION_LIST_TEAM_CHANNELS = &Permission{ | ||||
| 		"list_team_channels", | ||||
| 		"authentication.permissions.list_team_channels.name", | ||||
| 		"authentication.permissions.list_team_channels.description", | ||||
| 	} | ||||
| 	PERMISSION_JOIN_PUBLIC_CHANNELS = &Permission{ | ||||
| 		"join_public_channels", | ||||
| 		"authentication.permissions.join_public_channels.name", | ||||
| 		"authentication.permissions.join_public_channels.description", | ||||
| 	} | ||||
| 	PERMISSION_DELETE_PUBLIC_CHANNEL = &Permission{ | ||||
| 		"delete_public_channel", | ||||
| 		"authentication.permissions.delete_public_channel.name", | ||||
| 		"authentication.permissions.delete_public_channel.description", | ||||
| 	} | ||||
| 	PERMISSION_DELETE_PRIVATE_CHANNEL = &Permission{ | ||||
| 		"delete_private_channel", | ||||
| 		"authentication.permissions.delete_private_channel.name", | ||||
| 		"authentication.permissions.delete_private_channel.description", | ||||
| 	} | ||||
| 	PERMISSION_EDIT_OTHER_USERS = &Permission{ | ||||
| 		"edit_other_users", | ||||
| 		"authentication.permissions.edit_other_users.name", | ||||
| 		"authentication.permissions.edit_other_users.description", | ||||
| 	} | ||||
| 	PERMISSION_READ_CHANNEL = &Permission{ | ||||
| 		"read_channel", | ||||
| 		"authentication.permissions.read_channel.name", | ||||
| 		"authentication.permissions.read_channel.description", | ||||
| 	} | ||||
| 	PERMISSION_PERMANENT_DELETE_USER = &Permission{ | ||||
| 		"permanent_delete_user", | ||||
| 		"authentication.permissions.permanent_delete_user.name", | ||||
| 		"authentication.permissions.permanent_delete_user.description", | ||||
| 	} | ||||
| 	PERMISSION_UPLOAD_FILE = &Permission{ | ||||
| 		"upload_file", | ||||
| 		"authentication.permissions.upload_file.name", | ||||
| 		"authentication.permissions.upload_file.description", | ||||
| 	} | ||||
| 	PERMISSION_GET_PUBLIC_LINK = &Permission{ | ||||
| 		"get_public_link", | ||||
| 		"authentication.permissions.get_public_link.name", | ||||
| 		"authentication.permissions.get_public_link.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_WEBHOOKS = &Permission{ | ||||
| 		"manage_webhooks", | ||||
| 		"authentication.permissions.manage_webhooks.name", | ||||
| 		"authentication.permissions.manage_webhooks.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_OTHERS_WEBHOOKS = &Permission{ | ||||
| 		"manage_others_webhooks", | ||||
| 		"authentication.permissions.manage_others_webhooks.name", | ||||
| 		"authentication.permissions.manage_others_webhooks.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_OAUTH = &Permission{ | ||||
| 		"manage_oauth", | ||||
| 		"authentication.permissions.manage_oauth.name", | ||||
| 		"authentication.permissions.manage_oauth.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH = &Permission{ | ||||
| 		"manage_sytem_wide_oauth", | ||||
| 		"authentication.permissions.manage_sytem_wide_oauth.name", | ||||
| 		"authentication.permissions.manage_sytem_wide_oauth.description", | ||||
| 	} | ||||
| 	PERMISSION_CREATE_POST = &Permission{ | ||||
| 		"create_post", | ||||
| 		"authentication.permissions.create_post.name", | ||||
| 		"authentication.permissions.create_post.description", | ||||
| 	} | ||||
| 	PERMISSION_EDIT_POST = &Permission{ | ||||
| 		"edit_post", | ||||
| 		"authentication.permissions.edit_post.name", | ||||
| 		"authentication.permissions.edit_post.description", | ||||
| 	} | ||||
| 	PERMISSION_EDIT_OTHERS_POSTS = &Permission{ | ||||
| 		"edit_others_posts", | ||||
| 		"authentication.permissions.edit_others_posts.name", | ||||
| 		"authentication.permissions.edit_others_posts.description", | ||||
| 	} | ||||
| 	PERMISSION_REMOVE_USER_FROM_TEAM = &Permission{ | ||||
| 		"remove_user_from_team", | ||||
| 		"authentication.permissions.remove_user_from_team.name", | ||||
| 		"authentication.permissions.remove_user_from_team.description", | ||||
| 	} | ||||
| 	PERMISSION_MANAGE_TEAM = &Permission{ | ||||
| 		"manage_team", | ||||
| 		"authentication.permissions.manage_team.name", | ||||
| 		"authentication.permissions.manage_team.description", | ||||
| 	} | ||||
| 	PERMISSION_IMPORT_TEAM = &Permission{ | ||||
| 		"import_team", | ||||
| 		"authentication.permissions.import_team.name", | ||||
| 		"authentication.permissions.import_team.description", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func InitalizeRoles() { | ||||
| 	InitalizePermissions() | ||||
| 	BuiltInRoles = make(map[string]*Role) | ||||
|  | ||||
| 	ROLE_CHANNEL_USER = &Role{ | ||||
| 		"channel_user", | ||||
| 		"authentication.roles.channel_user.name", | ||||
| 		"authentication.roles.channel_user.description", | ||||
| 		[]string{ | ||||
| 			PERMISSION_READ_CHANNEL.Id, | ||||
| 			PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, | ||||
| 			PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, | ||||
| 			PERMISSION_UPLOAD_FILE.Id, | ||||
| 			PERMISSION_GET_PUBLIC_LINK.Id, | ||||
| 			PERMISSION_CREATE_POST.Id, | ||||
| 			PERMISSION_EDIT_POST.Id, | ||||
| 			PERMISSION_USE_SLASH_COMMANDS.Id, | ||||
| 		}, | ||||
| 	} | ||||
| 	BuiltInRoles[ROLE_CHANNEL_USER.Id] = ROLE_CHANNEL_USER | ||||
| 	ROLE_CHANNEL_ADMIN = &Role{ | ||||
| 		"channel_admin", | ||||
| 		"authentication.roles.channel_admin.name", | ||||
| 		"authentication.roles.channel_admin.description", | ||||
| 		[]string{}, | ||||
| 	} | ||||
| 	BuiltInRoles[ROLE_CHANNEL_ADMIN.Id] = ROLE_CHANNEL_ADMIN | ||||
| 	ROLE_CHANNEL_GUEST = &Role{ | ||||
| 		"guest", | ||||
| 		"authentication.roles.global_guest.name", | ||||
| 		"authentication.roles.global_guest.description", | ||||
| 		[]string{}, | ||||
| 	} | ||||
| 	BuiltInRoles[ROLE_CHANNEL_GUEST.Id] = ROLE_CHANNEL_GUEST | ||||
|  | ||||
| 	ROLE_TEAM_USER = &Role{ | ||||
| 		"team_user", | ||||
| 		"authentication.roles.team_user.name", | ||||
| 		"authentication.roles.team_user.description", | ||||
| 		[]string{ | ||||
| 			PERMISSION_LIST_TEAM_CHANNELS.Id, | ||||
| 			PERMISSION_JOIN_PUBLIC_CHANNELS.Id, | ||||
| 		}, | ||||
| 	} | ||||
| 	BuiltInRoles[ROLE_TEAM_USER.Id] = ROLE_TEAM_USER | ||||
| 	ROLE_TEAM_ADMIN = &Role{ | ||||
| 		"team_admin", | ||||
| 		"authentication.roles.team_admin.name", | ||||
| 		"authentication.roles.team_admin.description", | ||||
| 		[]string{ | ||||
| 			PERMISSION_EDIT_OTHERS_POSTS.Id, | ||||
| 			PERMISSION_ADD_USER_TO_TEAM.Id, | ||||
| 			PERMISSION_REMOVE_USER_FROM_TEAM.Id, | ||||
| 			PERMISSION_MANAGE_TEAM.Id, | ||||
| 			PERMISSION_IMPORT_TEAM.Id, | ||||
| 			PERMISSION_MANAGE_ROLES.Id, | ||||
| 			PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, | ||||
| 			PERMISSION_MANAGE_SLASH_COMMANDS.Id, | ||||
| 			PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id, | ||||
| 			PERMISSION_MANAGE_WEBHOOKS.Id, | ||||
| 		}, | ||||
| 	} | ||||
| 	BuiltInRoles[ROLE_TEAM_ADMIN.Id] = ROLE_TEAM_ADMIN | ||||
|  | ||||
| 	ROLE_SYSTEM_USER = &Role{ | ||||
| 		"system_user", | ||||
| 		"authentication.roles.global_user.name", | ||||
| 		"authentication.roles.global_user.description", | ||||
| 		[]string{ | ||||
| 			PERMISSION_CREATE_DIRECT_CHANNEL.Id, | ||||
| 			PERMISSION_PERMANENT_DELETE_USER.Id, | ||||
| 			PERMISSION_MANAGE_OAUTH.Id, | ||||
| 		}, | ||||
| 	} | ||||
| 	BuiltInRoles[ROLE_SYSTEM_USER.Id] = ROLE_SYSTEM_USER | ||||
| 	ROLE_SYSTEM_ADMIN = &Role{ | ||||
| 		"system_admin", | ||||
| 		"authentication.roles.global_admin.name", | ||||
| 		"authentication.roles.global_admin.description", | ||||
| 		// System admins can do anything channel and team admins can do | ||||
| 		// plus everything members of teams and channels can do to all teams | ||||
| 		// and channels on the system | ||||
| 		append( | ||||
| 			append( | ||||
| 				append( | ||||
| 					append( | ||||
| 						[]string{ | ||||
| 							PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id, | ||||
| 							PERMISSION_MANAGE_SYSTEM.Id, | ||||
| 							PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id, | ||||
| 							PERMISSION_DELETE_PUBLIC_CHANNEL.Id, | ||||
| 							PERMISSION_CREATE_PUBLIC_CHANNEL.Id, | ||||
| 							PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id, | ||||
| 							PERMISSION_DELETE_PRIVATE_CHANNEL.Id, | ||||
| 							PERMISSION_CREATE_PRIVATE_CHANNEL.Id, | ||||
| 							PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id, | ||||
| 							PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id, | ||||
| 							PERMISSION_EDIT_OTHER_USERS.Id, | ||||
| 							PERMISSION_MANAGE_OAUTH.Id, | ||||
| 							PERMISSION_INVITE_USER.Id, | ||||
| 						}, | ||||
| 						ROLE_TEAM_USER.Permissions..., | ||||
| 					), | ||||
| 					ROLE_CHANNEL_USER.Permissions..., | ||||
| 				), | ||||
| 				ROLE_TEAM_ADMIN.Permissions..., | ||||
| 			), | ||||
| 			ROLE_CHANNEL_ADMIN.Permissions..., | ||||
| 		), | ||||
| 	} | ||||
| 	BuiltInRoles[ROLE_SYSTEM_ADMIN.Id] = ROLE_SYSTEM_ADMIN | ||||
|  | ||||
| } | ||||
|  | ||||
| func RoleIdsToString(roles []string) string { | ||||
| 	output := "" | ||||
| 	for _, role := range roles { | ||||
| 		output += role + ", " | ||||
| 	} | ||||
|  | ||||
| 	if output == "" { | ||||
| 		return "[<NO ROLES>]" | ||||
| 	} | ||||
|  | ||||
| 	return output[:len(output)-1] | ||||
| } | ||||
|  | ||||
| func init() { | ||||
| 	InitalizeRoles() | ||||
| } | ||||
							
								
								
									
										24
									
								
								vendor/github.com/mattermost/platform/model/channel.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/mattermost/platform/model/channel.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -10,10 +10,14 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	CHANNEL_OPEN    = "O" | ||||
| 	CHANNEL_PRIVATE = "P" | ||||
| 	CHANNEL_DIRECT  = "D" | ||||
| 	DEFAULT_CHANNEL = "town-square" | ||||
| 	CHANNEL_OPEN                   = "O" | ||||
| 	CHANNEL_PRIVATE                = "P" | ||||
| 	CHANNEL_DIRECT                 = "D" | ||||
| 	DEFAULT_CHANNEL                = "town-square" | ||||
| 	CHANNEL_DISPLAY_NAME_MAX_RUNES = 64 | ||||
| 	CHANNEL_NAME_MAX_LENGTH        = 64 | ||||
| 	CHANNEL_HEADER_MAX_RUNES       = 1024 | ||||
| 	CHANNEL_PURPOSE_MAX_RUNES      = 250 | ||||
| ) | ||||
|  | ||||
| type Channel struct { | ||||
| @@ -57,8 +61,8 @@ func (o *Channel) Etag() string { | ||||
| 	return Etag(o.Id, o.UpdateAt) | ||||
| } | ||||
|  | ||||
| func (o *Channel) ExtraEtag(memberLimit int) string { | ||||
| 	return Etag(o.Id, o.ExtraUpdateAt, memberLimit) | ||||
| func (o *Channel) StatsEtag() string { | ||||
| 	return Etag(o.Id, o.ExtraUpdateAt) | ||||
| } | ||||
|  | ||||
| func (o *Channel) IsValid() *AppError { | ||||
| @@ -75,11 +79,11 @@ func (o *Channel) IsValid() *AppError { | ||||
| 		return NewLocAppError("Channel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(o.DisplayName) > 64 { | ||||
| 	if utf8.RuneCountInString(o.DisplayName) > CHANNEL_DISPLAY_NAME_MAX_RUNES { | ||||
| 		return NewLocAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if len(o.Name) > 64 { | ||||
| 	if len(o.Name) > CHANNEL_NAME_MAX_LENGTH { | ||||
| 		return NewLocAppError("Channel.IsValid", "model.channel.is_valid.name.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| @@ -91,11 +95,11 @@ func (o *Channel) IsValid() *AppError { | ||||
| 		return NewLocAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(o.Header) > 1024 { | ||||
| 	if utf8.RuneCountInString(o.Header) > CHANNEL_HEADER_MAX_RUNES { | ||||
| 		return NewLocAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(o.Purpose) > 128 { | ||||
| 	if utf8.RuneCountInString(o.Purpose) > CHANNEL_PURPOSE_MAX_RUNES { | ||||
| 		return NewLocAppError("Channel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										49
									
								
								vendor/github.com/mattermost/platform/model/channel_extra.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/mattermost/platform/model/channel_extra.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,49 +0,0 @@ | ||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type ExtraMember struct { | ||||
| 	Id       string `json:"id"` | ||||
| 	Nickname string `json:"nickname"` | ||||
| 	Email    string `json:"email"` | ||||
| 	Roles    string `json:"roles"` | ||||
| 	Username string `json:"username"` | ||||
| } | ||||
|  | ||||
| func (o *ExtraMember) Sanitize(options map[string]bool) { | ||||
| 	if len(options) == 0 || !options["email"] { | ||||
| 		o.Email = "" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type ChannelExtra struct { | ||||
| 	Id          string        `json:"id"` | ||||
| 	Members     []ExtraMember `json:"members"` | ||||
| 	MemberCount int64         `json:"member_count"` | ||||
| } | ||||
|  | ||||
| func (o *ChannelExtra) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ChannelExtraFromJson(data io.Reader) *ChannelExtra { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o ChannelExtra | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										35
									
								
								vendor/github.com/mattermost/platform/model/channel_list.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/mattermost/platform/model/channel_list.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -8,15 +8,11 @@ import ( | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type ChannelList struct { | ||||
| 	Channels []*Channel                `json:"channels"` | ||||
| 	Members  map[string]*ChannelMember `json:"members"` | ||||
| } | ||||
| type ChannelList []*Channel | ||||
|  | ||||
| func (o *ChannelList) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		return "[]" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| @@ -28,7 +24,7 @@ func (o *ChannelList) Etag() string { | ||||
| 	var t int64 = 0 | ||||
| 	var delta int64 = 0 | ||||
|  | ||||
| 	for _, v := range o.Channels { | ||||
| 	for _, v := range *o { | ||||
| 		if v.LastPostAt > t { | ||||
| 			t = v.LastPostAt | ||||
| 			id = v.Id | ||||
| @@ -39,30 +35,9 @@ func (o *ChannelList) Etag() string { | ||||
| 			id = v.Id | ||||
| 		} | ||||
|  | ||||
| 		member := o.Members[v.Id] | ||||
|  | ||||
| 		if member != nil { | ||||
| 			max := v.LastPostAt | ||||
| 			if v.UpdateAt > max { | ||||
| 				max = v.UpdateAt | ||||
| 			} | ||||
|  | ||||
| 			delta += max - member.LastViewedAt | ||||
|  | ||||
| 			if member.LastViewedAt > t { | ||||
| 				t = member.LastViewedAt | ||||
| 				id = v.Id | ||||
| 			} | ||||
|  | ||||
| 			if member.LastUpdateAt > t { | ||||
| 				t = member.LastUpdateAt | ||||
| 				id = v.Id | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return Etag(id, t, delta, len(o.Channels)) | ||||
| 	return Etag(id, t, delta, len(*o)) | ||||
| } | ||||
|  | ||||
| func ChannelListFromJson(data io.Reader) *ChannelList { | ||||
|   | ||||
							
								
								
									
										40
									
								
								vendor/github.com/mattermost/platform/model/channel_member.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/mattermost/platform/model/channel_member.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -10,7 +10,6 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	CHANNEL_ROLE_ADMIN          = "admin" | ||||
| 	CHANNEL_NOTIFY_DEFAULT      = "default" | ||||
| 	CHANNEL_NOTIFY_ALL          = "all" | ||||
| 	CHANNEL_NOTIFY_MENTION      = "mention" | ||||
| @@ -19,6 +18,14 @@ const ( | ||||
| 	CHANNEL_MARK_UNREAD_MENTION = "mention" | ||||
| ) | ||||
|  | ||||
| type ChannelUnread struct { | ||||
| 	TeamId        string | ||||
| 	TotalMsgCount int64 | ||||
| 	MsgCount      int64 | ||||
| 	MentionCount  int64 | ||||
| 	NotifyProps   StringMap | ||||
| } | ||||
|  | ||||
| type ChannelMember struct { | ||||
| 	ChannelId    string    `json:"channel_id"` | ||||
| 	UserId       string    `json:"user_id"` | ||||
| @@ -30,6 +37,27 @@ type ChannelMember struct { | ||||
| 	LastUpdateAt int64     `json:"last_update_at"` | ||||
| } | ||||
|  | ||||
| type ChannelMembers []ChannelMember | ||||
|  | ||||
| func (o *ChannelMembers) ToJson() string { | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		return "[]" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ChannelMembersFromJson(data io.Reader) *ChannelMembers { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o ChannelMembers | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *ChannelMember) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| @@ -60,12 +88,6 @@ func (o *ChannelMember) IsValid() *AppError { | ||||
| 		return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	for _, role := range strings.Split(o.Roles, " ") { | ||||
| 		if !(role == "" || role == CHANNEL_ROLE_ADMIN) { | ||||
| 			return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.role.app_error", nil, "role="+role) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	notifyLevel := o.NotifyProps["desktop"] | ||||
| 	if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) { | ||||
| 		return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.notify_level.app_error", | ||||
| @@ -89,6 +111,10 @@ func (o *ChannelMember) PreUpdate() { | ||||
| 	o.LastUpdateAt = GetMillis() | ||||
| } | ||||
|  | ||||
| func (o *ChannelMember) GetRoles() []string { | ||||
| 	return strings.Fields(o.Roles) | ||||
| } | ||||
|  | ||||
| func IsChannelNotifyLevelValid(notifyLevel string) bool { | ||||
| 	return notifyLevel == CHANNEL_NOTIFY_DEFAULT || | ||||
| 		notifyLevel == CHANNEL_NOTIFY_ALL || | ||||
|   | ||||
							
								
								
									
										35
									
								
								vendor/github.com/mattermost/platform/model/channel_search.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/mattermost/platform/model/channel_search.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type ChannelSearch struct { | ||||
| 	Term string `json:"term"` | ||||
| } | ||||
|  | ||||
| // ToJson convert a Channel to a json string | ||||
| func (c *ChannelSearch) ToJson() string { | ||||
| 	b, err := json.Marshal(c) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ChannelSearchFromJson will decode the input and return a Channel | ||||
| func ChannelSearchFromJson(data io.Reader) *ChannelSearch { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var cs ChannelSearch | ||||
| 	err := decoder.Decode(&cs) | ||||
| 	if err == nil { | ||||
| 		return &cs | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| @@ -5,32 +5,15 @@ package model | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| ) | ||||
| 
 | ||||
| type TeamSignup struct { | ||||
| 	Team    Team     `json:"team"` | ||||
| 	User    User     `json:"user"` | ||||
| 	Invites []string `json:"invites"` | ||||
| 	Data    string   `json:"data"` | ||||
| 	Hash    string   `json:"hash"` | ||||
| type ChannelStats struct { | ||||
| 	ChannelId   string `json:"channel_id"` | ||||
| 	MemberCount int64  `json:"member_count"` | ||||
| } | ||||
| 
 | ||||
| func TeamSignupFromJson(data io.Reader) *TeamSignup { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o TeamSignup | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		fmt.Println(err) | ||||
| 
 | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (o *TeamSignup) ToJson() string { | ||||
| func (o *ChannelStats) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| @@ -38,3 +21,14 @@ func (o *TeamSignup) ToJson() string { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ChannelStatsFromJson(data io.Reader) *ChannelStats { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o ChannelStats | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										34
									
								
								vendor/github.com/mattermost/platform/model/channel_view.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								vendor/github.com/mattermost/platform/model/channel_view.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type ChannelView struct { | ||||
| 	ChannelId     string `json:"channel_id"` | ||||
| 	PrevChannelId string `json:"prev_channel_id"` | ||||
| } | ||||
|  | ||||
| func (o *ChannelView) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ChannelViewFromJson(data io.Reader) *ChannelView { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o ChannelView | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										623
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										623
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,7 +6,6 @@ package model | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	l4g "github.com/alecthomas/log4go" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"mime/multipart" | ||||
| @@ -15,6 +14,8 @@ import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	l4g "github.com/alecthomas/log4go" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -48,6 +49,13 @@ type Result struct { | ||||
| 	Data      interface{} | ||||
| } | ||||
|  | ||||
| type ResponseMetadata struct { | ||||
| 	StatusCode int | ||||
| 	Error      *AppError | ||||
| 	RequestId  string | ||||
| 	Etag       string | ||||
| } | ||||
|  | ||||
| type Client struct { | ||||
| 	Url           string       // The location of the server like "http://localhost:8065" | ||||
| 	ApiUrl        string       // The api location of the server like "http://localhost:8065/api/v3" | ||||
| @@ -108,6 +116,10 @@ func (c *Client) GetChannelRoute(channelId string) string { | ||||
| 	return fmt.Sprintf("/teams/%v/channels/%v", c.GetTeamId(), channelId) | ||||
| } | ||||
|  | ||||
| func (c *Client) GetUserRequiredRoute(userId string) string { | ||||
| 	return fmt.Sprintf("/users/%v", userId) | ||||
| } | ||||
|  | ||||
| func (c *Client) GetChannelNameRoute(channelName string) string { | ||||
| 	return fmt.Sprintf("/teams/%v/channels/name/%v", c.GetTeamId(), channelName) | ||||
| } | ||||
| @@ -120,9 +132,14 @@ func (c *Client) GetGeneralRoute() string { | ||||
| 	return "/general" | ||||
| } | ||||
|  | ||||
| func (c *Client) GetFileRoute(fileId string) string { | ||||
| 	return fmt.Sprintf("/files/%v", fileId) | ||||
| } | ||||
|  | ||||
| func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) { | ||||
| 	rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data)) | ||||
| 	rq.Header.Set("Content-Type", contentType) | ||||
| 	rq.Close = true | ||||
|  | ||||
| 	if rp, err := c.HttpClient.Do(rq); err != nil { | ||||
| 		return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) | ||||
| @@ -136,6 +153,7 @@ func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppErro | ||||
|  | ||||
| func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) { | ||||
| 	rq, _ := http.NewRequest("POST", c.ApiUrl+url, strings.NewReader(data)) | ||||
| 	rq.Close = true | ||||
|  | ||||
| 	if len(c.AuthToken) > 0 { | ||||
| 		rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken) | ||||
| @@ -153,6 +171,7 @@ func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) | ||||
|  | ||||
| func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, *AppError) { | ||||
| 	rq, _ := http.NewRequest("GET", c.ApiUrl+url, strings.NewReader(data)) | ||||
| 	rq.Close = true | ||||
|  | ||||
| 	if len(etag) > 0 { | ||||
| 		rq.Header.Set(HEADER_ETAG_CLIENT, etag) | ||||
| @@ -280,34 +299,6 @@ func (c *Client) GetPing() (map[string]string, *AppError) { | ||||
|  | ||||
| // Team Routes Section | ||||
|  | ||||
| // SignupTeam sends an email with a team sign-up link to the provided address if email | ||||
| // verification is enabled, otherwise it returns a map with a "follow_link" entry | ||||
| // containing the team sign-up link. | ||||
| func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["email"] = email | ||||
| 	m["display_name"] = displayName | ||||
| 	if r, err := c.DoApiPost("/teams/signup", MapToJson(m)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CreateTeamFromSignup creates a team based on the provided TeamSignup struct. On success | ||||
| // it returns the TeamSignup struct. | ||||
| func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamSignupFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // CreateTeam creates a team based on the provided Team struct. On success it returns | ||||
| // the Team struct with the Id, CreateAt and other server-decided fields populated. | ||||
| func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { | ||||
| @@ -489,6 +480,32 @@ func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // getByUsername returns a user based on a provided username string. Must be authenticated. | ||||
| func (c *Client) GetByUsername(username string, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf("/users/name/%v", username), "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // getByEmail returns a user based on a provided username string. Must be authenticated. | ||||
| func (c *Client) GetByEmail(email string, etag string) (*User, *ResponseMetadata) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf("/users/email/%v", email), "", etag); err != nil { | ||||
| 		return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return UserFromJson(r.Body), | ||||
| 			&ResponseMetadata{ | ||||
| 				StatusCode: r.StatusCode, | ||||
| 				RequestId:  r.Header.Get(HEADER_REQUEST_ID), | ||||
| 				Etag:       r.Header.Get(HEADER_ETAG_SERVER), | ||||
| 			} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetMe returns the current user. | ||||
| func (c *Client) GetMe(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/me", "", etag); err != nil { | ||||
| @@ -500,10 +517,9 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetProfilesForDirectMessageList returns a map of users for a team that can be direct | ||||
| // messaged, using user id as the key. Must be authenticated. | ||||
| func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil { | ||||
| // GetProfiles returns a map of users using user id as the key. Must be authenticated. | ||||
| func (c *Client) GetProfiles(offset int, limit int, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf("/users/%v/%v", offset, limit), "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| @@ -512,10 +528,10 @@ func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppEr | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetProfiles returns a map of users for a team using user id as the key. Must | ||||
| // GetProfilesInTeam returns a map of users for a team using user id as the key. Must | ||||
| // be authenticated. | ||||
| func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil { | ||||
| func (c *Client) GetProfilesInTeam(teamId string, offset int, limit int, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/users/%v/%v", teamId, offset, limit), "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| @@ -524,10 +540,10 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetDirectProfiles gets a map of users that are currently shown in the sidebar, | ||||
| // using user id as the key. Must be authenticated. | ||||
| func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil { | ||||
| // GetProfilesInChannel returns a map of users for a channel using user id as the key. Must | ||||
| // be authenticated. | ||||
| func (c *Client) GetProfilesInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/%v/%v", offset, limit), "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| @@ -536,6 +552,83 @@ func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetProfilesNotInChannel returns a map of users not in a channel but on the team using user id as the key. Must | ||||
| // be authenticated. | ||||
| func (c *Client) GetProfilesNotInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/not_in_channel/%v/%v", offset, limit), "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetProfilesByIds returns a map of users based on the user ids provided. Must | ||||
| // be authenticated. | ||||
| func (c *Client) GetProfilesByIds(userIds []string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/ids", ArrayToJson(userIds)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SearchUsers returns a list of users that have a username matching or similar to the search term. Must | ||||
| // be authenticated. | ||||
| func (c *Client) SearchUsers(params UserSearch) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/search", params.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AutocompleteUsersInChannel returns two lists for autocompletion of users in a channel. The first list "in_channel", | ||||
| // specifies users in the channel. The second list "out_of_channel" specifies users outside of the | ||||
| // channel. Term, the string to search against, is required, channel id is also required. Must be authenticated. | ||||
| func (c *Client) AutocompleteUsersInChannel(term string, channelId string) (*Result, *AppError) { | ||||
| 	url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetChannelRoute(channelId), url.QueryEscape(term)) | ||||
| 	if r, err := c.DoApiGet(url, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInChannelFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AutocompleteUsersInTeam returns a list for autocompletion of users in a team. The list "in_team" specifies | ||||
| // the users in the team that match the provided term, matching against username, full name and | ||||
| // nickname. Must be authenticated. | ||||
| func (c *Client) AutocompleteUsersInTeam(term string) (*Result, *AppError) { | ||||
| 	url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term)) | ||||
| 	if r, err := c.DoApiGet(url, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInTeamFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AutocompleteUsers returns a list for autocompletion of users on the system that match the provided term, | ||||
| // matching against username, full name and nickname. Must be authenticated. | ||||
| func (c *Client) AutocompleteUsers(term string) (*Result, *AppError) { | ||||
| 	url := fmt.Sprintf("/users/autocomplete?term=%s", url.QueryEscape(term)) | ||||
| 	if r, err := c.DoApiGet(url, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // LoginById authenticates a user by user id and password. | ||||
| func (c *Client) LoginById(id string, password string) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| @@ -622,15 +715,16 @@ func (c *Client) CheckMfa(loginId string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GenerateMfaQrCode returns a QR code imagem containing the secret, to be scanned | ||||
| // by a multi-factor authentication mobile application. Must be authenticated. | ||||
| func (c *Client) GenerateMfaQrCode() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/generate_mfa_qr", "", ""); err != nil { | ||||
| // GenerateMfaSecret returns a QR code image containing the secret, to be scanned | ||||
| // by a multi-factor authentication mobile application. It also returns the secret | ||||
| // for manual entry. Must be authenticated. | ||||
| func (c *Client) GenerateMfaSecret() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/users/generate_mfa_secret", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), r.Body}, nil | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -727,12 +821,9 @@ func (c *Client) EmailToLDAP(m map[string]string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) Command(channelId string, command string, suggest bool) (*Result, *AppError) { | ||||
| 	m := make(map[string]string) | ||||
| 	m["command"] = command | ||||
| 	m["channelId"] = channelId | ||||
| 	m["suggest"] = strconv.FormatBool(suggest) | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", MapToJson(m)); err != nil { | ||||
| func (c *Client) Command(channelId string, command string) (*Result, *AppError) { | ||||
| 	args := &CommandArgs{ChannelId: channelId, Command: command} | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", args.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| @@ -771,6 +862,16 @@ func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) UpdateCommand(cmd *Command) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/update", cmd.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/regen_token", MapToJson(data)); err != nil { | ||||
| 		return nil, err | ||||
| @@ -865,6 +966,16 @@ func (c *Client) ReloadConfig() (bool, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) InvalidateAllCaches() (bool, *AppError) { | ||||
| 	c.clearExtraProperties() | ||||
| 	if r, err := c.DoApiGet("/admin/invalidate_all_caches", "", ""); err != nil { | ||||
| 		return false, err | ||||
| 	} else { | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return c.CheckStatusOK(r), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) SaveConfig(config *Config) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/admin/save_config", config.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -934,6 +1045,7 @@ func (c *Client) SaveComplianceReport(job *Compliance) (*Result, *AppError) { | ||||
| func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) { | ||||
| 	var rq *http.Request | ||||
| 	rq, _ = http.NewRequest("GET", c.ApiUrl+"/admin/download_compliance_report/"+id, nil) | ||||
| 	rq.Close = true | ||||
|  | ||||
| 	if len(c.AuthToken) > 0 { | ||||
| 		rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) | ||||
| @@ -1047,13 +1159,13 @@ func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetChannels(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", etag); err != nil { | ||||
| func (c *Client) GetMyChannelMembers() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/members", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelMembersFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -1067,6 +1179,7 @@ func (c *Client) GetChannel(id, etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SCHEDULED FOR DEPRECATION IN 3.7 - use GetMoreChannelsPage instead | ||||
| func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/more", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| @@ -1077,6 +1190,43 @@ func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetMoreChannelsPage will return a page of open channels the user is not in based on | ||||
| // the provided offset and limit. Must be authenticated. | ||||
| func (c *Client) GetMoreChannelsPage(offset int, limit int) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf(c.GetTeamRoute()+"/channels/more/%v/%v", offset, limit), "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SearchMoreChannels will return a list of open channels the user is not in, that matches | ||||
| // the search criteria provided. Must be authenticated. | ||||
| func (c *Client) SearchMoreChannels(channelSearch ChannelSearch) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/more/search", channelSearch.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // AutocompleteChannels will return a list of open channels that match the provided | ||||
| // string. Must be authenticated. | ||||
| func (c *Client) AutocompleteChannels(term string) (*Result, *AppError) { | ||||
| 	url := fmt.Sprintf("%s/channels/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term)) | ||||
| 	if r, err := c.DoApiGet(url, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/counts", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| @@ -1087,6 +1237,26 @@ func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetChannels(etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetChannelByName(channelName string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetChannelNameRoute(channelName), "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) JoinChannel(id string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/join", ""); err != nil { | ||||
| 		return nil, err | ||||
| @@ -1154,6 +1324,7 @@ func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) { | ||||
| // UpdateLastViewedAt will mark a channel as read. | ||||
| // The channelId indicates the channel to mark as read. If active is true, push notifications | ||||
| // will be cleared if there are unread messages. The default for active is true. | ||||
| // SCHEDULED FOR DEPRECATION IN 3.8 - use ViewChannel instead | ||||
| func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *AppError) { | ||||
| 	data := make(map[string]interface{}) | ||||
| 	data["active"] = active | ||||
| @@ -1166,13 +1337,52 @@ func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *Ap | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil { | ||||
| // ViewChannel performs all the actions related to viewing a channel. This includes marking | ||||
| // the channel and the previous one as read, and marking the channel as being actively viewed. | ||||
| // ChannelId is required but may be blank to indicate no channel is being viewed. | ||||
| // PrevChannelId is optional, populate to indicate a channel switch occurred. | ||||
| func (c *Client) ViewChannel(params ChannelView) (bool, *ResponseMetadata) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/view", params.ToJson()); err != nil { | ||||
| 		return false, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} | ||||
| 	} else { | ||||
| 		return c.CheckStatusOK(r), | ||||
| 			&ResponseMetadata{ | ||||
| 				StatusCode: r.StatusCode, | ||||
| 				RequestId:  r.Header.Get(HEADER_REQUEST_ID), | ||||
| 				Etag:       r.Header.Get(HEADER_ETAG_SERVER), | ||||
| 			} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetChannelStats(id string, etag string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/stats", "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelExtraFromJson(r.Body)}, nil | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelStatsFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetChannelMember(channelId string, userId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/members/"+userId, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelMemberFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetChannelMembersByIds will return channel member objects as an array based on the | ||||
| // channel id and a list of user ids provided. Must be authenticated. | ||||
| func (c *Client) GetChannelMembersByIds(channelId string, userIds []string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/members/ids", ArrayToJson(userIds)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), ChannelMembersFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -1246,6 +1456,21 @@ func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetPostById returns a post and any posts in the same thread by post id | ||||
| func (c *Client) GetPostById(postId string, etag string) (*PostList, *ResponseMetadata) { | ||||
| 	if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/%v", postId), "", etag); err != nil { | ||||
| 		return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err} | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return PostListFromJson(r.Body), | ||||
| 			&ResponseMetadata{ | ||||
| 				StatusCode: r.StatusCode, | ||||
| 				RequestId:  r.Header.Get(HEADER_REQUEST_ID), | ||||
| 				Etag:       r.Header.Get(HEADER_ETAG_SERVER), | ||||
| 			} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/delete", postId), ""); err != nil { | ||||
| 		return nil, err | ||||
| @@ -1285,13 +1510,39 @@ func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *A | ||||
| 	return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType) | ||||
| } | ||||
|  | ||||
| func (c *Client) UploadPostAttachment(data []byte, contentType string) (*Result, *AppError) { | ||||
| 	return c.uploadFile(c.ApiUrl+c.GetTeamRoute()+"/files/upload", data, contentType) | ||||
| func (c *Client) UploadPostAttachment(data []byte, channelId string, filename string) (*FileUploadResponse, *AppError) { | ||||
| 	c.clearExtraProperties() | ||||
|  | ||||
| 	body := &bytes.Buffer{} | ||||
| 	writer := multipart.NewWriter(body) | ||||
|  | ||||
| 	if part, err := writer.CreateFormFile("files", filename); err != nil { | ||||
| 		return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error()) | ||||
| 	} else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil { | ||||
| 		return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	if part, err := writer.CreateFormField("channel_id"); err != nil { | ||||
| 		return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error()) | ||||
| 	} else if _, err = io.Copy(part, strings.NewReader(channelId)); err != nil { | ||||
| 		return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	if err := writer.Close(); err != nil { | ||||
| 		return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.writer.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	if result, err := c.uploadFile(c.ApiUrl+c.GetTeamRoute()+"/files/upload", body.Bytes(), writer.FormDataContentType()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		return result.Data.(*FileUploadResponse), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) uploadFile(url string, data []byte, contentType string) (*Result, *AppError) { | ||||
| 	rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) | ||||
| 	rq.Header.Set("Content-Type", contentType) | ||||
| 	rq.Close = true | ||||
|  | ||||
| 	if len(c.AuthToken) > 0 { | ||||
| 		rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) | ||||
| @@ -1308,55 +1559,51 @@ func (c *Client) uploadFile(url string, data []byte, contentType string) (*Resul | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) { | ||||
| 	var rq *http.Request | ||||
| 	if isFullUrl { | ||||
| 		rq, _ = http.NewRequest("GET", url, nil) | ||||
| func (c *Client) GetFile(fileId string) (io.ReadCloser, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetTeamRoute()+"/files/get"+url, nil) | ||||
| 	} | ||||
|  | ||||
| 	if len(c.AuthToken) > 0 { | ||||
| 		rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) | ||||
| 	} | ||||
|  | ||||
| 	if rp, err := c.HttpClient.Do(rq); err != nil { | ||||
| 		return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) | ||||
| 	} else if rp.StatusCode >= 300 { | ||||
| 		return nil, AppErrorFromJson(rp.Body) | ||||
| 	} else { | ||||
| 		defer closeBody(rp) | ||||
| 		return &Result{rp.Header.Get(HEADER_REQUEST_ID), | ||||
| 			rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return r.Body, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetFileInfo(url string) (*Result, *AppError) { | ||||
| 	var rq *http.Request | ||||
| 	rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetTeamRoute()+"/files/get_info"+url, nil) | ||||
|  | ||||
| 	if len(c.AuthToken) > 0 { | ||||
| 		rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) | ||||
| 	} | ||||
|  | ||||
| 	if rp, err := c.HttpClient.Do(rq); err != nil { | ||||
| 		return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) | ||||
| 	} else if rp.StatusCode >= 300 { | ||||
| 		return nil, AppErrorFromJson(rp.Body) | ||||
| func (c *Client) GetFileThumbnail(fileId string) (io.ReadCloser, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_thumbnail", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(rp) | ||||
| 		return &Result{rp.Header.Get(HEADER_REQUEST_ID), | ||||
| 			rp.Header.Get(HEADER_ETAG_SERVER), FileInfoFromJson(rp.Body)}, nil | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return r.Body, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetPublicLink(filename string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/files/get_public_link", MapToJson(map[string]string{"filename": filename})); err != nil { | ||||
| func (c *Client) GetFilePreview(fileId string) (io.ReadCloser, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_preview", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), StringFromJson(r.Body)}, nil | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return r.Body, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetFileInfo(fileId string) (*FileInfo, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_info", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return FileInfoFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetPublicLink(fileId string) (string, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_public_link", "", ""); err != nil { | ||||
| 		return "", err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return StringFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -1370,8 +1617,25 @@ func (c *Client) UpdateUser(user *User) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/update_roles", MapToJson(data)); err != nil { | ||||
| func (c *Client) UpdateUserRoles(userId string, roles string) (*Result, *AppError) { | ||||
| 	data := make(map[string]string) | ||||
| 	data["new_roles"] = roles | ||||
|  | ||||
| 	if r, err := c.DoApiPost(c.GetUserRequiredRoute(userId)+"/update_roles", MapToJson(data)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) UpdateTeamRoles(userId string, roles string) (*Result, *AppError) { | ||||
| 	data := make(map[string]string) | ||||
| 	data["new_roles"] = roles | ||||
| 	data["user_id"] = userId | ||||
|  | ||||
| 	if r, err := c.DoApiPost(c.GetTeamRoute()+"/update_member_roles", MapToJson(data)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| @@ -1479,9 +1743,22 @@ func (c *Client) GetStatuses() (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetStatusesByIds returns a map of string statuses using user id as the key, | ||||
| // based on the provided user ids | ||||
| func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/users/status/ids", ArrayToJson(userIds)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SetActiveChannel sets the the channel id the user is currently viewing. | ||||
| // The channelId key is required but the value can be blank. Returns standard | ||||
| // response. | ||||
| // SCHEDULED FOR DEPRECATION IN 3.8 - use ViewChannel instead | ||||
| func (c *Client) SetActiveChannel(channelId string) (*Result, *AppError) { | ||||
| 	data := map[string]string{} | ||||
| 	data["channel_id"] = channelId | ||||
| @@ -1504,8 +1781,88 @@ func (c *Client) GetMyTeam(etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/teams/members/"+teamId, "", ""); err != nil { | ||||
| // GetTeamMembers will return a page of team member objects as an array paged based on the | ||||
| // team id, offset and limit provided. Must be authenticated. | ||||
| func (c *Client) GetTeamMembers(teamId string, offset int, limit int) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v/%v", teamId, offset, limit), "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetMyTeamMembers will return an array with team member objects that the current user | ||||
| // is a member of. Must be authenticated. | ||||
| func (c *Client) GetMyTeamMembers() (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet("/teams/members", "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetMyTeamsUnread will return an array with TeamUnread objects that contain the amount of | ||||
| // unread messages and mentions the current user has for the teams it belongs to. | ||||
| // An optional team ID can be set to exclude that team from the results. Must be authenticated. | ||||
| func (c *Client) GetMyTeamsUnread(teamId string) (*Result, *AppError) { | ||||
| 	endpoint := "/teams/unread" | ||||
|  | ||||
| 	if teamId != "" { | ||||
| 		endpoint += fmt.Sprintf("?id=%s", url.QueryEscape(teamId)) | ||||
| 	} | ||||
| 	if r, err := c.DoApiGet(endpoint, "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamsUnreadFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetTeamMember will return a team member object based on the team id and user id provided. | ||||
| // Must be authenticated. | ||||
| func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v", teamId, userId), "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamMemberFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetTeamStats will return a team stats object containing the number of users on the team | ||||
| // based on the team id provided. Must be authenticated. | ||||
| func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/stats", teamId), "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetTeamStats will return a team stats object containing the number of users on the team | ||||
| // based on the team id provided. Must be authenticated. | ||||
| func (c *Client) GetTeamByName(teamName string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiGet(fmt.Sprintf("/teams/name/%v", teamName), "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return &Result{r.Header.Get(HEADER_REQUEST_ID), | ||||
| 			r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetTeamMembersByIds will return team member objects as an array based on the | ||||
| // team id and a list of user ids provided. Must be authenticated. | ||||
| func (c *Client) GetTeamMembersByIds(teamId string, userIds []string) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJson(userIds)); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| @@ -1820,6 +2177,7 @@ func (c *Client) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoj | ||||
|  | ||||
| 	rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetEmojiRoute()+"/create", body) | ||||
| 	rq.Header.Set("Content-Type", writer.FormDataContentType()) | ||||
| 	rq.Close = true | ||||
|  | ||||
| 	if len(c.AuthToken) > 0 { | ||||
| 		rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) | ||||
| @@ -1844,6 +2202,7 @@ func (c *Client) DeleteEmoji(id string) (bool, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetEmojiRoute()+"/delete", MapToJson(data)); err != nil { | ||||
| 		return false, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return c.CheckStatusOK(r), nil | ||||
| 	} | ||||
| @@ -1862,6 +2221,7 @@ func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppErro | ||||
| 	url := c.ApiUrl + "/admin/add_certificate" | ||||
| 	rq, _ := http.NewRequest("POST", url, bytes.NewReader(data)) | ||||
| 	rq.Header.Set("Content-Type", contentType) | ||||
| 	rq.Close = true | ||||
|  | ||||
| 	if len(c.AuthToken) > 0 { | ||||
| 		rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) | ||||
| @@ -1873,6 +2233,7 @@ func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppErro | ||||
| 		return AppErrorFromJson(rp.Body) | ||||
| 	} else { | ||||
| 		defer closeBody(rp) | ||||
| 		c.fillInExtraProperties(rp) | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| @@ -1884,6 +2245,7 @@ func (c *Client) RemoveCertificateFile(filename string) *AppError { | ||||
| 		return err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| @@ -1895,6 +2257,65 @@ func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{}, | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return StringInterfaceFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetWebrtcToken if Successful returns a map with a valid token, stun server and turn server with credentials to use with | ||||
| // the Mattermost WebRTC service, otherwise returns an AppError. Must be authenticated user. | ||||
| func (c *Client) GetWebrtcToken() (map[string]string, *AppError) { | ||||
| 	if r, err := c.DoApiPost("/webrtc/token", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		return MapFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetFileInfosForPost returns a list of FileInfo objects for a given post id, if successful. | ||||
| // Otherwise, it returns an error. | ||||
| func (c *Client) GetFileInfosForPost(channelId string, postId string, etag string) ([]*FileInfo, *AppError) { | ||||
| 	c.clearExtraProperties() | ||||
|  | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/get_file_infos", postId), "", etag); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return FileInfosFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Saves an emoji reaction for a post in the given channel. Returns the saved reaction if successful, otherwise returns an AppError. | ||||
| func (c *Client) SaveReaction(channelId string, reaction *Reaction) (*Reaction, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions/save", reaction.PostId), reaction.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return ReactionFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Removes an emoji reaction for a post in the given channel. Returns nil if successful, otherwise returns an AppError. | ||||
| func (c *Client) DeleteReaction(channelId string, reaction *Reaction) *AppError { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions/delete", reaction.PostId), reaction.ToJson()); err != nil { | ||||
| 		return err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Lists all emoji reactions made for the given post in the given channel. Returns a list of Reactions if successful, otherwise returns an AppError. | ||||
| func (c *Client) ListReactions(channelId string, postId string) ([]*Reaction, *AppError) { | ||||
| 	if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions", postId), "", ""); err != nil { | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return ReactionsFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										36
									
								
								vendor/github.com/mattermost/platform/model/cluster_stats.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								vendor/github.com/mattermost/platform/model/cluster_stats.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type ClusterStats struct { | ||||
| 	Id                        string `json:"id"` | ||||
| 	TotalWebsocketConnections int    `json:"total_websocket_connections"` | ||||
| 	TotalReadDbConnections    int    `json:"total_read_db_connections"` | ||||
| 	TotalMasterDbConnections  int    `json:"total_master_db_connections"` | ||||
| } | ||||
|  | ||||
| func (me *ClusterStats) ToJson() string { | ||||
| 	b, err := json.Marshal(me) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ClusterStatsFromJson(data io.Reader) *ClusterStats { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var me ClusterStats | ||||
| 	err := decoder.Decode(&me) | ||||
| 	if err == nil { | ||||
| 		return &me | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										36
									
								
								vendor/github.com/mattermost/platform/model/command_args.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								vendor/github.com/mattermost/platform/model/command_args.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type CommandArgs struct { | ||||
| 	ChannelId string `json:"channel_id"` | ||||
| 	RootId    string `json:"root_id"` | ||||
| 	ParentId  string `json:"parent_id"` | ||||
| 	Command   string `json:"command"` | ||||
| } | ||||
|  | ||||
| func (o *CommandArgs) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func CommandArgsFromJson(data io.Reader) *CommandArgs { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o CommandArgs | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										2
									
								
								vendor/github.com/mattermost/platform/model/command_response.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/mattermost/platform/model/command_response.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -16,6 +16,8 @@ const ( | ||||
| type CommandResponse struct { | ||||
| 	ResponseType string      `json:"response_type"` | ||||
| 	Text         string      `json:"text"` | ||||
| 	Username     string      `json:"username"` | ||||
| 	IconURL      string      `json:"icon_url"` | ||||
| 	GotoLocation string      `json:"goto_location"` | ||||
| 	Attachments  interface{} `json:"attachments"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										6
									
								
								vendor/github.com/mattermost/platform/model/compliance_post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/mattermost/platform/model/compliance_post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -34,7 +34,7 @@ type CompliancePost struct { | ||||
| 	PostType       string | ||||
| 	PostProps      string | ||||
| 	PostHashtags   string | ||||
| 	PostFilenames  string | ||||
| 	PostFileIds    string | ||||
| } | ||||
|  | ||||
| func CompliancePostHeader() []string { | ||||
| @@ -60,7 +60,7 @@ func CompliancePostHeader() []string { | ||||
| 		"PostType", | ||||
| 		"PostProps", | ||||
| 		"PostHashtags", | ||||
| 		"PostFilenames", | ||||
| 		"PostFileIds", | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -99,6 +99,6 @@ func (me *CompliancePost) Row() []string { | ||||
| 		me.PostType, | ||||
| 		me.PostProps, | ||||
| 		me.PostHashtags, | ||||
| 		me.PostFilenames, | ||||
| 		me.PostFileIds, | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										346
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										346
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -38,9 +38,10 @@ const ( | ||||
| 	DIRECT_MESSAGE_ANY  = "any" | ||||
| 	DIRECT_MESSAGE_TEAM = "team" | ||||
|  | ||||
| 	PERMISSIONS_ALL          = "all" | ||||
| 	PERMISSIONS_TEAM_ADMIN   = "team_admin" | ||||
| 	PERMISSIONS_SYSTEM_ADMIN = "system_admin" | ||||
| 	PERMISSIONS_ALL           = "all" | ||||
| 	PERMISSIONS_CHANNEL_ADMIN = "channel_admin" | ||||
| 	PERMISSIONS_TEAM_ADMIN    = "team_admin" | ||||
| 	PERMISSIONS_SYSTEM_ADMIN  = "system_admin" | ||||
|  | ||||
| 	FAKE_SETTING = "********************************" | ||||
|  | ||||
| @@ -57,6 +58,14 @@ const ( | ||||
| type ServiceSettings struct { | ||||
| 	SiteURL                           *string | ||||
| 	ListenAddress                     string | ||||
| 	ConnectionSecurity                *string | ||||
| 	TLSCertFile                       *string | ||||
| 	TLSKeyFile                        *string | ||||
| 	UseLetsEncrypt                    *bool | ||||
| 	LetsEncryptCertificateCacheFile   *string | ||||
| 	Forward80To443                    *bool | ||||
| 	ReadTimeout                       *int | ||||
| 	WriteTimeout                      *int | ||||
| 	MaximumLoginAttempts              int | ||||
| 	SegmentDeveloperKey               string | ||||
| 	GoogleDeveloperKey                string | ||||
| @@ -72,6 +81,7 @@ type ServiceSettings struct { | ||||
| 	EnableSecurityFixAlert            *bool | ||||
| 	EnableInsecureOutgoingConnections *bool | ||||
| 	EnableMultifactorAuthentication   *bool | ||||
| 	EnforceMultifactorAuthentication  *bool | ||||
| 	AllowCorsFrom                     *string | ||||
| 	SessionLengthWebInDays            *int | ||||
| 	SessionLengthMobileInDays         *int | ||||
| @@ -90,6 +100,16 @@ type ClusterSettings struct { | ||||
| 	InterNodeUrls          []string | ||||
| } | ||||
|  | ||||
| type MetricsSettings struct { | ||||
| 	Enable           *bool | ||||
| 	BlockProfileRate *int | ||||
| 	ListenAddress    *string | ||||
| } | ||||
|  | ||||
| type AnalyticsSettings struct { | ||||
| 	MaxUsersForStatistics *int | ||||
| } | ||||
|  | ||||
| type SSOSettings struct { | ||||
| 	Enable          bool | ||||
| 	Secret          string | ||||
| @@ -130,26 +150,24 @@ type PasswordSettings struct { | ||||
| } | ||||
|  | ||||
| type FileSettings struct { | ||||
| 	MaxFileSize                *int64 | ||||
| 	DriverName                 string | ||||
| 	Directory                  string | ||||
| 	EnablePublicLink           bool | ||||
| 	PublicLinkSalt             *string | ||||
| 	ThumbnailWidth             int | ||||
| 	ThumbnailHeight            int | ||||
| 	PreviewWidth               int | ||||
| 	PreviewHeight              int | ||||
| 	ProfileWidth               int | ||||
| 	ProfileHeight              int | ||||
| 	InitialFont                string | ||||
| 	AmazonS3AccessKeyId        string | ||||
| 	AmazonS3SecretAccessKey    string | ||||
| 	AmazonS3Bucket             string | ||||
| 	AmazonS3Region             string | ||||
| 	AmazonS3Endpoint           string | ||||
| 	AmazonS3BucketEndpoint     string | ||||
| 	AmazonS3LocationConstraint *bool | ||||
| 	AmazonS3LowercaseBucket    *bool | ||||
| 	MaxFileSize             *int64 | ||||
| 	DriverName              string | ||||
| 	Directory               string | ||||
| 	EnablePublicLink        bool | ||||
| 	PublicLinkSalt          *string | ||||
| 	ThumbnailWidth          int | ||||
| 	ThumbnailHeight         int | ||||
| 	PreviewWidth            int | ||||
| 	PreviewHeight           int | ||||
| 	ProfileWidth            int | ||||
| 	ProfileHeight           int | ||||
| 	InitialFont             string | ||||
| 	AmazonS3AccessKeyId     string | ||||
| 	AmazonS3SecretAccessKey string | ||||
| 	AmazonS3Bucket          string | ||||
| 	AmazonS3Region          string | ||||
| 	AmazonS3Endpoint        string | ||||
| 	AmazonS3SSL             *bool | ||||
| } | ||||
|  | ||||
| type EmailSettings struct { | ||||
| @@ -177,11 +195,12 @@ type EmailSettings struct { | ||||
| } | ||||
|  | ||||
| type RateLimitSettings struct { | ||||
| 	EnableRateLimiter bool | ||||
| 	PerSec            int | ||||
| 	MemoryStoreSize   int | ||||
| 	VaryByRemoteAddr  bool | ||||
| 	VaryByHeader      string | ||||
| 	Enable           *bool | ||||
| 	PerSec           int | ||||
| 	MaxBurst         *int | ||||
| 	MemoryStoreSize  int | ||||
| 	VaryByRemoteAddr bool | ||||
| 	VaryByHeader     string | ||||
| } | ||||
|  | ||||
| type PrivacySettings struct { | ||||
| @@ -205,7 +224,6 @@ type TeamSettings struct { | ||||
| 	EnableUserCreation               bool | ||||
| 	EnableOpenServer                 *bool | ||||
| 	RestrictCreationToDomains        string | ||||
| 	RestrictTeamNames                *bool | ||||
| 	EnableCustomBrand                *bool | ||||
| 	CustomBrandText                  *string | ||||
| 	CustomDescriptionText            *string | ||||
| @@ -213,7 +231,13 @@ type TeamSettings struct { | ||||
| 	RestrictTeamInvite               *string | ||||
| 	RestrictPublicChannelManagement  *string | ||||
| 	RestrictPrivateChannelManagement *string | ||||
| 	RestrictPublicChannelCreation    *string | ||||
| 	RestrictPrivateChannelCreation   *string | ||||
| 	RestrictPublicChannelDeletion    *string | ||||
| 	RestrictPrivateChannelDeletion   *string | ||||
| 	UserStatusAwayTimeout            *int64 | ||||
| 	MaxChannelsPerTeam               *int64 | ||||
| 	MaxNotificationsPerChannel       *int64 | ||||
| } | ||||
|  | ||||
| type LdapSettings struct { | ||||
| @@ -236,6 +260,7 @@ type LdapSettings struct { | ||||
| 	UsernameAttribute  *string | ||||
| 	NicknameAttribute  *string | ||||
| 	IdAttribute        *string | ||||
| 	PositionAttribute  *string | ||||
|  | ||||
| 	// Syncronization | ||||
| 	SyncIntervalMinutes *int | ||||
| @@ -282,6 +307,7 @@ type SamlSettings struct { | ||||
| 	UsernameAttribute  *string | ||||
| 	NicknameAttribute  *string | ||||
| 	LocaleAttribute    *string | ||||
| 	PositionAttribute  *string | ||||
|  | ||||
| 	LoginButtonText *string | ||||
| } | ||||
| @@ -292,6 +318,17 @@ type NativeAppSettings struct { | ||||
| 	IosAppDownloadLink     *string | ||||
| } | ||||
|  | ||||
| type WebrtcSettings struct { | ||||
| 	Enable              *bool | ||||
| 	GatewayWebsocketUrl *string | ||||
| 	GatewayAdminUrl     *string | ||||
| 	GatewayAdminSecret  *string | ||||
| 	StunURI             *string | ||||
| 	TurnURI             *string | ||||
| 	TurnUsername        *string | ||||
| 	TurnSharedKey       *string | ||||
| } | ||||
|  | ||||
| type Config struct { | ||||
| 	ServiceSettings      ServiceSettings | ||||
| 	TeamSettings         TeamSettings | ||||
| @@ -312,6 +349,9 @@ type Config struct { | ||||
| 	SamlSettings         SamlSettings | ||||
| 	NativeAppSettings    NativeAppSettings | ||||
| 	ClusterSettings      ClusterSettings | ||||
| 	MetricsSettings      MetricsSettings | ||||
| 	AnalyticsSettings    AnalyticsSettings | ||||
| 	WebrtcSettings       WebrtcSettings | ||||
| } | ||||
|  | ||||
| func (o *Config) ToJson() string { | ||||
| @@ -353,6 +393,21 @@ func (o *Config) SetDefaults() { | ||||
| 		o.SqlSettings.AtRestEncryptKey = NewRandomString(32) | ||||
| 	} | ||||
|  | ||||
| 	if o.FileSettings.AmazonS3Endpoint == "" { | ||||
| 		// Defaults to "s3.amazonaws.com" | ||||
| 		o.FileSettings.AmazonS3Endpoint = "s3.amazonaws.com" | ||||
| 	} | ||||
|  | ||||
| 	if o.FileSettings.AmazonS3Region == "" { | ||||
| 		// Defaults to "us-east-1" region. | ||||
| 		o.FileSettings.AmazonS3Region = "us-east-1" | ||||
| 	} | ||||
|  | ||||
| 	if o.FileSettings.AmazonS3SSL == nil { | ||||
| 		o.FileSettings.AmazonS3SSL = new(bool) | ||||
| 		*o.FileSettings.AmazonS3SSL = true // Secure by default. | ||||
| 	} | ||||
|  | ||||
| 	if o.FileSettings.MaxFileSize == nil { | ||||
| 		o.FileSettings.MaxFileSize = new(int64) | ||||
| 		*o.FileSettings.MaxFileSize = 52428800 // 50 MB | ||||
| @@ -363,14 +418,9 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.FileSettings.PublicLinkSalt = NewRandomString(32) | ||||
| 	} | ||||
|  | ||||
| 	if o.FileSettings.AmazonS3LocationConstraint == nil { | ||||
| 		o.FileSettings.AmazonS3LocationConstraint = new(bool) | ||||
| 		*o.FileSettings.AmazonS3LocationConstraint = false | ||||
| 	} | ||||
|  | ||||
| 	if o.FileSettings.AmazonS3LowercaseBucket == nil { | ||||
| 		o.FileSettings.AmazonS3LowercaseBucket = new(bool) | ||||
| 		*o.FileSettings.AmazonS3LowercaseBucket = false | ||||
| 	if o.FileSettings.InitialFont == "" { | ||||
| 		// Defaults to "luximbi.ttf" | ||||
| 		o.FileSettings.InitialFont = "luximbi.ttf" | ||||
| 	} | ||||
|  | ||||
| 	if len(o.EmailSettings.InviteSalt) == 0 { | ||||
| @@ -406,6 +456,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.ServiceSettings.EnableMultifactorAuthentication = false | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.EnforceMultifactorAuthentication == nil { | ||||
| 		o.ServiceSettings.EnforceMultifactorAuthentication = new(bool) | ||||
| 		*o.ServiceSettings.EnforceMultifactorAuthentication = false | ||||
| 	} | ||||
|  | ||||
| 	if o.PasswordSettings.MinimumLength == nil { | ||||
| 		o.PasswordSettings.MinimumLength = new(int) | ||||
| 		*o.PasswordSettings.MinimumLength = PASSWORD_MINIMUM_LENGTH | ||||
| @@ -431,11 +486,6 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.PasswordSettings.Symbol = false | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.RestrictTeamNames == nil { | ||||
| 		o.TeamSettings.RestrictTeamNames = new(bool) | ||||
| 		*o.TeamSettings.RestrictTeamNames = true | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.EnableCustomBrand == nil { | ||||
| 		o.TeamSettings.EnableCustomBrand = new(bool) | ||||
| 		*o.TeamSettings.EnableCustomBrand = false | ||||
| @@ -476,11 +526,45 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.TeamSettings.RestrictPrivateChannelManagement = PERMISSIONS_ALL | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.RestrictPublicChannelCreation == nil { | ||||
| 		o.TeamSettings.RestrictPublicChannelCreation = new(string) | ||||
| 		// If this setting does not exist, assume migration from <3.6, so use management setting as default. | ||||
| 		*o.TeamSettings.RestrictPublicChannelCreation = *o.TeamSettings.RestrictPublicChannelManagement | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.RestrictPrivateChannelCreation == nil { | ||||
| 		o.TeamSettings.RestrictPrivateChannelCreation = new(string) | ||||
| 		// If this setting does not exist, assume migration from <3.6, so use management setting as default. | ||||
| 		*o.TeamSettings.RestrictPrivateChannelCreation = *o.TeamSettings.RestrictPrivateChannelManagement | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.RestrictPublicChannelDeletion == nil { | ||||
| 		o.TeamSettings.RestrictPublicChannelDeletion = new(string) | ||||
| 		// If this setting does not exist, assume migration from <3.6, so use management setting as default. | ||||
| 		*o.TeamSettings.RestrictPublicChannelDeletion = *o.TeamSettings.RestrictPublicChannelManagement | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.RestrictPrivateChannelDeletion == nil { | ||||
| 		o.TeamSettings.RestrictPrivateChannelDeletion = new(string) | ||||
| 		// If this setting does not exist, assume migration from <3.6, so use management setting as default. | ||||
| 		*o.TeamSettings.RestrictPrivateChannelDeletion = *o.TeamSettings.RestrictPrivateChannelManagement | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.UserStatusAwayTimeout == nil { | ||||
| 		o.TeamSettings.UserStatusAwayTimeout = new(int64) | ||||
| 		*o.TeamSettings.UserStatusAwayTimeout = 300 | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.MaxChannelsPerTeam == nil { | ||||
| 		o.TeamSettings.MaxChannelsPerTeam = new(int64) | ||||
| 		*o.TeamSettings.MaxChannelsPerTeam = 2000 | ||||
| 	} | ||||
|  | ||||
| 	if o.TeamSettings.MaxNotificationsPerChannel == nil { | ||||
| 		o.TeamSettings.MaxNotificationsPerChannel = new(int64) | ||||
| 		*o.TeamSettings.MaxNotificationsPerChannel = 1000 | ||||
| 	} | ||||
|  | ||||
| 	if o.EmailSettings.EnableSignInWithEmail == nil { | ||||
| 		o.EmailSettings.EnableSignInWithEmail = new(bool) | ||||
|  | ||||
| @@ -651,6 +735,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.LdapSettings.IdAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.PositionAttribute == nil { | ||||
| 		o.LdapSettings.PositionAttribute = new(string) | ||||
| 		*o.LdapSettings.PositionAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.LdapSettings.SyncIntervalMinutes == nil { | ||||
| 		o.LdapSettings.SyncIntervalMinutes = new(int) | ||||
| 		*o.LdapSettings.SyncIntervalMinutes = 60 | ||||
| @@ -752,6 +841,21 @@ func (o *Config) SetDefaults() { | ||||
| 		o.ClusterSettings.InterNodeUrls = []string{} | ||||
| 	} | ||||
|  | ||||
| 	if o.MetricsSettings.ListenAddress == nil { | ||||
| 		o.MetricsSettings.ListenAddress = new(string) | ||||
| 		*o.MetricsSettings.ListenAddress = ":8067" | ||||
| 	} | ||||
|  | ||||
| 	if o.MetricsSettings.Enable == nil { | ||||
| 		o.MetricsSettings.Enable = new(bool) | ||||
| 		*o.MetricsSettings.Enable = false | ||||
| 	} | ||||
|  | ||||
| 	if o.AnalyticsSettings.MaxUsersForStatistics == nil { | ||||
| 		o.AnalyticsSettings.MaxUsersForStatistics = new(int) | ||||
| 		*o.AnalyticsSettings.MaxUsersForStatistics = 2500 | ||||
| 	} | ||||
|  | ||||
| 	if o.ComplianceSettings.Enable == nil { | ||||
| 		o.ComplianceSettings.Enable = new(bool) | ||||
| 		*o.ComplianceSettings.Enable = false | ||||
| @@ -862,6 +966,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.SamlSettings.NicknameAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.PositionAttribute == nil { | ||||
| 		o.SamlSettings.PositionAttribute = new(string) | ||||
| 		*o.SamlSettings.PositionAttribute = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.SamlSettings.LocaleAttribute == nil { | ||||
| 		o.SamlSettings.LocaleAttribute = new(string) | ||||
| 		*o.SamlSettings.LocaleAttribute = "" | ||||
| @@ -881,6 +990,63 @@ func (o *Config) SetDefaults() { | ||||
| 		o.NativeAppSettings.IosAppDownloadLink = new(string) | ||||
| 		*o.NativeAppSettings.IosAppDownloadLink = "https://about.mattermost.com/mattermost-ios-app/" | ||||
| 	} | ||||
|  | ||||
| 	if o.RateLimitSettings.Enable == nil { | ||||
| 		o.RateLimitSettings.Enable = new(bool) | ||||
| 		*o.RateLimitSettings.Enable = false | ||||
| 	} | ||||
|  | ||||
| 	if o.RateLimitSettings.MaxBurst == nil { | ||||
| 		o.RateLimitSettings.MaxBurst = new(int) | ||||
| 		*o.RateLimitSettings.MaxBurst = 100 | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.ConnectionSecurity == nil { | ||||
| 		o.ServiceSettings.ConnectionSecurity = new(string) | ||||
| 		*o.ServiceSettings.ConnectionSecurity = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.TLSKeyFile == nil { | ||||
| 		o.ServiceSettings.TLSKeyFile = new(string) | ||||
| 		*o.ServiceSettings.TLSKeyFile = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.TLSCertFile == nil { | ||||
| 		o.ServiceSettings.TLSCertFile = new(string) | ||||
| 		*o.ServiceSettings.TLSCertFile = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.UseLetsEncrypt == nil { | ||||
| 		o.ServiceSettings.UseLetsEncrypt = new(bool) | ||||
| 		*o.ServiceSettings.UseLetsEncrypt = false | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.LetsEncryptCertificateCacheFile == nil { | ||||
| 		o.ServiceSettings.LetsEncryptCertificateCacheFile = new(string) | ||||
| 		*o.ServiceSettings.LetsEncryptCertificateCacheFile = "./config/letsencrypt.cache" | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.ReadTimeout == nil { | ||||
| 		o.ServiceSettings.ReadTimeout = new(int) | ||||
| 		*o.ServiceSettings.ReadTimeout = 300 | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.WriteTimeout == nil { | ||||
| 		o.ServiceSettings.WriteTimeout = new(int) | ||||
| 		*o.ServiceSettings.WriteTimeout = 300 | ||||
| 	} | ||||
|  | ||||
| 	if o.ServiceSettings.Forward80To443 == nil { | ||||
| 		o.ServiceSettings.Forward80To443 = new(bool) | ||||
| 		*o.ServiceSettings.Forward80To443 = false | ||||
| 	} | ||||
|  | ||||
| 	if o.MetricsSettings.BlockProfileRate == nil { | ||||
| 		o.MetricsSettings.BlockProfileRate = new(int) | ||||
| 		*o.MetricsSettings.BlockProfileRate = 0 | ||||
| 	} | ||||
|  | ||||
| 	o.defaultWebrtcSettings() | ||||
| } | ||||
|  | ||||
| func (o *Config) IsValid() *AppError { | ||||
| @@ -911,6 +1077,14 @@ func (o *Config) IsValid() *AppError { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.TeamSettings.MaxChannelsPerTeam <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.max_channels.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.TeamSettings.MaxNotificationsPerChannel <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.max_notify_per_channel.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if !(*o.TeamSettings.RestrictDirectMessage == DIRECT_MESSAGE_ANY || *o.TeamSettings.RestrictDirectMessage == DIRECT_MESSAGE_TEAM) { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.restrict_direct_message.app_error", nil, "") | ||||
| 	} | ||||
| @@ -1083,6 +1257,26 @@ func (o *Config) IsValid() *AppError { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SITENAME_MAX_LENGTH}, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.RateLimitSettings.MaxBurst <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.max_burst.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if err := o.isValidWebrtcSettings(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if !(*o.ServiceSettings.ConnectionSecurity == CONN_SECURITY_NONE || *o.ServiceSettings.ConnectionSecurity == CONN_SECURITY_TLS) { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.webserver_security.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.ServiceSettings.ReadTimeout <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.read_timeout.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if *o.ServiceSettings.WriteTimeout <= 0 { | ||||
| 		return NewLocAppError("Config.IsValid", "model.config.is_valid.write_timeout.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -1121,3 +1315,71 @@ func (o *Config) Sanitize() { | ||||
| 		o.SqlSettings.DataSourceReplicas[i] = FAKE_SETTING | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *Config) defaultWebrtcSettings() { | ||||
| 	if o.WebrtcSettings.Enable == nil { | ||||
| 		o.WebrtcSettings.Enable = new(bool) | ||||
| 		*o.WebrtcSettings.Enable = false | ||||
| 	} | ||||
|  | ||||
| 	if o.WebrtcSettings.GatewayWebsocketUrl == nil { | ||||
| 		o.WebrtcSettings.GatewayWebsocketUrl = new(string) | ||||
| 		*o.WebrtcSettings.GatewayWebsocketUrl = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.WebrtcSettings.GatewayAdminUrl == nil { | ||||
| 		o.WebrtcSettings.GatewayAdminUrl = new(string) | ||||
| 		*o.WebrtcSettings.GatewayAdminUrl = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.WebrtcSettings.GatewayAdminSecret == nil { | ||||
| 		o.WebrtcSettings.GatewayAdminSecret = new(string) | ||||
| 		*o.WebrtcSettings.GatewayAdminSecret = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.WebrtcSettings.StunURI == nil { | ||||
| 		o.WebrtcSettings.StunURI = new(string) | ||||
| 		*o.WebrtcSettings.StunURI = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.WebrtcSettings.TurnURI == nil { | ||||
| 		o.WebrtcSettings.TurnURI = new(string) | ||||
| 		*o.WebrtcSettings.TurnURI = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.WebrtcSettings.TurnUsername == nil { | ||||
| 		o.WebrtcSettings.TurnUsername = new(string) | ||||
| 		*o.WebrtcSettings.TurnUsername = "" | ||||
| 	} | ||||
|  | ||||
| 	if o.WebrtcSettings.TurnSharedKey == nil { | ||||
| 		o.WebrtcSettings.TurnSharedKey = new(string) | ||||
| 		*o.WebrtcSettings.TurnSharedKey = "" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *Config) isValidWebrtcSettings() *AppError { | ||||
| 	if *o.WebrtcSettings.Enable { | ||||
| 		if len(*o.WebrtcSettings.GatewayWebsocketUrl) == 0 || !IsValidWebsocketUrl(*o.WebrtcSettings.GatewayWebsocketUrl) { | ||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_ws_url.app_error", nil, "") | ||||
| 		} else if len(*o.WebrtcSettings.GatewayAdminUrl) == 0 || !IsValidHttpUrl(*o.WebrtcSettings.GatewayAdminUrl) { | ||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_url.app_error", nil, "") | ||||
| 		} else if len(*o.WebrtcSettings.GatewayAdminSecret) == 0 { | ||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_secret.app_error", nil, "") | ||||
| 		} else if len(*o.WebrtcSettings.StunURI) != 0 && !IsValidTurnOrStunServer(*o.WebrtcSettings.StunURI) { | ||||
| 			return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_stun_uri.app_error", nil, "") | ||||
| 		} else if len(*o.WebrtcSettings.TurnURI) != 0 { | ||||
| 			if !IsValidTurnOrStunServer(*o.WebrtcSettings.TurnURI) { | ||||
| 				return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_uri.app_error", nil, "") | ||||
| 			} | ||||
| 			if len(*o.WebrtcSettings.TurnUsername) == 0 { | ||||
| 				return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_username.app_error", nil, "") | ||||
| 			} else if len(*o.WebrtcSettings.TurnSharedKey) == 0 { | ||||
| 				return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_shared_key.app_error", nil, "") | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										4
									
								
								vendor/github.com/mattermost/platform/model/file.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/mattermost/platform/model/file.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -14,8 +14,8 @@ var ( | ||||
| ) | ||||
|  | ||||
| type FileUploadResponse struct { | ||||
| 	Filenames []string `json:"filenames"` | ||||
| 	ClientIds []string `json:"client_ids"` | ||||
| 	FileInfos []*FileInfo `json:"file_infos"` | ||||
| 	ClientIds []string    `json:"client_ids"` | ||||
| } | ||||
|  | ||||
| func FileUploadResponseFromJson(data io.Reader) *FileUploadResponse { | ||||
|   | ||||
							
								
								
									
										175
									
								
								vendor/github.com/mattermost/platform/model/file_info.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										175
									
								
								vendor/github.com/mattermost/platform/model/file_info.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,54 +6,31 @@ package model | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"image" | ||||
| 	"image/gif" | ||||
| 	"io" | ||||
| 	"mime" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| type FileInfo struct { | ||||
| 	Filename        string `json:"filename"` | ||||
| 	Size            int    `json:"size"` | ||||
| 	Id              string `json:"id"` | ||||
| 	CreatorId       string `json:"user_id"` | ||||
| 	PostId          string `json:"post_id,omitempty"` | ||||
| 	CreateAt        int64  `json:"create_at"` | ||||
| 	UpdateAt        int64  `json:"update_at"` | ||||
| 	DeleteAt        int64  `json:"delete_at"` | ||||
| 	Path            string `json:"-"` // not sent back to the client | ||||
| 	ThumbnailPath   string `json:"-"` // not sent back to the client | ||||
| 	PreviewPath     string `json:"-"` // not sent back to the client | ||||
| 	Name            string `json:"name"` | ||||
| 	Extension       string `json:"extension"` | ||||
| 	Size            int64  `json:"size"` | ||||
| 	MimeType        string `json:"mime_type"` | ||||
| 	HasPreviewImage bool   `json:"has_preview_image"` | ||||
| } | ||||
|  | ||||
| func GetInfoForBytes(filename string, data []byte) (*FileInfo, *AppError) { | ||||
| 	size := len(data) | ||||
|  | ||||
| 	var mimeType string | ||||
| 	extension := filepath.Ext(filename) | ||||
| 	isImage := IsFileExtImage(extension) | ||||
| 	if isImage { | ||||
| 		mimeType = GetImageMimeType(extension) | ||||
| 	} else { | ||||
| 		mimeType = mime.TypeByExtension(extension) | ||||
| 	} | ||||
|  | ||||
| 	if extension != "" && extension[0] == '.' { | ||||
| 		// the client expects a file extension without the leading period | ||||
| 		extension = extension[1:] | ||||
| 	} | ||||
|  | ||||
| 	hasPreviewImage := isImage | ||||
| 	if mimeType == "image/gif" { | ||||
| 		// just show the gif itself instead of a preview image for animated gifs | ||||
| 		if gifImage, err := gif.DecodeAll(bytes.NewReader(data)); err != nil { | ||||
| 			return nil, NewLocAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "filename="+filename) | ||||
| 		} else { | ||||
| 			hasPreviewImage = len(gifImage.Image) == 1 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &FileInfo{ | ||||
| 		Filename:        filename, | ||||
| 		Size:            size, | ||||
| 		Extension:       extension, | ||||
| 		MimeType:        mimeType, | ||||
| 		HasPreviewImage: hasPreviewImage, | ||||
| 	}, nil | ||||
| 	Width           int    `json:"width,omitempty"` | ||||
| 	Height          int    `json:"height,omitempty"` | ||||
| 	HasPreviewImage bool   `json:"has_preview_image,omitempty"` | ||||
| } | ||||
|  | ||||
| func (info *FileInfo) ToJson() string { | ||||
| @@ -75,3 +52,123 @@ func FileInfoFromJson(data io.Reader) *FileInfo { | ||||
| 		return &info | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func FileInfosToJson(infos []*FileInfo) string { | ||||
| 	b, err := json.Marshal(infos) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func FileInfosFromJson(data io.Reader) []*FileInfo { | ||||
| 	decoder := json.NewDecoder(data) | ||||
|  | ||||
| 	var infos []*FileInfo | ||||
| 	if err := decoder.Decode(&infos); err != nil { | ||||
| 		return nil | ||||
| 	} else { | ||||
| 		return infos | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *FileInfo) PreSave() { | ||||
| 	if o.Id == "" { | ||||
| 		o.Id = NewId() | ||||
| 	} | ||||
|  | ||||
| 	if o.CreateAt == 0 { | ||||
| 		o.CreateAt = GetMillis() | ||||
| 		o.UpdateAt = o.CreateAt | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *FileInfo) IsValid() *AppError { | ||||
| 	if len(o.Id) != 26 { | ||||
| 		return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if len(o.CreatorId) != 26 { | ||||
| 		return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if len(o.PostId) != 0 && len(o.PostId) != 26 { | ||||
| 		return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if o.CreateAt == 0 { | ||||
| 		return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.create_at.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if o.UpdateAt == 0 { | ||||
| 		return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.update_at.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if o.Path == "" { | ||||
| 		return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.path.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (o *FileInfo) IsImage() bool { | ||||
| 	return strings.HasPrefix(o.MimeType, "image") | ||||
| } | ||||
|  | ||||
| func GetInfoForBytes(name string, data []byte) (*FileInfo, *AppError) { | ||||
| 	info := &FileInfo{ | ||||
| 		Name: name, | ||||
| 		Size: int64(len(data)), | ||||
| 	} | ||||
| 	var err *AppError | ||||
|  | ||||
| 	extension := strings.ToLower(filepath.Ext(name)) | ||||
| 	info.MimeType = mime.TypeByExtension(extension) | ||||
|  | ||||
| 	if extension != "" && extension[0] == '.' { | ||||
| 		// The client expects a file extension without the leading period | ||||
| 		info.Extension = extension[1:] | ||||
| 	} else { | ||||
| 		info.Extension = extension | ||||
| 	} | ||||
|  | ||||
| 	if info.IsImage() { | ||||
| 		// Only set the width and height if it's actually an image that we can understand | ||||
| 		if config, _, err := image.DecodeConfig(bytes.NewReader(data)); err == nil { | ||||
| 			info.Width = config.Width | ||||
| 			info.Height = config.Height | ||||
|  | ||||
| 			if info.MimeType == "image/gif" { | ||||
| 				// Just show the gif itself instead of a preview image for animated gifs | ||||
| 				if gifConfig, err := gif.DecodeAll(bytes.NewReader(data)); err != nil { | ||||
| 					// Still return the rest of the info even though it doesn't appear to be an actual gif | ||||
| 					info.HasPreviewImage = true | ||||
| 					err = NewLocAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "name="+name) | ||||
| 				} else { | ||||
| 					info.HasPreviewImage = len(gifConfig.Image) == 1 | ||||
| 				} | ||||
| 			} else { | ||||
| 				info.HasPreviewImage = true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return info, err | ||||
| } | ||||
|  | ||||
| func GetEtagForFileInfos(infos []*FileInfo) string { | ||||
| 	if len(infos) == 0 { | ||||
| 		return Etag() | ||||
| 	} | ||||
|  | ||||
| 	var maxUpdateAt int64 | ||||
|  | ||||
| 	for _, info := range infos { | ||||
| 		if info.UpdateAt > maxUpdateAt { | ||||
| 			maxUpdateAt = info.UpdateAt | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return Etag(infos[0].PostId, maxUpdateAt) | ||||
| } | ||||
|   | ||||
							
								
								
									
										3
									
								
								vendor/github.com/mattermost/platform/model/incoming_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/mattermost/platform/model/incoming_webhook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,6 +6,7 @@ package model | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| @@ -233,7 +234,7 @@ func expandAnnouncements(i *IncomingWebhookRequest) { | ||||
| 				for _, field := range fields { | ||||
| 					f := field.(map[string]interface{}) | ||||
| 					if f["value"] != nil { | ||||
| 						f["value"] = expandAnnouncement(f["value"].(string)) | ||||
| 						f["value"] = expandAnnouncement(fmt.Sprintf("%v", f["value"])) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|   | ||||
							
								
								
									
										15
									
								
								vendor/github.com/mattermost/platform/model/initial_load.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/mattermost/platform/model/initial_load.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -9,14 +9,13 @@ import ( | ||||
| ) | ||||
|  | ||||
| type InitialLoad struct { | ||||
| 	User           *User             `json:"user"` | ||||
| 	TeamMembers    []*TeamMember     `json:"team_members"` | ||||
| 	Teams          []*Team           `json:"teams"` | ||||
| 	DirectProfiles map[string]*User  `json:"direct_profiles"` | ||||
| 	Preferences    Preferences       `json:"preferences"` | ||||
| 	ClientCfg      map[string]string `json:"client_cfg"` | ||||
| 	LicenseCfg     map[string]string `json:"license_cfg"` | ||||
| 	NoAccounts     bool              `json:"no_accounts"` | ||||
| 	User        *User             `json:"user"` | ||||
| 	TeamMembers []*TeamMember     `json:"team_members"` | ||||
| 	Teams       []*Team           `json:"teams"` | ||||
| 	Preferences Preferences       `json:"preferences"` | ||||
| 	ClientCfg   map[string]string `json:"client_cfg"` | ||||
| 	LicenseCfg  map[string]string `json:"license_cfg"` | ||||
| 	NoAccounts  bool              `json:"no_accounts"` | ||||
| } | ||||
|  | ||||
| func (me *InitialLoad) ToJson() string { | ||||
|   | ||||
							
								
								
									
										10
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -39,11 +39,13 @@ type Features struct { | ||||
| 	Office365OAuth       *bool `json:"office365_oauth"` | ||||
| 	Compliance           *bool `json:"compliance"` | ||||
| 	Cluster              *bool `json:"cluster"` | ||||
| 	Metrics              *bool `json:"metrics"` | ||||
| 	CustomBrand          *bool `json:"custom_brand"` | ||||
| 	MHPNS                *bool `json:"mhpns"` | ||||
| 	SAML                 *bool `json:"saml"` | ||||
| 	PasswordRequirements *bool `json:"password_requirements"` | ||||
| 	FutureFeatures       *bool `json:"future_features"` | ||||
| 	// after we enabled more features for webrtc we'll need to control them with this | ||||
| 	FutureFeatures *bool `json:"future_features"` | ||||
| } | ||||
|  | ||||
| func (f *Features) ToMap() map[string]interface{} { | ||||
| @@ -54,6 +56,7 @@ func (f *Features) ToMap() map[string]interface{} { | ||||
| 		"office365":    *f.Office365OAuth, | ||||
| 		"compliance":   *f.Compliance, | ||||
| 		"cluster":      *f.Cluster, | ||||
| 		"metrics":      *f.Metrics, | ||||
| 		"custom_brand": *f.CustomBrand, | ||||
| 		"mhpns":        *f.MHPNS, | ||||
| 		"saml":         *f.SAML, | ||||
| @@ -103,6 +106,11 @@ func (f *Features) SetDefaults() { | ||||
| 		*f.Cluster = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.Metrics == nil { | ||||
| 		f.Metrics = new(bool) | ||||
| 		*f.Metrics = *f.FutureFeatures | ||||
| 	} | ||||
|  | ||||
| 	if f.CustomBrand == nil { | ||||
| 		f.CustomBrand = new(bool) | ||||
| 		*f.CustomBrand = *f.FutureFeatures | ||||
|   | ||||
							
								
								
									
										33
									
								
								vendor/github.com/mattermost/platform/model/post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/mattermost/platform/model/post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -17,8 +17,14 @@ const ( | ||||
| 	POST_JOIN_LEAVE            = "system_join_leave" | ||||
| 	POST_ADD_REMOVE            = "system_add_remove" | ||||
| 	POST_HEADER_CHANGE         = "system_header_change" | ||||
| 	POST_DISPLAYNAME_CHANGE    = "system_displayname_change" | ||||
| 	POST_CHANNEL_DELETED       = "system_channel_deleted" | ||||
| 	POST_EPHEMERAL             = "system_ephemeral" | ||||
| 	POST_FILEIDS_MAX_RUNES     = 150 | ||||
| 	POST_FILENAMES_MAX_RUNES   = 4000 | ||||
| 	POST_HASHTAGS_MAX_RUNES    = 1000 | ||||
| 	POST_MESSAGE_MAX_RUNES     = 4000 | ||||
| 	POST_PROPS_MAX_RUNES       = 8000 | ||||
| ) | ||||
|  | ||||
| type Post struct { | ||||
| @@ -35,8 +41,10 @@ type Post struct { | ||||
| 	Type          string          `json:"type"` | ||||
| 	Props         StringInterface `json:"props"` | ||||
| 	Hashtags      string          `json:"hashtags"` | ||||
| 	Filenames     StringArray     `json:"filenames"` | ||||
| 	Filenames     StringArray     `json:"filenames,omitempty"` // Deprecated, do not use this field any more | ||||
| 	FileIds       StringArray     `json:"file_ids,omitempty"` | ||||
| 	PendingPostId string          `json:"pending_post_id" db:"-"` | ||||
| 	HasReactions  bool            `json:"has_reactions,omitempty"` | ||||
| } | ||||
|  | ||||
| func (o *Post) ToJson() string { | ||||
| @@ -101,24 +109,30 @@ func (o *Post) IsValid() *AppError { | ||||
| 		return NewLocAppError("Post.IsValid", "model.post.is_valid.original_id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(o.Message) > 4000 { | ||||
| 	if utf8.RuneCountInString(o.Message) > POST_MESSAGE_MAX_RUNES { | ||||
| 		return NewLocAppError("Post.IsValid", "model.post.is_valid.msg.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(o.Hashtags) > 1000 { | ||||
| 	if utf8.RuneCountInString(o.Hashtags) > POST_HASHTAGS_MAX_RUNES { | ||||
| 		return NewLocAppError("Post.IsValid", "model.post.is_valid.hashtags.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	// should be removed once more message types are supported | ||||
| 	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) { | ||||
| 	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 || | ||||
| 		o.Type == POST_DISPLAYNAME_CHANGE || o.Type == POST_CHANNEL_DELETED) { | ||||
| 		return NewLocAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type) | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(ArrayToJson(o.Filenames)) > 4000 { | ||||
| 	if utf8.RuneCountInString(ArrayToJson(o.Filenames)) > POST_FILENAMES_MAX_RUNES { | ||||
| 		return NewLocAppError("Post.IsValid", "model.post.is_valid.filenames.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(StringInterfaceToJson(o.Props)) > 8000 { | ||||
| 	if utf8.RuneCountInString(ArrayToJson(o.FileIds)) > POST_FILEIDS_MAX_RUNES { | ||||
| 		return NewLocAppError("Post.IsValid", "model.post.is_valid.file_ids.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(StringInterfaceToJson(o.Props)) > POST_PROPS_MAX_RUNES { | ||||
| 		return NewLocAppError("Post.IsValid", "model.post.is_valid.props.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| @@ -145,15 +159,16 @@ func (o *Post) PreSave() { | ||||
| 	if o.Filenames == nil { | ||||
| 		o.Filenames = []string{} | ||||
| 	} | ||||
|  | ||||
| 	if o.FileIds == nil { | ||||
| 		o.FileIds = []string{} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *Post) MakeNonNil() { | ||||
| 	if o.Props == nil { | ||||
| 		o.Props = make(map[string]interface{}) | ||||
| 	} | ||||
| 	if o.Filenames == nil { | ||||
| 		o.Filenames = []string{} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *Post) AddProp(key string, value interface{}) { | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/github.com/mattermost/platform/model/preference.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/mattermost/platform/model/preference.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -33,6 +33,7 @@ const ( | ||||
|  | ||||
| 	PREFERENCE_CATEGORY_LAST     = "last" | ||||
| 	PREFERENCE_NAME_LAST_CHANNEL = "channel" | ||||
| 	PREFERENCE_NAME_LAST_TEAM    = "team" | ||||
|  | ||||
| 	PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications" | ||||
| 	PREFERENCE_NAME_EMAIL_INTERVAL    = "email_interval" | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/github.com/mattermost/platform/model/push_notification.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/mattermost/platform/model/push_notification.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -30,6 +30,7 @@ type PushNotification struct { | ||||
| 	Message          string `json:"message"` | ||||
| 	Badge            int    `json:"badge"` | ||||
| 	ContentAvailable int    `json:"cont_ava"` | ||||
| 	TeamId           string `json:"team_id"` | ||||
| 	ChannelId        string `json:"channel_id"` | ||||
| 	ChannelName      string `json:"channel_name"` | ||||
| 	Type             string `json:"type"` | ||||
|   | ||||
							
								
								
									
										78
									
								
								vendor/github.com/mattermost/platform/model/reaction.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								vendor/github.com/mattermost/platform/model/reaction.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type Reaction struct { | ||||
| 	UserId    string `json:"user_id"` | ||||
| 	PostId    string `json:"post_id"` | ||||
| 	EmojiName string `json:"emoji_name"` | ||||
| 	CreateAt  int64  `json:"create_at"` | ||||
| } | ||||
|  | ||||
| func (o *Reaction) ToJson() string { | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ReactionFromJson(data io.Reader) *Reaction { | ||||
| 	var o Reaction | ||||
|  | ||||
| 	if err := json.NewDecoder(data).Decode(&o); err != nil { | ||||
| 		return nil | ||||
| 	} else { | ||||
| 		return &o | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ReactionsToJson(o []*Reaction) string { | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ReactionsFromJson(data io.Reader) []*Reaction { | ||||
| 	var o []*Reaction | ||||
|  | ||||
| 	if err := json.NewDecoder(data).Decode(&o); err != nil { | ||||
| 		return nil | ||||
| 	} else { | ||||
| 		return o | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *Reaction) IsValid() *AppError { | ||||
| 	if len(o.UserId) != 26 { | ||||
| 		return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.user_id.app_error", nil, "user_id="+o.UserId) | ||||
| 	} | ||||
|  | ||||
| 	if len(o.PostId) != 26 { | ||||
| 		return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.post_id.app_error", nil, "post_id="+o.PostId) | ||||
| 	} | ||||
|  | ||||
| 	if len(o.EmojiName) == 0 || len(o.EmojiName) > 64 { | ||||
| 		return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.emoji_name.app_error", nil, "emoji_name="+o.EmojiName) | ||||
| 	} | ||||
|  | ||||
| 	if o.CreateAt == 0 { | ||||
| 		return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.create_at.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (o *Reaction) PreSave() { | ||||
| 	if o.CreateAt == 0 { | ||||
| 		o.CreateAt = GetMillis() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										6
									
								
								vendor/github.com/mattermost/platform/model/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/mattermost/platform/model/session.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -11,7 +11,7 @@ import ( | ||||
|  | ||||
| const ( | ||||
| 	SESSION_COOKIE_TOKEN  = "MMAUTHTOKEN" | ||||
| 	SESSION_CACHE_SIZE    = 10000 | ||||
| 	SESSION_CACHE_SIZE    = 25000 | ||||
| 	SESSION_PROP_PLATFORM = "platform" | ||||
| 	SESSION_PROP_OS       = "os" | ||||
| 	SESSION_PROP_BROWSER  = "browser" | ||||
| @@ -115,6 +115,10 @@ func (me *Session) IsMobileApp() bool { | ||||
| 		(strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_ANDROID+":")) | ||||
| } | ||||
|  | ||||
| func (me *Session) GetUserRoles() []string { | ||||
| 	return strings.Fields(me.Roles) | ||||
| } | ||||
|  | ||||
| func SessionsToJson(o []*Session) string { | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		return "[]" | ||||
|   | ||||
							
								
								
									
										18
									
								
								vendor/github.com/mattermost/platform/model/status.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/mattermost/platform/model/status.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -12,8 +12,9 @@ const ( | ||||
| 	STATUS_OFFLINE         = "offline" | ||||
| 	STATUS_AWAY            = "away" | ||||
| 	STATUS_ONLINE          = "online" | ||||
| 	STATUS_CACHE_SIZE      = 10000 | ||||
| 	STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds | ||||
| 	STATUS_CACHE_SIZE      = 25000 | ||||
| 	STATUS_CHANNEL_TIMEOUT = 20000  // 20 seconds | ||||
| 	STATUS_MIN_UPDATE_TIME = 120000 // 2 minutes | ||||
| ) | ||||
|  | ||||
| type Status struct { | ||||
| @@ -21,7 +22,7 @@ type Status struct { | ||||
| 	Status         string `json:"status"` | ||||
| 	Manual         bool   `json:"manual"` | ||||
| 	LastActivityAt int64  `json:"last_activity_at"` | ||||
| 	ActiveChannel  string `json:"active_channel"` | ||||
| 	ActiveChannel  string `json:"active_channel" db:"-"` | ||||
| } | ||||
|  | ||||
| func (o *Status) ToJson() string { | ||||
| @@ -43,3 +44,14 @@ func StatusFromJson(data io.Reader) *Status { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func StatusMapToInterfaceMap(statusMap map[string]*Status) map[string]interface{} { | ||||
| 	interfaceMap := map[string]interface{}{} | ||||
| 	for _, s := range statusMap { | ||||
| 		// Omitted statues mean offline | ||||
| 		if s.Status != STATUS_OFFLINE { | ||||
| 			interfaceMap[s.UserId] = s.Status | ||||
| 		} | ||||
| 	} | ||||
| 	return interfaceMap | ||||
| } | ||||
|   | ||||
							
								
								
									
										11
									
								
								vendor/github.com/mattermost/platform/model/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								vendor/github.com/mattermost/platform/model/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -24,6 +24,7 @@ type Team struct { | ||||
| 	DeleteAt        int64  `json:"delete_at"` | ||||
| 	DisplayName     string `json:"display_name"` | ||||
| 	Name            string `json:"name"` | ||||
| 	Description     string `json:"description"` | ||||
| 	Email           string `json:"email"` | ||||
| 	Type            string `json:"type"` | ||||
| 	CompanyName     string `json:"company_name"` | ||||
| @@ -100,7 +101,7 @@ func (o *Team) Etag() string { | ||||
| 	return Etag(o.Id, o.UpdateAt) | ||||
| } | ||||
|  | ||||
| func (o *Team) IsValid(restrictTeamNames bool) *AppError { | ||||
| func (o *Team) IsValid() *AppError { | ||||
|  | ||||
| 	if len(o.Id) != 26 { | ||||
| 		return NewLocAppError("Team.IsValid", "model.team.is_valid.id.app_error", nil, "") | ||||
| @@ -130,7 +131,11 @@ func (o *Team) IsValid(restrictTeamNames bool) *AppError { | ||||
| 		return NewLocAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if restrictTeamNames && IsReservedTeamName(o.Name) { | ||||
| 	if len(o.Description) > 255 { | ||||
| 		return NewLocAppError("Team.IsValid", "model.team.is_valid.description.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	if IsReservedTeamName(o.Name) { | ||||
| 		return NewLocAppError("Team.IsValid", "model.team.is_valid.reserved.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| @@ -188,7 +193,7 @@ func IsValidTeamName(s string) bool { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if len(s) <= 3 { | ||||
| 	if len(s) <= 1 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										72
									
								
								vendor/github.com/mattermost/platform/model/team_member.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										72
									
								
								vendor/github.com/mattermost/platform/model/team_member.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -9,10 +9,6 @@ import ( | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	ROLE_TEAM_ADMIN = "admin" | ||||
| ) | ||||
|  | ||||
| type TeamMember struct { | ||||
| 	TeamId   string `json:"team_id"` | ||||
| 	UserId   string `json:"user_id"` | ||||
| @@ -20,6 +16,12 @@ type TeamMember struct { | ||||
| 	DeleteAt int64  `json:"delete_at"` | ||||
| } | ||||
|  | ||||
| type TeamUnread struct { | ||||
| 	TeamId       string `json:"team_id"` | ||||
| 	MsgCount     int64  `json:"msg_count"` | ||||
| 	MentionCount int64  `json:"mention_count"` | ||||
| } | ||||
|  | ||||
| func (o *TeamMember) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| @@ -59,46 +61,23 @@ func TeamMembersFromJson(data io.Reader) []*TeamMember { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func IsValidTeamRoles(teamRoles string) bool { | ||||
|  | ||||
| 	roles := strings.Split(teamRoles, " ") | ||||
|  | ||||
| 	for _, r := range roles { | ||||
| 		if !isValidTeamRole(r) { | ||||
| 			return false | ||||
| 		} | ||||
| func TeamsUnreadToJson(o []*TeamUnread) string { | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		return "[]" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func isValidTeamRole(role string) bool { | ||||
| 	if role == "" { | ||||
| 		return true | ||||
| func TeamsUnreadFromJson(data io.Reader) []*TeamUnread { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o []*TeamUnread | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if role == ROLE_TEAM_ADMIN { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func IsInTeamRole(teamRoles string, inRole string) bool { | ||||
| 	roles := strings.Split(teamRoles, " ") | ||||
|  | ||||
| 	for _, r := range roles { | ||||
| 		if r == inRole { | ||||
| 			return true | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (o *TeamMember) IsTeamAdmin() bool { | ||||
| 	return IsInTeamRole(o.Roles, ROLE_TEAM_ADMIN) | ||||
| } | ||||
|  | ||||
| func (o *TeamMember) IsValid() *AppError { | ||||
| @@ -111,11 +90,12 @@ func (o *TeamMember) IsValid() *AppError { | ||||
| 		return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.user_id.app_error", nil, "") | ||||
| 	} | ||||
|  | ||||
| 	for _, role := range strings.Split(o.Roles, " ") { | ||||
| 		if !(role == "" || role == ROLE_TEAM_ADMIN) { | ||||
| 			return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.role.app_error", nil, "role="+role) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (o *TeamMember) PreUpdate() { | ||||
| } | ||||
|  | ||||
| func (o *TeamMember) GetRoles() []string { | ||||
| 	return strings.Fields(o.Roles) | ||||
| } | ||||
|   | ||||
							
								
								
									
										35
									
								
								vendor/github.com/mattermost/platform/model/team_stats.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								vendor/github.com/mattermost/platform/model/team_stats.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type TeamStats struct { | ||||
| 	TeamId            string `json:"team_id"` | ||||
| 	TotalMemberCount  int64  `json:"total_member_count"` | ||||
| 	ActiveMemberCount int64  `json:"active_member_count"` | ||||
| } | ||||
|  | ||||
| func (o *TeamStats) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TeamStatsFromJson(data io.Reader) *TeamStats { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o TeamStats | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										59
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										59
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -15,7 +15,6 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	ROLE_SYSTEM_ADMIN          = "system_admin" | ||||
| 	USER_NOTIFY_ALL            = "all" | ||||
| 	USER_NOTIFY_MENTION        = "mention" | ||||
| 	USER_NOTIFY_NONE           = "none" | ||||
| @@ -38,6 +37,7 @@ type User struct { | ||||
| 	Nickname           string    `json:"nickname"` | ||||
| 	FirstName          string    `json:"first_name"` | ||||
| 	LastName           string    `json:"last_name"` | ||||
| 	Position           string    `json:"position"` | ||||
| 	Roles              string    `json:"roles"` | ||||
| 	AllowMarketing     bool      `json:"allow_marketing,omitempty"` | ||||
| 	Props              StringMap `json:"props,omitempty"` | ||||
| @@ -79,6 +79,10 @@ func (u *User) IsValid() *AppError { | ||||
| 		return NewLocAppError("User.IsValid", "model.user.is_valid.nickname.app_error", nil, "user_id="+u.Id) | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(u.Position) > 35 { | ||||
| 		return NewLocAppError("User.IsValid", "model.user.is_valid.position.app_error", nil, "user_id="+u.Id) | ||||
| 	} | ||||
|  | ||||
| 	if utf8.RuneCountInString(u.FirstName) > 64 { | ||||
| 		return NewLocAppError("User.IsValid", "model.user.is_valid.first_name.app_error", nil, "user_id="+u.Id) | ||||
| 	} | ||||
| @@ -233,14 +237,15 @@ func (u *User) Sanitize(options map[string]bool) { | ||||
| 	if len(options) != 0 && !options["passwordupdate"] { | ||||
| 		u.LastPasswordUpdate = 0 | ||||
| 	} | ||||
| 	if len(options) != 0 && !options["authservice"] { | ||||
| 		u.AuthService = "" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (u *User) ClearNonProfileFields() { | ||||
| 	u.Password = "" | ||||
| 	u.AuthData = new(string) | ||||
| 	*u.AuthData = "" | ||||
| 	u.AuthService = "" | ||||
| 	u.MfaActive = false | ||||
| 	u.MfaSecret = "" | ||||
| 	u.EmailVerified = false | ||||
| 	u.AllowMarketing = false | ||||
| @@ -319,9 +324,17 @@ func (u *User) GetDisplayNameForPreference(nameFormat string) string { | ||||
| 	return displayName | ||||
| } | ||||
|  | ||||
| func (u *User) GetRoles() []string { | ||||
| 	return strings.Fields(u.Roles) | ||||
| } | ||||
|  | ||||
| func (u *User) GetRawRoles() string { | ||||
| 	return u.Roles | ||||
| } | ||||
|  | ||||
| func IsValidUserRoles(userRoles string) bool { | ||||
|  | ||||
| 	roles := strings.Split(userRoles, " ") | ||||
| 	roles := strings.Fields(userRoles) | ||||
|  | ||||
| 	for _, r := range roles { | ||||
| 		if !isValidRole(r) { | ||||
| @@ -329,19 +342,17 @@ func IsValidUserRoles(userRoles string) bool { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Exclude just the system_admin role explicitly to prevent mistakes | ||||
| 	if len(roles) == 1 && roles[0] == "system_admin" { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func isValidRole(role string) bool { | ||||
| 	if role == "" { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	if role == ROLE_SYSTEM_ADMIN { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| func isValidRole(roleId string) bool { | ||||
| 	_, ok := BuiltInRoles[roleId] | ||||
| 	return ok | ||||
| } | ||||
|  | ||||
| // Make sure you acually want to use this function. In context.go there are functions to check permissions | ||||
| @@ -411,6 +422,26 @@ func UserMapFromJson(data io.Reader) map[string]*User { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func UserListToJson(u []*User) string { | ||||
| 	b, err := json.Marshal(u) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func UserListFromJson(data io.Reader) []*User { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var users []*User | ||||
| 	err := decoder.Decode(&users) | ||||
| 	if err == nil { | ||||
| 		return users | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // HashPassword generates a hash using the bcrypt.GenerateFromPassword | ||||
| func HashPassword(password string) string { | ||||
| 	hash, err := bcrypt.GenerateFromPassword([]byte(password), 10) | ||||
|   | ||||
							
								
								
									
										58
									
								
								vendor/github.com/mattermost/platform/model/user_autocomplete.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								vendor/github.com/mattermost/platform/model/user_autocomplete.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type UserAutocompleteInChannel struct { | ||||
| 	InChannel    []*User `json:"in_channel"` | ||||
| 	OutOfChannel []*User `json:"out_of_channel"` | ||||
| } | ||||
|  | ||||
| type UserAutocompleteInTeam struct { | ||||
| 	InTeam []*User `json:"in_team"` | ||||
| } | ||||
|  | ||||
| func (o *UserAutocompleteInChannel) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func UserAutocompleteInChannelFromJson(data io.Reader) *UserAutocompleteInChannel { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o UserAutocompleteInChannel | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *UserAutocompleteInTeam) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func UserAutocompleteInTeamFromJson(data io.Reader) *UserAutocompleteInTeam { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o UserAutocompleteInTeam | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										39
									
								
								vendor/github.com/mattermost/platform/model/user_search.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								vendor/github.com/mattermost/platform/model/user_search.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| // Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type UserSearch struct { | ||||
| 	Term           string `json:"term"` | ||||
| 	TeamId         string `json:"team_id"` | ||||
| 	InChannelId    string `json:"in_channel_id"` | ||||
| 	NotInChannelId string `json:"not_in_channel_id"` | ||||
| 	AllowInactive  bool   `json:"allow_inactive"` | ||||
| } | ||||
|  | ||||
| // ToJson convert a User to a json string | ||||
| func (u *UserSearch) ToJson() string { | ||||
| 	b, err := json.Marshal(u) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // UserSearchFromJson will decode the input and return a User | ||||
| func UserSearchFromJson(data io.Reader) *UserSearch { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var us UserSearch | ||||
| 	err := decoder.Decode(&us) | ||||
| 	if err == nil { | ||||
| 		return &us | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										103
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										103
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -10,6 +10,7 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net/mail" | ||||
| 	"net/url" | ||||
| 	"regexp" | ||||
| @@ -74,13 +75,21 @@ func (er *AppError) ToJson() string { | ||||
|  | ||||
| // AppErrorFromJson will decode the input and return an AppError | ||||
| func AppErrorFromJson(data io.Reader) *AppError { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	str := "" | ||||
| 	bytes, rerr := ioutil.ReadAll(data) | ||||
| 	if rerr != nil { | ||||
| 		str = rerr.Error() | ||||
| 	} else { | ||||
| 		str = string(bytes) | ||||
| 	} | ||||
|  | ||||
| 	decoder := json.NewDecoder(strings.NewReader(str)) | ||||
| 	var er AppError | ||||
| 	err := decoder.Decode(&er) | ||||
| 	if err == nil { | ||||
| 		return &er | ||||
| 	} else { | ||||
| 		return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, err.Error()) | ||||
| 		return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -166,6 +175,23 @@ func ArrayFromJson(data io.Reader) []string { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ArrayFromInterface(data interface{}) []string { | ||||
| 	stringArray := []string{} | ||||
|  | ||||
| 	dataArray, ok := data.([]interface{}) | ||||
| 	if !ok { | ||||
| 		return stringArray | ||||
| 	} | ||||
|  | ||||
| 	for _, v := range dataArray { | ||||
| 		if str, ok := v.(string); ok { | ||||
| 			stringArray = append(stringArray, str) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return stringArray | ||||
| } | ||||
|  | ||||
| func StringInterfaceToJson(objmap map[string]interface{}) string { | ||||
| 	if b, err := json.Marshal(objmap); err != nil { | ||||
| 		return "" | ||||
| @@ -227,58 +253,15 @@ func IsValidEmail(email string) bool { | ||||
| } | ||||
|  | ||||
| var reservedName = []string{ | ||||
| 	"www", | ||||
| 	"web", | ||||
| 	"signup", | ||||
| 	"login", | ||||
| 	"admin", | ||||
| 	"support", | ||||
| 	"notify", | ||||
| 	"test", | ||||
| 	"demo", | ||||
| 	"mail", | ||||
| 	"team", | ||||
| 	"channel", | ||||
| 	"internal", | ||||
| 	"localhost", | ||||
| 	"dockerhost", | ||||
| 	"stag", | ||||
| 	"post", | ||||
| 	"cluster", | ||||
| 	"api", | ||||
| 	"oauth", | ||||
| } | ||||
|  | ||||
| var wwwStart = regexp.MustCompile(`^www`) | ||||
| var betaStart = regexp.MustCompile(`^beta`) | ||||
| var ciStart = regexp.MustCompile(`^ci`) | ||||
|  | ||||
| func GetSubDomain(s string) (string, string) { | ||||
| 	s = strings.Replace(s, "http://", "", 1) | ||||
| 	s = strings.Replace(s, "https://", "", 1) | ||||
|  | ||||
| 	match := wwwStart.MatchString(s) | ||||
| 	if match { | ||||
| 		return "", "" | ||||
| 	} | ||||
|  | ||||
| 	match = betaStart.MatchString(s) | ||||
| 	if match { | ||||
| 		return "", "" | ||||
| 	} | ||||
|  | ||||
| 	match = ciStart.MatchString(s) | ||||
| 	if match { | ||||
| 		return "", "" | ||||
| 	} | ||||
|  | ||||
| 	parts := strings.Split(s, ".") | ||||
|  | ||||
| 	if len(parts) != 3 { | ||||
| 		return "", "" | ||||
| 	} | ||||
|  | ||||
| 	return parts[0], parts[1] | ||||
| } | ||||
|  | ||||
| func IsValidChannelIdentifier(s string) bool { | ||||
|  | ||||
| 	if !IsValidAlphaNum(s, true) { | ||||
| @@ -321,7 +304,7 @@ func Etag(parts ...interface{}) string { | ||||
| 	return etag | ||||
| } | ||||
|  | ||||
| var validHashtag = regexp.MustCompile(`^(#[A-Za-zäöüÄÖÜß]+[A-Za-z0-9äöüÄÖÜß_\-]*[A-Za-z0-9äöüÄÖÜß])$`) | ||||
| var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`) | ||||
| var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) | ||||
| var hashtagStart = regexp.MustCompile(`^#{2,}`) | ||||
| var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) | ||||
| @@ -413,6 +396,18 @@ func IsValidHttpsUrl(rawUrl string) bool { | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func IsValidTurnOrStunServer(rawUri string) bool { | ||||
| 	if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if _, err := url.ParseRequestURI(rawUri); err != nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func IsSafeLink(link *string) bool { | ||||
| 	if link != nil { | ||||
| 		if IsValidHttpUrl(*link) { | ||||
| @@ -426,3 +421,15 @@ func IsSafeLink(link *string) bool { | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func IsValidWebsocketUrl(rawUrl string) bool { | ||||
| 	if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if _, err := url.ParseRequestURI(rawUrl); err != nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -13,6 +13,8 @@ import ( | ||||
| // It should be maitained in chronological order with most current | ||||
| // release at the front of the list. | ||||
| var versions = []string{ | ||||
| 	"3.6.0", | ||||
| 	"3.5.0", | ||||
| 	"3.4.0", | ||||
| 	"3.3.0", | ||||
| 	"3.2.0", | ||||
|   | ||||
							
								
								
									
										21
									
								
								vendor/github.com/mattermost/platform/model/webrtc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/mattermost/platform/model/webrtc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| package model | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type GatewayResponse struct { | ||||
| 	Status string `json:"janus"` | ||||
| } | ||||
|  | ||||
| func GatewayResponseFromJson(data io.Reader) *GatewayResponse { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o GatewayResponse | ||||
| 	err := decoder.Decode(&o) | ||||
| 	if err == nil { | ||||
| 		return &o | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										45
									
								
								vendor/github.com/mattermost/platform/model/websocket_client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										45
									
								
								vendor/github.com/mattermost/platform/model/websocket_client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -6,7 +6,6 @@ package model | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"github.com/gorilla/websocket" | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| type WebSocketClient struct { | ||||
| @@ -17,19 +16,18 @@ type WebSocketClient struct { | ||||
| 	Sequence        int64           // The ever-incrementing sequence attached to each WebSocket action | ||||
| 	EventChannel    chan *WebSocketEvent | ||||
| 	ResponseChannel chan *WebSocketResponse | ||||
| 	ListenError     *AppError | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| 	conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", nil) | ||||
| 	if err != nil { | ||||
| 		return nil, NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return &WebSocketClient{ | ||||
| 	client := &WebSocketClient{ | ||||
| 		url, | ||||
| 		url + API_URL_SUFFIX, | ||||
| 		conn, | ||||
| @@ -37,19 +35,26 @@ func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) { | ||||
| 		1, | ||||
| 		make(chan *WebSocketEvent, 100), | ||||
| 		make(chan *WebSocketResponse, 100), | ||||
| 	}, nil | ||||
| 		nil, | ||||
| 	} | ||||
|  | ||||
| 	client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken}) | ||||
|  | ||||
| 	return client, 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) | ||||
| 	wsc.Conn, _, err = websocket.DefaultDialer.Dial(wsc.ApiUrl+"/users/websocket", nil) | ||||
| 	if err != nil { | ||||
| 		return NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	wsc.EventChannel = make(chan *WebSocketEvent, 100) | ||||
| 	wsc.ResponseChannel = make(chan *WebSocketResponse, 100) | ||||
|  | ||||
| 	wsc.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": wsc.AuthToken}) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -59,10 +64,20 @@ func (wsc *WebSocketClient) Close() { | ||||
|  | ||||
| func (wsc *WebSocketClient) Listen() { | ||||
| 	go func() { | ||||
| 		defer func() { | ||||
| 			wsc.Conn.Close() | ||||
| 			close(wsc.EventChannel) | ||||
| 			close(wsc.ResponseChannel) | ||||
| 		}() | ||||
|  | ||||
| 		for { | ||||
| 			var rawMsg json.RawMessage | ||||
| 			var err error | ||||
| 			if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil { | ||||
| 				if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { | ||||
| 					wsc.ListenError = NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error()) | ||||
| 				} | ||||
|  | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| @@ -77,6 +92,7 @@ func (wsc *WebSocketClient) Listen() { | ||||
| 				wsc.ResponseChannel <- &response | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
| @@ -107,3 +123,12 @@ func (wsc *WebSocketClient) UserTyping(channelId, parentId string) { | ||||
| func (wsc *WebSocketClient) GetStatuses() { | ||||
| 	wsc.SendMessage("get_statuses", nil) | ||||
| } | ||||
|  | ||||
| // GetStatusesByIds will fetch certain user statuses based on ids and return | ||||
| // a map of string statuses using user id as the key | ||||
| func (wsc *WebSocketClient) GetStatusesByIds(userIds []string) { | ||||
| 	data := map[string]interface{}{ | ||||
| 		"user_ids": userIds, | ||||
| 	} | ||||
| 	wsc.SendMessage("get_statuses_by_ids", data) | ||||
| } | ||||
|   | ||||
							
								
								
									
										72
									
								
								vendor/github.com/mattermost/platform/model/websocket_message.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										72
									
								
								vendor/github.com/mattermost/platform/model/websocket_message.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -18,6 +18,7 @@ const ( | ||||
| 	WEBSOCKET_EVENT_DIRECT_ADDED       = "direct_added" | ||||
| 	WEBSOCKET_EVENT_NEW_USER           = "new_user" | ||||
| 	WEBSOCKET_EVENT_LEAVE_TEAM         = "leave_team" | ||||
| 	WEBSOCKET_EVENT_UPDATE_TEAM        = "update_team" | ||||
| 	WEBSOCKET_EVENT_USER_ADDED         = "user_added" | ||||
| 	WEBSOCKET_EVENT_USER_UPDATED       = "user_updated" | ||||
| 	WEBSOCKET_EVENT_USER_REMOVED       = "user_removed" | ||||
| @@ -25,33 +26,64 @@ const ( | ||||
| 	WEBSOCKET_EVENT_EPHEMERAL_MESSAGE  = "ephemeral_message" | ||||
| 	WEBSOCKET_EVENT_STATUS_CHANGE      = "status_change" | ||||
| 	WEBSOCKET_EVENT_HELLO              = "hello" | ||||
| 	WEBSOCKET_EVENT_WEBRTC             = "webrtc" | ||||
| 	WEBSOCKET_AUTHENTICATION_CHALLENGE = "authentication_challenge" | ||||
| 	WEBSOCKET_EVENT_REACTION_ADDED     = "reaction_added" | ||||
| 	WEBSOCKET_EVENT_REACTION_REMOVED   = "reaction_removed" | ||||
| ) | ||||
|  | ||||
| type WebSocketMessage interface { | ||||
| 	ToJson() string | ||||
| 	IsValid() bool | ||||
| 	DoPreComputeJson() | ||||
| 	GetPreComputeJson() []byte | ||||
| 	EventType() string | ||||
| } | ||||
|  | ||||
| type WebsocketBroadcast struct { | ||||
| 	OmitUsers map[string]bool `json:"omit_users"` // broadcast is omitted for users listed here | ||||
| 	UserId    string          `json:"user_id"`    // broadcast only occurs for this user | ||||
| 	ChannelId string          `json:"channel_id"` // broadcast only occurs for users in this channel | ||||
| 	TeamId    string          `json:"team_id"`    // broadcast only occurs for users in this team | ||||
| } | ||||
|  | ||||
| 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"` | ||||
| 	Event          string                 `json:"event"` | ||||
| 	Data           map[string]interface{} `json:"data"` | ||||
| 	Broadcast      *WebsocketBroadcast    `json:"broadcast"` | ||||
| 	PreComputeJson []byte                 `json:"-"` | ||||
| } | ||||
|  | ||||
| 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 NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[string]bool) *WebSocketEvent { | ||||
| 	return &WebSocketEvent{Event: event, Data: make(map[string]interface{}), | ||||
| 		Broadcast: &WebsocketBroadcast{TeamId: teamId, ChannelId: channelId, UserId: userId, OmitUsers: omitUsers}} | ||||
| } | ||||
|  | ||||
| func (o *WebSocketEvent) IsValid() bool { | ||||
| 	return o.Event != "" | ||||
| } | ||||
|  | ||||
| func (o *WebSocketEvent) EventType() string { | ||||
| 	return o.Event | ||||
| } | ||||
|  | ||||
| func (o *WebSocketEvent) DoPreComputeJson() { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		o.PreComputeJson = []byte("") | ||||
| 	} else { | ||||
| 		o.PreComputeJson = b | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *WebSocketEvent) GetPreComputeJson() []byte { | ||||
| 	return o.PreComputeJson | ||||
| } | ||||
|  | ||||
| func (o *WebSocketEvent) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| @@ -73,10 +105,11 @@ func WebSocketEventFromJson(data io.Reader) *WebSocketEvent { | ||||
| } | ||||
|  | ||||
| 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"` | ||||
| 	Status         string                 `json:"status"` | ||||
| 	SeqReply       int64                  `json:"seq_reply,omitempty"` | ||||
| 	Data           map[string]interface{} `json:"data,omitempty"` | ||||
| 	Error          *AppError              `json:"error,omitempty"` | ||||
| 	PreComputeJson []byte                 `json:"-"` | ||||
| } | ||||
|  | ||||
| func (m *WebSocketResponse) Add(key string, value interface{}) { | ||||
| @@ -95,6 +128,10 @@ func (o *WebSocketResponse) IsValid() bool { | ||||
| 	return o.Status != "" | ||||
| } | ||||
|  | ||||
| func (o *WebSocketResponse) EventType() string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (o *WebSocketResponse) ToJson() string { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| @@ -104,6 +141,19 @@ func (o *WebSocketResponse) ToJson() string { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *WebSocketResponse) DoPreComputeJson() { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		o.PreComputeJson = []byte("") | ||||
| 	} else { | ||||
| 		o.PreComputeJson = b | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *WebSocketResponse) GetPreComputeJson() []byte { | ||||
| 	return o.PreComputeJson | ||||
| } | ||||
|  | ||||
| func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse { | ||||
| 	decoder := json.NewDecoder(data) | ||||
| 	var o WebSocketResponse | ||||
|   | ||||
							
								
								
									
										67
									
								
								vendor/github.com/mattn/go-xmpp/xmpp.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										67
									
								
								vendor/github.com/mattn/go-xmpp/xmpp.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -536,12 +536,13 @@ func (c *Client) IsEncrypted() bool { | ||||
|  | ||||
| // 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 | ||||
| 	Remote    string | ||||
| 	Type      string | ||||
| 	Text      string | ||||
| 	Roster    Roster | ||||
| 	Other     []string | ||||
| 	OtherElem []XMLElement | ||||
| 	Stamp     time.Time | ||||
| } | ||||
|  | ||||
| type Roster []Contact | ||||
| @@ -584,11 +585,12 @@ func (c *Client) Recv() (stanza interface{}, err error) { | ||||
| 				v.Delay.Stamp, | ||||
| 			) | ||||
| 			chat := Chat{ | ||||
| 				Remote: v.From, | ||||
| 				Type:   v.Type, | ||||
| 				Text:   v.Body, | ||||
| 				Other:  v.Other, | ||||
| 				Stamp:  stamp, | ||||
| 				Remote:    v.From, | ||||
| 				Type:      v.Type, | ||||
| 				Text:      v.Body, | ||||
| 				Other:     v.OtherStrings(), | ||||
| 				OtherElem: v.Other, | ||||
| 				Stamp:     stamp, | ||||
| 			} | ||||
| 			return chat, nil | ||||
| 		case *clientQuery: | ||||
| @@ -600,6 +602,12 @@ func (c *Client) Recv() (stanza interface{}, err error) { | ||||
| 		case *clientPresence: | ||||
| 			return Presence{v.From, v.To, v.Type, v.Show, v.Status}, nil | ||||
| 		case *clientIQ: | ||||
| 			if bytes.Equal(v.Query, []byte(`<ping xmlns='urn:xmpp:ping'/>`)) { | ||||
| 				err := c.SendResultPing(v.ID, v.From) | ||||
| 				if err != nil { | ||||
| 					return Chat{}, err | ||||
| 				} | ||||
| 			} | ||||
| 			return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil | ||||
| 		} | ||||
| 	} | ||||
| @@ -714,11 +722,46 @@ type clientMessage struct { | ||||
| 	Thread  string `xml:"thread"` | ||||
|  | ||||
| 	// Any hasn't matched element | ||||
| 	Other []string `xml:",any"` | ||||
| 	Other []XMLElement `xml:",any"` | ||||
|  | ||||
| 	Delay Delay `xml:"delay"` | ||||
| } | ||||
|  | ||||
| func (m *clientMessage) OtherStrings() []string { | ||||
| 	a := make([]string, len(m.Other)) | ||||
| 	for i, e := range m.Other { | ||||
| 		a[i] = e.String() | ||||
| 	} | ||||
| 	return a | ||||
| } | ||||
|  | ||||
| type XMLElement struct { | ||||
| 	XMLName  xml.Name | ||||
| 	InnerXML string `xml:",innerxml"` | ||||
| } | ||||
|  | ||||
| func (e *XMLElement) String() string { | ||||
| 	r := bytes.NewReader([]byte(e.InnerXML)) | ||||
| 	d := xml.NewDecoder(r) | ||||
| 	var buf bytes.Buffer | ||||
| 	for { | ||||
| 		tok, err := d.Token() | ||||
| 		if err != nil { | ||||
| 			break | ||||
| 		} | ||||
| 		switch v := tok.(type) { | ||||
| 		case xml.StartElement: | ||||
| 			err = d.Skip() | ||||
| 		case xml.CharData: | ||||
| 			_, err = buf.Write(v) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	return buf.String() | ||||
| } | ||||
|  | ||||
| type Delay struct { | ||||
| 	Stamp string `xml:"stamp,attr"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										6
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_ping.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/mattn/go-xmpp/xmpp_ping.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -25,3 +25,9 @@ func (c *Client) PingS2S(fromServer, toServer string) error { | ||||
| 		xmlEscape(fromServer), xmlEscape(toServer)) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (c *Client) SendResultPing(id, toServer string) error { | ||||
| 	_, err := fmt.Fprintf(c.conn, "<iq type='result' to='%s' id='%s'/>", | ||||
| 		xmlEscape(toServer), xmlEscape(id)) | ||||
| 	return err | ||||
| } | ||||
|   | ||||
							
								
								
									
										29
									
								
								vendor/github.com/russross/blackfriday/LICENSE.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/russross/blackfriday/LICENSE.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| Blackfriday is distributed under the Simplified BSD License: | ||||
|  | ||||
| > Copyright © 2011 Russ Ross | ||||
| > 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. | ||||
							
								
								
									
										1430
									
								
								vendor/github.com/russross/blackfriday/block.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1430
									
								
								vendor/github.com/russross/blackfriday/block.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										949
									
								
								vendor/github.com/russross/blackfriday/html.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										949
									
								
								vendor/github.com/russross/blackfriday/html.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,949 @@ | ||||
| // | ||||
| // Blackfriday Markdown Processor | ||||
| // Available at http://github.com/russross/blackfriday | ||||
| // | ||||
| // Copyright © 2011 Russ Ross <russ@russross.com>. | ||||
| // Distributed under the Simplified BSD License. | ||||
| // See README.md for details. | ||||
| // | ||||
|  | ||||
| // | ||||
| // | ||||
| // HTML rendering backend | ||||
| // | ||||
| // | ||||
|  | ||||
| package blackfriday | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // Html renderer configuration options. | ||||
| const ( | ||||
| 	HTML_SKIP_HTML                 = 1 << iota // skip preformatted HTML blocks | ||||
| 	HTML_SKIP_STYLE                            // skip embedded <style> elements | ||||
| 	HTML_SKIP_IMAGES                           // skip embedded images | ||||
| 	HTML_SKIP_LINKS                            // skip all links | ||||
| 	HTML_SAFELINK                              // only link to trusted protocols | ||||
| 	HTML_NOFOLLOW_LINKS                        // only link with rel="nofollow" | ||||
| 	HTML_NOREFERRER_LINKS                      // only link with rel="noreferrer" | ||||
| 	HTML_HREF_TARGET_BLANK                     // add a blank target | ||||
| 	HTML_TOC                                   // generate a table of contents | ||||
| 	HTML_OMIT_CONTENTS                         // skip the main contents (for a standalone table of contents) | ||||
| 	HTML_COMPLETE_PAGE                         // generate a complete HTML page | ||||
| 	HTML_USE_XHTML                             // generate XHTML output instead of HTML | ||||
| 	HTML_USE_SMARTYPANTS                       // enable smart punctuation substitutions | ||||
| 	HTML_SMARTYPANTS_FRACTIONS                 // enable smart fractions (with HTML_USE_SMARTYPANTS) | ||||
| 	HTML_SMARTYPANTS_DASHES                    // enable smart dashes (with HTML_USE_SMARTYPANTS) | ||||
| 	HTML_SMARTYPANTS_LATEX_DASHES              // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES) | ||||
| 	HTML_SMARTYPANTS_ANGLED_QUOTES             // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering | ||||
| 	HTML_FOOTNOTE_RETURN_LINKS                 // generate a link at the end of a footnote to return to the source | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	alignments = []string{ | ||||
| 		"left", | ||||
| 		"right", | ||||
| 		"center", | ||||
| 	} | ||||
|  | ||||
| 	// TODO: improve this regexp to catch all possible entities: | ||||
| 	htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`) | ||||
| ) | ||||
|  | ||||
| type HtmlRendererParameters struct { | ||||
| 	// Prepend this text to each relative URL. | ||||
| 	AbsolutePrefix string | ||||
| 	// Add this text to each footnote anchor, to ensure uniqueness. | ||||
| 	FootnoteAnchorPrefix string | ||||
| 	// Show this text inside the <a> tag for a footnote return link, if the | ||||
| 	// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string | ||||
| 	// <sup>[return]</sup> is used. | ||||
| 	FootnoteReturnLinkContents string | ||||
| 	// If set, add this text to the front of each Header ID, to ensure | ||||
| 	// uniqueness. | ||||
| 	HeaderIDPrefix string | ||||
| 	// If set, add this text to the back of each Header ID, to ensure uniqueness. | ||||
| 	HeaderIDSuffix string | ||||
| } | ||||
|  | ||||
| // Html is a type that implements the Renderer interface for HTML output. | ||||
| // | ||||
| // Do not create this directly, instead use the HtmlRenderer function. | ||||
| type Html struct { | ||||
| 	flags    int    // HTML_* options | ||||
| 	closeTag string // how to end singleton tags: either " />" or ">" | ||||
| 	title    string // document title | ||||
| 	css      string // optional css file url (used with HTML_COMPLETE_PAGE) | ||||
|  | ||||
| 	parameters HtmlRendererParameters | ||||
|  | ||||
| 	// table of contents data | ||||
| 	tocMarker    int | ||||
| 	headerCount  int | ||||
| 	currentLevel int | ||||
| 	toc          *bytes.Buffer | ||||
|  | ||||
| 	// Track header IDs to prevent ID collision in a single generation. | ||||
| 	headerIDs map[string]int | ||||
|  | ||||
| 	smartypants *smartypantsRenderer | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	xhtmlClose = " />" | ||||
| 	htmlClose  = ">" | ||||
| ) | ||||
|  | ||||
| // HtmlRenderer creates and configures an Html object, which | ||||
| // satisfies the Renderer interface. | ||||
| // | ||||
| // flags is a set of HTML_* options ORed together. | ||||
| // title is the title of the document, and css is a URL for the document's | ||||
| // stylesheet. | ||||
| // title and css are only used when HTML_COMPLETE_PAGE is selected. | ||||
| func HtmlRenderer(flags int, title string, css string) Renderer { | ||||
| 	return HtmlRendererWithParameters(flags, title, css, HtmlRendererParameters{}) | ||||
| } | ||||
|  | ||||
| func HtmlRendererWithParameters(flags int, title string, | ||||
| 	css string, renderParameters HtmlRendererParameters) Renderer { | ||||
| 	// configure the rendering engine | ||||
| 	closeTag := htmlClose | ||||
| 	if flags&HTML_USE_XHTML != 0 { | ||||
| 		closeTag = xhtmlClose | ||||
| 	} | ||||
|  | ||||
| 	if renderParameters.FootnoteReturnLinkContents == "" { | ||||
| 		renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>` | ||||
| 	} | ||||
|  | ||||
| 	return &Html{ | ||||
| 		flags:      flags, | ||||
| 		closeTag:   closeTag, | ||||
| 		title:      title, | ||||
| 		css:        css, | ||||
| 		parameters: renderParameters, | ||||
|  | ||||
| 		headerCount:  0, | ||||
| 		currentLevel: 0, | ||||
| 		toc:          new(bytes.Buffer), | ||||
|  | ||||
| 		headerIDs: make(map[string]int), | ||||
|  | ||||
| 		smartypants: smartypants(flags), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Using if statements is a bit faster than a switch statement. As the compiler | ||||
| // improves, this should be unnecessary this is only worthwhile because | ||||
| // attrEscape is the single largest CPU user in normal use. | ||||
| // Also tried using map, but that gave a ~3x slowdown. | ||||
| func escapeSingleChar(char byte) (string, bool) { | ||||
| 	if char == '"' { | ||||
| 		return """, true | ||||
| 	} | ||||
| 	if char == '&' { | ||||
| 		return "&", true | ||||
| 	} | ||||
| 	if char == '<' { | ||||
| 		return "<", true | ||||
| 	} | ||||
| 	if char == '>' { | ||||
| 		return ">", true | ||||
| 	} | ||||
| 	return "", false | ||||
| } | ||||
|  | ||||
| func attrEscape(out *bytes.Buffer, src []byte) { | ||||
| 	org := 0 | ||||
| 	for i, ch := range src { | ||||
| 		if entity, ok := escapeSingleChar(ch); ok { | ||||
| 			if i > org { | ||||
| 				// copy all the normal characters since the last escape | ||||
| 				out.Write(src[org:i]) | ||||
| 			} | ||||
| 			org = i + 1 | ||||
| 			out.WriteString(entity) | ||||
| 		} | ||||
| 	} | ||||
| 	if org < len(src) { | ||||
| 		out.Write(src[org:]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func entityEscapeWithSkip(out *bytes.Buffer, src []byte, skipRanges [][]int) { | ||||
| 	end := 0 | ||||
| 	for _, rang := range skipRanges { | ||||
| 		attrEscape(out, src[end:rang[0]]) | ||||
| 		out.Write(src[rang[0]:rang[1]]) | ||||
| 		end = rang[1] | ||||
| 	} | ||||
| 	attrEscape(out, src[end:]) | ||||
| } | ||||
|  | ||||
| func (options *Html) GetFlags() int { | ||||
| 	return options.flags | ||||
| } | ||||
|  | ||||
| func (options *Html) TitleBlock(out *bytes.Buffer, text []byte) { | ||||
| 	text = bytes.TrimPrefix(text, []byte("% ")) | ||||
| 	text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1) | ||||
| 	out.WriteString("<h1 class=\"title\">") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("\n</h1>") | ||||
| } | ||||
|  | ||||
| func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id string) { | ||||
| 	marker := out.Len() | ||||
| 	doubleSpace(out) | ||||
|  | ||||
| 	if id == "" && options.flags&HTML_TOC != 0 { | ||||
| 		id = fmt.Sprintf("toc_%d", options.headerCount) | ||||
| 	} | ||||
|  | ||||
| 	if id != "" { | ||||
| 		id = options.ensureUniqueHeaderID(id) | ||||
|  | ||||
| 		if options.parameters.HeaderIDPrefix != "" { | ||||
| 			id = options.parameters.HeaderIDPrefix + id | ||||
| 		} | ||||
|  | ||||
| 		if options.parameters.HeaderIDSuffix != "" { | ||||
| 			id = id + options.parameters.HeaderIDSuffix | ||||
| 		} | ||||
|  | ||||
| 		out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id)) | ||||
| 	} else { | ||||
| 		out.WriteString(fmt.Sprintf("<h%d>", level)) | ||||
| 	} | ||||
|  | ||||
| 	tocMarker := out.Len() | ||||
| 	if !text() { | ||||
| 		out.Truncate(marker) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// are we building a table of contents? | ||||
| 	if options.flags&HTML_TOC != 0 { | ||||
| 		options.TocHeaderWithAnchor(out.Bytes()[tocMarker:], level, id) | ||||
| 	} | ||||
|  | ||||
| 	out.WriteString(fmt.Sprintf("</h%d>\n", level)) | ||||
| } | ||||
|  | ||||
| func (options *Html) BlockHtml(out *bytes.Buffer, text []byte) { | ||||
| 	if options.flags&HTML_SKIP_HTML != 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	doubleSpace(out) | ||||
| 	out.Write(text) | ||||
| 	out.WriteByte('\n') | ||||
| } | ||||
|  | ||||
| func (options *Html) HRule(out *bytes.Buffer) { | ||||
| 	doubleSpace(out) | ||||
| 	out.WriteString("<hr") | ||||
| 	out.WriteString(options.closeTag) | ||||
| 	out.WriteByte('\n') | ||||
| } | ||||
|  | ||||
| func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) { | ||||
| 	doubleSpace(out) | ||||
|  | ||||
| 	// parse out the language names/classes | ||||
| 	count := 0 | ||||
| 	for _, elt := range strings.Fields(lang) { | ||||
| 		if elt[0] == '.' { | ||||
| 			elt = elt[1:] | ||||
| 		} | ||||
| 		if len(elt) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		if count == 0 { | ||||
| 			out.WriteString("<pre><code class=\"language-") | ||||
| 		} else { | ||||
| 			out.WriteByte(' ') | ||||
| 		} | ||||
| 		attrEscape(out, []byte(elt)) | ||||
| 		count++ | ||||
| 	} | ||||
|  | ||||
| 	if count == 0 { | ||||
| 		out.WriteString("<pre><code>") | ||||
| 	} else { | ||||
| 		out.WriteString("\">") | ||||
| 	} | ||||
|  | ||||
| 	attrEscape(out, text) | ||||
| 	out.WriteString("</code></pre>\n") | ||||
| } | ||||
|  | ||||
| func (options *Html) BlockQuote(out *bytes.Buffer, text []byte) { | ||||
| 	doubleSpace(out) | ||||
| 	out.WriteString("<blockquote>\n") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("</blockquote>\n") | ||||
| } | ||||
|  | ||||
| func (options *Html) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) { | ||||
| 	doubleSpace(out) | ||||
| 	out.WriteString("<table>\n<thead>\n") | ||||
| 	out.Write(header) | ||||
| 	out.WriteString("</thead>\n\n<tbody>\n") | ||||
| 	out.Write(body) | ||||
| 	out.WriteString("</tbody>\n</table>\n") | ||||
| } | ||||
|  | ||||
| func (options *Html) TableRow(out *bytes.Buffer, text []byte) { | ||||
| 	doubleSpace(out) | ||||
| 	out.WriteString("<tr>\n") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("\n</tr>\n") | ||||
| } | ||||
|  | ||||
| func (options *Html) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { | ||||
| 	doubleSpace(out) | ||||
| 	switch align { | ||||
| 	case TABLE_ALIGNMENT_LEFT: | ||||
| 		out.WriteString("<th align=\"left\">") | ||||
| 	case TABLE_ALIGNMENT_RIGHT: | ||||
| 		out.WriteString("<th align=\"right\">") | ||||
| 	case TABLE_ALIGNMENT_CENTER: | ||||
| 		out.WriteString("<th align=\"center\">") | ||||
| 	default: | ||||
| 		out.WriteString("<th>") | ||||
| 	} | ||||
|  | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("</th>") | ||||
| } | ||||
|  | ||||
| func (options *Html) TableCell(out *bytes.Buffer, text []byte, align int) { | ||||
| 	doubleSpace(out) | ||||
| 	switch align { | ||||
| 	case TABLE_ALIGNMENT_LEFT: | ||||
| 		out.WriteString("<td align=\"left\">") | ||||
| 	case TABLE_ALIGNMENT_RIGHT: | ||||
| 		out.WriteString("<td align=\"right\">") | ||||
| 	case TABLE_ALIGNMENT_CENTER: | ||||
| 		out.WriteString("<td align=\"center\">") | ||||
| 	default: | ||||
| 		out.WriteString("<td>") | ||||
| 	} | ||||
|  | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("</td>") | ||||
| } | ||||
|  | ||||
| func (options *Html) Footnotes(out *bytes.Buffer, text func() bool) { | ||||
| 	out.WriteString("<div class=\"footnotes\">\n") | ||||
| 	options.HRule(out) | ||||
| 	options.List(out, text, LIST_TYPE_ORDERED) | ||||
| 	out.WriteString("</div>\n") | ||||
| } | ||||
|  | ||||
| func (options *Html) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) { | ||||
| 	if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 { | ||||
| 		doubleSpace(out) | ||||
| 	} | ||||
| 	slug := slugify(name) | ||||
| 	out.WriteString(`<li id="`) | ||||
| 	out.WriteString(`fn:`) | ||||
| 	out.WriteString(options.parameters.FootnoteAnchorPrefix) | ||||
| 	out.Write(slug) | ||||
| 	out.WriteString(`">`) | ||||
| 	out.Write(text) | ||||
| 	if options.flags&HTML_FOOTNOTE_RETURN_LINKS != 0 { | ||||
| 		out.WriteString(` <a class="footnote-return" href="#`) | ||||
| 		out.WriteString(`fnref:`) | ||||
| 		out.WriteString(options.parameters.FootnoteAnchorPrefix) | ||||
| 		out.Write(slug) | ||||
| 		out.WriteString(`">`) | ||||
| 		out.WriteString(options.parameters.FootnoteReturnLinkContents) | ||||
| 		out.WriteString(`</a>`) | ||||
| 	} | ||||
| 	out.WriteString("</li>\n") | ||||
| } | ||||
|  | ||||
| func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) { | ||||
| 	marker := out.Len() | ||||
| 	doubleSpace(out) | ||||
|  | ||||
| 	if flags&LIST_TYPE_DEFINITION != 0 { | ||||
| 		out.WriteString("<dl>") | ||||
| 	} else if flags&LIST_TYPE_ORDERED != 0 { | ||||
| 		out.WriteString("<ol>") | ||||
| 	} else { | ||||
| 		out.WriteString("<ul>") | ||||
| 	} | ||||
| 	if !text() { | ||||
| 		out.Truncate(marker) | ||||
| 		return | ||||
| 	} | ||||
| 	if flags&LIST_TYPE_DEFINITION != 0 { | ||||
| 		out.WriteString("</dl>\n") | ||||
| 	} else if flags&LIST_TYPE_ORDERED != 0 { | ||||
| 		out.WriteString("</ol>\n") | ||||
| 	} else { | ||||
| 		out.WriteString("</ul>\n") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) { | ||||
| 	if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) || | ||||
| 		flags&LIST_ITEM_BEGINNING_OF_LIST != 0 { | ||||
| 		doubleSpace(out) | ||||
| 	} | ||||
| 	if flags&LIST_TYPE_TERM != 0 { | ||||
| 		out.WriteString("<dt>") | ||||
| 	} else if flags&LIST_TYPE_DEFINITION != 0 { | ||||
| 		out.WriteString("<dd>") | ||||
| 	} else { | ||||
| 		out.WriteString("<li>") | ||||
| 	} | ||||
| 	out.Write(text) | ||||
| 	if flags&LIST_TYPE_TERM != 0 { | ||||
| 		out.WriteString("</dt>\n") | ||||
| 	} else if flags&LIST_TYPE_DEFINITION != 0 { | ||||
| 		out.WriteString("</dd>\n") | ||||
| 	} else { | ||||
| 		out.WriteString("</li>\n") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) { | ||||
| 	marker := out.Len() | ||||
| 	doubleSpace(out) | ||||
|  | ||||
| 	out.WriteString("<p>") | ||||
| 	if !text() { | ||||
| 		out.Truncate(marker) | ||||
| 		return | ||||
| 	} | ||||
| 	out.WriteString("</p>\n") | ||||
| } | ||||
|  | ||||
| func (options *Html) AutoLink(out *bytes.Buffer, link []byte, kind int) { | ||||
| 	skipRanges := htmlEntity.FindAllIndex(link, -1) | ||||
| 	if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) && kind != LINK_TYPE_EMAIL { | ||||
| 		// mark it but don't link it if it is not a safe link: no smartypants | ||||
| 		out.WriteString("<tt>") | ||||
| 		entityEscapeWithSkip(out, link, skipRanges) | ||||
| 		out.WriteString("</tt>") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	out.WriteString("<a href=\"") | ||||
| 	if kind == LINK_TYPE_EMAIL { | ||||
| 		out.WriteString("mailto:") | ||||
| 	} else { | ||||
| 		options.maybeWriteAbsolutePrefix(out, link) | ||||
| 	} | ||||
|  | ||||
| 	entityEscapeWithSkip(out, link, skipRanges) | ||||
|  | ||||
| 	var relAttrs []string | ||||
| 	if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) { | ||||
| 		relAttrs = append(relAttrs, "nofollow") | ||||
| 	} | ||||
| 	if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) { | ||||
| 		relAttrs = append(relAttrs, "noreferrer") | ||||
| 	} | ||||
| 	if len(relAttrs) > 0 { | ||||
| 		out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " "))) | ||||
| 	} | ||||
|  | ||||
| 	// blank target only add to external link | ||||
| 	if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) { | ||||
| 		out.WriteString("\" target=\"_blank") | ||||
| 	} | ||||
|  | ||||
| 	out.WriteString("\">") | ||||
|  | ||||
| 	// Pretty print: if we get an email address as | ||||
| 	// an actual URI, e.g. `mailto:foo@bar.com`, we don't | ||||
| 	// want to print the `mailto:` prefix | ||||
| 	switch { | ||||
| 	case bytes.HasPrefix(link, []byte("mailto://")): | ||||
| 		attrEscape(out, link[len("mailto://"):]) | ||||
| 	case bytes.HasPrefix(link, []byte("mailto:")): | ||||
| 		attrEscape(out, link[len("mailto:"):]) | ||||
| 	default: | ||||
| 		entityEscapeWithSkip(out, link, skipRanges) | ||||
| 	} | ||||
|  | ||||
| 	out.WriteString("</a>") | ||||
| } | ||||
|  | ||||
| func (options *Html) CodeSpan(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("<code>") | ||||
| 	attrEscape(out, text) | ||||
| 	out.WriteString("</code>") | ||||
| } | ||||
|  | ||||
| func (options *Html) DoubleEmphasis(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("<strong>") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("</strong>") | ||||
| } | ||||
|  | ||||
| func (options *Html) Emphasis(out *bytes.Buffer, text []byte) { | ||||
| 	if len(text) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	out.WriteString("<em>") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("</em>") | ||||
| } | ||||
|  | ||||
| func (options *Html) maybeWriteAbsolutePrefix(out *bytes.Buffer, link []byte) { | ||||
| 	if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' { | ||||
| 		out.WriteString(options.parameters.AbsolutePrefix) | ||||
| 		if link[0] != '/' { | ||||
| 			out.WriteByte('/') | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (options *Html) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { | ||||
| 	if options.flags&HTML_SKIP_IMAGES != 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	out.WriteString("<img src=\"") | ||||
| 	options.maybeWriteAbsolutePrefix(out, link) | ||||
| 	attrEscape(out, link) | ||||
| 	out.WriteString("\" alt=\"") | ||||
| 	if len(alt) > 0 { | ||||
| 		attrEscape(out, alt) | ||||
| 	} | ||||
| 	if len(title) > 0 { | ||||
| 		out.WriteString("\" title=\"") | ||||
| 		attrEscape(out, title) | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte('"') | ||||
| 	out.WriteString(options.closeTag) | ||||
| } | ||||
|  | ||||
| func (options *Html) LineBreak(out *bytes.Buffer) { | ||||
| 	out.WriteString("<br") | ||||
| 	out.WriteString(options.closeTag) | ||||
| 	out.WriteByte('\n') | ||||
| } | ||||
|  | ||||
| func (options *Html) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { | ||||
| 	if options.flags&HTML_SKIP_LINKS != 0 { | ||||
| 		// write the link text out but don't link it, just mark it with typewriter font | ||||
| 		out.WriteString("<tt>") | ||||
| 		attrEscape(out, content) | ||||
| 		out.WriteString("</tt>") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) { | ||||
| 		// write the link text out but don't link it, just mark it with typewriter font | ||||
| 		out.WriteString("<tt>") | ||||
| 		attrEscape(out, content) | ||||
| 		out.WriteString("</tt>") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	out.WriteString("<a href=\"") | ||||
| 	options.maybeWriteAbsolutePrefix(out, link) | ||||
| 	attrEscape(out, link) | ||||
| 	if len(title) > 0 { | ||||
| 		out.WriteString("\" title=\"") | ||||
| 		attrEscape(out, title) | ||||
| 	} | ||||
| 	var relAttrs []string | ||||
| 	if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) { | ||||
| 		relAttrs = append(relAttrs, "nofollow") | ||||
| 	} | ||||
| 	if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) { | ||||
| 		relAttrs = append(relAttrs, "noreferrer") | ||||
| 	} | ||||
| 	if len(relAttrs) > 0 { | ||||
| 		out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " "))) | ||||
| 	} | ||||
|  | ||||
| 	// blank target only add to external link | ||||
| 	if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) { | ||||
| 		out.WriteString("\" target=\"_blank") | ||||
| 	} | ||||
|  | ||||
| 	out.WriteString("\">") | ||||
| 	out.Write(content) | ||||
| 	out.WriteString("</a>") | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func (options *Html) RawHtmlTag(out *bytes.Buffer, text []byte) { | ||||
| 	if options.flags&HTML_SKIP_HTML != 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	if options.flags&HTML_SKIP_STYLE != 0 && isHtmlTag(text, "style") { | ||||
| 		return | ||||
| 	} | ||||
| 	if options.flags&HTML_SKIP_LINKS != 0 && isHtmlTag(text, "a") { | ||||
| 		return | ||||
| 	} | ||||
| 	if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") { | ||||
| 		return | ||||
| 	} | ||||
| 	out.Write(text) | ||||
| } | ||||
|  | ||||
| func (options *Html) TripleEmphasis(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("<strong><em>") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("</em></strong>") | ||||
| } | ||||
|  | ||||
| func (options *Html) StrikeThrough(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("<del>") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("</del>") | ||||
| } | ||||
|  | ||||
| func (options *Html) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { | ||||
| 	slug := slugify(ref) | ||||
| 	out.WriteString(`<sup class="footnote-ref" id="`) | ||||
| 	out.WriteString(`fnref:`) | ||||
| 	out.WriteString(options.parameters.FootnoteAnchorPrefix) | ||||
| 	out.Write(slug) | ||||
| 	out.WriteString(`"><a rel="footnote" href="#`) | ||||
| 	out.WriteString(`fn:`) | ||||
| 	out.WriteString(options.parameters.FootnoteAnchorPrefix) | ||||
| 	out.Write(slug) | ||||
| 	out.WriteString(`">`) | ||||
| 	out.WriteString(strconv.Itoa(id)) | ||||
| 	out.WriteString(`</a></sup>`) | ||||
| } | ||||
|  | ||||
| func (options *Html) Entity(out *bytes.Buffer, entity []byte) { | ||||
| 	out.Write(entity) | ||||
| } | ||||
|  | ||||
| func (options *Html) NormalText(out *bytes.Buffer, text []byte) { | ||||
| 	if options.flags&HTML_USE_SMARTYPANTS != 0 { | ||||
| 		options.Smartypants(out, text) | ||||
| 	} else { | ||||
| 		attrEscape(out, text) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (options *Html) Smartypants(out *bytes.Buffer, text []byte) { | ||||
| 	smrt := smartypantsData{false, false} | ||||
|  | ||||
| 	// first do normal entity escaping | ||||
| 	var escaped bytes.Buffer | ||||
| 	attrEscape(&escaped, text) | ||||
| 	text = escaped.Bytes() | ||||
|  | ||||
| 	mark := 0 | ||||
| 	for i := 0; i < len(text); i++ { | ||||
| 		if action := options.smartypants[text[i]]; action != nil { | ||||
| 			if i > mark { | ||||
| 				out.Write(text[mark:i]) | ||||
| 			} | ||||
|  | ||||
| 			previousChar := byte(0) | ||||
| 			if i > 0 { | ||||
| 				previousChar = text[i-1] | ||||
| 			} | ||||
| 			i += action(out, &smrt, previousChar, text[i:]) | ||||
| 			mark = i + 1 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if mark < len(text) { | ||||
| 		out.Write(text[mark:]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (options *Html) DocumentHeader(out *bytes.Buffer) { | ||||
| 	if options.flags&HTML_COMPLETE_PAGE == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ending := "" | ||||
| 	if options.flags&HTML_USE_XHTML != 0 { | ||||
| 		out.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ") | ||||
| 		out.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n") | ||||
| 		out.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n") | ||||
| 		ending = " /" | ||||
| 	} else { | ||||
| 		out.WriteString("<!DOCTYPE html>\n") | ||||
| 		out.WriteString("<html>\n") | ||||
| 	} | ||||
| 	out.WriteString("<head>\n") | ||||
| 	out.WriteString("  <title>") | ||||
| 	options.NormalText(out, []byte(options.title)) | ||||
| 	out.WriteString("</title>\n") | ||||
| 	out.WriteString("  <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v") | ||||
| 	out.WriteString(VERSION) | ||||
| 	out.WriteString("\"") | ||||
| 	out.WriteString(ending) | ||||
| 	out.WriteString(">\n") | ||||
| 	out.WriteString("  <meta charset=\"utf-8\"") | ||||
| 	out.WriteString(ending) | ||||
| 	out.WriteString(">\n") | ||||
| 	if options.css != "" { | ||||
| 		out.WriteString("  <link rel=\"stylesheet\" type=\"text/css\" href=\"") | ||||
| 		attrEscape(out, []byte(options.css)) | ||||
| 		out.WriteString("\"") | ||||
| 		out.WriteString(ending) | ||||
| 		out.WriteString(">\n") | ||||
| 	} | ||||
| 	out.WriteString("</head>\n") | ||||
| 	out.WriteString("<body>\n") | ||||
|  | ||||
| 	options.tocMarker = out.Len() | ||||
| } | ||||
|  | ||||
| func (options *Html) DocumentFooter(out *bytes.Buffer) { | ||||
| 	// finalize and insert the table of contents | ||||
| 	if options.flags&HTML_TOC != 0 { | ||||
| 		options.TocFinalize() | ||||
|  | ||||
| 		// now we have to insert the table of contents into the document | ||||
| 		var temp bytes.Buffer | ||||
|  | ||||
| 		// start by making a copy of everything after the document header | ||||
| 		temp.Write(out.Bytes()[options.tocMarker:]) | ||||
|  | ||||
| 		// now clear the copied material from the main output buffer | ||||
| 		out.Truncate(options.tocMarker) | ||||
|  | ||||
| 		// corner case spacing issue | ||||
| 		if options.flags&HTML_COMPLETE_PAGE != 0 { | ||||
| 			out.WriteByte('\n') | ||||
| 		} | ||||
|  | ||||
| 		// insert the table of contents | ||||
| 		out.WriteString("<nav>\n") | ||||
| 		out.Write(options.toc.Bytes()) | ||||
| 		out.WriteString("</nav>\n") | ||||
|  | ||||
| 		// corner case spacing issue | ||||
| 		if options.flags&HTML_COMPLETE_PAGE == 0 && options.flags&HTML_OMIT_CONTENTS == 0 { | ||||
| 			out.WriteByte('\n') | ||||
| 		} | ||||
|  | ||||
| 		// write out everything that came after it | ||||
| 		if options.flags&HTML_OMIT_CONTENTS == 0 { | ||||
| 			out.Write(temp.Bytes()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if options.flags&HTML_COMPLETE_PAGE != 0 { | ||||
| 		out.WriteString("\n</body>\n") | ||||
| 		out.WriteString("</html>\n") | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func (options *Html) TocHeaderWithAnchor(text []byte, level int, anchor string) { | ||||
| 	for level > options.currentLevel { | ||||
| 		switch { | ||||
| 		case bytes.HasSuffix(options.toc.Bytes(), []byte("</li>\n")): | ||||
| 			// this sublist can nest underneath a header | ||||
| 			size := options.toc.Len() | ||||
| 			options.toc.Truncate(size - len("</li>\n")) | ||||
|  | ||||
| 		case options.currentLevel > 0: | ||||
| 			options.toc.WriteString("<li>") | ||||
| 		} | ||||
| 		if options.toc.Len() > 0 { | ||||
| 			options.toc.WriteByte('\n') | ||||
| 		} | ||||
| 		options.toc.WriteString("<ul>\n") | ||||
| 		options.currentLevel++ | ||||
| 	} | ||||
|  | ||||
| 	for level < options.currentLevel { | ||||
| 		options.toc.WriteString("</ul>") | ||||
| 		if options.currentLevel > 1 { | ||||
| 			options.toc.WriteString("</li>\n") | ||||
| 		} | ||||
| 		options.currentLevel-- | ||||
| 	} | ||||
|  | ||||
| 	options.toc.WriteString("<li><a href=\"#") | ||||
| 	if anchor != "" { | ||||
| 		options.toc.WriteString(anchor) | ||||
| 	} else { | ||||
| 		options.toc.WriteString("toc_") | ||||
| 		options.toc.WriteString(strconv.Itoa(options.headerCount)) | ||||
| 	} | ||||
| 	options.toc.WriteString("\">") | ||||
| 	options.headerCount++ | ||||
|  | ||||
| 	options.toc.Write(text) | ||||
|  | ||||
| 	options.toc.WriteString("</a></li>\n") | ||||
| } | ||||
|  | ||||
| func (options *Html) TocHeader(text []byte, level int) { | ||||
| 	options.TocHeaderWithAnchor(text, level, "") | ||||
| } | ||||
|  | ||||
| func (options *Html) TocFinalize() { | ||||
| 	for options.currentLevel > 1 { | ||||
| 		options.toc.WriteString("</ul></li>\n") | ||||
| 		options.currentLevel-- | ||||
| 	} | ||||
|  | ||||
| 	if options.currentLevel > 0 { | ||||
| 		options.toc.WriteString("</ul>\n") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func isHtmlTag(tag []byte, tagname string) bool { | ||||
| 	found, _ := findHtmlTagPos(tag, tagname) | ||||
| 	return found | ||||
| } | ||||
|  | ||||
| // Look for a character, but ignore it when it's in any kind of quotes, it | ||||
| // might be JavaScript | ||||
| func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int { | ||||
| 	inSingleQuote := false | ||||
| 	inDoubleQuote := false | ||||
| 	inGraveQuote := false | ||||
| 	i := start | ||||
| 	for i < len(html) { | ||||
| 		switch { | ||||
| 		case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote: | ||||
| 			return i | ||||
| 		case html[i] == '\'': | ||||
| 			inSingleQuote = !inSingleQuote | ||||
| 		case html[i] == '"': | ||||
| 			inDoubleQuote = !inDoubleQuote | ||||
| 		case html[i] == '`': | ||||
| 			inGraveQuote = !inGraveQuote | ||||
| 		} | ||||
| 		i++ | ||||
| 	} | ||||
| 	return start | ||||
| } | ||||
|  | ||||
| func findHtmlTagPos(tag []byte, tagname string) (bool, int) { | ||||
| 	i := 0 | ||||
| 	if i < len(tag) && tag[0] != '<' { | ||||
| 		return false, -1 | ||||
| 	} | ||||
| 	i++ | ||||
| 	i = skipSpace(tag, i) | ||||
|  | ||||
| 	if i < len(tag) && tag[i] == '/' { | ||||
| 		i++ | ||||
| 	} | ||||
|  | ||||
| 	i = skipSpace(tag, i) | ||||
| 	j := 0 | ||||
| 	for ; i < len(tag); i, j = i+1, j+1 { | ||||
| 		if j >= len(tagname) { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		if strings.ToLower(string(tag[i]))[0] != tagname[j] { | ||||
| 			return false, -1 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if i == len(tag) { | ||||
| 		return false, -1 | ||||
| 	} | ||||
|  | ||||
| 	rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>') | ||||
| 	if rightAngle > i { | ||||
| 		return true, rightAngle | ||||
| 	} | ||||
|  | ||||
| 	return false, -1 | ||||
| } | ||||
|  | ||||
| func skipUntilChar(text []byte, start int, char byte) int { | ||||
| 	i := start | ||||
| 	for i < len(text) && text[i] != char { | ||||
| 		i++ | ||||
| 	} | ||||
| 	return i | ||||
| } | ||||
|  | ||||
| func skipSpace(tag []byte, i int) int { | ||||
| 	for i < len(tag) && isspace(tag[i]) { | ||||
| 		i++ | ||||
| 	} | ||||
| 	return i | ||||
| } | ||||
|  | ||||
| func skipChar(data []byte, start int, char byte) int { | ||||
| 	i := start | ||||
| 	for i < len(data) && data[i] == char { | ||||
| 		i++ | ||||
| 	} | ||||
| 	return i | ||||
| } | ||||
|  | ||||
| func doubleSpace(out *bytes.Buffer) { | ||||
| 	if out.Len() > 0 { | ||||
| 		out.WriteByte('\n') | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func isRelativeLink(link []byte) (yes bool) { | ||||
| 	// a tag begin with '#' | ||||
| 	if link[0] == '#' { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// link begin with '/' but not '//', the second maybe a protocol relative link | ||||
| 	if len(link) >= 2 && link[0] == '/' && link[1] != '/' { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// only the root '/' | ||||
| 	if len(link) == 1 && link[0] == '/' { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// current directory : begin with "./" | ||||
| 	if bytes.HasPrefix(link, []byte("./")) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	// parent directory : begin with "../" | ||||
| 	if bytes.HasPrefix(link, []byte("../")) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (options *Html) ensureUniqueHeaderID(id string) string { | ||||
| 	for count, found := options.headerIDs[id]; found; count, found = options.headerIDs[id] { | ||||
| 		tmp := fmt.Sprintf("%s-%d", id, count+1) | ||||
|  | ||||
| 		if _, tmpFound := options.headerIDs[tmp]; !tmpFound { | ||||
| 			options.headerIDs[id] = count + 1 | ||||
| 			id = tmp | ||||
| 		} else { | ||||
| 			id = id + "-1" | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if _, found := options.headerIDs[id]; !found { | ||||
| 		options.headerIDs[id] = 0 | ||||
| 	} | ||||
|  | ||||
| 	return id | ||||
| } | ||||
							
								
								
									
										1148
									
								
								vendor/github.com/russross/blackfriday/inline.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1148
									
								
								vendor/github.com/russross/blackfriday/inline.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										332
									
								
								vendor/github.com/russross/blackfriday/latex.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										332
									
								
								vendor/github.com/russross/blackfriday/latex.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,332 @@ | ||||
| // | ||||
| // Blackfriday Markdown Processor | ||||
| // Available at http://github.com/russross/blackfriday | ||||
| // | ||||
| // Copyright © 2011 Russ Ross <russ@russross.com>. | ||||
| // Distributed under the Simplified BSD License. | ||||
| // See README.md for details. | ||||
| // | ||||
|  | ||||
| // | ||||
| // | ||||
| // LaTeX rendering backend | ||||
| // | ||||
| // | ||||
|  | ||||
| package blackfriday | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| ) | ||||
|  | ||||
| // Latex is a type that implements the Renderer interface for LaTeX output. | ||||
| // | ||||
| // Do not create this directly, instead use the LatexRenderer function. | ||||
| type Latex struct { | ||||
| } | ||||
|  | ||||
| // LatexRenderer creates and configures a Latex object, which | ||||
| // satisfies the Renderer interface. | ||||
| // | ||||
| // flags is a set of LATEX_* options ORed together (currently no such options | ||||
| // are defined). | ||||
| func LatexRenderer(flags int) Renderer { | ||||
| 	return &Latex{} | ||||
| } | ||||
|  | ||||
| func (options *Latex) GetFlags() int { | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // render code chunks using verbatim, or listings if we have a language | ||||
| func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) { | ||||
| 	if lang == "" { | ||||
| 		out.WriteString("\n\\begin{verbatim}\n") | ||||
| 	} else { | ||||
| 		out.WriteString("\n\\begin{lstlisting}[language=") | ||||
| 		out.WriteString(lang) | ||||
| 		out.WriteString("]\n") | ||||
| 	} | ||||
| 	out.Write(text) | ||||
| 	if lang == "" { | ||||
| 		out.WriteString("\n\\end{verbatim}\n") | ||||
| 	} else { | ||||
| 		out.WriteString("\n\\end{lstlisting}\n") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) { | ||||
|  | ||||
| } | ||||
|  | ||||
| func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("\n\\begin{quotation}\n") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("\n\\end{quotation}\n") | ||||
| } | ||||
|  | ||||
| func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) { | ||||
| 	// a pretty lame thing to do... | ||||
| 	out.WriteString("\n\\begin{verbatim}\n") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("\n\\end{verbatim}\n") | ||||
| } | ||||
|  | ||||
| func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) { | ||||
| 	marker := out.Len() | ||||
|  | ||||
| 	switch level { | ||||
| 	case 1: | ||||
| 		out.WriteString("\n\\section{") | ||||
| 	case 2: | ||||
| 		out.WriteString("\n\\subsection{") | ||||
| 	case 3: | ||||
| 		out.WriteString("\n\\subsubsection{") | ||||
| 	case 4: | ||||
| 		out.WriteString("\n\\paragraph{") | ||||
| 	case 5: | ||||
| 		out.WriteString("\n\\subparagraph{") | ||||
| 	case 6: | ||||
| 		out.WriteString("\n\\textbf{") | ||||
| 	} | ||||
| 	if !text() { | ||||
| 		out.Truncate(marker) | ||||
| 		return | ||||
| 	} | ||||
| 	out.WriteString("}\n") | ||||
| } | ||||
|  | ||||
| func (options *Latex) HRule(out *bytes.Buffer) { | ||||
| 	out.WriteString("\n\\HRule\n") | ||||
| } | ||||
|  | ||||
| func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) { | ||||
| 	marker := out.Len() | ||||
| 	if flags&LIST_TYPE_ORDERED != 0 { | ||||
| 		out.WriteString("\n\\begin{enumerate}\n") | ||||
| 	} else { | ||||
| 		out.WriteString("\n\\begin{itemize}\n") | ||||
| 	} | ||||
| 	if !text() { | ||||
| 		out.Truncate(marker) | ||||
| 		return | ||||
| 	} | ||||
| 	if flags&LIST_TYPE_ORDERED != 0 { | ||||
| 		out.WriteString("\n\\end{enumerate}\n") | ||||
| 	} else { | ||||
| 		out.WriteString("\n\\end{itemize}\n") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) { | ||||
| 	out.WriteString("\n\\item ") | ||||
| 	out.Write(text) | ||||
| } | ||||
|  | ||||
| func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) { | ||||
| 	marker := out.Len() | ||||
| 	out.WriteString("\n") | ||||
| 	if !text() { | ||||
| 		out.Truncate(marker) | ||||
| 		return | ||||
| 	} | ||||
| 	out.WriteString("\n") | ||||
| } | ||||
|  | ||||
| func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) { | ||||
| 	out.WriteString("\n\\begin{tabular}{") | ||||
| 	for _, elt := range columnData { | ||||
| 		switch elt { | ||||
| 		case TABLE_ALIGNMENT_LEFT: | ||||
| 			out.WriteByte('l') | ||||
| 		case TABLE_ALIGNMENT_RIGHT: | ||||
| 			out.WriteByte('r') | ||||
| 		default: | ||||
| 			out.WriteByte('c') | ||||
| 		} | ||||
| 	} | ||||
| 	out.WriteString("}\n") | ||||
| 	out.Write(header) | ||||
| 	out.WriteString(" \\\\\n\\hline\n") | ||||
| 	out.Write(body) | ||||
| 	out.WriteString("\n\\end{tabular}\n") | ||||
| } | ||||
|  | ||||
| func (options *Latex) TableRow(out *bytes.Buffer, text []byte) { | ||||
| 	if out.Len() > 0 { | ||||
| 		out.WriteString(" \\\\\n") | ||||
| 	} | ||||
| 	out.Write(text) | ||||
| } | ||||
|  | ||||
| func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { | ||||
| 	if out.Len() > 0 { | ||||
| 		out.WriteString(" & ") | ||||
| 	} | ||||
| 	out.Write(text) | ||||
| } | ||||
|  | ||||
| func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) { | ||||
| 	if out.Len() > 0 { | ||||
| 		out.WriteString(" & ") | ||||
| 	} | ||||
| 	out.Write(text) | ||||
| } | ||||
|  | ||||
| // TODO: this | ||||
| func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) { | ||||
|  | ||||
| } | ||||
|  | ||||
| func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) { | ||||
|  | ||||
| } | ||||
|  | ||||
| func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) { | ||||
| 	out.WriteString("\\href{") | ||||
| 	if kind == LINK_TYPE_EMAIL { | ||||
| 		out.WriteString("mailto:") | ||||
| 	} | ||||
| 	out.Write(link) | ||||
| 	out.WriteString("}{") | ||||
| 	out.Write(link) | ||||
| 	out.WriteString("}") | ||||
| } | ||||
|  | ||||
| func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("\\texttt{") | ||||
| 	escapeSpecialChars(out, text) | ||||
| 	out.WriteString("}") | ||||
| } | ||||
|  | ||||
| func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("\\textbf{") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("}") | ||||
| } | ||||
|  | ||||
| func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("\\textit{") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("}") | ||||
| } | ||||
|  | ||||
| func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { | ||||
| 	if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) { | ||||
| 		// treat it like a link | ||||
| 		out.WriteString("\\href{") | ||||
| 		out.Write(link) | ||||
| 		out.WriteString("}{") | ||||
| 		out.Write(alt) | ||||
| 		out.WriteString("}") | ||||
| 	} else { | ||||
| 		out.WriteString("\\includegraphics{") | ||||
| 		out.Write(link) | ||||
| 		out.WriteString("}") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (options *Latex) LineBreak(out *bytes.Buffer) { | ||||
| 	out.WriteString(" \\\\\n") | ||||
| } | ||||
|  | ||||
| func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { | ||||
| 	out.WriteString("\\href{") | ||||
| 	out.Write(link) | ||||
| 	out.WriteString("}{") | ||||
| 	out.Write(content) | ||||
| 	out.WriteString("}") | ||||
| } | ||||
|  | ||||
| func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) { | ||||
| } | ||||
|  | ||||
| func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("\\textbf{\\textit{") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("}}") | ||||
| } | ||||
|  | ||||
| func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) { | ||||
| 	out.WriteString("\\sout{") | ||||
| 	out.Write(text) | ||||
| 	out.WriteString("}") | ||||
| } | ||||
|  | ||||
| // TODO: this | ||||
| func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { | ||||
|  | ||||
| } | ||||
|  | ||||
| func needsBackslash(c byte) bool { | ||||
| 	for _, r := range []byte("_{}%$&\\~#") { | ||||
| 		if c == r { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func escapeSpecialChars(out *bytes.Buffer, text []byte) { | ||||
| 	for i := 0; i < len(text); i++ { | ||||
| 		// directly copy normal characters | ||||
| 		org := i | ||||
|  | ||||
| 		for i < len(text) && !needsBackslash(text[i]) { | ||||
| 			i++ | ||||
| 		} | ||||
| 		if i > org { | ||||
| 			out.Write(text[org:i]) | ||||
| 		} | ||||
|  | ||||
| 		// escape a character | ||||
| 		if i >= len(text) { | ||||
| 			break | ||||
| 		} | ||||
| 		out.WriteByte('\\') | ||||
| 		out.WriteByte(text[i]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (options *Latex) Entity(out *bytes.Buffer, entity []byte) { | ||||
| 	// TODO: convert this into a unicode character or something | ||||
| 	out.Write(entity) | ||||
| } | ||||
|  | ||||
| func (options *Latex) NormalText(out *bytes.Buffer, text []byte) { | ||||
| 	escapeSpecialChars(out, text) | ||||
| } | ||||
|  | ||||
| // header and footer | ||||
| func (options *Latex) DocumentHeader(out *bytes.Buffer) { | ||||
| 	out.WriteString("\\documentclass{article}\n") | ||||
| 	out.WriteString("\n") | ||||
| 	out.WriteString("\\usepackage{graphicx}\n") | ||||
| 	out.WriteString("\\usepackage{listings}\n") | ||||
| 	out.WriteString("\\usepackage[margin=1in]{geometry}\n") | ||||
| 	out.WriteString("\\usepackage[utf8]{inputenc}\n") | ||||
| 	out.WriteString("\\usepackage{verbatim}\n") | ||||
| 	out.WriteString("\\usepackage[normalem]{ulem}\n") | ||||
| 	out.WriteString("\\usepackage{hyperref}\n") | ||||
| 	out.WriteString("\n") | ||||
| 	out.WriteString("\\hypersetup{colorlinks,%\n") | ||||
| 	out.WriteString("  citecolor=black,%\n") | ||||
| 	out.WriteString("  filecolor=black,%\n") | ||||
| 	out.WriteString("  linkcolor=black,%\n") | ||||
| 	out.WriteString("  urlcolor=black,%\n") | ||||
| 	out.WriteString("  pdfstartview=FitH,%\n") | ||||
| 	out.WriteString("  breaklinks=true,%\n") | ||||
| 	out.WriteString("  pdfauthor={Blackfriday Markdown Processor v") | ||||
| 	out.WriteString(VERSION) | ||||
| 	out.WriteString("}}\n") | ||||
| 	out.WriteString("\n") | ||||
| 	out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n") | ||||
| 	out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n") | ||||
| 	out.WriteString("\\parindent=0pt\n") | ||||
| 	out.WriteString("\n") | ||||
| 	out.WriteString("\\begin{document}\n") | ||||
| } | ||||
|  | ||||
| func (options *Latex) DocumentFooter(out *bytes.Buffer) { | ||||
| 	out.WriteString("\n\\end{document}\n") | ||||
| } | ||||
							
								
								
									
										926
									
								
								vendor/github.com/russross/blackfriday/markdown.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										926
									
								
								vendor/github.com/russross/blackfriday/markdown.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,926 @@ | ||||
| // | ||||
| // Blackfriday Markdown Processor | ||||
| // Available at http://github.com/russross/blackfriday | ||||
| // | ||||
| // Copyright © 2011 Russ Ross <russ@russross.com>. | ||||
| // Distributed under the Simplified BSD License. | ||||
| // See README.md for details. | ||||
| // | ||||
|  | ||||
| // | ||||
| // | ||||
| // Markdown parsing and processing | ||||
| // | ||||
| // | ||||
|  | ||||
| // Blackfriday markdown processor. | ||||
| // | ||||
| // Translates plain text with simple formatting rules into HTML or LaTeX. | ||||
| package blackfriday | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
|  | ||||
| const VERSION = "1.5" | ||||
|  | ||||
| // These are the supported markdown parsing extensions. | ||||
| // OR these values together to select multiple extensions. | ||||
| const ( | ||||
| 	EXTENSION_NO_INTRA_EMPHASIS          = 1 << iota // ignore emphasis markers inside words | ||||
| 	EXTENSION_TABLES                                 // render tables | ||||
| 	EXTENSION_FENCED_CODE                            // render fenced code blocks | ||||
| 	EXTENSION_AUTOLINK                               // detect embedded URLs that are not explicitly marked | ||||
| 	EXTENSION_STRIKETHROUGH                          // strikethrough text using ~~test~~ | ||||
| 	EXTENSION_LAX_HTML_BLOCKS                        // loosen up HTML block parsing rules | ||||
| 	EXTENSION_SPACE_HEADERS                          // be strict about prefix header rules | ||||
| 	EXTENSION_HARD_LINE_BREAK                        // translate newlines into line breaks | ||||
| 	EXTENSION_TAB_SIZE_EIGHT                         // expand tabs to eight spaces instead of four | ||||
| 	EXTENSION_FOOTNOTES                              // Pandoc-style footnotes | ||||
| 	EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK             // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block | ||||
| 	EXTENSION_HEADER_IDS                             // specify header IDs  with {#id} | ||||
| 	EXTENSION_TITLEBLOCK                             // Titleblock ala pandoc | ||||
| 	EXTENSION_AUTO_HEADER_IDS                        // Create the header ID from the text | ||||
| 	EXTENSION_BACKSLASH_LINE_BREAK                   // translate trailing backslashes into line breaks | ||||
| 	EXTENSION_DEFINITION_LISTS                       // render definition lists | ||||
|  | ||||
| 	commonHtmlFlags = 0 | | ||||
| 		HTML_USE_XHTML | | ||||
| 		HTML_USE_SMARTYPANTS | | ||||
| 		HTML_SMARTYPANTS_FRACTIONS | | ||||
| 		HTML_SMARTYPANTS_DASHES | | ||||
| 		HTML_SMARTYPANTS_LATEX_DASHES | ||||
|  | ||||
| 	commonExtensions = 0 | | ||||
| 		EXTENSION_NO_INTRA_EMPHASIS | | ||||
| 		EXTENSION_TABLES | | ||||
| 		EXTENSION_FENCED_CODE | | ||||
| 		EXTENSION_AUTOLINK | | ||||
| 		EXTENSION_STRIKETHROUGH | | ||||
| 		EXTENSION_SPACE_HEADERS | | ||||
| 		EXTENSION_HEADER_IDS | | ||||
| 		EXTENSION_BACKSLASH_LINE_BREAK | | ||||
| 		EXTENSION_DEFINITION_LISTS | ||||
| ) | ||||
|  | ||||
| // These are the possible flag values for the link renderer. | ||||
| // Only a single one of these values will be used; they are not ORed together. | ||||
| // These are mostly of interest if you are writing a new output format. | ||||
| const ( | ||||
| 	LINK_TYPE_NOT_AUTOLINK = iota | ||||
| 	LINK_TYPE_NORMAL | ||||
| 	LINK_TYPE_EMAIL | ||||
| ) | ||||
|  | ||||
| // These are the possible flag values for the ListItem renderer. | ||||
| // Multiple flag values may be ORed together. | ||||
| // These are mostly of interest if you are writing a new output format. | ||||
| const ( | ||||
| 	LIST_TYPE_ORDERED = 1 << iota | ||||
| 	LIST_TYPE_DEFINITION | ||||
| 	LIST_TYPE_TERM | ||||
| 	LIST_ITEM_CONTAINS_BLOCK | ||||
| 	LIST_ITEM_BEGINNING_OF_LIST | ||||
| 	LIST_ITEM_END_OF_LIST | ||||
| ) | ||||
|  | ||||
| // These are the possible flag values for the table cell renderer. | ||||
| // Only a single one of these values will be used; they are not ORed together. | ||||
| // These are mostly of interest if you are writing a new output format. | ||||
| const ( | ||||
| 	TABLE_ALIGNMENT_LEFT = 1 << iota | ||||
| 	TABLE_ALIGNMENT_RIGHT | ||||
| 	TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT) | ||||
| ) | ||||
|  | ||||
| // The size of a tab stop. | ||||
| const ( | ||||
| 	TAB_SIZE_DEFAULT = 4 | ||||
| 	TAB_SIZE_EIGHT   = 8 | ||||
| ) | ||||
|  | ||||
| // blockTags is a set of tags that are recognized as HTML block tags. | ||||
| // Any of these can be included in markdown text without special escaping. | ||||
| var blockTags = map[string]struct{}{ | ||||
| 	"blockquote": {}, | ||||
| 	"del":        {}, | ||||
| 	"div":        {}, | ||||
| 	"dl":         {}, | ||||
| 	"fieldset":   {}, | ||||
| 	"form":       {}, | ||||
| 	"h1":         {}, | ||||
| 	"h2":         {}, | ||||
| 	"h3":         {}, | ||||
| 	"h4":         {}, | ||||
| 	"h5":         {}, | ||||
| 	"h6":         {}, | ||||
| 	"iframe":     {}, | ||||
| 	"ins":        {}, | ||||
| 	"math":       {}, | ||||
| 	"noscript":   {}, | ||||
| 	"ol":         {}, | ||||
| 	"pre":        {}, | ||||
| 	"p":          {}, | ||||
| 	"script":     {}, | ||||
| 	"style":      {}, | ||||
| 	"table":      {}, | ||||
| 	"ul":         {}, | ||||
|  | ||||
| 	// HTML5 | ||||
| 	"address":    {}, | ||||
| 	"article":    {}, | ||||
| 	"aside":      {}, | ||||
| 	"canvas":     {}, | ||||
| 	"figcaption": {}, | ||||
| 	"figure":     {}, | ||||
| 	"footer":     {}, | ||||
| 	"header":     {}, | ||||
| 	"hgroup":     {}, | ||||
| 	"main":       {}, | ||||
| 	"nav":        {}, | ||||
| 	"output":     {}, | ||||
| 	"progress":   {}, | ||||
| 	"section":    {}, | ||||
| 	"video":      {}, | ||||
| } | ||||
|  | ||||
| // Renderer is the rendering interface. | ||||
| // This is mostly of interest if you are implementing a new rendering format. | ||||
| // | ||||
| // When a byte slice is provided, it contains the (rendered) contents of the | ||||
| // element. | ||||
| // | ||||
| // When a callback is provided instead, it will write the contents of the | ||||
| // respective element directly to the output buffer and return true on success. | ||||
| // If the callback returns false, the rendering function should reset the | ||||
| // output buffer as though it had never been called. | ||||
| // | ||||
| // Currently Html and Latex implementations are provided | ||||
| type Renderer interface { | ||||
| 	// block-level callbacks | ||||
| 	BlockCode(out *bytes.Buffer, text []byte, lang string) | ||||
| 	BlockQuote(out *bytes.Buffer, text []byte) | ||||
| 	BlockHtml(out *bytes.Buffer, text []byte) | ||||
| 	Header(out *bytes.Buffer, text func() bool, level int, id string) | ||||
| 	HRule(out *bytes.Buffer) | ||||
| 	List(out *bytes.Buffer, text func() bool, flags int) | ||||
| 	ListItem(out *bytes.Buffer, text []byte, flags int) | ||||
| 	Paragraph(out *bytes.Buffer, text func() bool) | ||||
| 	Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) | ||||
| 	TableRow(out *bytes.Buffer, text []byte) | ||||
| 	TableHeaderCell(out *bytes.Buffer, text []byte, flags int) | ||||
| 	TableCell(out *bytes.Buffer, text []byte, flags int) | ||||
| 	Footnotes(out *bytes.Buffer, text func() bool) | ||||
| 	FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) | ||||
| 	TitleBlock(out *bytes.Buffer, text []byte) | ||||
|  | ||||
| 	// Span-level callbacks | ||||
| 	AutoLink(out *bytes.Buffer, link []byte, kind int) | ||||
| 	CodeSpan(out *bytes.Buffer, text []byte) | ||||
| 	DoubleEmphasis(out *bytes.Buffer, text []byte) | ||||
| 	Emphasis(out *bytes.Buffer, text []byte) | ||||
| 	Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) | ||||
| 	LineBreak(out *bytes.Buffer) | ||||
| 	Link(out *bytes.Buffer, link []byte, title []byte, content []byte) | ||||
| 	RawHtmlTag(out *bytes.Buffer, tag []byte) | ||||
| 	TripleEmphasis(out *bytes.Buffer, text []byte) | ||||
| 	StrikeThrough(out *bytes.Buffer, text []byte) | ||||
| 	FootnoteRef(out *bytes.Buffer, ref []byte, id int) | ||||
|  | ||||
| 	// Low-level callbacks | ||||
| 	Entity(out *bytes.Buffer, entity []byte) | ||||
| 	NormalText(out *bytes.Buffer, text []byte) | ||||
|  | ||||
| 	// Header and footer | ||||
| 	DocumentHeader(out *bytes.Buffer) | ||||
| 	DocumentFooter(out *bytes.Buffer) | ||||
|  | ||||
| 	GetFlags() int | ||||
| } | ||||
|  | ||||
| // Callback functions for inline parsing. One such function is defined | ||||
| // for each character that triggers a response when parsing inline data. | ||||
| type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int | ||||
|  | ||||
| // Parser holds runtime state used by the parser. | ||||
| // This is constructed by the Markdown function. | ||||
| type parser struct { | ||||
| 	r              Renderer | ||||
| 	refOverride    ReferenceOverrideFunc | ||||
| 	refs           map[string]*reference | ||||
| 	inlineCallback [256]inlineParser | ||||
| 	flags          int | ||||
| 	nesting        int | ||||
| 	maxNesting     int | ||||
| 	insideLink     bool | ||||
|  | ||||
| 	// Footnotes need to be ordered as well as available to quickly check for | ||||
| 	// presence. If a ref is also a footnote, it's stored both in refs and here | ||||
| 	// in notes. Slice is nil if footnotes not enabled. | ||||
| 	notes []*reference | ||||
| } | ||||
|  | ||||
| func (p *parser) getRef(refid string) (ref *reference, found bool) { | ||||
| 	if p.refOverride != nil { | ||||
| 		r, overridden := p.refOverride(refid) | ||||
| 		if overridden { | ||||
| 			if r == nil { | ||||
| 				return nil, false | ||||
| 			} | ||||
| 			return &reference{ | ||||
| 				link:     []byte(r.Link), | ||||
| 				title:    []byte(r.Title), | ||||
| 				noteId:   0, | ||||
| 				hasBlock: false, | ||||
| 				text:     []byte(r.Text)}, true | ||||
| 		} | ||||
| 	} | ||||
| 	// refs are case insensitive | ||||
| 	ref, found = p.refs[strings.ToLower(refid)] | ||||
| 	return ref, found | ||||
| } | ||||
|  | ||||
| // | ||||
| // | ||||
| // Public interface | ||||
| // | ||||
| // | ||||
|  | ||||
| // Reference represents the details of a link. | ||||
| // See the documentation in Options for more details on use-case. | ||||
| type Reference struct { | ||||
| 	// Link is usually the URL the reference points to. | ||||
| 	Link string | ||||
| 	// Title is the alternate text describing the link in more detail. | ||||
| 	Title string | ||||
| 	// Text is the optional text to override the ref with if the syntax used was | ||||
| 	// [refid][] | ||||
| 	Text string | ||||
| } | ||||
|  | ||||
| // ReferenceOverrideFunc is expected to be called with a reference string and | ||||
| // return either a valid Reference type that the reference string maps to or | ||||
| // nil. If overridden is false, the default reference logic will be executed. | ||||
| // See the documentation in Options for more details on use-case. | ||||
| type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool) | ||||
|  | ||||
| // Options represents configurable overrides and callbacks (in addition to the | ||||
| // extension flag set) for configuring a Markdown parse. | ||||
| type Options struct { | ||||
| 	// Extensions is a flag set of bit-wise ORed extension bits. See the | ||||
| 	// EXTENSION_* flags defined in this package. | ||||
| 	Extensions int | ||||
|  | ||||
| 	// ReferenceOverride is an optional function callback that is called every | ||||
| 	// time a reference is resolved. | ||||
| 	// | ||||
| 	// In Markdown, the link reference syntax can be made to resolve a link to | ||||
| 	// a reference instead of an inline URL, in one of the following ways: | ||||
| 	// | ||||
| 	//  * [link text][refid] | ||||
| 	//  * [refid][] | ||||
| 	// | ||||
| 	// Usually, the refid is defined at the bottom of the Markdown document. If | ||||
| 	// this override function is provided, the refid is passed to the override | ||||
| 	// function first, before consulting the defined refids at the bottom. If | ||||
| 	// the override function indicates an override did not occur, the refids at | ||||
| 	// the bottom will be used to fill in the link details. | ||||
| 	ReferenceOverride ReferenceOverrideFunc | ||||
| } | ||||
|  | ||||
| // MarkdownBasic is a convenience function for simple rendering. | ||||
| // It processes markdown input with no extensions enabled. | ||||
| func MarkdownBasic(input []byte) []byte { | ||||
| 	// set up the HTML renderer | ||||
| 	htmlFlags := HTML_USE_XHTML | ||||
| 	renderer := HtmlRenderer(htmlFlags, "", "") | ||||
|  | ||||
| 	// set up the parser | ||||
| 	return MarkdownOptions(input, renderer, Options{Extensions: 0}) | ||||
| } | ||||
|  | ||||
| // Call Markdown with most useful extensions enabled | ||||
| // MarkdownCommon is a convenience function for simple rendering. | ||||
| // It processes markdown input with common extensions enabled, including: | ||||
| // | ||||
| // * Smartypants processing with smart fractions and LaTeX dashes | ||||
| // | ||||
| // * Intra-word emphasis suppression | ||||
| // | ||||
| // * Tables | ||||
| // | ||||
| // * Fenced code blocks | ||||
| // | ||||
| // * Autolinking | ||||
| // | ||||
| // * Strikethrough support | ||||
| // | ||||
| // * Strict header parsing | ||||
| // | ||||
| // * Custom Header IDs | ||||
| func MarkdownCommon(input []byte) []byte { | ||||
| 	// set up the HTML renderer | ||||
| 	renderer := HtmlRenderer(commonHtmlFlags, "", "") | ||||
| 	return MarkdownOptions(input, renderer, Options{ | ||||
| 		Extensions: commonExtensions}) | ||||
| } | ||||
|  | ||||
| // Markdown is the main rendering function. | ||||
| // It parses and renders a block of markdown-encoded text. | ||||
| // The supplied Renderer is used to format the output, and extensions dictates | ||||
| // which non-standard extensions are enabled. | ||||
| // | ||||
| // To use the supplied Html or LaTeX renderers, see HtmlRenderer and | ||||
| // LatexRenderer, respectively. | ||||
| func Markdown(input []byte, renderer Renderer, extensions int) []byte { | ||||
| 	return MarkdownOptions(input, renderer, Options{ | ||||
| 		Extensions: extensions}) | ||||
| } | ||||
|  | ||||
| // MarkdownOptions is just like Markdown but takes additional options through | ||||
| // the Options struct. | ||||
| func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte { | ||||
| 	// no point in parsing if we can't render | ||||
| 	if renderer == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	extensions := opts.Extensions | ||||
|  | ||||
| 	// fill in the render structure | ||||
| 	p := new(parser) | ||||
| 	p.r = renderer | ||||
| 	p.flags = extensions | ||||
| 	p.refOverride = opts.ReferenceOverride | ||||
| 	p.refs = make(map[string]*reference) | ||||
| 	p.maxNesting = 16 | ||||
| 	p.insideLink = false | ||||
|  | ||||
| 	// register inline parsers | ||||
| 	p.inlineCallback['*'] = emphasis | ||||
| 	p.inlineCallback['_'] = emphasis | ||||
| 	if extensions&EXTENSION_STRIKETHROUGH != 0 { | ||||
| 		p.inlineCallback['~'] = emphasis | ||||
| 	} | ||||
| 	p.inlineCallback['`'] = codeSpan | ||||
| 	p.inlineCallback['\n'] = lineBreak | ||||
| 	p.inlineCallback['['] = link | ||||
| 	p.inlineCallback['<'] = leftAngle | ||||
| 	p.inlineCallback['\\'] = escape | ||||
| 	p.inlineCallback['&'] = entity | ||||
|  | ||||
| 	if extensions&EXTENSION_AUTOLINK != 0 { | ||||
| 		p.inlineCallback[':'] = autoLink | ||||
| 	} | ||||
|  | ||||
| 	if extensions&EXTENSION_FOOTNOTES != 0 { | ||||
| 		p.notes = make([]*reference, 0) | ||||
| 	} | ||||
|  | ||||
| 	first := firstPass(p, input) | ||||
| 	second := secondPass(p, first) | ||||
| 	return second | ||||
| } | ||||
|  | ||||
| // first pass: | ||||
| // - normalize newlines | ||||
| // - extract references (outside of fenced code blocks) | ||||
| // - expand tabs (outside of fenced code blocks) | ||||
| // - copy everything else | ||||
| func firstPass(p *parser, input []byte) []byte { | ||||
| 	var out bytes.Buffer | ||||
| 	tabSize := TAB_SIZE_DEFAULT | ||||
| 	if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 { | ||||
| 		tabSize = TAB_SIZE_EIGHT | ||||
| 	} | ||||
| 	beg := 0 | ||||
| 	lastFencedCodeBlockEnd := 0 | ||||
| 	for beg < len(input) { | ||||
| 		// Find end of this line, then process the line. | ||||
| 		end := beg | ||||
| 		for end < len(input) && input[end] != '\n' && input[end] != '\r' { | ||||
| 			end++ | ||||
| 		} | ||||
|  | ||||
| 		if p.flags&EXTENSION_FENCED_CODE != 0 { | ||||
| 			// track fenced code block boundaries to suppress tab expansion | ||||
| 			// and reference extraction inside them: | ||||
| 			if beg >= lastFencedCodeBlockEnd { | ||||
| 				if i := p.fencedCodeBlock(&out, input[beg:], false); i > 0 { | ||||
| 					lastFencedCodeBlockEnd = beg + i | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// add the line body if present | ||||
| 		if end > beg { | ||||
| 			if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks. | ||||
| 				out.Write(input[beg:end]) | ||||
| 			} else if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 { | ||||
| 				beg += refEnd | ||||
| 				continue | ||||
| 			} else { | ||||
| 				expandTabs(&out, input[beg:end], tabSize) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if end < len(input) && input[end] == '\r' { | ||||
| 			end++ | ||||
| 		} | ||||
| 		if end < len(input) && input[end] == '\n' { | ||||
| 			end++ | ||||
| 		} | ||||
| 		out.WriteByte('\n') | ||||
|  | ||||
| 		beg = end | ||||
| 	} | ||||
|  | ||||
| 	// empty input? | ||||
| 	if out.Len() == 0 { | ||||
| 		out.WriteByte('\n') | ||||
| 	} | ||||
|  | ||||
| 	return out.Bytes() | ||||
| } | ||||
|  | ||||
| // second pass: actual rendering | ||||
| func secondPass(p *parser, input []byte) []byte { | ||||
| 	var output bytes.Buffer | ||||
|  | ||||
| 	p.r.DocumentHeader(&output) | ||||
| 	p.block(&output, input) | ||||
|  | ||||
| 	if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 { | ||||
| 		p.r.Footnotes(&output, func() bool { | ||||
| 			flags := LIST_ITEM_BEGINNING_OF_LIST | ||||
| 			for i := 0; i < len(p.notes); i += 1 { | ||||
| 				ref := p.notes[i] | ||||
| 				var buf bytes.Buffer | ||||
| 				if ref.hasBlock { | ||||
| 					flags |= LIST_ITEM_CONTAINS_BLOCK | ||||
| 					p.block(&buf, ref.title) | ||||
| 				} else { | ||||
| 					p.inline(&buf, ref.title) | ||||
| 				} | ||||
| 				p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags) | ||||
| 				flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK | ||||
| 			} | ||||
|  | ||||
| 			return true | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	p.r.DocumentFooter(&output) | ||||
|  | ||||
| 	if p.nesting != 0 { | ||||
| 		panic("Nesting level did not end at zero") | ||||
| 	} | ||||
|  | ||||
| 	return output.Bytes() | ||||
| } | ||||
|  | ||||
| // | ||||
| // Link references | ||||
| // | ||||
| // This section implements support for references that (usually) appear | ||||
| // as footnotes in a document, and can be referenced anywhere in the document. | ||||
| // The basic format is: | ||||
| // | ||||
| //    [1]: http://www.google.com/ "Google" | ||||
| //    [2]: http://www.github.com/ "Github" | ||||
| // | ||||
| // Anywhere in the document, the reference can be linked by referring to its | ||||
| // label, i.e., 1 and 2 in this example, as in: | ||||
| // | ||||
| //    This library is hosted on [Github][2], a git hosting site. | ||||
| // | ||||
| // Actual footnotes as specified in Pandoc and supported by some other Markdown | ||||
| // libraries such as php-markdown are also taken care of. They look like this: | ||||
| // | ||||
| //    This sentence needs a bit of further explanation.[^note] | ||||
| // | ||||
| //    [^note]: This is the explanation. | ||||
| // | ||||
| // Footnotes should be placed at the end of the document in an ordered list. | ||||
| // Inline footnotes such as: | ||||
| // | ||||
| //    Inline footnotes^[Not supported.] also exist. | ||||
| // | ||||
| // are not yet supported. | ||||
|  | ||||
| // References are parsed and stored in this struct. | ||||
| type reference struct { | ||||
| 	link     []byte | ||||
| 	title    []byte | ||||
| 	noteId   int // 0 if not a footnote ref | ||||
| 	hasBlock bool | ||||
| 	text     []byte | ||||
| } | ||||
|  | ||||
| func (r *reference) String() string { | ||||
| 	return fmt.Sprintf("{link: %q, title: %q, text: %q, noteId: %d, hasBlock: %v}", | ||||
| 		r.link, r.title, r.text, r.noteId, r.hasBlock) | ||||
| } | ||||
|  | ||||
| // Check whether or not data starts with a reference link. | ||||
| // If so, it is parsed and stored in the list of references | ||||
| // (in the render struct). | ||||
| // Returns the number of bytes to skip to move past it, | ||||
| // or zero if the first line is not a reference. | ||||
| func isReference(p *parser, data []byte, tabSize int) int { | ||||
| 	// up to 3 optional leading spaces | ||||
| 	if len(data) < 4 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	i := 0 | ||||
| 	for i < 3 && data[i] == ' ' { | ||||
| 		i++ | ||||
| 	} | ||||
|  | ||||
| 	noteId := 0 | ||||
|  | ||||
| 	// id part: anything but a newline between brackets | ||||
| 	if data[i] != '[' { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	i++ | ||||
| 	if p.flags&EXTENSION_FOOTNOTES != 0 { | ||||
| 		if i < len(data) && data[i] == '^' { | ||||
| 			// we can set it to anything here because the proper noteIds will | ||||
| 			// be assigned later during the second pass. It just has to be != 0 | ||||
| 			noteId = 1 | ||||
| 			i++ | ||||
| 		} | ||||
| 	} | ||||
| 	idOffset := i | ||||
| 	for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' { | ||||
| 		i++ | ||||
| 	} | ||||
| 	if i >= len(data) || data[i] != ']' { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	idEnd := i | ||||
|  | ||||
| 	// spacer: colon (space | tab)* newline? (space | tab)* | ||||
| 	i++ | ||||
| 	if i >= len(data) || data[i] != ':' { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	i++ | ||||
| 	for i < len(data) && (data[i] == ' ' || data[i] == '\t') { | ||||
| 		i++ | ||||
| 	} | ||||
| 	if i < len(data) && (data[i] == '\n' || data[i] == '\r') { | ||||
| 		i++ | ||||
| 		if i < len(data) && data[i] == '\n' && data[i-1] == '\r' { | ||||
| 			i++ | ||||
| 		} | ||||
| 	} | ||||
| 	for i < len(data) && (data[i] == ' ' || data[i] == '\t') { | ||||
| 		i++ | ||||
| 	} | ||||
| 	if i >= len(data) { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	var ( | ||||
| 		linkOffset, linkEnd   int | ||||
| 		titleOffset, titleEnd int | ||||
| 		lineEnd               int | ||||
| 		raw                   []byte | ||||
| 		hasBlock              bool | ||||
| 	) | ||||
|  | ||||
| 	if p.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 { | ||||
| 		linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize) | ||||
| 		lineEnd = linkEnd | ||||
| 	} else { | ||||
| 		linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i) | ||||
| 	} | ||||
| 	if lineEnd == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	// a valid ref has been found | ||||
|  | ||||
| 	ref := &reference{ | ||||
| 		noteId:   noteId, | ||||
| 		hasBlock: hasBlock, | ||||
| 	} | ||||
|  | ||||
| 	if noteId > 0 { | ||||
| 		// reusing the link field for the id since footnotes don't have links | ||||
| 		ref.link = data[idOffset:idEnd] | ||||
| 		// if footnote, it's not really a title, it's the contained text | ||||
| 		ref.title = raw | ||||
| 	} else { | ||||
| 		ref.link = data[linkOffset:linkEnd] | ||||
| 		ref.title = data[titleOffset:titleEnd] | ||||
| 	} | ||||
|  | ||||
| 	// id matches are case-insensitive | ||||
| 	id := string(bytes.ToLower(data[idOffset:idEnd])) | ||||
|  | ||||
| 	p.refs[id] = ref | ||||
|  | ||||
| 	return lineEnd | ||||
| } | ||||
|  | ||||
| func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) { | ||||
| 	// link: whitespace-free sequence, optionally between angle brackets | ||||
| 	if data[i] == '<' { | ||||
| 		i++ | ||||
| 	} | ||||
| 	linkOffset = i | ||||
| 	if i == len(data) { | ||||
| 		return | ||||
| 	} | ||||
| 	for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' { | ||||
| 		i++ | ||||
| 	} | ||||
| 	linkEnd = i | ||||
| 	if data[linkOffset] == '<' && data[linkEnd-1] == '>' { | ||||
| 		linkOffset++ | ||||
| 		linkEnd-- | ||||
| 	} | ||||
|  | ||||
| 	// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) | ||||
| 	for i < len(data) && (data[i] == ' ' || data[i] == '\t') { | ||||
| 		i++ | ||||
| 	} | ||||
| 	if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// compute end-of-line | ||||
| 	if i >= len(data) || data[i] == '\r' || data[i] == '\n' { | ||||
| 		lineEnd = i | ||||
| 	} | ||||
| 	if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' { | ||||
| 		lineEnd++ | ||||
| 	} | ||||
|  | ||||
| 	// optional (space|tab)* spacer after a newline | ||||
| 	if lineEnd > 0 { | ||||
| 		i = lineEnd + 1 | ||||
| 		for i < len(data) && (data[i] == ' ' || data[i] == '\t') { | ||||
| 			i++ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// optional title: any non-newline sequence enclosed in '"() alone on its line | ||||
| 	if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') { | ||||
| 		i++ | ||||
| 		titleOffset = i | ||||
|  | ||||
| 		// look for EOL | ||||
| 		for i < len(data) && data[i] != '\n' && data[i] != '\r' { | ||||
| 			i++ | ||||
| 		} | ||||
| 		if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' { | ||||
| 			titleEnd = i + 1 | ||||
| 		} else { | ||||
| 			titleEnd = i | ||||
| 		} | ||||
|  | ||||
| 		// step back | ||||
| 		i-- | ||||
| 		for i > titleOffset && (data[i] == ' ' || data[i] == '\t') { | ||||
| 			i-- | ||||
| 		} | ||||
| 		if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') { | ||||
| 			lineEnd = titleEnd | ||||
| 			titleEnd = i | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // The first bit of this logic is the same as (*parser).listItem, but the rest | ||||
| // is much simpler. This function simply finds the entire block and shifts it | ||||
| // over by one tab if it is indeed a block (just returns the line if it's not). | ||||
| // blockEnd is the end of the section in the input buffer, and contents is the | ||||
| // extracted text that was shifted over one tab. It will need to be rendered at | ||||
| // the end of the document. | ||||
| func scanFootnote(p *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) { | ||||
| 	if i == 0 || len(data) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// skip leading whitespace on first line | ||||
| 	for i < len(data) && data[i] == ' ' { | ||||
| 		i++ | ||||
| 	} | ||||
|  | ||||
| 	blockStart = i | ||||
|  | ||||
| 	// find the end of the line | ||||
| 	blockEnd = i | ||||
| 	for i < len(data) && data[i-1] != '\n' { | ||||
| 		i++ | ||||
| 	} | ||||
|  | ||||
| 	// get working buffer | ||||
| 	var raw bytes.Buffer | ||||
|  | ||||
| 	// put the first line into the working buffer | ||||
| 	raw.Write(data[blockEnd:i]) | ||||
| 	blockEnd = i | ||||
|  | ||||
| 	// process the following lines | ||||
| 	containsBlankLine := false | ||||
|  | ||||
| gatherLines: | ||||
| 	for blockEnd < len(data) { | ||||
| 		i++ | ||||
|  | ||||
| 		// find the end of this line | ||||
| 		for i < len(data) && data[i-1] != '\n' { | ||||
| 			i++ | ||||
| 		} | ||||
|  | ||||
| 		// if it is an empty line, guess that it is part of this item | ||||
| 		// and move on to the next line | ||||
| 		if p.isEmpty(data[blockEnd:i]) > 0 { | ||||
| 			containsBlankLine = true | ||||
| 			blockEnd = i | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		n := 0 | ||||
| 		if n = isIndented(data[blockEnd:i], indentSize); n == 0 { | ||||
| 			// this is the end of the block. | ||||
| 			// we don't want to include this last line in the index. | ||||
| 			break gatherLines | ||||
| 		} | ||||
|  | ||||
| 		// if there were blank lines before this one, insert a new one now | ||||
| 		if containsBlankLine { | ||||
| 			raw.WriteByte('\n') | ||||
| 			containsBlankLine = false | ||||
| 		} | ||||
|  | ||||
| 		// get rid of that first tab, write to buffer | ||||
| 		raw.Write(data[blockEnd+n : i]) | ||||
| 		hasBlock = true | ||||
|  | ||||
| 		blockEnd = i | ||||
| 	} | ||||
|  | ||||
| 	if data[blockEnd-1] != '\n' { | ||||
| 		raw.WriteByte('\n') | ||||
| 	} | ||||
|  | ||||
| 	contents = raw.Bytes() | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // | ||||
| // | ||||
| // Miscellaneous helper functions | ||||
| // | ||||
| // | ||||
|  | ||||
| // Test if a character is a punctuation symbol. | ||||
| // Taken from a private function in regexp in the stdlib. | ||||
| func ispunct(c byte) bool { | ||||
| 	for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") { | ||||
| 		if c == r { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // Test if a character is a whitespace character. | ||||
| func isspace(c byte) bool { | ||||
| 	return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v' | ||||
| } | ||||
|  | ||||
| // Test if a character is letter. | ||||
| func isletter(c byte) bool { | ||||
| 	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') | ||||
| } | ||||
|  | ||||
| // Test if a character is a letter or a digit. | ||||
| // TODO: check when this is looking for ASCII alnum and when it should use unicode | ||||
| func isalnum(c byte) bool { | ||||
| 	return (c >= '0' && c <= '9') || isletter(c) | ||||
| } | ||||
|  | ||||
| // Replace tab characters with spaces, aligning to the next TAB_SIZE column. | ||||
| // always ends output with a newline | ||||
| func expandTabs(out *bytes.Buffer, line []byte, tabSize int) { | ||||
| 	// first, check for common cases: no tabs, or only tabs at beginning of line | ||||
| 	i, prefix := 0, 0 | ||||
| 	slowcase := false | ||||
| 	for i = 0; i < len(line); i++ { | ||||
| 		if line[i] == '\t' { | ||||
| 			if prefix == i { | ||||
| 				prefix++ | ||||
| 			} else { | ||||
| 				slowcase = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// no need to decode runes if all tabs are at the beginning of the line | ||||
| 	if !slowcase { | ||||
| 		for i = 0; i < prefix*tabSize; i++ { | ||||
| 			out.WriteByte(' ') | ||||
| 		} | ||||
| 		out.Write(line[prefix:]) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// the slow case: we need to count runes to figure out how | ||||
| 	// many spaces to insert for each tab | ||||
| 	column := 0 | ||||
| 	i = 0 | ||||
| 	for i < len(line) { | ||||
| 		start := i | ||||
| 		for i < len(line) && line[i] != '\t' { | ||||
| 			_, size := utf8.DecodeRune(line[i:]) | ||||
| 			i += size | ||||
| 			column++ | ||||
| 		} | ||||
|  | ||||
| 		if i > start { | ||||
| 			out.Write(line[start:i]) | ||||
| 		} | ||||
|  | ||||
| 		if i >= len(line) { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		for { | ||||
| 			out.WriteByte(' ') | ||||
| 			column++ | ||||
| 			if column%tabSize == 0 { | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		i++ | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Find if a line counts as indented or not. | ||||
| // Returns number of characters the indent is (0 = not indented). | ||||
| func isIndented(data []byte, indentSize int) int { | ||||
| 	if len(data) == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	if data[0] == '\t' { | ||||
| 		return 1 | ||||
| 	} | ||||
| 	if len(data) < indentSize { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	for i := 0; i < indentSize; i++ { | ||||
| 		if data[i] != ' ' { | ||||
| 			return 0 | ||||
| 		} | ||||
| 	} | ||||
| 	return indentSize | ||||
| } | ||||
|  | ||||
| // Create a url-safe slug for fragments | ||||
| func slugify(in []byte) []byte { | ||||
| 	if len(in) == 0 { | ||||
| 		return in | ||||
| 	} | ||||
| 	out := make([]byte, 0, len(in)) | ||||
| 	sym := false | ||||
|  | ||||
| 	for _, ch := range in { | ||||
| 		if isalnum(ch) { | ||||
| 			sym = false | ||||
| 			out = append(out, ch) | ||||
| 		} else if sym { | ||||
| 			continue | ||||
| 		} else { | ||||
| 			out = append(out, '-') | ||||
| 			sym = true | ||||
| 		} | ||||
| 	} | ||||
| 	var a, b int | ||||
| 	var ch byte | ||||
| 	for a, ch = range out { | ||||
| 		if ch != '-' { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	for b = len(out) - 1; b > 0; b-- { | ||||
| 		if out[b] != '-' { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	return out[a : b+1] | ||||
| } | ||||
							
								
								
									
										400
									
								
								vendor/github.com/russross/blackfriday/smartypants.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								vendor/github.com/russross/blackfriday/smartypants.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,400 @@ | ||||
| // | ||||
| // Blackfriday Markdown Processor | ||||
| // Available at http://github.com/russross/blackfriday | ||||
| // | ||||
| // Copyright © 2011 Russ Ross <russ@russross.com>. | ||||
| // Distributed under the Simplified BSD License. | ||||
| // See README.md for details. | ||||
| // | ||||
|  | ||||
| // | ||||
| // | ||||
| // SmartyPants rendering | ||||
| // | ||||
| // | ||||
|  | ||||
| package blackfriday | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| ) | ||||
|  | ||||
| type smartypantsData struct { | ||||
| 	inSingleQuote bool | ||||
| 	inDoubleQuote bool | ||||
| } | ||||
|  | ||||
| func wordBoundary(c byte) bool { | ||||
| 	return c == 0 || isspace(c) || ispunct(c) | ||||
| } | ||||
|  | ||||
| func tolower(c byte) byte { | ||||
| 	if c >= 'A' && c <= 'Z' { | ||||
| 		return c - 'A' + 'a' | ||||
| 	} | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| func isdigit(c byte) bool { | ||||
| 	return c >= '0' && c <= '9' | ||||
| } | ||||
|  | ||||
| func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool { | ||||
| 	// edge of the buffer is likely to be a tag that we don't get to see, | ||||
| 	// so we treat it like text sometimes | ||||
|  | ||||
| 	// enumerate all sixteen possibilities for (previousChar, nextChar) | ||||
| 	// each can be one of {0, space, punct, other} | ||||
| 	switch { | ||||
| 	case previousChar == 0 && nextChar == 0: | ||||
| 		// context is not any help here, so toggle | ||||
| 		*isOpen = !*isOpen | ||||
| 	case isspace(previousChar) && nextChar == 0: | ||||
| 		// [ "] might be [ "<code>foo...] | ||||
| 		*isOpen = true | ||||
| 	case ispunct(previousChar) && nextChar == 0: | ||||
| 		// [!"] hmm... could be [Run!"] or [("<code>...] | ||||
| 		*isOpen = false | ||||
| 	case /* isnormal(previousChar) && */ nextChar == 0: | ||||
| 		// [a"] is probably a close | ||||
| 		*isOpen = false | ||||
| 	case previousChar == 0 && isspace(nextChar): | ||||
| 		// [" ] might be [...foo</code>" ] | ||||
| 		*isOpen = false | ||||
| 	case isspace(previousChar) && isspace(nextChar): | ||||
| 		// [ " ] context is not any help here, so toggle | ||||
| 		*isOpen = !*isOpen | ||||
| 	case ispunct(previousChar) && isspace(nextChar): | ||||
| 		// [!" ] is probably a close | ||||
| 		*isOpen = false | ||||
| 	case /* isnormal(previousChar) && */ isspace(nextChar): | ||||
| 		// [a" ] this is one of the easy cases | ||||
| 		*isOpen = false | ||||
| 	case previousChar == 0 && ispunct(nextChar): | ||||
| 		// ["!] hmm... could be ["$1.95] or [</code>"!...] | ||||
| 		*isOpen = false | ||||
| 	case isspace(previousChar) && ispunct(nextChar): | ||||
| 		// [ "!] looks more like [ "$1.95] | ||||
| 		*isOpen = true | ||||
| 	case ispunct(previousChar) && ispunct(nextChar): | ||||
| 		// [!"!] context is not any help here, so toggle | ||||
| 		*isOpen = !*isOpen | ||||
| 	case /* isnormal(previousChar) && */ ispunct(nextChar): | ||||
| 		// [a"!] is probably a close | ||||
| 		*isOpen = false | ||||
| 	case previousChar == 0 /* && isnormal(nextChar) */ : | ||||
| 		// ["a] is probably an open | ||||
| 		*isOpen = true | ||||
| 	case isspace(previousChar) /* && isnormal(nextChar) */ : | ||||
| 		// [ "a] this is one of the easy cases | ||||
| 		*isOpen = true | ||||
| 	case ispunct(previousChar) /* && isnormal(nextChar) */ : | ||||
| 		// [!"a] is probably an open | ||||
| 		*isOpen = true | ||||
| 	default: | ||||
| 		// [a'b] maybe a contraction? | ||||
| 		*isOpen = false | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte('&') | ||||
| 	if *isOpen { | ||||
| 		out.WriteByte('l') | ||||
| 	} else { | ||||
| 		out.WriteByte('r') | ||||
| 	} | ||||
| 	out.WriteByte(quote) | ||||
| 	out.WriteString("quo;") | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	if len(text) >= 2 { | ||||
| 		t1 := tolower(text[1]) | ||||
|  | ||||
| 		if t1 == '\'' { | ||||
| 			nextChar := byte(0) | ||||
| 			if len(text) >= 3 { | ||||
| 				nextChar = text[2] | ||||
| 			} | ||||
| 			if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) { | ||||
| 				return 1 | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) { | ||||
| 			out.WriteString("’") | ||||
| 			return 0 | ||||
| 		} | ||||
|  | ||||
| 		if len(text) >= 3 { | ||||
| 			t2 := tolower(text[2]) | ||||
|  | ||||
| 			if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) && | ||||
| 				(len(text) < 4 || wordBoundary(text[3])) { | ||||
| 				out.WriteString("’") | ||||
| 				return 0 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	nextChar := byte(0) | ||||
| 	if len(text) > 1 { | ||||
| 		nextChar = text[1] | ||||
| 	} | ||||
| 	if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte(text[0]) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	if len(text) >= 3 { | ||||
| 		t1 := tolower(text[1]) | ||||
| 		t2 := tolower(text[2]) | ||||
|  | ||||
| 		if t1 == 'c' && t2 == ')' { | ||||
| 			out.WriteString("©") | ||||
| 			return 2 | ||||
| 		} | ||||
|  | ||||
| 		if t1 == 'r' && t2 == ')' { | ||||
| 			out.WriteString("®") | ||||
| 			return 2 | ||||
| 		} | ||||
|  | ||||
| 		if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' { | ||||
| 			out.WriteString("™") | ||||
| 			return 3 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte(text[0]) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	if len(text) >= 2 { | ||||
| 		if text[1] == '-' { | ||||
| 			out.WriteString("—") | ||||
| 			return 1 | ||||
| 		} | ||||
|  | ||||
| 		if wordBoundary(previousChar) && wordBoundary(text[1]) { | ||||
| 			out.WriteString("–") | ||||
| 			return 0 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte(text[0]) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	if len(text) >= 3 && text[1] == '-' && text[2] == '-' { | ||||
| 		out.WriteString("—") | ||||
| 		return 2 | ||||
| 	} | ||||
| 	if len(text) >= 2 && text[1] == '-' { | ||||
| 		out.WriteString("–") | ||||
| 		return 1 | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte(text[0]) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int { | ||||
| 	if bytes.HasPrefix(text, []byte(""")) { | ||||
| 		nextChar := byte(0) | ||||
| 		if len(text) >= 7 { | ||||
| 			nextChar = text[6] | ||||
| 		} | ||||
| 		if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) { | ||||
| 			return 5 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if bytes.HasPrefix(text, []byte("�")) { | ||||
| 		return 3 | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte('&') | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	return smartAmpVariant(out, smrt, previousChar, text, 'd') | ||||
| } | ||||
|  | ||||
| func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	return smartAmpVariant(out, smrt, previousChar, text, 'a') | ||||
| } | ||||
|  | ||||
| func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	if len(text) >= 3 && text[1] == '.' && text[2] == '.' { | ||||
| 		out.WriteString("…") | ||||
| 		return 2 | ||||
| 	} | ||||
|  | ||||
| 	if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' { | ||||
| 		out.WriteString("…") | ||||
| 		return 4 | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte(text[0]) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	if len(text) >= 2 && text[1] == '`' { | ||||
| 		nextChar := byte(0) | ||||
| 		if len(text) >= 3 { | ||||
| 			nextChar = text[2] | ||||
| 		} | ||||
| 		if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) { | ||||
| 			return 1 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte(text[0]) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { | ||||
| 		// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b | ||||
| 		// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8) | ||||
| 		//       and avoid changing dates like 1/23/2005 into fractions. | ||||
| 		numEnd := 0 | ||||
| 		for len(text) > numEnd && isdigit(text[numEnd]) { | ||||
| 			numEnd++ | ||||
| 		} | ||||
| 		if numEnd == 0 { | ||||
| 			out.WriteByte(text[0]) | ||||
| 			return 0 | ||||
| 		} | ||||
| 		denStart := numEnd + 1 | ||||
| 		if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 { | ||||
| 			denStart = numEnd + 3 | ||||
| 		} else if len(text) < numEnd+2 || text[numEnd] != '/' { | ||||
| 			out.WriteByte(text[0]) | ||||
| 			return 0 | ||||
| 		} | ||||
| 		denEnd := denStart | ||||
| 		for len(text) > denEnd && isdigit(text[denEnd]) { | ||||
| 			denEnd++ | ||||
| 		} | ||||
| 		if denEnd == denStart { | ||||
| 			out.WriteByte(text[0]) | ||||
| 			return 0 | ||||
| 		} | ||||
| 		if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' { | ||||
| 			out.WriteString("<sup>") | ||||
| 			out.Write(text[:numEnd]) | ||||
| 			out.WriteString("</sup>⁄<sub>") | ||||
| 			out.Write(text[denStart:denEnd]) | ||||
| 			out.WriteString("</sub>") | ||||
| 			return denEnd - 1 | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte(text[0]) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { | ||||
| 		if text[0] == '1' && text[1] == '/' && text[2] == '2' { | ||||
| 			if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' { | ||||
| 				out.WriteString("½") | ||||
| 				return 2 | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if text[0] == '1' && text[1] == '/' && text[2] == '4' { | ||||
| 			if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') { | ||||
| 				out.WriteString("¼") | ||||
| 				return 2 | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if text[0] == '3' && text[1] == '/' && text[2] == '4' { | ||||
| 			if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') { | ||||
| 				out.WriteString("¾") | ||||
| 				return 2 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	out.WriteByte(text[0]) | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int { | ||||
| 	nextChar := byte(0) | ||||
| 	if len(text) > 1 { | ||||
| 		nextChar = text[1] | ||||
| 	} | ||||
| 	if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) { | ||||
| 		out.WriteString(""") | ||||
| 	} | ||||
|  | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd') | ||||
| } | ||||
|  | ||||
| func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a') | ||||
| } | ||||
|  | ||||
| func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { | ||||
| 	i := 0 | ||||
|  | ||||
| 	for i < len(text) && text[i] != '>' { | ||||
| 		i++ | ||||
| 	} | ||||
|  | ||||
| 	out.Write(text[:i+1]) | ||||
| 	return i | ||||
| } | ||||
|  | ||||
| type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int | ||||
|  | ||||
| type smartypantsRenderer [256]smartCallback | ||||
|  | ||||
| func smartypants(flags int) *smartypantsRenderer { | ||||
| 	r := new(smartypantsRenderer) | ||||
| 	if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 { | ||||
| 		r['"'] = smartDoubleQuote | ||||
| 		r['&'] = smartAmp | ||||
| 	} else { | ||||
| 		r['"'] = smartAngledDoubleQuote | ||||
| 		r['&'] = smartAmpAngledQuote | ||||
| 	} | ||||
| 	r['\''] = smartSingleQuote | ||||
| 	r['('] = smartParens | ||||
| 	if flags&HTML_SMARTYPANTS_DASHES != 0 { | ||||
| 		if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 { | ||||
| 			r['-'] = smartDash | ||||
| 		} else { | ||||
| 			r['-'] = smartDashLatex | ||||
| 		} | ||||
| 	} | ||||
| 	r['.'] = smartPeriod | ||||
| 	if flags&HTML_SMARTYPANTS_FRACTIONS == 0 { | ||||
| 		r['1'] = smartNumber | ||||
| 		r['3'] = smartNumber | ||||
| 	} else { | ||||
| 		for ch := '1'; ch <= '9'; ch++ { | ||||
| 			r[ch] = smartNumberGeneric | ||||
| 		} | ||||
| 	} | ||||
| 	r['<'] = smartLeftAngle | ||||
| 	r['`'] = smartBacktick | ||||
| 	return r | ||||
| } | ||||
							
								
								
									
										19
									
								
								vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| Copyright (c) 2015 Dmitri Shuralyov | ||||
|  | ||||
| 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. | ||||
							
								
								
									
										29
									
								
								vendor/github.com/shurcooL/sanitized_anchor_name/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/shurcooL/sanitized_anchor_name/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| // Package sanitized_anchor_name provides a func to create sanitized anchor names. | ||||
| // | ||||
| // Its logic can be reused by multiple packages to create interoperable anchor names | ||||
| // and links to those anchors. | ||||
| // | ||||
| // At this time, it does not try to ensure that generated anchor names | ||||
| // are unique, that responsibility falls on the caller. | ||||
| package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name" | ||||
|  | ||||
| import "unicode" | ||||
|  | ||||
| // Create returns a sanitized anchor name for the given text. | ||||
| func Create(text string) string { | ||||
| 	var anchorName []rune | ||||
| 	var futureDash = false | ||||
| 	for _, r := range []rune(text) { | ||||
| 		switch { | ||||
| 		case unicode.IsLetter(r) || unicode.IsNumber(r): | ||||
| 			if futureDash && len(anchorName) > 0 { | ||||
| 				anchorName = append(anchorName, '-') | ||||
| 			} | ||||
| 			futureDash = false | ||||
| 			anchorName = append(anchorName, unicode.ToLower(r)) | ||||
| 		default: | ||||
| 			futureDash = true | ||||
| 		} | ||||
| 	} | ||||
| 	return string(anchorName) | ||||
| } | ||||
							
								
								
									
										7
									
								
								vendor/github.com/technoweenie/multipartstreamer/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/technoweenie/multipartstreamer/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| Copyright (c) 2013-* rick olson | ||||
|  | ||||
| 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. | ||||
							
								
								
									
										31
									
								
								vendor/github.com/technoweenie/multipartstreamer/examples/multipart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/technoweenie/multipartstreamer/examples/multipart.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"mime/multipart" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	defaultPath, _ := os.Getwd() | ||||
| 	defaultFile := filepath.Join(defaultPath, "streamer.go") | ||||
| 	fullpath := flag.String("path", defaultFile, "Path to the include in the multipart data.") | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	buffer := bytes.NewBufferString("") | ||||
| 	writer := multipart.NewWriter(buffer) | ||||
|  | ||||
| 	fmt.Println("Adding the file to the multipart writer") | ||||
| 	fileWriter, _ := writer.CreateFormFile("file", *fullpath) | ||||
| 	fileData, _ := os.Open(*fullpath) | ||||
| 	io.Copy(fileWriter, fileData) | ||||
| 	writer.Close() | ||||
|  | ||||
| 	fmt.Println("Writing the multipart data to a file") | ||||
| 	output, _ := os.Create("multiparttest") | ||||
| 	io.Copy(output, buffer) | ||||
| } | ||||
							
								
								
									
										27
									
								
								vendor/github.com/technoweenie/multipartstreamer/examples/streamer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/technoweenie/multipartstreamer/examples/streamer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"github.com/technoweenie/multipartstreamer" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	defaultPath, _ := os.Getwd() | ||||
| 	defaultFile := filepath.Join(defaultPath, "streamer.go") | ||||
| 	fullpath := flag.String("path", defaultFile, "Path to the include in the multipart data.") | ||||
| 	flag.Parse() | ||||
|  | ||||
| 	ms := multipartstreamer.New() | ||||
|  | ||||
| 	fmt.Println("Adding the file to the multipart writer") | ||||
| 	ms.WriteFile("file", *fullpath) | ||||
| 	reader := ms.GetReader() | ||||
|  | ||||
| 	fmt.Println("Writing the multipart data to a file") | ||||
| 	file, _ := os.Create("streamtest") | ||||
| 	io.Copy(file, reader) | ||||
| } | ||||
							
								
								
									
										101
									
								
								vendor/github.com/technoweenie/multipartstreamer/multipartstreamer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								vendor/github.com/technoweenie/multipartstreamer/multipartstreamer.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| /* | ||||
| Package multipartstreamer helps you encode large files in MIME multipart format | ||||
| without reading the entire content into memory.  It uses io.MultiReader to | ||||
| combine an inner multipart.Reader with a file handle. | ||||
| */ | ||||
| package multipartstreamer | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"mime/multipart" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| ) | ||||
|  | ||||
| type MultipartStreamer struct { | ||||
| 	ContentType   string | ||||
| 	bodyBuffer    *bytes.Buffer | ||||
| 	bodyWriter    *multipart.Writer | ||||
| 	closeBuffer   *bytes.Buffer | ||||
| 	reader        io.Reader | ||||
| 	contentLength int64 | ||||
| } | ||||
|  | ||||
| // New initializes a new MultipartStreamer. | ||||
| func New() (m *MultipartStreamer) { | ||||
| 	m = &MultipartStreamer{bodyBuffer: new(bytes.Buffer)} | ||||
|  | ||||
| 	m.bodyWriter = multipart.NewWriter(m.bodyBuffer) | ||||
| 	boundary := m.bodyWriter.Boundary() | ||||
| 	m.ContentType = "multipart/form-data; boundary=" + boundary | ||||
|  | ||||
| 	closeBoundary := fmt.Sprintf("\r\n--%s--\r\n", boundary) | ||||
| 	m.closeBuffer = bytes.NewBufferString(closeBoundary) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // WriteFields writes multiple form fields to the multipart.Writer. | ||||
| func (m *MultipartStreamer) WriteFields(fields map[string]string) error { | ||||
| 	var err error | ||||
|  | ||||
| 	for key, value := range fields { | ||||
| 		err = m.bodyWriter.WriteField(key, value) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WriteReader adds an io.Reader to get the content of a file.  The reader is | ||||
| // not accessed until the multipart.Reader is copied to some output writer. | ||||
| func (m *MultipartStreamer) WriteReader(key, filename string, size int64, reader io.Reader) (err error) { | ||||
| 	m.reader = reader | ||||
| 	m.contentLength = size | ||||
|  | ||||
| 	_, err = m.bodyWriter.CreateFormFile(key, filename) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // WriteFile is a shortcut for adding a local file as an io.Reader. | ||||
| func (m *MultipartStreamer) WriteFile(key, filename string) error { | ||||
| 	fh, err := os.Open(filename) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	stat, err := fh.Stat() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return m.WriteReader(key, filepath.Base(filename), stat.Size(), fh) | ||||
| } | ||||
|  | ||||
| // SetupRequest sets up the http.Request body, and some crucial HTTP headers. | ||||
| func (m *MultipartStreamer) SetupRequest(req *http.Request) { | ||||
| 	req.Body = m.GetReader() | ||||
| 	req.Header.Add("Content-Type", m.ContentType) | ||||
| 	req.ContentLength = m.Len() | ||||
| } | ||||
|  | ||||
| func (m *MultipartStreamer) Boundary() string { | ||||
| 	return m.bodyWriter.Boundary() | ||||
| } | ||||
|  | ||||
| // Len calculates the byte size of the multipart content. | ||||
| func (m *MultipartStreamer) Len() int64 { | ||||
| 	return m.contentLength + int64(m.bodyBuffer.Len()) + int64(m.closeBuffer.Len()) | ||||
| } | ||||
|  | ||||
| // GetReader gets an io.ReadCloser for passing to an http.Request. | ||||
| func (m *MultipartStreamer) GetReader() io.ReadCloser { | ||||
| 	reader := io.MultiReader(m.bodyBuffer, m.reader, m.closeBuffer) | ||||
| 	return ioutil.NopCloser(reader) | ||||
| } | ||||
							
								
								
									
										26
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							| @@ -59,6 +59,14 @@ | ||||
| 			"branch": "master", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"importpath": "github.com/go-telegram-bot-api/telegram-bot-api", | ||||
| 			"repository": "https://github.com/go-telegram-bot-api/telegram-bot-api", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "a7f48eb2dd301356942677e65bebe0c9aef07013", | ||||
| 			"branch": "master", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"importpath": "github.com/gorilla/schema", | ||||
| 			"repository": "https://github.com/gorilla/schema", | ||||
| @@ -87,8 +95,8 @@ | ||||
| 			"importpath": "github.com/mattermost/platform/einterfaces", | ||||
| 			"repository": "https://github.com/mattermost/platform", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "57f25fa59c71821cc38fd220b133aa6a40815e12", | ||||
| 			"branch": "release-3.4", | ||||
| 			"revision": "976296cd52533ff565407e55e872339cc312a0cf", | ||||
| 			"branch": "release-3.6", | ||||
| 			"path": "/einterfaces", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @@ -96,8 +104,8 @@ | ||||
| 			"importpath": "github.com/mattermost/platform/model", | ||||
| 			"repository": "https://github.com/mattermost/platform", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "57f25fa59c71821cc38fd220b133aa6a40815e12", | ||||
| 			"branch": "release-3.4", | ||||
| 			"revision": "976296cd52533ff565407e55e872339cc312a0cf", | ||||
| 			"branch": "release-3.6", | ||||
| 			"path": "/model", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @@ -105,7 +113,7 @@ | ||||
| 			"importpath": "github.com/mattn/go-xmpp", | ||||
| 			"repository": "https://github.com/mattn/go-xmpp", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "e44d1877bb457f5c3991903e9934a31e55c3a2ad", | ||||
| 			"revision": "f4550b5399387339df5ce4c3f88c1ef85333bdd5", | ||||
| 			"branch": "master", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @@ -166,6 +174,14 @@ | ||||
| 			"branch": "master", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"importpath": "github.com/technoweenie/multipartstreamer", | ||||
| 			"repository": "https://github.com/technoweenie/multipartstreamer", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "a90a01d73ae432e2611d178c18367fbaa13e0154", | ||||
| 			"branch": "master", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"importpath": "github.com/thoj/go-ircevent", | ||||
| 			"repository": "https://github.com/thoj/go-ircevent", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user