forked from lug/matterbridge
		
	Compare commits
	
		
			32 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | e9105003b0 | ||
|   | 587bb06558 | ||
|   | 53e9664cde | ||
|   | 482fbac68f | ||
|   | dcccd43427 | ||
|   | 397b8ff892 | ||
|   | 38a4cf315a | ||
|   | 5f8b24e32c | ||
|   | 678a7ceb4e | ||
|   | 077d494c7b | ||
|   | 09b243d8c2 | ||
|   | 991183e514 | ||
|   | 9bf10e4b58 | ||
|   | 884599d27d | ||
|   | f8a6e65bfd | ||
|   | 6df6c5d615 | ||
|   | 93114b7682 | ||
|   | 9987ac3f13 | ||
|   | 01a32b2154 | ||
|   | b3c3142bb2 | ||
|   | 77f1a959c3 | ||
|   | e3dda0e812 | ||
|   | 38103d36b4 | ||
|   | 7685fe1724 | ||
|   | 01afe03a3f | ||
|   | 7fbbf89c58 | ||
|   | 84d259d8b3 | ||
|   | 8b47670a74 | ||
|   | 7f5dc1d461 | ||
|   | 43e765f4f9 | ||
|   | adec73f542 | ||
|   | fee159541f | 
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,9 +1,9 @@ | ||||
| # matterbridge | ||||
|  | ||||
|  | ||||
| Simple bridge between mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram and Hipchat(via xmpp). | ||||
| 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 and Hipchat (via xmpp). Pick and mix. | ||||
| * 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. | ||||
| @@ -26,6 +26,7 @@ Accounts to one of the supported bridges | ||||
| * [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``` | ||||
| @@ -35,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.9.0](https://github.com/42wim/matterircd/releases/tag/v0.9.0) | ||||
| * For use with mattermost 3.5.x - 3.6.0 [v0.9.2](https://github.com/42wim/matterircd/releases/tag/v0.9.2) | ||||
| * 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.9.0 works with mattermost 3.5.0+ [3.5.1 release](https://github.com/mattermost/platform/releases/tag/v3.5.1) | ||||
| * Matterbridge v0.9.2 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,6 +6,7 @@ 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" | ||||
| @@ -59,6 +60,9 @@ func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Brid | ||||
| 	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 b | ||||
| } | ||||
|   | ||||
| @@ -52,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 { | ||||
| @@ -80,6 +85,7 @@ type Config struct { | ||||
| 	Xmpp               map[string]Protocol | ||||
| 	Discord            map[string]Protocol | ||||
| 	Telegram           map[string]Protocol | ||||
| 	Rocketchat         map[string]Protocol | ||||
| 	General            Protocol | ||||
| 	Gateway            []Gateway | ||||
| 	SameChannelGateway []SameChannelGateway | ||||
|   | ||||
							
								
								
									
										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} | ||||
| 	} | ||||
| } | ||||
| @@ -159,7 +159,7 @@ 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") | ||||
| @@ -204,6 +204,14 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) { | ||||
| 			b.channels = ev.Info.Channels | ||||
| 			b.si = ev.Info | ||||
| 			b.Users, _ = b.sc.GetUsers() | ||||
| 			// add private channels | ||||
| 			groups, _ := b.sc.GetGroups(true) | ||||
| 			for _, g := range groups { | ||||
| 				channel := new(slack.Channel) | ||||
| 				channel.ID = g.ID | ||||
| 				channel.Name = g.Name | ||||
| 				b.channels = append(b.channels, *channel) | ||||
| 			} | ||||
| 		case *slack.InvalidAuthEvent: | ||||
| 			flog.Fatalf("Invalid Token %#v", ev) | ||||
| 		default: | ||||
|   | ||||
| @@ -1,10 +1,14 @@ | ||||
| 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" | ||||
| 	"strconv" | ||||
| 	"github.com/russross/blackfriday" | ||||
| ) | ||||
|  | ||||
| type Btelegram struct { | ||||
| @@ -51,24 +55,107 @@ 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 | ||||
| 	} | ||||
| 	m := tgbotapi.NewMessage(chatid, msg.Text) | ||||
|  | ||||
| 	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, html.EscapeString(msg.Username)+string(parsed)) | ||||
| 	m.ParseMode = "HTML" | ||||
| 	_, err = b.c.Send(m) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { | ||||
| 	username := "" | ||||
| 	text := "" | ||||
| 	channel := "" | ||||
| 	for update := range updates { | ||||
| 		if update.Message == nil { | ||||
| 			continue | ||||
| 		// handle channels | ||||
| 		if update.ChannelPost != nil { | ||||
| 			if update.ChannelPost.From != nil { | ||||
| 				username = update.ChannelPost.From.FirstName | ||||
| 				if username == "" { | ||||
| 					username = update.ChannelPost.From.UserName | ||||
| 				} | ||||
| 			} | ||||
| 			text = update.ChannelPost.Text | ||||
| 			channel = strconv.FormatInt(update.ChannelPost.Chat.ID, 10) | ||||
| 		} | ||||
| 		// handle groups | ||||
| 		if update.Message != nil { | ||||
| 			if update.Message.From != nil { | ||||
| 				username = update.Message.From.FirstName | ||||
| 				if username == "" { | ||||
| 					username = update.Message.From.UserName | ||||
| 				} | ||||
| 			} | ||||
| 			text = update.Message.Text | ||||
| 			channel = strconv.FormatInt(update.Message.Chat.ID, 10) | ||||
| 		} | ||||
| 		if username == "" { | ||||
| 			username = "unknown" | ||||
| 		} | ||||
| 		if text != "" { | ||||
| 			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account) | ||||
| 			b.Remote <- config.Message{Username: username, Text: text, Channel: channel, Account: b.Account} | ||||
| 		} | ||||
| 		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,6 +4,7 @@ import ( | ||||
| 	"github.com/42wim/matterbridge/bridge/config" | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/mattn/go-xmpp" | ||||
| 	"crypto/tls" | ||||
|  | ||||
| 	"strings" | ||||
| 	"time" | ||||
| @@ -58,12 +59,17 @@ func (b *Bxmpp) Send(msg config.Message) error { | ||||
| } | ||||
|  | ||||
| func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { | ||||
| 	tc := new(tls.Config) | ||||
| 	tc.InsecureSkipVerify = b.Config.SkipTLSVerify | ||||
| 	tc.ServerName = strings.Split(b.Config.Server, ":")[0] | ||||
| 	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, | ||||
|   | ||||
							
								
								
									
										25
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,3 +1,27 @@ | ||||
| # v0.9.2 | ||||
| ## Bugfix | ||||
| * general: make ignorenicks work again #115 | ||||
| * telegram: fix receiving from channels and groups #112 | ||||
| * telegram: use html for username | ||||
| * telegram: use ```unknown``` as username when username is not visible. | ||||
| * irc: update vendor (fixes some crashes) #117 | ||||
| * xmpp: fix tls by setting ServerName #114 | ||||
|  | ||||
| ## New features | ||||
| * slack: support private channels #118 | ||||
|  | ||||
| # 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) | ||||
| @@ -66,6 +90,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 | ||||
|  | ||||
|   | ||||
| @@ -13,12 +13,12 @@ type Gateway struct { | ||||
| 	*config.Config | ||||
| 	MyConfig *config.Gateway | ||||
| 	//Bridges     []*bridge.Bridge | ||||
| 	Bridges     map[string]*bridge.Bridge | ||||
| 	ChannelsOut map[string][]string | ||||
| 	ChannelsIn  map[string][]string | ||||
| 	ignoreNicks map[string][]string | ||||
| 	Name        string | ||||
| 	Message     chan config.Message | ||||
| 	Bridges        map[string]*bridge.Bridge | ||||
| 	ChannelsOut    map[string][]string | ||||
| 	ChannelsIn     map[string][]string | ||||
| 	ChannelOptions map[string]config.ChannelOptions | ||||
| 	Name           string | ||||
| 	Message        chan config.Message | ||||
| } | ||||
|  | ||||
| func New(cfg *config.Config, gateway *config.Gateway) *Gateway { | ||||
| @@ -47,8 +47,13 @@ func (gw *Gateway) AddBridge(cfg *config.Bridge) error { | ||||
| 	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) | ||||
| 			br.JoinChannel(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 | ||||
| 		} | ||||
| 	} | ||||
| @@ -63,8 +68,6 @@ func (gw *Gateway) Start() error { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	//TODO fix mapIgnores | ||||
| 	//gw.mapIgnores() | ||||
| 	go gw.handleReceive() | ||||
| 	return nil | ||||
| } | ||||
| @@ -73,41 +76,39 @@ func (gw *Gateway) handleReceive() { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case msg := <-gw.Message: | ||||
| 			for _, br := range gw.Bridges { | ||||
| 				gw.handleMessage(msg, br) | ||||
| 			if !gw.ignoreMessage(&msg) { | ||||
| 				for _, br := range gw.Bridges { | ||||
| 					gw.handleMessage(msg, br) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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 | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) mapIgnores() { | ||||
| 	m := make(map[string][]string) | ||||
| 	for _, br := range gw.MyConfig.In { | ||||
| 		accInfo := strings.Split(br.Account, ".") | ||||
| 		m[br.Account] = strings.Fields(gw.Config.IRC[accInfo[1]].IgnoreNicks) | ||||
| 	} | ||||
| 	gw.ignoreNicks = m | ||||
| } | ||||
|  | ||||
| func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string { | ||||
| 	channels := gw.ChannelsIn[msg.Account] | ||||
| 	// broadcast to every out channel (irc QUIT) | ||||
| @@ -123,9 +124,6 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string { | ||||
| } | ||||
|  | ||||
| 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 | ||||
| @@ -152,9 +150,9 @@ 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.Account] { | ||||
| 	for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreNicks) { | ||||
| 		if msg.Username == entry { | ||||
| 			log.Debugf("ignoring %s from %s", msg.Username, msg.Account) | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -47,8 +47,10 @@ func (gw *SameChannelGateway) handleReceive(c chan config.Message) { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case msg := <-c: | ||||
| 			for _, br := range gw.Bridges { | ||||
| 				gw.handleMessage(msg, br) | ||||
| 			if !gw.ignoreMessage(&msg) { | ||||
| 				for _, br := range gw.Bridges { | ||||
| 					gw.handleMessage(msg, br) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -71,6 +73,16 @@ func (gw *SameChannelGateway) handleMessage(msg config.Message, dest *bridge.Bri | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (gw *SameChannelGateway) ignoreMessage(msg *config.Message) bool { | ||||
| 	for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreNicks) { | ||||
| 		if msg.Username == entry { | ||||
| 			log.Debugf("ignoring %s from %s", msg.Username, msg.Account) | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (gw *SameChannelGateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) { | ||||
| 	br := gw.Bridges[msg.Account] | ||||
| 	nick := gw.Config.General.RemoteNickFormat | ||||
|   | ||||
							
								
								
									
										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.9.0" | ||||
| var version = "0.9.2" | ||||
|  | ||||
| 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) | ||||
| 	} | ||||
| @@ -52,7 +52,7 @@ func main() { | ||||
| 		g := gateway.New(cfg, &gw) | ||||
| 		err := g.Start() | ||||
| 		if err != nil { | ||||
| 			log.Debugf("starting gateway failed %#v", err) | ||||
| 			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 | ||||
| @@ -93,6 +97,11 @@ 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 | ||||
| @@ -396,6 +405,7 @@ ShowJoinPart=false | ||||
| #REQUIRED | ||||
| [telegram.secure] | ||||
| #Token to connect with telegram API | ||||
| #See https://core.telegram.org/bots#6-botfather and https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau | ||||
| #REQUIRED | ||||
| Token="Yourtokenhere" | ||||
|  | ||||
| @@ -415,6 +425,64 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
| #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 | ||||
| ################################################################### | ||||
| @@ -427,8 +495,6 @@ ShowJoinPart=false | ||||
| #OPTIONAL (default empty) | ||||
| RemoteNickFormat="[{PROTOCOL}] <{NICK}> " | ||||
|  | ||||
|  | ||||
|  | ||||
| ################################################################### | ||||
| #Gateway configuration | ||||
| ################################################################### | ||||
| @@ -469,22 +535,38 @@ enable=true | ||||
|     #           - ID:123456789 (where 123456789 is the channel ID)  | ||||
|     #               (https://github.com/42wim/matterbridge/issues/57) | ||||
|     #telegram   - chatid (a large negative number, eg -123456789) | ||||
|     #             see (https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau) | ||||
|     #hipchat    - id_channel (see https://www.hipchat.com/account/xmpp for the correct channel) | ||||
|     #rocketchat - #channel (# is required) | ||||
|     #REQUIRED | ||||
|     channel="#testing" | ||||
|  | ||||
|         #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" | ||||
|  | ||||
|         #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 | ||||
|   | ||||
| @@ -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) | ||||
| 				} | ||||
| @@ -581,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 | ||||
| } | ||||
| @@ -621,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(0, 50000, "") | ||||
| 		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 { | ||||
|   | ||||
							
								
								
									
										78
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										78
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -7,7 +7,6 @@ import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/technoweenie/multipartstreamer" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| @@ -16,6 +15,8 @@ import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/technoweenie/multipartstreamer" | ||||
| ) | ||||
|  | ||||
| // BotAPI allows you to interact with the Telegram Bot API. | ||||
| @@ -80,7 +81,7 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, | ||||
| 	json.Unmarshal(bytes, &apiResp) | ||||
|  | ||||
| 	if !apiResp.Ok { | ||||
| 		return APIResponse{}, errors.New(apiResp.Description) | ||||
| 		return apiResp, errors.New(apiResp.Description) | ||||
| 	} | ||||
|  | ||||
| 	return apiResp, nil | ||||
| @@ -105,16 +106,17 @@ func (bot *BotAPI) makeMessageRequest(endpoint string, params url.Values) (Messa | ||||
| // | ||||
| // 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. | ||||
| // a FileReader struct, or a url.URL. | ||||
| // | ||||
| // 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: | ||||
| 		ms.WriteFields(params) | ||||
|  | ||||
| 		fileHandle, err := os.Open(f) | ||||
| 		if err != nil { | ||||
| 			return APIResponse{}, err | ||||
| @@ -128,9 +130,13 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna | ||||
|  | ||||
| 		ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle) | ||||
| 	case FileBytes: | ||||
| 		ms.WriteFields(params) | ||||
|  | ||||
| 		buf := bytes.NewBuffer(f.Bytes) | ||||
| 		ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf) | ||||
| 	case FileReader: | ||||
| 		ms.WriteFields(params) | ||||
|  | ||||
| 		if f.Size != -1 { | ||||
| 			ms.WriteReader(fieldname, f.Name, f.Size, f.Reader) | ||||
|  | ||||
| @@ -145,6 +151,10 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna | ||||
| 		buf := bytes.NewBuffer(data) | ||||
|  | ||||
| 		ms.WriteReader(fieldname, f.Name, int64(len(data)), buf) | ||||
| 	case url.URL: | ||||
| 		params[fieldname] = f.String() | ||||
|  | ||||
| 		ms.WriteFields(params) | ||||
| 	default: | ||||
| 		return APIResponse{}, errors.New(ErrBadFileType) | ||||
| 	} | ||||
| @@ -399,15 +409,22 @@ func (bot *BotAPI) RemoveWebhook() (APIResponse, error) { | ||||
| // 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()) | ||||
| 		if config.MaxConnections != 0 { | ||||
| 			v.Add("max_connections", strconv.Itoa(config.MaxConnections)) | ||||
| 		} | ||||
|  | ||||
| 		return bot.MakeRequest("setWebhook", v) | ||||
| 	} | ||||
|  | ||||
| 	params := make(map[string]string) | ||||
| 	params["url"] = config.URL.String() | ||||
| 	if config.MaxConnections != 0 { | ||||
| 		params["max_connections"] = strconv.Itoa(config.MaxConnections) | ||||
| 	} | ||||
|  | ||||
| 	resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate) | ||||
| 	if err != nil { | ||||
| @@ -424,9 +441,23 @@ func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) { | ||||
| 	return apiResp, nil | ||||
| } | ||||
|  | ||||
| // GetWebhookInfo allows you to fetch information about a webhook and if | ||||
| // one currently is set, along with pending update count and error messages. | ||||
| func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) { | ||||
| 	resp, err := bot.MakeRequest("getWebhookInfo", url.Values{}) | ||||
| 	if err != nil { | ||||
| 		return WebhookInfo{}, err | ||||
| 	} | ||||
|  | ||||
| 	var info WebhookInfo | ||||
| 	err = json.Unmarshal(resp.Result, &info) | ||||
|  | ||||
| 	return info, err | ||||
| } | ||||
|  | ||||
| // GetUpdatesChan starts and returns a channel for getting updates. | ||||
| func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (<-chan Update, error) { | ||||
| 	updatesChan := make(chan Update, 100) | ||||
| func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) { | ||||
| 	ch := make(chan Update, 100) | ||||
|  | ||||
| 	go func() { | ||||
| 		for { | ||||
| @@ -442,18 +473,18 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (<-chan Update, error) { | ||||
| 			for _, update := range updates { | ||||
| 				if update.UpdateID >= config.Offset { | ||||
| 					config.Offset = update.UpdateID + 1 | ||||
| 					updatesChan <- update | ||||
| 					ch <- update | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	return updatesChan, nil | ||||
| 	return ch, nil | ||||
| } | ||||
|  | ||||
| // ListenForWebhook registers a http handler for a webhook. | ||||
| func (bot *BotAPI) ListenForWebhook(pattern string) <-chan Update { | ||||
| 	updatesChan := make(chan Update, 100) | ||||
| func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel { | ||||
| 	ch := make(chan Update, 100) | ||||
|  | ||||
| 	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { | ||||
| 		bytes, _ := ioutil.ReadAll(r.Body) | ||||
| @@ -461,10 +492,10 @@ func (bot *BotAPI) ListenForWebhook(pattern string) <-chan Update { | ||||
| 		var update Update | ||||
| 		json.Unmarshal(bytes, &update) | ||||
|  | ||||
| 		updatesChan <- update | ||||
| 		ch <- update | ||||
| 	}) | ||||
|  | ||||
| 	return updatesChan | ||||
| 	return ch | ||||
| } | ||||
|  | ||||
| // AnswerInlineQuery sends a response to an inline query. | ||||
| @@ -495,8 +526,14 @@ func (bot *BotAPI) AnswerCallbackQuery(config CallbackConfig) (APIResponse, erro | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	v.Add("callback_query_id", config.CallbackQueryID) | ||||
| 	v.Add("text", config.Text) | ||||
| 	if config.Text != "" { | ||||
| 		v.Add("text", config.Text) | ||||
| 	} | ||||
| 	v.Add("show_alert", strconv.FormatBool(config.ShowAlert)) | ||||
| 	if config.URL != "" { | ||||
| 		v.Add("url", config.URL) | ||||
| 	} | ||||
| 	v.Add("cache_time", strconv.Itoa(config.CacheTime)) | ||||
|  | ||||
| 	bot.debugLog("answerCallbackQuery", v, nil) | ||||
|  | ||||
| @@ -648,3 +685,18 @@ func (bot *BotAPI) UnbanChatMember(config ChatMemberConfig) (APIResponse, error) | ||||
|  | ||||
| 	return bot.MakeRequest("unbanChatMember", v) | ||||
| } | ||||
|  | ||||
| // GetGameHighScores allows you to get the high scores for a game. | ||||
| func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) { | ||||
| 	v, _ := config.values() | ||||
|  | ||||
| 	resp, err := bot.MakeRequest(config.method(), v) | ||||
| 	if err != nil { | ||||
| 		return []GameHighScore{}, err | ||||
| 	} | ||||
|  | ||||
| 	var highScores []GameHighScore | ||||
| 	err = json.Unmarshal(resp.Result, &highScores) | ||||
|  | ||||
| 	return highScores, err | ||||
| } | ||||
|   | ||||
							
								
								
									
										164
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										164
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -198,7 +198,10 @@ type MessageConfig struct { | ||||
|  | ||||
| // values returns a url.Values representation of MessageConfig. | ||||
| func (config MessageConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
| 	v.Add("text", config.Text) | ||||
| 	v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) | ||||
| 	if config.ParseMode != "" { | ||||
| @@ -223,7 +226,10 @@ type ForwardConfig struct { | ||||
|  | ||||
| // values returns a url.Values representation of ForwardConfig. | ||||
| func (config ForwardConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
| 	v.Add("from_chat_id", strconv.FormatInt(config.FromChatID, 10)) | ||||
| 	v.Add("message_id", strconv.Itoa(config.MessageID)) | ||||
| 	return v, nil | ||||
| @@ -253,7 +259,10 @@ func (config PhotoConfig) params() (map[string]string, error) { | ||||
|  | ||||
| // Values returns a url.Values representation of PhotoConfig. | ||||
| func (config PhotoConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
| 	if config.Caption != "" { | ||||
| @@ -275,6 +284,7 @@ func (config PhotoConfig) method() string { | ||||
| // AudioConfig contains information about a SendAudio request. | ||||
| type AudioConfig struct { | ||||
| 	BaseFile | ||||
| 	Caption   string | ||||
| 	Duration  int | ||||
| 	Performer string | ||||
| 	Title     string | ||||
| @@ -282,7 +292,10 @@ type AudioConfig struct { | ||||
|  | ||||
| // values returns a url.Values representation of AudioConfig. | ||||
| func (config AudioConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
| 	if config.Duration != 0 { | ||||
| @@ -295,6 +308,9 @@ func (config AudioConfig) values() (url.Values, error) { | ||||
| 	if config.Title != "" { | ||||
| 		v.Add("title", config.Title) | ||||
| 	} | ||||
| 	if config.Caption != "" { | ||||
| 		v.Add("caption", config.Caption) | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
| @@ -313,6 +329,9 @@ func (config AudioConfig) params() (map[string]string, error) { | ||||
| 	if config.Title != "" { | ||||
| 		params["title"] = config.Title | ||||
| 	} | ||||
| 	if config.Caption != "" { | ||||
| 		params["caption"] = config.Caption | ||||
| 	} | ||||
|  | ||||
| 	return params, nil | ||||
| } | ||||
| @@ -334,7 +353,10 @@ type DocumentConfig struct { | ||||
|  | ||||
| // values returns a url.Values representation of DocumentConfig. | ||||
| func (config DocumentConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
|  | ||||
| @@ -365,7 +387,10 @@ type StickerConfig struct { | ||||
|  | ||||
| // values returns a url.Values representation of StickerConfig. | ||||
| func (config StickerConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
|  | ||||
| @@ -398,7 +423,10 @@ type VideoConfig struct { | ||||
|  | ||||
| // values returns a url.Values representation of VideoConfig. | ||||
| func (config VideoConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
| 	if config.Duration != 0 { | ||||
| @@ -431,12 +459,16 @@ func (config VideoConfig) method() string { | ||||
| // VoiceConfig contains information about a SendVoice request. | ||||
| type VoiceConfig struct { | ||||
| 	BaseFile | ||||
| 	Caption  string | ||||
| 	Duration int | ||||
| } | ||||
|  | ||||
| // values returns a url.Values representation of VoiceConfig. | ||||
| func (config VoiceConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add(config.name(), config.FileID) | ||||
| 	if config.Duration != 0 { | ||||
| @@ -476,7 +508,10 @@ type LocationConfig struct { | ||||
|  | ||||
| // values returns a url.Values representation of LocationConfig. | ||||
| func (config LocationConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) | ||||
| 	v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) | ||||
| @@ -500,7 +535,10 @@ type VenueConfig struct { | ||||
| } | ||||
|  | ||||
| func (config VenueConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) | ||||
| 	v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) | ||||
| @@ -526,7 +564,10 @@ type ContactConfig struct { | ||||
| } | ||||
|  | ||||
| func (config ContactConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add("phone_number", config.PhoneNumber) | ||||
| 	v.Add("first_name", config.FirstName) | ||||
| @@ -539,6 +580,94 @@ func (config ContactConfig) method() string { | ||||
| 	return "sendContact" | ||||
| } | ||||
|  | ||||
| // GameConfig allows you to send a game. | ||||
| type GameConfig struct { | ||||
| 	BaseChat | ||||
| 	GameShortName string | ||||
| } | ||||
|  | ||||
| func (config GameConfig) values() (url.Values, error) { | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add("game_short_name", config.GameShortName) | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| func (config GameConfig) method() string { | ||||
| 	return "sendGame" | ||||
| } | ||||
|  | ||||
| // SetGameScoreConfig allows you to update the game score in a chat. | ||||
| type SetGameScoreConfig struct { | ||||
| 	UserID             int | ||||
| 	Score              int | ||||
| 	Force              bool | ||||
| 	DisableEditMessage bool | ||||
| 	ChatID             int | ||||
| 	ChannelUsername    string | ||||
| 	MessageID          int | ||||
| 	InlineMessageID    string | ||||
| } | ||||
|  | ||||
| func (config SetGameScoreConfig) values() (url.Values, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	v.Add("user_id", strconv.Itoa(config.UserID)) | ||||
| 	v.Add("score", strconv.Itoa(config.Score)) | ||||
| 	if config.InlineMessageID == "" { | ||||
| 		if config.ChannelUsername == "" { | ||||
| 			v.Add("chat_id", strconv.Itoa(config.ChatID)) | ||||
| 		} else { | ||||
| 			v.Add("chat_id", config.ChannelUsername) | ||||
| 		} | ||||
| 		v.Add("message_id", strconv.Itoa(config.MessageID)) | ||||
| 	} else { | ||||
| 		v.Add("inline_message_id", config.InlineMessageID) | ||||
| 	} | ||||
| 	v.Add("disable_edit_message", strconv.FormatBool(config.DisableEditMessage)) | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| func (config SetGameScoreConfig) method() string { | ||||
| 	return "setGameScore" | ||||
| } | ||||
|  | ||||
| // GetGameHighScoresConfig allows you to fetch the high scores for a game. | ||||
| type GetGameHighScoresConfig struct { | ||||
| 	UserID          int | ||||
| 	ChatID          int | ||||
| 	ChannelUsername string | ||||
| 	MessageID       int | ||||
| 	InlineMessageID string | ||||
| } | ||||
|  | ||||
| func (config GetGameHighScoresConfig) values() (url.Values, error) { | ||||
| 	v := url.Values{} | ||||
|  | ||||
| 	v.Add("user_id", strconv.Itoa(config.UserID)) | ||||
| 	if config.InlineMessageID == "" { | ||||
| 		if config.ChannelUsername == "" { | ||||
| 			v.Add("chat_id", strconv.Itoa(config.ChatID)) | ||||
| 		} else { | ||||
| 			v.Add("chat_id", config.ChannelUsername) | ||||
| 		} | ||||
| 		v.Add("message_id", strconv.Itoa(config.MessageID)) | ||||
| 	} else { | ||||
| 		v.Add("inline_message_id", config.InlineMessageID) | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| func (config GetGameHighScoresConfig) method() string { | ||||
| 	return "getGameHighScores" | ||||
| } | ||||
|  | ||||
| // ChatActionConfig contains information about a SendChatAction request. | ||||
| type ChatActionConfig struct { | ||||
| 	BaseChat | ||||
| @@ -547,7 +676,10 @@ type ChatActionConfig struct { | ||||
|  | ||||
| // values returns a url.Values representation of ChatActionConfig. | ||||
| func (config ChatActionConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseChat.values() | ||||
| 	v, err := config.BaseChat.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
| 	v.Add("action", config.Action) | ||||
| 	return v, nil | ||||
| } | ||||
| @@ -566,7 +698,10 @@ type EditMessageTextConfig struct { | ||||
| } | ||||
|  | ||||
| func (config EditMessageTextConfig) values() (url.Values, error) { | ||||
| 	v, _ := config.BaseEdit.values() | ||||
| 	v, err := config.BaseEdit.values() | ||||
| 	if err != nil { | ||||
| 		return v, err | ||||
| 	} | ||||
|  | ||||
| 	v.Add("text", config.Text) | ||||
| 	v.Add("parse_mode", config.ParseMode) | ||||
| @@ -635,6 +770,7 @@ type UpdateConfig struct { | ||||
| type WebhookConfig struct { | ||||
| 	URL         *url.URL | ||||
| 	Certificate interface{} | ||||
| 	MaxConnections int | ||||
| } | ||||
|  | ||||
| // FileBytes contains information about a set of bytes to upload | ||||
| @@ -669,6 +805,8 @@ type CallbackConfig struct { | ||||
| 	CallbackQueryID string `json:"callback_query_id"` | ||||
| 	Text            string `json:"text"` | ||||
| 	ShowAlert       bool   `json:"show_alert"` | ||||
| 	URL             string `json:"url"` | ||||
| 	CacheTime       int    `json:"cache_time"` | ||||
| } | ||||
|  | ||||
| // ChatMemberConfig contains information about a user in a chat for use | ||||
|   | ||||
							
								
								
									
										28
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										28
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,6 +1,7 @@ | ||||
| package tgbotapi | ||||
|  | ||||
| import ( | ||||
| 	"log" | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
| @@ -20,6 +21,7 @@ func NewMessage(chatID int64, text string) MessageConfig { | ||||
|  | ||||
| // 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{ | ||||
| @@ -316,6 +318,21 @@ func NewWebhookWithCert(link string, file interface{}) WebhookConfig { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewWebhookWithCert creates a new webhook with a certificate and max_connections. | ||||
| // | ||||
| // link is the url you wish to get webhooks, | ||||
| // file contains a string to a file, FileReader, or FileBytes. | ||||
| // maxConnections defines maximum number of connections from telegram to your server | ||||
| func NewWebhookWithCertAndMaxConnections(link string, file interface{}, maxConnections int) WebhookConfig { | ||||
| 	u, _ := url.Parse(link) | ||||
|  | ||||
| 	return WebhookConfig{ | ||||
| 		URL:         u, | ||||
| 		Certificate: file, | ||||
| 		MaxConnections: maxConnections, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewInlineQueryResultArticle creates a new inline query article. | ||||
| func NewInlineQueryResultArticle(id, title, messageText string) InlineQueryResultArticle { | ||||
| 	return InlineQueryResultArticle{ | ||||
| @@ -479,12 +496,23 @@ func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKe | ||||
| // NewHideKeyboard hides the keyboard, with the option for being selective | ||||
| // or hiding for everyone. | ||||
| func NewHideKeyboard(selective bool) ReplyKeyboardHide { | ||||
| 	log.Println("NewHideKeyboard is deprecated, please use NewRemoveKeyboard") | ||||
|  | ||||
| 	return ReplyKeyboardHide{ | ||||
| 		HideKeyboard: true, | ||||
| 		Selective:    selective, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewRemoveKeyboard hides the keyboard, with the option for being selective | ||||
| // or hiding for everyone. | ||||
| func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove { | ||||
| 	return ReplyKeyboardRemove{ | ||||
| 		RemoveKeyboard: true, | ||||
| 		Selective:      selective, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewKeyboardButton creates a regular keyboard button. | ||||
| func NewKeyboardButton(text string) KeyboardButton { | ||||
| 	return KeyboardButton{ | ||||
|   | ||||
							
								
								
									
										117
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										117
									
								
								vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -12,10 +12,17 @@ import ( | ||||
| // 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"` | ||||
| 	Ok          bool                `json:"ok"` | ||||
| 	Result      json.RawMessage     `json:"result"` | ||||
| 	ErrorCode   int                 `json:"error_code"` | ||||
| 	Description string              `json:"description"` | ||||
| 	Parameters  *ResponseParameters `json:"parameters"` | ||||
| } | ||||
|  | ||||
| // ResponseParameters are various errors that can be returned in APIResponse. | ||||
| type ResponseParameters struct { | ||||
| 	MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional | ||||
| 	RetryAfter      int   `json:"retry_after"`        // optional | ||||
| } | ||||
|  | ||||
| // Update is an update response, from GetUpdates. | ||||
| @@ -23,11 +30,23 @@ type Update struct { | ||||
| 	UpdateID           int                 `json:"update_id"` | ||||
| 	Message            *Message            `json:"message"` | ||||
| 	EditedMessage      *Message            `json:"edited_message"` | ||||
| 	ChannelPost        *Message            `json:"channel_post"` | ||||
| 	EditedChannelPost  *Message            `json:"edited_channel_post"` | ||||
| 	InlineQuery        *InlineQuery        `json:"inline_query"` | ||||
| 	ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"` | ||||
| 	CallbackQuery      *CallbackQuery      `json:"callback_query"` | ||||
| } | ||||
|  | ||||
| // UpdatesChannel is the channel for getting updates. | ||||
| type UpdatesChannel <-chan Update | ||||
|  | ||||
| // Clear discards all unprocessed incoming updates. | ||||
| func (ch UpdatesChannel) Clear() { | ||||
| 	for len(ch) != 0 { | ||||
| 		<-ch | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // User is a user on Telegram. | ||||
| type User struct { | ||||
| 	ID        int    `json:"id"` | ||||
| @@ -61,12 +80,13 @@ type GroupChat struct { | ||||
|  | ||||
| // 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 | ||||
| 	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 | ||||
| 	AllMembersAreAdmins bool   `json:"all_members_are_administrators"` // optional | ||||
| } | ||||
|  | ||||
| // IsPrivate returns if the Chat is a private conversation. | ||||
| @@ -103,6 +123,7 @@ type Message struct { | ||||
| 	Chat                  *Chat            `json:"chat"` | ||||
| 	ForwardFrom           *User            `json:"forward_from"`            // optional | ||||
| 	ForwardFromChat       *Chat            `json:"forward_from_chat"`       // optional | ||||
| 	ForwardFromMessageID  int              `json:"forward_from_message_id"` // optional | ||||
| 	ForwardDate           int              `json:"forward_date"`            // optional | ||||
| 	ReplyToMessage        *Message         `json:"reply_to_message"`        // optional | ||||
| 	EditDate              int              `json:"edit_date"`               // optional | ||||
| @@ -110,6 +131,7 @@ type Message struct { | ||||
| 	Entities              *[]MessageEntity `json:"entities"`                // optional | ||||
| 	Audio                 *Audio           `json:"audio"`                   // optional | ||||
| 	Document              *Document        `json:"document"`                // optional | ||||
| 	Game                  *Game            `json:"game"`                    // optional | ||||
| 	Photo                 *[]PhotoSize     `json:"photo"`                   // optional | ||||
| 	Sticker               *Sticker         `json:"sticker"`                 // optional | ||||
| 	Video                 *Video           `json:"video"`                   // optional | ||||
| @@ -314,6 +336,12 @@ type ReplyKeyboardHide struct { | ||||
| 	Selective    bool `json:"selective"` // optional | ||||
| } | ||||
|  | ||||
| // ReplyKeyboardRemove allows the Bot to hide a custom keyboard. | ||||
| type ReplyKeyboardRemove struct { | ||||
| 	RemoveKeyboard bool `json:"remove_keyboard"` | ||||
| 	Selective      bool `json:"selective"` | ||||
| } | ||||
|  | ||||
| // InlineKeyboardMarkup is a custom keyboard presented for an inline bot. | ||||
| type InlineKeyboardMarkup struct { | ||||
| 	InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"` | ||||
| @@ -324,11 +352,15 @@ type InlineKeyboardMarkup struct { | ||||
| // | ||||
| // Note that some values are references as even an empty string | ||||
| // will change behavior. | ||||
| // | ||||
| // CallbackGame, if set, MUST be first button in first row. | ||||
| 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 | ||||
| 	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 | ||||
| 	SwitchInlineQueryCurrentChat *string       `json:"switch_inline_query_current_chat,omitempty"` // optional | ||||
| 	CallbackGame                 *CallbackGame `json:"callback_game,omitempty"`                    // optional | ||||
| } | ||||
|  | ||||
| // CallbackQuery is data sent when a keyboard button with callback data | ||||
| @@ -338,7 +370,9 @@ type CallbackQuery struct { | ||||
| 	From            *User    `json:"from"` | ||||
| 	Message         *Message `json:"message"`           // optional | ||||
| 	InlineMessageID string   `json:"inline_message_id"` // optional | ||||
| 	Data            string   `json:"data"`              // optional | ||||
| 	ChatInstance    string   `json:"chat_instance"` | ||||
| 	Data            string   `json:"data"`            // optional | ||||
| 	GameShortName   string   `json:"game_short_name"` // optional | ||||
| } | ||||
|  | ||||
| // ForceReply allows the Bot to have users directly reply to it without | ||||
| @@ -369,6 +403,49 @@ 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" } | ||||
|  | ||||
| // Game is a game within Telegram. | ||||
| type Game struct { | ||||
| 	Title        string          `json:"title"` | ||||
| 	Description  string          `json:"description"` | ||||
| 	Photo        []PhotoSize     `json:"photo"` | ||||
| 	Text         string          `json:"text"` | ||||
| 	TextEntities []MessageEntity `json:"text_entities"` | ||||
| 	Animation    Animation       `json:"animation"` | ||||
| } | ||||
|  | ||||
| // Animation is a GIF animation demonstrating the game. | ||||
| type Animation struct { | ||||
| 	FileID   string    `json:"file_id"` | ||||
| 	Thumb    PhotoSize `json:"thumb"` | ||||
| 	FileName string    `json:"file_name"` | ||||
| 	MimeType string    `json:"mime_type"` | ||||
| 	FileSize int       `json:"file_size"` | ||||
| } | ||||
|  | ||||
| // GameHighScore is a user's score and position on the leaderboard. | ||||
| type GameHighScore struct { | ||||
| 	Position int  `json:"position"` | ||||
| 	User     User `json:"user"` | ||||
| 	Score    int  `json:"score"` | ||||
| } | ||||
|  | ||||
| // CallbackGame is for starting a game in an inline keyboard button. | ||||
| type CallbackGame struct{} | ||||
|  | ||||
| // WebhookInfo is information about a currently set webhook. | ||||
| type WebhookInfo struct { | ||||
| 	URL                  string `json:"url"` | ||||
| 	HasCustomCertificate bool   `json:"has_custom_certificate"` | ||||
| 	PendingUpdateCount   int    `json:"pending_update_count"` | ||||
| 	LastErrorDate        int    `json:"last_error_date"`    // optional | ||||
| 	LastErrorMessage     string `json:"last_error_message"` // optional | ||||
| } | ||||
|  | ||||
| // IsSet returns true if a webhook is currently set. | ||||
| func (info WebhookInfo) IsSet() bool { | ||||
| 	return info.URL != "" | ||||
| } | ||||
|  | ||||
| // InlineQuery is a Query from Telegram for an inline request. | ||||
| type InlineQuery struct { | ||||
| 	ID       string    `json:"id"` | ||||
| @@ -460,6 +537,7 @@ type InlineQueryResultAudio struct { | ||||
| 	ID                  string                `json:"id"`        // required | ||||
| 	URL                 string                `json:"audio_url"` // required | ||||
| 	Title               string                `json:"title"`     // required | ||||
| 	Caption             string                `json:"caption"` | ||||
| 	Performer           string                `json:"performer"` | ||||
| 	Duration            int                   `json:"audio_duration"` | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| @@ -472,6 +550,7 @@ type InlineQueryResultVoice struct { | ||||
| 	ID                  string                `json:"id"`        // required | ||||
| 	URL                 string                `json:"voice_url"` // required | ||||
| 	Title               string                `json:"title"`     // required | ||||
| 	Caption             string                `json:"caption"` | ||||
| 	Duration            int                   `json:"voice_duration"` | ||||
| 	ReplyMarkup         *InlineKeyboardMarkup `json:"reply_markup,omitempty"` | ||||
| 	InputMessageContent interface{}           `json:"input_message_content,omitempty"` | ||||
| @@ -507,6 +586,14 @@ type InlineQueryResultLocation struct { | ||||
| 	ThumbHeight         int                   `json:"thumb_height"` | ||||
| } | ||||
|  | ||||
| // InlineQueryResultGame is an inline query response game. | ||||
| type InlineQueryResultGame struct { | ||||
| 	Type          string                `json:"type"` | ||||
| 	ID            string                `json:"id"` | ||||
| 	GameShortName string                `json:"game_short_name"` | ||||
| 	ReplyMarkup   *InlineKeyboardMarkup `json:"reply_markup"` | ||||
| } | ||||
|  | ||||
| // ChosenInlineResult is an inline query result chosen by a User | ||||
| type ChosenInlineResult struct { | ||||
| 	ResultID        string    `json:"result_id"` | ||||
|   | ||||
							
								
								
									
										4
									
								
								vendor/github.com/mattermost/platform/einterfaces/cluster.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/mattermost/platform/einterfaces/cluster.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -11,13 +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 | ||||
| } | ||||
							
								
								
									
										8
									
								
								vendor/github.com/mattermost/platform/model/channel_member.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/mattermost/platform/model/channel_member.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -18,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"` | ||||
|   | ||||
							
								
								
									
										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 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										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 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										279
									
								
								vendor/github.com/mattermost/platform/model/client.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										279
									
								
								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" | ||||
| @@ -291,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) { | ||||
| @@ -500,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 { | ||||
| @@ -610,6 +616,19 @@ func (c *Client) AutocompleteUsersInTeam(term string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| @@ -802,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) | ||||
| @@ -846,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 | ||||
| @@ -940,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 | ||||
| @@ -1143,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 | ||||
| @@ -1153,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 | ||||
| @@ -1173,6 +1247,16 @@ func (c *Client) GetChannels(etag string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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 | ||||
| @@ -1240,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 | ||||
| @@ -1252,6 +1337,23 @@ func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *Ap | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| @@ -1272,6 +1374,18 @@ func (c *Client) GetChannelMember(channelId string, userId string) (*Result, *Ap | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) CreatePost(post *Post) (*Result, *AppError) { | ||||
| 	if r, err := c.DoApiPost(c.GetChannelRoute(post.ChannelId)+"/posts/create", post.ToJson()); err != nil { | ||||
| 		return nil, err | ||||
| @@ -1342,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 | ||||
| @@ -1629,6 +1758,7 @@ func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) { | ||||
| // 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 | ||||
| @@ -1663,6 +1793,36 @@ func (c *Client) GetTeamMembers(teamId string, offset int, limit int) (*Result, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) { | ||||
| @@ -1687,6 +1847,18 @@ func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) { | ||||
| @@ -2030,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 | ||||
| 	} | ||||
| @@ -2060,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 | ||||
| 	} | ||||
| } | ||||
| @@ -2071,6 +2245,7 @@ func (c *Client) RemoveCertificateFile(filename string) *AppError { | ||||
| 		return err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| @@ -2082,6 +2257,7 @@ func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{}, | ||||
| 		return nil, err | ||||
| 	} else { | ||||
| 		defer closeBody(r) | ||||
| 		c.fillInExtraProperties(r) | ||||
| 		return StringInterfaceFromJson(r.Body), nil | ||||
| 	} | ||||
| } | ||||
| @@ -2110,3 +2286,36 @@ func (c *Client) GetFileInfosForPost(channelId string, postId string, etag strin | ||||
| 		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"` | ||||
| } | ||||
|   | ||||
							
								
								
									
										104
									
								
								vendor/github.com/mattermost/platform/model/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										104
									
								
								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 = "********************************" | ||||
|  | ||||
| @@ -80,6 +81,7 @@ type ServiceSettings struct { | ||||
| 	EnableSecurityFixAlert            *bool | ||||
| 	EnableInsecureOutgoingConnections *bool | ||||
| 	EnableMultifactorAuthentication   *bool | ||||
| 	EnforceMultifactorAuthentication  *bool | ||||
| 	AllowCorsFrom                     *string | ||||
| 	SessionLengthWebInDays            *int | ||||
| 	SessionLengthMobileInDays         *int | ||||
| @@ -98,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 | ||||
| @@ -219,8 +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 { | ||||
| @@ -243,6 +260,7 @@ type LdapSettings struct { | ||||
| 	UsernameAttribute  *string | ||||
| 	NicknameAttribute  *string | ||||
| 	IdAttribute        *string | ||||
| 	PositionAttribute  *string | ||||
|  | ||||
| 	// Syncronization | ||||
| 	SyncIntervalMinutes *int | ||||
| @@ -289,6 +307,7 @@ type SamlSettings struct { | ||||
| 	UsernameAttribute  *string | ||||
| 	NicknameAttribute  *string | ||||
| 	LocaleAttribute    *string | ||||
| 	PositionAttribute  *string | ||||
|  | ||||
| 	LoginButtonText *string | ||||
| } | ||||
| @@ -330,6 +349,8 @@ type Config struct { | ||||
| 	SamlSettings         SamlSettings | ||||
| 	NativeAppSettings    NativeAppSettings | ||||
| 	ClusterSettings      ClusterSettings | ||||
| 	MetricsSettings      MetricsSettings | ||||
| 	AnalyticsSettings    AnalyticsSettings | ||||
| 	WebrtcSettings       WebrtcSettings | ||||
| } | ||||
|  | ||||
| @@ -376,23 +397,32 @@ func (o *Config) SetDefaults() { | ||||
| 		// 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 | ||||
| 	} | ||||
|  | ||||
| 	if len(*o.FileSettings.PublicLinkSalt) == 0 { | ||||
| 		o.FileSettings.PublicLinkSalt = new(string) | ||||
| 		*o.FileSettings.PublicLinkSalt = NewRandomString(32) | ||||
| 	} | ||||
|  | ||||
| 	if o.FileSettings.InitialFont == "" { | ||||
| 		// Defaults to "luximbi.ttf" | ||||
| 		o.FileSettings.InitialFont = "luximbi.ttf" | ||||
| 	} | ||||
|  | ||||
| 	if len(o.EmailSettings.InviteSalt) == 0 { | ||||
| 		o.EmailSettings.InviteSalt = NewRandomString(32) | ||||
| 	} | ||||
| @@ -426,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 | ||||
| @@ -491,6 +526,30 @@ 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 | ||||
| @@ -501,6 +560,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*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) | ||||
|  | ||||
| @@ -671,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 | ||||
| @@ -772,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 | ||||
| @@ -882,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 = "" | ||||
| @@ -952,6 +1041,11 @@ func (o *Config) SetDefaults() { | ||||
| 		*o.ServiceSettings.Forward80To443 = false | ||||
| 	} | ||||
|  | ||||
| 	if o.MetricsSettings.BlockProfileRate == nil { | ||||
| 		o.MetricsSettings.BlockProfileRate = new(int) | ||||
| 		*o.MetricsSettings.BlockProfileRate = 0 | ||||
| 	} | ||||
|  | ||||
| 	o.defaultWebrtcSettings() | ||||
| } | ||||
|  | ||||
| @@ -987,6 +1081,10 @@ func (o *Config) IsValid() *AppError { | ||||
| 		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, "") | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										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"])) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|   | ||||
							
								
								
									
										7
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/mattermost/platform/model/license.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -39,6 +39,7 @@ 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"` | ||||
| @@ -55,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, | ||||
| @@ -104,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 | ||||
|   | ||||
							
								
								
									
										21
									
								
								vendor/github.com/mattermost/platform/model/post.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								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 { | ||||
| @@ -38,6 +44,7 @@ type Post struct { | ||||
| 	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 { | ||||
| @@ -102,28 +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(ArrayToJson(o.FileIds)) > 150 { | ||||
| 	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)) > 8000 { | ||||
| 	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) | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										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() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										13
									
								
								vendor/github.com/mattermost/platform/model/status.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								vendor/github.com/mattermost/platform/model/status.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -22,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 { | ||||
| @@ -44,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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										5
									
								
								vendor/github.com/mattermost/platform/model/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								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"` | ||||
| @@ -130,6 +131,10 @@ func (o *Team) IsValid() *AppError { | ||||
| 		return NewLocAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id) | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										25
									
								
								vendor/github.com/mattermost/platform/model/team_member.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/mattermost/platform/model/team_member.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -16,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 { | ||||
| @@ -55,6 +61,25 @@ func TeamMembersFromJson(data io.Reader) []*TeamMember { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TeamsUnreadToJson(o []*TeamUnread) string { | ||||
| 	if b, err := json.Marshal(o); err != nil { | ||||
| 		return "[]" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (o *TeamMember) IsValid() *AppError { | ||||
|  | ||||
| 	if len(o.TeamId) != 26 { | ||||
|   | ||||
							
								
								
									
										40
									
								
								vendor/github.com/mattermost/platform/model/team_signup.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/mattermost/platform/model/team_signup.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,40 +0,0 @@ | ||||
| // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. | ||||
| // See License.txt for license information. | ||||
|  | ||||
| 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"` | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 	b, err := json.Marshal(o) | ||||
| 	if err != nil { | ||||
| 		return "" | ||||
| 	} else { | ||||
| 		return string(b) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										5
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/mattermost/platform/model/user.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -37,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"` | ||||
| @@ -78,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) | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/mattermost/platform/model/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -304,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]+$`) | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/mattermost/platform/model/version.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -13,6 +13,7 @@ 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", | ||||
|   | ||||
							
								
								
									
										12
									
								
								vendor/github.com/mattermost/platform/model/websocket_message.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								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" | ||||
| @@ -27,6 +28,8 @@ const ( | ||||
| 	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 { | ||||
| @@ -34,6 +37,7 @@ type WebSocketMessage interface { | ||||
| 	IsValid() bool | ||||
| 	DoPreComputeJson() | ||||
| 	GetPreComputeJson() []byte | ||||
| 	EventType() string | ||||
| } | ||||
|  | ||||
| type WebsocketBroadcast struct { | ||||
| @@ -63,6 +67,10 @@ 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 { | ||||
| @@ -120,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 { | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/nlopes/slack/bots.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/nlopes/slack/bots.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -30,7 +30,7 @@ func botRequest(path string, values url.Values, debug bool) (*botResponseFull, e | ||||
| 	return response, nil | ||||
| } | ||||
|  | ||||
| // GetBotInfo will retrive the complete bot information | ||||
| // GetBotInfo will retrieve the complete bot information | ||||
| func (api *Client) GetBotInfo(bot string) (*Bot, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
|   | ||||
							
								
								
									
										24
									
								
								vendor/github.com/nlopes/slack/chat.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/nlopes/slack/chat.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -29,18 +29,18 @@ type chatResponseFull struct { | ||||
|  | ||||
| // PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request | ||||
| type PostMessageParameters struct { | ||||
| 	Text        string | ||||
| 	Username    string | ||||
| 	AsUser      bool | ||||
| 	Parse       string | ||||
| 	LinkNames   int | ||||
| 	Attachments []Attachment | ||||
| 	UnfurlLinks bool | ||||
| 	UnfurlMedia bool | ||||
| 	IconURL     string | ||||
| 	IconEmoji   string | ||||
| 	Markdown    bool `json:"mrkdwn,omitempty"` | ||||
| 	EscapeText  bool | ||||
| 	Text        string       `json:"text"` | ||||
| 	Username    string       `json:"user_name"` | ||||
| 	AsUser      bool         `json:"as_user"` | ||||
| 	Parse       string       `json:"parse"` | ||||
| 	LinkNames   int          `json:"link_names"` | ||||
| 	Attachments []Attachment `json:"attachments"` | ||||
| 	UnfurlLinks bool         `json:"unfurl_links"` | ||||
| 	UnfurlMedia bool         `json:"unfurl_media"` | ||||
| 	IconURL     string       `json:"icon_url"` | ||||
| 	IconEmoji   string       `json:"icon_emoji"` | ||||
| 	Markdown    bool         `json:"mrkdwn,omitempty"` | ||||
| 	EscapeText  bool         `json:"escape_text"` | ||||
| } | ||||
|  | ||||
| // NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set | ||||
|   | ||||
							
								
								
									
										52
									
								
								vendor/github.com/nlopes/slack/examples/websocket/websocket.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										52
									
								
								vendor/github.com/nlopes/slack/examples/websocket/websocket.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -17,42 +17,38 @@ func main() { | ||||
| 	rtm := api.NewRTM() | ||||
| 	go rtm.ManageConnection() | ||||
|  | ||||
| Loop: | ||||
| 	for { | ||||
| 		select { | ||||
| 		case msg := <-rtm.IncomingEvents: | ||||
| 			fmt.Print("Event Received: ") | ||||
| 			switch ev := msg.Data.(type) { | ||||
| 			case *slack.HelloEvent: | ||||
| 				// Ignore hello | ||||
| 	for msg := range rtm.IncomingEvents { | ||||
| 		fmt.Print("Event Received: ") | ||||
| 		switch ev := msg.Data.(type) { | ||||
| 		case *slack.HelloEvent: | ||||
| 			// Ignore hello | ||||
|  | ||||
| 			case *slack.ConnectedEvent: | ||||
| 				fmt.Println("Infos:", ev.Info) | ||||
| 				fmt.Println("Connection counter:", ev.ConnectionCount) | ||||
| 				// Replace #general with your Channel ID | ||||
| 				rtm.SendMessage(rtm.NewOutgoingMessage("Hello world", "#general")) | ||||
| 		case *slack.ConnectedEvent: | ||||
| 			fmt.Println("Infos:", ev.Info) | ||||
| 			fmt.Println("Connection counter:", ev.ConnectionCount) | ||||
| 			// Replace #general with your Channel ID | ||||
| 			rtm.SendMessage(rtm.NewOutgoingMessage("Hello world", "#general")) | ||||
|  | ||||
| 			case *slack.MessageEvent: | ||||
| 				fmt.Printf("Message: %v\n", ev) | ||||
| 		case *slack.MessageEvent: | ||||
| 			fmt.Printf("Message: %v\n", ev) | ||||
|  | ||||
| 			case *slack.PresenceChangeEvent: | ||||
| 				fmt.Printf("Presence Change: %v\n", ev) | ||||
| 		case *slack.PresenceChangeEvent: | ||||
| 			fmt.Printf("Presence Change: %v\n", ev) | ||||
|  | ||||
| 			case *slack.LatencyReport: | ||||
| 				fmt.Printf("Current latency: %v\n", ev.Value) | ||||
| 		case *slack.LatencyReport: | ||||
| 			fmt.Printf("Current latency: %v\n", ev.Value) | ||||
|  | ||||
| 			case *slack.RTMError: | ||||
| 				fmt.Printf("Error: %s\n", ev.Error()) | ||||
| 		case *slack.RTMError: | ||||
| 			fmt.Printf("Error: %s\n", ev.Error()) | ||||
|  | ||||
| 			case *slack.InvalidAuthEvent: | ||||
| 				fmt.Printf("Invalid credentials") | ||||
| 				break Loop | ||||
| 		case *slack.InvalidAuthEvent: | ||||
| 			fmt.Printf("Invalid credentials") | ||||
| 			return | ||||
|  | ||||
| 			default: | ||||
| 		default: | ||||
|  | ||||
| 				// Ignore other events.. | ||||
| 				// fmt.Printf("Unexpected: %v\n", msg.Data) | ||||
| 			} | ||||
| 			// Ignore other events.. | ||||
| 			// fmt.Printf("Unexpected: %v\n", msg.Data) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										10
									
								
								vendor/github.com/nlopes/slack/info.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/nlopes/slack/info.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -198,3 +198,13 @@ func (info Info) GetGroupByID(groupID string) *Group { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GetIMByID returns an IM given an IM id | ||||
| func (info Info) GetIMByID(imID string) *IM { | ||||
| 	for _, im := range info.IMs { | ||||
| 		if im.ID == imID { | ||||
| 			return &im | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/nlopes/slack/oauth.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/nlopes/slack/oauth.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -8,6 +8,7 @@ import ( | ||||
| type OAuthResponseIncomingWebhook struct { | ||||
| 	URL              string `json:"url"` | ||||
| 	Channel          string `json:"channel"` | ||||
| 	ChannelID        string `json:"channel_id,omitempty"` | ||||
| 	ConfigurationURL string `json:"configuration_url"` | ||||
| } | ||||
|  | ||||
| @@ -23,6 +24,7 @@ type OAuthResponse struct { | ||||
| 	TeamID          string                       `json:"team_id"` | ||||
| 	IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"` | ||||
| 	Bot             OAuthResponseBot             `json:"bot"` | ||||
| 	UserID          string                       `json:"user_id,omitempty"` | ||||
| 	SlackResponse | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										41
									
								
								vendor/github.com/nlopes/slack/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										41
									
								
								vendor/github.com/nlopes/slack/team.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -44,6 +44,16 @@ type Login struct { | ||||
| 	Region    string `json:"region"` | ||||
| } | ||||
|  | ||||
| type BillableInfoResponse struct { | ||||
| 	BillableInfo map[string]BillingActive `json:"billable_info"` | ||||
| 	SlackResponse | ||||
|  | ||||
| } | ||||
|  | ||||
| type BillingActive struct { | ||||
| 	BillingActive bool `json:"billing_active"` | ||||
| } | ||||
|  | ||||
| // AccessLogParameters contains all the parameters necessary (including the optional ones) for a GetAccessLogs() request | ||||
| type AccessLogParameters struct { | ||||
| 	Count         int | ||||
| @@ -73,6 +83,20 @@ func teamRequest(path string, values url.Values, debug bool) (*TeamResponse, err | ||||
| 	return response, nil | ||||
| } | ||||
|  | ||||
| func billableInfoRequest(path string, values url.Values, debug bool) (map[string]BillingActive, error) { | ||||
| 	response := &BillableInfoResponse{} | ||||
| 	err := post(path, values, response, debug) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if !response.Ok { | ||||
| 		return nil, errors.New(response.Error) | ||||
| 	} | ||||
|  | ||||
| 	return response.BillableInfo, nil | ||||
| } | ||||
|  | ||||
| func accessLogsRequest(path string, values url.Values, debug bool) (*LoginResponse, error) { | ||||
| 	response := &LoginResponse{} | ||||
| 	err := post(path, values, response, debug) | ||||
| @@ -117,3 +141,20 @@ func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging, | ||||
| 	return response.Logins, &response.Paging, nil | ||||
| } | ||||
|  | ||||
| func (api *Client) GetBillableInfo(user string) (map[string]BillingActive, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 		"user": {user}, | ||||
| 	} | ||||
|  | ||||
| 	return billableInfoRequest("team.billableInfo", values, api.debug) | ||||
| } | ||||
|  | ||||
| // GetBillableInfoForTeam returns the billing_active status of all users on the team. | ||||
| func (api *Client) GetBillableInfoForTeam() (map[string]BillingActive, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
| 	} | ||||
|  | ||||
| 	return billableInfoRequest("team.billableInfo", values, api.debug) | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/nlopes/slack/users.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/nlopes/slack/users.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -122,7 +122,7 @@ func (api *Client) GetUserPresence(user string) (*UserPresence, error) { | ||||
| 	return &response.UserPresence, nil | ||||
| } | ||||
|  | ||||
| // GetUserInfo will retrive the complete user information | ||||
| // GetUserInfo will retrieve the complete user information | ||||
| func (api *Client) GetUserInfo(user string) (*User, error) { | ||||
| 	values := url.Values{ | ||||
| 		"token": {api.config.token}, | ||||
|   | ||||
							
								
								
									
										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) | ||||
| } | ||||
							
								
								
									
										45
									
								
								vendor/github.com/thoj/go-ircevent/irc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										45
									
								
								vendor/github.com/thoj/go-ircevent/irc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -74,7 +74,9 @@ func (irc *Connection) readLoop() { | ||||
| 				irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg)) | ||||
| 			} | ||||
|  | ||||
| 			irc.Lock() | ||||
| 			irc.lastMessage = time.Now() | ||||
| 			irc.Unlock() | ||||
| 			event, err := parseToEvent(msg) | ||||
| 			event.Connection = irc | ||||
| 			if err == nil { | ||||
| @@ -171,10 +173,12 @@ func (irc *Connection) pingLoop() { | ||||
| 			//Ping at the ping frequency | ||||
| 			irc.SendRawf("PING %d", time.Now().UnixNano()) | ||||
| 			//Try to recapture nickname if it's not as configured. | ||||
| 			irc.Lock() | ||||
| 			if irc.nick != irc.nickcurrent { | ||||
| 				irc.nickcurrent = irc.nick | ||||
| 				irc.SendRawf("NICK %s", irc.nick) | ||||
| 			} | ||||
| 			irc.Unlock() | ||||
| 		case <-irc.end: | ||||
| 			ticker.Stop() | ||||
| 			ticker2.Stop() | ||||
| @@ -183,13 +187,21 @@ func (irc *Connection) pingLoop() { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (irc *Connection) isQuitting() bool { | ||||
| 	irc.Lock() | ||||
| 	defer irc.Unlock() | ||||
| 	return irc.quit | ||||
| } | ||||
|  | ||||
| // Main loop to control the connection. | ||||
| func (irc *Connection) Loop() { | ||||
| 	errChan := irc.ErrorChan() | ||||
| 	for !irc.quit { | ||||
| 	for !irc.isQuitting() { | ||||
| 		err := <-errChan | ||||
| 		irc.Log.Printf("Error, disconnected: %s\n", err) | ||||
| 		for !irc.quit { | ||||
| 		close(irc.end) | ||||
| 		irc.Wait() | ||||
| 		for !irc.isQuitting() { | ||||
| 			irc.Log.Printf("Error, disconnected: %s\n", err) | ||||
| 			if err = irc.Reconnect(); err != nil { | ||||
| 				irc.Log.Printf("Error while reconnecting: %s\n", err) | ||||
| 				time.Sleep(60 * time.Second) | ||||
| @@ -211,8 +223,10 @@ func (irc *Connection) Quit() { | ||||
| 	} | ||||
|  | ||||
| 	irc.SendRaw(quit) | ||||
| 	irc.Lock() | ||||
| 	irc.stopped = true | ||||
| 	irc.quit = true | ||||
| 	irc.Unlock() | ||||
| } | ||||
|  | ||||
| // Use the connection to join a given channel. | ||||
| @@ -341,37 +355,14 @@ func (irc *Connection) Connected() bool { | ||||
| // A disconnect sends all buffered messages (if possible), | ||||
| // stops all goroutines and then closes the socket. | ||||
| func (irc *Connection) Disconnect() { | ||||
| 	for event := range irc.events { | ||||
| 		irc.ClearCallback(event) | ||||
| 	} | ||||
| 	if irc.end != nil { | ||||
| 		close(irc.end) | ||||
| 	} | ||||
|  | ||||
| 	irc.end = nil | ||||
|  | ||||
| 	if irc.pwrite != nil { | ||||
| 		close(irc.pwrite) | ||||
| 	} | ||||
|  | ||||
| 	irc.Wait() | ||||
| 	if irc.socket != nil { | ||||
| 		irc.socket.Close() | ||||
| 	} | ||||
| 	irc.socket = nil | ||||
| 	irc.ErrorChan() <- ErrDisconnected | ||||
| } | ||||
|  | ||||
| // Reconnect to a server using the current connection. | ||||
| func (irc *Connection) Reconnect() error { | ||||
| 	if irc.end != nil { | ||||
| 		close(irc.end) | ||||
| 	} | ||||
|  | ||||
| 	irc.end = nil | ||||
|  | ||||
| 	irc.Wait() //make sure that wait group is cleared ensuring that all spawned goroutines have completed | ||||
|  | ||||
| 	irc.end = make(chan struct{}) | ||||
| 	return irc.Connect(irc.Server) | ||||
| } | ||||
| @@ -427,7 +418,7 @@ func (irc *Connection) Connect(server string) error { | ||||
| 	} | ||||
|  | ||||
| 	irc.stopped = false | ||||
| 	//irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr()) | ||||
| 	irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr()) | ||||
|  | ||||
| 	irc.pwrite = make(chan string, 10) | ||||
| 	irc.Error = make(chan error, 2) | ||||
|   | ||||
							
								
								
									
										9
									
								
								vendor/github.com/thoj/go-ircevent/irc_callback.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/thoj/go-ircevent/irc_callback.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -136,9 +136,8 @@ func (irc *Connection) RunCallbacks(event *Event) { | ||||
| func (irc *Connection) setupCallbacks() { | ||||
| 	irc.events = make(map[string]map[int]func(*Event)) | ||||
|  | ||||
| 	//Handle error events. This has to be called in a new thred to allow | ||||
| 	//readLoop to exit | ||||
| 	irc.AddCallback("ERROR", func(e *Event) { go irc.Disconnect() }) | ||||
| 	//Handle error events. | ||||
| 	irc.AddCallback("ERROR", func(e *Event) { irc.Disconnect() }) | ||||
|  | ||||
| 	//Handle ping events | ||||
| 	irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) }) | ||||
| @@ -201,7 +200,7 @@ func (irc *Connection) setupCallbacks() { | ||||
| 		ns, _ := strconv.ParseInt(e.Message(), 10, 64) | ||||
| 		delta := time.Duration(time.Now().UnixNano() - ns) | ||||
| 		if irc.Debug { | ||||
| 			irc.Log.Printf("Lag: %vs\n", delta) | ||||
| 			irc.Log.Printf("Lag: %.3f s\n", delta.Seconds()) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| @@ -216,6 +215,8 @@ func (irc *Connection) setupCallbacks() { | ||||
| 	// 1: RPL_WELCOME "Welcome to the Internet Relay Network <nick>!<user>@<host>" | ||||
| 	// Set irc.nickcurrent to the actually used nick in this connection. | ||||
| 	irc.AddCallback("001", func(e *Event) { | ||||
| 		irc.Lock() | ||||
| 		irc.nickcurrent = e.Arguments[0] | ||||
| 		irc.Unlock() | ||||
| 	}) | ||||
| } | ||||
|   | ||||
							
								
								
									
										3
									
								
								vendor/github.com/thoj/go-ircevent/irc_struct.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/thoj/go-ircevent/irc_struct.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -13,6 +13,7 @@ import ( | ||||
| ) | ||||
|  | ||||
| type Connection struct { | ||||
| 	sync.Mutex | ||||
| 	sync.WaitGroup | ||||
| 	Debug        bool | ||||
| 	Error        chan error | ||||
| @@ -46,7 +47,7 @@ type Connection struct { | ||||
| 	Log                    *log.Logger | ||||
|  | ||||
| 	stopped bool | ||||
| 	quit    bool | ||||
| 	quit    bool //User called Quit, do not reconnect. | ||||
| } | ||||
|  | ||||
| // A struct to represent an event. | ||||
|   | ||||
							
								
								
									
										14
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							| @@ -63,7 +63,7 @@ | ||||
| 			"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", | ||||
| 			"revision": "3293f3ad8411de32d99e443cd82aec7c3bb01a5a", | ||||
| 			"branch": "master", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @@ -95,8 +95,8 @@ | ||||
| 			"importpath": "github.com/mattermost/platform/einterfaces", | ||||
| 			"repository": "https://github.com/mattermost/platform", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "b55ec6148caa93d54b660afe55408c643d217108", | ||||
| 			"branch": "release-3.5", | ||||
| 			"revision": "976296cd52533ff565407e55e872339cc312a0cf", | ||||
| 			"branch": "release-3.6", | ||||
| 			"path": "/einterfaces", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @@ -104,8 +104,8 @@ | ||||
| 			"importpath": "github.com/mattermost/platform/model", | ||||
| 			"repository": "https://github.com/mattermost/platform", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "b55ec6148caa93d54b660afe55408c643d217108", | ||||
| 			"branch": "release-3.5", | ||||
| 			"revision": "976296cd52533ff565407e55e872339cc312a0cf", | ||||
| 			"branch": "release-3.6", | ||||
| 			"path": "/model", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @@ -146,7 +146,7 @@ | ||||
| 			"importpath": "github.com/nlopes/slack", | ||||
| 			"repository": "https://github.com/nlopes/slack", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "e595e9d8590a04ff76407e4e7d1791d25b095c66", | ||||
| 			"revision": "248e1d53d27901137764ae625890c127bf67e7aa", | ||||
| 			"branch": "master", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @@ -186,7 +186,7 @@ | ||||
| 			"importpath": "github.com/thoj/go-ircevent", | ||||
| 			"repository": "https://github.com/thoj/go-ircevent", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "98c1902dd2097f38142384167e60206ba26f1585", | ||||
| 			"revision": "1b0acb5f2f1b615cfbd4b9f91abb14cb39a18769", | ||||
| 			"branch": "master", | ||||
| 			"notests": true | ||||
| 		}, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user