mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-06-26 22:19:26 +00:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
f8a6e65bfd | |||
6df6c5d615 | |||
93114b7682 | |||
9987ac3f13 | |||
01a32b2154 | |||
b3c3142bb2 | |||
77f1a959c3 | |||
e3dda0e812 | |||
38103d36b4 | |||
7685fe1724 | |||
01afe03a3f | |||
7fbbf89c58 | |||
84d259d8b3 | |||
8b47670a74 | |||
7f5dc1d461 | |||
43e765f4f9 | |||
adec73f542 | |||
fee159541f | |||
d81e6bf6ce | |||
70c93d970c | |||
4960273832 | |||
6c018ee6fe | |||
4ef32103ca | |||
e4ec27c5e2 | |||
20c04f7977 | |||
571f50d734 | |||
780ea6f7c0 | |||
4279906f6e | |||
2e54b97fc2 | |||
e1641b2c2e | |||
e0e1e4be80 | |||
d5845ce900 | |||
85f2cde4c3 | |||
cef64e01b3 | |||
94ea775232 | |||
2e4b7fac11 | |||
2867ec459a | |||
cd18d89894 | |||
449ed31e25 | |||
1f36904588 | |||
f7495dd0c3 | |||
a11f77835d | |||
af1ad82c8e | |||
4976338677 | |||
99d130d1ed | |||
4fb0544b0e | |||
0b4ac61435 | |||
1d5cd1d7c4 | |||
14830d9f1c |
20
.github/ISSUE_TEMPLATE.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
Please answer the following questions.
|
||||
|
||||
### Which version of matterbridge are you using?
|
||||
run ```matterbridge -version```
|
||||
|
||||
### If you're having problems with mattermost please specify mattermost version.
|
||||
|
||||
|
||||
### Please describe the expected behavior.
|
||||
|
||||
|
||||
### Please describe the actual behavior.
|
||||
#### Use logs from running ```matterbridge -debug``` if possible.
|
||||
|
||||
|
||||
### Any steps to reproduce the behavior?
|
||||
|
||||
|
||||
### Please add your configuration file
|
||||
#### (be sure to exclude or anonymize private data (tokens/passwords))
|
23
README.md
23
README.md
@ -1,11 +1,9 @@
|
||||
# matterbridge
|
||||

|
||||
|
||||
:warning: Look at [README-0.6.md] (https://github.com/42wim/matterbridge/blob/master/README-0.6.md) for the documentation of the current stable.
|
||||
The information below is about the develop version.
|
||||
Simple bridge between mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat and Hipchat(via xmpp).
|
||||
|
||||
Simple bridge between mattermost, IRC, XMPP, Gitter, Slack and Discord
|
||||
|
||||
* Relays public channel messages between multiple mattermost, IRC, XMPP, Gitter, Slack and Discord. Pick and mix.
|
||||
* 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.
|
||||
@ -14,8 +12,9 @@ Simple bridge between mattermost, IRC, XMPP, Gitter, Slack and Discord
|
||||
Look at [matterbridge.toml.sample] (https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.sample) for documentation and an example.
|
||||
Look at [matterbridge.toml.simple] (https://github.com/42wim/matterbridge/blob/master/matterbridge.toml.simple) for a simple example.
|
||||
|
||||
|
||||
## Changelog
|
||||
Since v0.7.0-dev the configuration has changed. More details in [changelog.md] (https://github.com/42wim/matterbridge/blob/master/changelog.md)
|
||||
Since v0.7.0 the configuration has changed. More details in [changelog.md] (https://github.com/42wim/matterbridge/blob/master/changelog.md)
|
||||
|
||||
## Requirements
|
||||
Accounts to one of the supported bridges
|
||||
@ -25,6 +24,9 @@ Accounts to one of the supported bridges
|
||||
* [Gitter] (https://gitter.im)
|
||||
* [Slack] (https://slack.com)
|
||||
* [Discord] (https://discordapp.com)
|
||||
* [Telegram] (https://telegram.org)
|
||||
* [Hipchat] (https://www.hipchat.com)
|
||||
* [Rocket.chat] (https://rocket.chat)
|
||||
|
||||
## Docker
|
||||
Create your matterbridge.toml file locally eg in ```/tmp/matterbridge.toml```
|
||||
@ -34,14 +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.3.0+ [v0.6.1](https://github.com/42wim/matterircd/releases/tag/v0.6.1)
|
||||
* For use with mattermost 3.0.0-3.2.0 [v0.5.0](https://github.com/42wim/matterircd/releases/tag/v0.5.0)
|
||||
* For use with mattermost 3.5.x - 3.6.0 [v0.9.1](https://github.com/42wim/matterircd/releases/tag/v0.9.1)
|
||||
* For use with mattermost 3.3.0 - 3.4.0 [v0.7.1](https://github.com/42wim/matterircd/releases/tag/v0.7.1)
|
||||
|
||||
## Compatibility
|
||||
### Mattermost
|
||||
* Matterbridge v0.6.1 works with mattermost 3.3.0 and higher [3.3.0 release](https://github.com/mattermost/platform/releases/tag/v3.3.0)
|
||||
* Matterbridge v0.5.0 works with mattermost 3.0.0 - 3.2.0 [3.2.0 release](https://github.com/mattermost/platform/releases/tag/v3.2.0)
|
||||
|
||||
* Matterbridge v0.9.1 works with mattermost 3.5.x - 3.6.0 [3.6.0 release](https://github.com/mattermost/platform/releases/tag/v3.6.0)
|
||||
* Matterbridge v0.7.1 works with mattermost 3.3.0 - 3.4.0 [3.4.0 release](https://github.com/mattermost/platform/releases/tag/v3.4.0)
|
||||
|
||||
#### Webhooks version
|
||||
* Configured incoming/outgoing [webhooks](https://www.mattermost.org/webhooks/) on your mattermost instance.
|
||||
|
@ -6,40 +6,63 @@ import (
|
||||
"github.com/42wim/matterbridge/bridge/gitter"
|
||||
"github.com/42wim/matterbridge/bridge/irc"
|
||||
"github.com/42wim/matterbridge/bridge/mattermost"
|
||||
"github.com/42wim/matterbridge/bridge/rocketchat"
|
||||
"github.com/42wim/matterbridge/bridge/slack"
|
||||
"github.com/42wim/matterbridge/bridge/telegram"
|
||||
"github.com/42wim/matterbridge/bridge/xmpp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Bridge interface {
|
||||
type Bridger interface {
|
||||
Send(msg config.Message) error
|
||||
Name() string
|
||||
Connect() error
|
||||
FullOrigin() string
|
||||
Origin() string
|
||||
Protocol() string
|
||||
JoinChannel(channel string) error
|
||||
}
|
||||
|
||||
func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) Bridge {
|
||||
type Bridge struct {
|
||||
Config config.Protocol
|
||||
Bridger
|
||||
Name string
|
||||
Account string
|
||||
Protocol string
|
||||
}
|
||||
|
||||
func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Bridge {
|
||||
b := new(Bridge)
|
||||
accInfo := strings.Split(bridge.Account, ".")
|
||||
protocol := accInfo[0]
|
||||
name := accInfo[1]
|
||||
b.Name = name
|
||||
b.Protocol = protocol
|
||||
b.Account = bridge.Account
|
||||
|
||||
// override config from environment
|
||||
config.OverrideCfgFromEnv(cfg, protocol, name)
|
||||
switch protocol {
|
||||
case "mattermost":
|
||||
return bmattermost.New(cfg.Mattermost[name], name, c)
|
||||
b.Config = cfg.Mattermost[name]
|
||||
b.Bridger = bmattermost.New(cfg.Mattermost[name], bridge.Account, c)
|
||||
case "irc":
|
||||
return birc.New(cfg.IRC[name], name, c)
|
||||
b.Config = cfg.IRC[name]
|
||||
b.Bridger = birc.New(cfg.IRC[name], bridge.Account, c)
|
||||
case "gitter":
|
||||
return bgitter.New(cfg.Gitter[name], name, c)
|
||||
b.Config = cfg.Gitter[name]
|
||||
b.Bridger = bgitter.New(cfg.Gitter[name], bridge.Account, c)
|
||||
case "slack":
|
||||
return bslack.New(cfg.Slack[name], name, c)
|
||||
b.Config = cfg.Slack[name]
|
||||
b.Bridger = bslack.New(cfg.Slack[name], bridge.Account, c)
|
||||
case "xmpp":
|
||||
return bxmpp.New(cfg.Xmpp[name], name, c)
|
||||
b.Config = cfg.Xmpp[name]
|
||||
b.Bridger = bxmpp.New(cfg.Xmpp[name], bridge.Account, c)
|
||||
case "discord":
|
||||
return bdiscord.New(cfg.Discord[name], name, c)
|
||||
b.Config = cfg.Discord[name]
|
||||
b.Bridger = bdiscord.New(cfg.Discord[name], bridge.Account, c)
|
||||
case "telegram":
|
||||
b.Config = cfg.Telegram[name]
|
||||
b.Bridger = btelegram.New(cfg.Telegram[name], bridge.Account, c)
|
||||
case "rocketchat":
|
||||
b.Config = cfg.Rocketchat[name]
|
||||
b.Bridger = brocketchat.New(cfg.Rocketchat[name], bridge.Account, c)
|
||||
}
|
||||
return nil
|
||||
return b
|
||||
}
|
||||
|
@ -8,14 +8,17 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
EVENT_JOIN_LEAVE = "join_leave"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Text string
|
||||
Channel string
|
||||
Username string
|
||||
Origin string
|
||||
FullOrigin string
|
||||
Protocol string
|
||||
Avatar string
|
||||
Text string
|
||||
Channel string
|
||||
Username string
|
||||
Avatar string
|
||||
Account string
|
||||
Event string
|
||||
}
|
||||
|
||||
type Protocol struct {
|
||||
@ -49,9 +52,14 @@ type Protocol struct {
|
||||
UseTLS bool // IRC
|
||||
}
|
||||
|
||||
type ChannelOptions struct {
|
||||
Key string // irc
|
||||
}
|
||||
|
||||
type Bridge struct {
|
||||
Account string
|
||||
Channel string
|
||||
Options ChannelOptions
|
||||
}
|
||||
|
||||
type Gateway struct {
|
||||
@ -59,6 +67,7 @@ type Gateway struct {
|
||||
Enable bool
|
||||
In []Bridge
|
||||
Out []Bridge
|
||||
InOut []Bridge
|
||||
}
|
||||
|
||||
type SameChannelGateway struct {
|
||||
@ -75,6 +84,9 @@ type Config struct {
|
||||
Gitter map[string]Protocol
|
||||
Xmpp map[string]Protocol
|
||||
Discord map[string]Protocol
|
||||
Telegram map[string]Protocol
|
||||
Rocketchat map[string]Protocol
|
||||
General Protocol
|
||||
Gateway []Gateway
|
||||
SameChannelGateway []SameChannelGateway
|
||||
}
|
||||
@ -126,16 +138,11 @@ func OverrideCfgFromEnv(cfg *Config, protocol string, account string) {
|
||||
|
||||
func GetIconURL(msg *Message, cfg *Protocol) string {
|
||||
iconURL := cfg.IconURL
|
||||
info := strings.Split(msg.Account, ".")
|
||||
protocol := info[0]
|
||||
name := info[1]
|
||||
iconURL = strings.Replace(iconURL, "{NICK}", msg.Username, -1)
|
||||
iconURL = strings.Replace(iconURL, "{BRIDGE}", msg.Origin, -1)
|
||||
iconURL = strings.Replace(iconURL, "{PROTOCOL}", msg.Protocol, -1)
|
||||
iconURL = strings.Replace(iconURL, "{BRIDGE}", name, -1)
|
||||
iconURL = strings.Replace(iconURL, "{PROTOCOL}", protocol, -1)
|
||||
return iconURL
|
||||
}
|
||||
|
||||
func GetNick(msg *Message, cfg *Protocol) string {
|
||||
nick := cfg.RemoteNickFormat
|
||||
nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
|
||||
nick = strings.Replace(nick, "{BRIDGE}", msg.Origin, -1)
|
||||
nick = strings.Replace(nick, "{PROTOCOL}", msg.Protocol, -1)
|
||||
return nick
|
||||
}
|
||||
|
@ -11,8 +11,7 @@ type bdiscord struct {
|
||||
c *discordgo.Session
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
protocol string
|
||||
origin string
|
||||
Account string
|
||||
Channels []*discordgo.Channel
|
||||
Nick string
|
||||
UseChannelID bool
|
||||
@ -25,18 +24,20 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, origin string, c chan config.Message) *bdiscord {
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *bdiscord {
|
||||
b := &bdiscord{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.protocol = protocol
|
||||
b.origin = origin
|
||||
b.Account = account
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *bdiscord) Connect() error {
|
||||
var err error
|
||||
flog.Info("Connecting")
|
||||
if !strings.HasPrefix(b.Config.Token, "Bot ") {
|
||||
b.Config.Token = "Bot " + b.Config.Token
|
||||
}
|
||||
b.c, err = discordgo.New(b.Config.Token)
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
@ -72,10 +73,6 @@ func (b *bdiscord) Connect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bdiscord) FullOrigin() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *bdiscord) JoinChannel(channel string) error {
|
||||
idcheck := strings.Split(channel, "ID:")
|
||||
if len(idcheck) > 1 {
|
||||
@ -84,18 +81,6 @@ func (b *bdiscord) JoinChannel(channel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bdiscord) Name() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *bdiscord) Protocol() string {
|
||||
return b.protocol
|
||||
}
|
||||
|
||||
func (b *bdiscord) Origin() string {
|
||||
return b.origin
|
||||
}
|
||||
|
||||
func (b *bdiscord) Send(msg config.Message) error {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
channelID := b.getChannelID(msg.Channel)
|
||||
@ -103,8 +88,7 @@ func (b *bdiscord) Send(msg config.Message) error {
|
||||
flog.Errorf("Could not find channelID for %v", msg.Channel)
|
||||
return nil
|
||||
}
|
||||
nick := config.GetNick(&msg, b.Config)
|
||||
b.c.ChannelMessageSend(channelID, nick+msg.Text)
|
||||
b.c.ChannelMessageSend(channelID, msg.Username+msg.Text)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -121,13 +105,13 @@ func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
|
||||
if m.Content == "" {
|
||||
return
|
||||
}
|
||||
flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.FullOrigin())
|
||||
flog.Debugf("Sending message from %s on %s to gateway", m.Author.Username, b.Account)
|
||||
channelName := b.getChannelName(m.ChannelID)
|
||||
if b.UseChannelID {
|
||||
channelName = "ID:" + m.ChannelID
|
||||
}
|
||||
b.Remote <- config.Message{Username: m.Author.Username, Text: m.ContentWithMentionsReplaced(), Channel: channelName,
|
||||
Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin(), Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg"}
|
||||
Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg"}
|
||||
}
|
||||
|
||||
func (b *bdiscord) getChannelID(name string) string {
|
||||
|
@ -8,13 +8,12 @@ import (
|
||||
)
|
||||
|
||||
type Bgitter struct {
|
||||
c *gitter.Gitter
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
protocol string
|
||||
origin string
|
||||
Users []gitter.User
|
||||
Rooms []gitter.Room
|
||||
c *gitter.Gitter
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
Users []gitter.User
|
||||
Rooms []gitter.Room
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -24,12 +23,11 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, origin string, c chan config.Message) *Bgitter {
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Bgitter {
|
||||
b := &Bgitter{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.protocol = protocol
|
||||
b.origin = origin
|
||||
b.Account = account
|
||||
return b
|
||||
}
|
||||
|
||||
@ -47,10 +45,6 @@ func (b *Bgitter) Connect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bgitter) FullOrigin() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *Bgitter) JoinChannel(channel string) error {
|
||||
room := channel
|
||||
roomID := b.getRoomID(room)
|
||||
@ -71,15 +65,14 @@ func (b *Bgitter) JoinChannel(channel string) error {
|
||||
go b.c.Listen(stream)
|
||||
|
||||
go func(stream *gitter.Stream, room string) {
|
||||
for {
|
||||
event := <-stream.Event
|
||||
for event := range stream.Event {
|
||||
switch ev := event.Data.(type) {
|
||||
case *gitter.MessageReceived:
|
||||
// check for ZWSP to see if it's not an echo
|
||||
if !strings.HasSuffix(ev.Message.Text, "") {
|
||||
flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.FullOrigin())
|
||||
flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account)
|
||||
b.Remote <- config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room,
|
||||
Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin(), Avatar: b.getAvatar(ev.Message.From.Username)}
|
||||
Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username)}
|
||||
}
|
||||
case *gitter.GitterConnectionClosed:
|
||||
flog.Errorf("connection with gitter closed for room %s", room)
|
||||
@ -89,18 +82,6 @@ func (b *Bgitter) JoinChannel(channel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bgitter) Name() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *Bgitter) Protocol() string {
|
||||
return b.protocol
|
||||
}
|
||||
|
||||
func (b *Bgitter) Origin() string {
|
||||
return b.origin
|
||||
}
|
||||
|
||||
func (b *Bgitter) Send(msg config.Message) error {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
roomID := b.getRoomID(msg.Channel)
|
||||
@ -108,9 +89,8 @@ func (b *Bgitter) Send(msg config.Message) error {
|
||||
flog.Errorf("Could not find roomID for %v", msg.Channel)
|
||||
return nil
|
||||
}
|
||||
nick := config.GetNick(&msg, b.Config)
|
||||
// add ZWSP because gitter echoes our own messages
|
||||
return b.c.SendMessage(roomID, nick+msg.Text+" ")
|
||||
return b.c.SendMessage(roomID, msg.Username+msg.Text+" ")
|
||||
}
|
||||
|
||||
func (b *Bgitter) getRoomID(channel string) string {
|
||||
|
@ -19,11 +19,10 @@ type Birc struct {
|
||||
Nick string
|
||||
names map[string][]string
|
||||
Config *config.Protocol
|
||||
origin string
|
||||
protocol string
|
||||
Remote chan config.Message
|
||||
connected chan struct{}
|
||||
Local chan config.Message // local queue for flood control
|
||||
Account string
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -33,14 +32,13 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, origin string, c chan config.Message) *Birc {
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Birc {
|
||||
b := &Birc{}
|
||||
b.Config = &cfg
|
||||
b.Nick = b.Config.Nick
|
||||
b.Remote = c
|
||||
b.names = make(map[string][]string)
|
||||
b.origin = origin
|
||||
b.protocol = protocol
|
||||
b.Account = account
|
||||
b.connected = make(chan struct{})
|
||||
if b.Config.MessageDelay == 0 {
|
||||
b.Config.MessageDelay = 1300
|
||||
@ -55,9 +53,9 @@ func New(cfg config.Protocol, origin string, c chan config.Message) *Birc {
|
||||
func (b *Birc) Command(msg *config.Message) string {
|
||||
switch msg.Text {
|
||||
case "!users":
|
||||
b.i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames)
|
||||
b.i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames)
|
||||
b.i.SendRaw("NAMES " + msg.Channel)
|
||||
b.i.ClearCallback(ircm.RPL_ENDOFNAMES)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@ -93,43 +91,26 @@ func (b *Birc) Connect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Birc) FullOrigin() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *Birc) JoinChannel(channel string) error {
|
||||
b.i.Join(channel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Birc) Name() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *Birc) Protocol() string {
|
||||
return b.protocol
|
||||
}
|
||||
|
||||
func (b *Birc) Origin() string {
|
||||
return b.origin
|
||||
}
|
||||
|
||||
func (b *Birc) Send(msg config.Message) error {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
if msg.FullOrigin == b.FullOrigin() {
|
||||
if msg.Account == b.Account {
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(msg.Text, "!") {
|
||||
b.Command(&msg)
|
||||
return nil
|
||||
}
|
||||
nick := config.GetNick(&msg, b.Config)
|
||||
for _, text := range strings.Split(msg.Text, "\n") {
|
||||
if len(b.Local) < b.Config.MessageQueue {
|
||||
if len(b.Local) == b.Config.MessageQueue-1 {
|
||||
text = text + " <message clipped>"
|
||||
}
|
||||
b.Local <- config.Message{Text: text, Username: nick, Channel: msg.Channel}
|
||||
b.Local <- config.Message{Text: text, Username: msg.Username, Channel: msg.Channel}
|
||||
} else {
|
||||
flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local))
|
||||
}
|
||||
@ -153,13 +134,15 @@ func (b *Birc) endNames(event *irc.Event) {
|
||||
continued := false
|
||||
for len(b.names[channel]) > maxNamesPerPost {
|
||||
b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost], continued),
|
||||
Channel: channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}
|
||||
Channel: channel, Account: b.Account}
|
||||
b.names[channel] = b.names[channel][maxNamesPerPost:]
|
||||
continued = true
|
||||
}
|
||||
b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel], continued), Channel: channel,
|
||||
Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}
|
||||
b.Remote <- config.Message{Username: b.Nick, Text: b.formatnicks(b.names[channel], continued),
|
||||
Channel: channel, Account: b.Account}
|
||||
b.names[channel] = nil
|
||||
b.i.ClearCallback(ircm.RPL_NAMREPLY)
|
||||
b.i.ClearCallback(ircm.RPL_ENDOFNAMES)
|
||||
}
|
||||
|
||||
func (b *Birc) handleNewConnection(event *irc.Event) {
|
||||
@ -169,18 +152,30 @@ func (b *Birc) handleNewConnection(event *irc.Event) {
|
||||
i.AddCallback("PRIVMSG", b.handlePrivMsg)
|
||||
i.AddCallback("CTCP_ACTION", b.handlePrivMsg)
|
||||
i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime)
|
||||
i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames)
|
||||
i.AddCallback(ircm.NOTICE, b.handleNotice)
|
||||
//i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) })
|
||||
i.AddCallback("PING", func(e *irc.Event) {
|
||||
i.SendRaw("PONG :" + e.Message())
|
||||
flog.Debugf("PING/PONG")
|
||||
})
|
||||
i.AddCallback("JOIN", b.handleJoinPart)
|
||||
i.AddCallback("PART", b.handleJoinPart)
|
||||
i.AddCallback("QUIT", b.handleJoinPart)
|
||||
i.AddCallback("*", b.handleOther)
|
||||
// we are now fully connected
|
||||
b.connected <- struct{}{}
|
||||
}
|
||||
|
||||
func (b *Birc) handleJoinPart(event *irc.Event) {
|
||||
flog.Debugf("Sending JOIN_LEAVE event from %s to gateway", b.Account)
|
||||
channel := event.Arguments[0]
|
||||
if event.Code == "QUIT" {
|
||||
channel = ""
|
||||
}
|
||||
b.Remote <- config.Message{Username: "system", Text: event.Nick + " " + strings.ToLower(event.Code) + "s", Channel: channel, Account: b.Account, Event: config.EVENT_JOIN_LEAVE}
|
||||
flog.Debugf("handle %#v", event)
|
||||
}
|
||||
|
||||
func (b *Birc) handleNotice(event *irc.Event) {
|
||||
if strings.Contains(event.Message(), "This nickname is registered") && event.Nick == b.Config.NickServNick {
|
||||
b.i.Privmsg(b.Config.NickServNick, "IDENTIFY "+b.Config.NickServPassword)
|
||||
@ -215,8 +210,8 @@ func (b *Birc) handlePrivMsg(event *irc.Event) {
|
||||
// strip IRC colors
|
||||
re := regexp.MustCompile(`[[:cntrl:]](\d+,|)\d+`)
|
||||
msg = re.ReplaceAllString(msg, "")
|
||||
flog.Debugf("Sending message from %s on %s to gateway", event.Arguments[0], b.FullOrigin())
|
||||
b.Remote <- config.Message{Username: event.Nick, Text: msg, Channel: event.Arguments[0], Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}
|
||||
flog.Debugf("Sending message from %s on %s to gateway", event.Arguments[0], b.Account)
|
||||
b.Remote <- config.Message{Username: event.Nick, Text: msg, Channel: event.Arguments[0], Account: b.Account}
|
||||
}
|
||||
|
||||
func (b *Birc) handleTopicWhoTime(event *irc.Event) {
|
||||
|
@ -26,12 +26,11 @@ type MMMessage struct {
|
||||
type Bmattermost struct {
|
||||
MMhook
|
||||
MMapi
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
name string
|
||||
origin string
|
||||
protocol string
|
||||
TeamId string
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
name string
|
||||
TeamId string
|
||||
Account string
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -41,13 +40,11 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, origin string, c chan config.Message) *Bmattermost {
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Bmattermost {
|
||||
b := &Bmattermost{}
|
||||
b.Config = &cfg
|
||||
b.origin = origin
|
||||
b.Remote = c
|
||||
b.protocol = "mattermost"
|
||||
b.name = cfg.Name
|
||||
b.Account = account
|
||||
b.mmMap = make(map[string]string)
|
||||
return b
|
||||
}
|
||||
@ -80,10 +77,6 @@ func (b *Bmattermost) Connect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bmattermost) FullOrigin() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *Bmattermost) JoinChannel(channel string) error {
|
||||
// we can only join channels using the API
|
||||
if b.Config.UseAPI {
|
||||
@ -92,21 +85,9 @@ func (b *Bmattermost) JoinChannel(channel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bmattermost) Name() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *Bmattermost) Origin() string {
|
||||
return b.origin
|
||||
}
|
||||
|
||||
func (b *Bmattermost) Protocol() string {
|
||||
return b.protocol
|
||||
}
|
||||
|
||||
func (b *Bmattermost) Send(msg config.Message) error {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
nick := config.GetNick(&msg, b.Config)
|
||||
nick := msg.Username
|
||||
message := msg.Text
|
||||
channel := msg.Channel
|
||||
|
||||
@ -144,8 +125,8 @@ func (b *Bmattermost) handleMatter() {
|
||||
go b.handleMatterHook(mchan)
|
||||
}
|
||||
for message := range mchan {
|
||||
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.FullOrigin())
|
||||
b.Remote <- config.Message{Text: message.Text, Username: message.Username, Channel: message.Channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}
|
||||
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
|
||||
b.Remote <- config.Message{Text: message.Text, Username: message.Username, Channel: message.Channel, Account: b.Account}
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,14 +134,14 @@ func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
|
||||
for message := range b.mc.MessageChan {
|
||||
// do not post our own messages back to irc
|
||||
// only listen to message from our team
|
||||
if message.Raw.Event == "posted" && b.mc.User.Username != message.Username && message.Raw.TeamId == b.TeamId {
|
||||
if message.Raw.Event == "posted" && b.mc.User.Username != message.Username && message.Raw.Data["team_id"].(string) == b.TeamId {
|
||||
flog.Debugf("Receiving from matterclient %#v", message)
|
||||
m := &MMMessage{}
|
||||
m.Username = message.Username
|
||||
m.Channel = message.Channel
|
||||
m.Text = message.Text
|
||||
if len(message.Post.Filenames) > 0 {
|
||||
for _, link := range b.mc.GetPublicLinks(message.Post.Filenames) {
|
||||
if len(message.Post.FileIds) > 0 {
|
||||
for _, link := range b.mc.GetPublicLinks(message.Post.FileIds) {
|
||||
m.Text = m.Text + "\n" + link
|
||||
}
|
||||
}
|
||||
|
82
bridge/rocketchat/rocketchat.go
Normal file
82
bridge/rocketchat/rocketchat.go
Normal file
@ -0,0 +1,82 @@
|
||||
package brocketchat
|
||||
|
||||
import (
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/42wim/matterbridge/hook/rockethook"
|
||||
"github.com/42wim/matterbridge/matterhook"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
type MMhook struct {
|
||||
mh *matterhook.Client
|
||||
rh *rockethook.Client
|
||||
}
|
||||
|
||||
type Brocketchat struct {
|
||||
MMhook
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
name string
|
||||
Account string
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "rocketchat"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Brocketchat {
|
||||
b := &Brocketchat{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.Account = account
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Command(cmd string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Connect() error {
|
||||
flog.Info("Connecting webhooks")
|
||||
b.mh = matterhook.New(b.Config.URL,
|
||||
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
|
||||
DisableServer: true})
|
||||
b.rh = rockethook.New(b.Config.URL, rockethook.Config{BindAddress: b.Config.BindAddress})
|
||||
go b.handleRocketHook()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Brocketchat) JoinChannel(channel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Brocketchat) Send(msg config.Message) error {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
|
||||
matterMessage.Channel = msg.Channel
|
||||
matterMessage.UserName = msg.Username
|
||||
matterMessage.Type = ""
|
||||
matterMessage.Text = msg.Text
|
||||
err := b.mh.Send(matterMessage)
|
||||
if err != nil {
|
||||
flog.Info(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Brocketchat) handleRocketHook() {
|
||||
for {
|
||||
message := b.rh.Receive()
|
||||
flog.Debugf("Receiving from rockethook %#v", message)
|
||||
// do not loop
|
||||
if message.UserName == b.Config.Nick {
|
||||
continue
|
||||
}
|
||||
flog.Debugf("Sending message from %s on %s to gateway", message.UserName, b.Account)
|
||||
b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/42wim/matterbridge/matterhook"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/nlopes/slack"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -25,8 +26,7 @@ type Bslack struct {
|
||||
Plus bool
|
||||
Remote chan config.Message
|
||||
Users []slack.User
|
||||
protocol string
|
||||
origin string
|
||||
Account string
|
||||
si *slack.Info
|
||||
channels []slack.Channel
|
||||
}
|
||||
@ -38,12 +38,11 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, origin string, c chan config.Message) *Bslack {
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Bslack {
|
||||
b := &Bslack{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.protocol = protocol
|
||||
b.origin = origin
|
||||
b.Account = account
|
||||
return b
|
||||
}
|
||||
|
||||
@ -66,10 +65,6 @@ func (b *Bslack) Connect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bslack) FullOrigin() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *Bslack) JoinChannel(channel string) error {
|
||||
// we can only join channels using the API
|
||||
if b.Config.UseAPI {
|
||||
@ -81,24 +76,12 @@ func (b *Bslack) JoinChannel(channel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bslack) Name() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *Bslack) Protocol() string {
|
||||
return b.protocol
|
||||
}
|
||||
|
||||
func (b *Bslack) Origin() string {
|
||||
return b.origin
|
||||
}
|
||||
|
||||
func (b *Bslack) Send(msg config.Message) error {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
if msg.FullOrigin == b.FullOrigin() {
|
||||
if msg.Account == b.Account {
|
||||
return nil
|
||||
}
|
||||
nick := config.GetNick(&msg, b.Config)
|
||||
nick := msg.Username
|
||||
message := msg.Text
|
||||
channel := msg.Channel
|
||||
if b.Config.PrefixMessagesWithNick {
|
||||
@ -154,14 +137,14 @@ func (b *Bslack) getAvatar(user string) string {
|
||||
|
||||
func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) {
|
||||
if b.channels == nil {
|
||||
return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.FullOrigin(), name)
|
||||
return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.Account, name)
|
||||
}
|
||||
for _, channel := range b.channels {
|
||||
if channel.Name == name {
|
||||
return &channel, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("%s: channel %s not found", b.FullOrigin(), name)
|
||||
return nil, fmt.Errorf("%s: channel %s not found", b.Account, name)
|
||||
}
|
||||
|
||||
func (b *Bslack) handleSlack() {
|
||||
@ -176,13 +159,13 @@ func (b *Bslack) handleSlack() {
|
||||
flog.Debug("Start listening for Slack messages")
|
||||
for message := range mchan {
|
||||
// do not send messages from ourself
|
||||
if message.Username == b.si.User.Name {
|
||||
if b.Config.UseAPI && message.Username == b.si.User.Name {
|
||||
continue
|
||||
}
|
||||
texts := strings.Split(message.Text, "\n")
|
||||
for _, text := range texts {
|
||||
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.FullOrigin())
|
||||
b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin(), Avatar: b.getAvatar(message.Username)}
|
||||
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
|
||||
b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username)}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,11 +192,14 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
|
||||
m.Channel = channel.Name
|
||||
m.Text = ev.Text
|
||||
m.Raw = ev
|
||||
m.Text = b.replaceMention(m.Text)
|
||||
mchan <- m
|
||||
}
|
||||
count++
|
||||
case *slack.OutgoingErrorEvent:
|
||||
flog.Debugf("%#v", ev.Error())
|
||||
case *slack.ChannelJoinedEvent:
|
||||
b.Users, _ = b.sc.GetUsers()
|
||||
case *slack.ConnectedEvent:
|
||||
b.channels = ev.Info.Channels
|
||||
b.si = ev.Info
|
||||
@ -232,7 +218,26 @@ func (b *Bslack) handleMatterHook(mchan chan *MMMessage) {
|
||||
m := &MMMessage{}
|
||||
m.Username = message.UserName
|
||||
m.Text = message.Text
|
||||
m.Text = b.replaceMention(m.Text)
|
||||
m.Channel = message.ChannelName
|
||||
mchan <- m
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bslack) userName(id string) string {
|
||||
for _, u := range b.Users {
|
||||
if u.ID == id {
|
||||
return u.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *Bslack) replaceMention(text string) string {
|
||||
results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1)
|
||||
for _, r := range results {
|
||||
text = strings.Replace(text, "<@"+r[1]+">", "@"+b.userName(r[1]), -1)
|
||||
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
134
bridge/telegram/telegram.go
Normal file
134
bridge/telegram/telegram.go
Normal file
@ -0,0 +1,134 @@
|
||||
package btelegram
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"html"
|
||||
"strconv"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
type Btelegram struct {
|
||||
c *tgbotapi.BotAPI
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
var protocol = "telegram"
|
||||
|
||||
func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram {
|
||||
b := &Btelegram{}
|
||||
b.Config = &cfg
|
||||
b.Remote = c
|
||||
b.Account = account
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Btelegram) Connect() error {
|
||||
var err error
|
||||
flog.Info("Connecting")
|
||||
b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0))
|
||||
if err != nil {
|
||||
flog.Debugf("%#v", err)
|
||||
return err
|
||||
}
|
||||
flog.Info("Connection succeeded")
|
||||
go b.handleRecv(updates)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Btelegram) JoinChannel(channel string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type customHtml struct {
|
||||
blackfriday.Renderer
|
||||
}
|
||||
|
||||
func (options *customHtml) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||
marker := out.Len()
|
||||
|
||||
if !text() {
|
||||
out.Truncate(marker)
|
||||
return
|
||||
}
|
||||
out.WriteString("\n")
|
||||
}
|
||||
|
||||
func (options *customHtml) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||
out.WriteString("<pre>")
|
||||
|
||||
out.WriteString(html.EscapeString(string(text)))
|
||||
out.WriteString("</pre>\n")
|
||||
}
|
||||
|
||||
func (options *customHtml) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
||||
options.Paragraph(out, text)
|
||||
}
|
||||
|
||||
func (options *customHtml) HRule(out *bytes.Buffer) {
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *customHtml) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("> ")
|
||||
out.Write(text)
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *customHtml) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||
options.Paragraph(out, text)
|
||||
}
|
||||
|
||||
func (options *customHtml) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
out.WriteString("- ")
|
||||
out.Write(text)
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (b *Btelegram) Send(msg config.Message) error {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parsed := blackfriday.Markdown([]byte(msg.Text),
|
||||
&customHtml{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")},
|
||||
blackfriday.EXTENSION_NO_INTRA_EMPHASIS|
|
||||
blackfriday.EXTENSION_FENCED_CODE|
|
||||
blackfriday.EXTENSION_AUTOLINK|
|
||||
blackfriday.EXTENSION_SPACE_HEADERS|
|
||||
blackfriday.EXTENSION_HEADER_IDS|
|
||||
blackfriday.EXTENSION_BACKSLASH_LINE_BREAK|
|
||||
blackfriday.EXTENSION_DEFINITION_LISTS)
|
||||
|
||||
m := tgbotapi.NewMessage(chatid, msg.Username+string(parsed))
|
||||
m.ParseMode = "HTML"
|
||||
_, err = b.c.Send(m)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
|
||||
for update := range updates {
|
||||
if update.Message == nil {
|
||||
continue
|
||||
}
|
||||
flog.Debugf("Sending message from %s on %s to gateway", update.Message.From.UserName, b.Account)
|
||||
b.Remote <- config.Message{Username: update.Message.From.UserName, Text: update.Message.Text, Channel: strconv.FormatInt(update.Message.Chat.ID, 10), Account: b.Account}
|
||||
}
|
||||
}
|
@ -4,18 +4,18 @@ import (
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/mattn/go-xmpp"
|
||||
"crypto/tls"
|
||||
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Bxmpp struct {
|
||||
xc *xmpp.Client
|
||||
xmppMap map[string]string
|
||||
Config *config.Protocol
|
||||
origin string
|
||||
protocol string
|
||||
Remote chan config.Message
|
||||
xc *xmpp.Client
|
||||
xmppMap map[string]string
|
||||
Config *config.Protocol
|
||||
Remote chan config.Message
|
||||
Account string
|
||||
}
|
||||
|
||||
var flog *log.Entry
|
||||
@ -25,12 +25,11 @@ func init() {
|
||||
flog = log.WithFields(log.Fields{"module": protocol})
|
||||
}
|
||||
|
||||
func New(cfg config.Protocol, origin string, c chan config.Message) *Bxmpp {
|
||||
func New(cfg config.Protocol, account string, c chan config.Message) *Bxmpp {
|
||||
b := &Bxmpp{}
|
||||
b.xmppMap = make(map[string]string)
|
||||
b.Config = &cfg
|
||||
b.protocol = protocol
|
||||
b.origin = origin
|
||||
b.Account = account
|
||||
b.Remote = c
|
||||
return b
|
||||
}
|
||||
@ -48,41 +47,28 @@ func (b *Bxmpp) Connect() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bxmpp) FullOrigin() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *Bxmpp) JoinChannel(channel string) error {
|
||||
b.xc.JoinMUCNoHistory(channel+"@"+b.Config.Muc, b.Config.Nick)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bxmpp) Name() string {
|
||||
return b.protocol + "." + b.origin
|
||||
}
|
||||
|
||||
func (b *Bxmpp) Protocol() string {
|
||||
return b.protocol
|
||||
}
|
||||
|
||||
func (b *Bxmpp) Origin() string {
|
||||
return b.origin
|
||||
}
|
||||
|
||||
func (b *Bxmpp) Send(msg config.Message) error {
|
||||
flog.Debugf("Receiving %#v", msg)
|
||||
nick := config.GetNick(&msg, b.Config)
|
||||
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: nick + msg.Text})
|
||||
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
|
||||
tc := new(tls.Config)
|
||||
tc.InsecureSkipVerify = b.Config.SkipTLSVerify
|
||||
options := xmpp.Options{
|
||||
Host: b.Config.Server,
|
||||
User: b.Config.Jid,
|
||||
Password: b.Config.Password,
|
||||
NoTLS: true,
|
||||
StartTLS: true,
|
||||
TLSConfig: tc,
|
||||
|
||||
//StartTLS: false,
|
||||
Debug: true,
|
||||
Session: true,
|
||||
@ -97,19 +83,27 @@ func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
|
||||
return b.xc, err
|
||||
}
|
||||
|
||||
func (b *Bxmpp) xmppKeepAlive() {
|
||||
func (b *Bxmpp) xmppKeepAlive() chan bool {
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
ticker := time.NewTicker(90 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
b.xc.Send(xmpp.Chat{})
|
||||
b.xc.PingC2S("", "")
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return done
|
||||
}
|
||||
|
||||
func (b *Bxmpp) handleXmpp() error {
|
||||
done := b.xmppKeepAlive()
|
||||
defer close(done)
|
||||
nodelay := time.Time{}
|
||||
for {
|
||||
m, err := b.xc.Recv()
|
||||
if err != nil {
|
||||
@ -127,9 +121,9 @@ func (b *Bxmpp) handleXmpp() error {
|
||||
if len(s) == 2 {
|
||||
nick = s[1]
|
||||
}
|
||||
if nick != b.Config.Nick {
|
||||
flog.Debugf("Sending message from %s on %s to gateway", nick, b.FullOrigin())
|
||||
b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}
|
||||
if nick != b.Config.Nick && v.Stamp == nodelay && v.Text != "" {
|
||||
flog.Debugf("Sending message from %s on %s to gateway", nick, b.Account)
|
||||
b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Account: b.Account}
|
||||
}
|
||||
}
|
||||
case xmpp.Presence:
|
||||
|
49
changelog.md
49
changelog.md
@ -1,4 +1,50 @@
|
||||
# v0.7-dev
|
||||
# v0.9.1
|
||||
## New features
|
||||
* Rocket.Chat: New protocol support added (https://rocket.chat)
|
||||
* irc: add channel key support #27 (see matterbrige.toml.sample for example)
|
||||
* xmpp: add SkipTLSVerify #106
|
||||
|
||||
## Bugfix
|
||||
* general: Exit when a bridge fails to start
|
||||
* mattermost: Check errors only on first connect. Keep retrying after first connection succeeds. #95
|
||||
* telegram: fix missing username #102
|
||||
* slack: do not use API functions in webhook (slack) #110
|
||||
|
||||
# v0.9.0
|
||||
## New features
|
||||
* Telegram: New protocol support added (https://telegram.org)
|
||||
* Hipchat: Add sample config to connect to hipchat via xmpp
|
||||
* discord: add "Bot " tag to discord tokens automatically
|
||||
* slack: Add support for dynamic Iconurl #43
|
||||
* general: Add ```gateway.inout``` config option for bidirectional bridges #85
|
||||
* general: Add ```[general]``` section so that ```RemoteNickFormat``` can be set globally
|
||||
|
||||
## Bugfix
|
||||
* general: when using samechannelgateway NickFormat get doubled by the NICK #77
|
||||
* general: fix ShowJoinPart for messages from irc bridge #72
|
||||
* gitter: fix high cpu usage #89
|
||||
* irc: fix !users command #78
|
||||
* xmpp: fix keepalive
|
||||
* xmpp: do not relay delayed/empty messages
|
||||
* slack: Replace id-mentions to usernames #86
|
||||
* mattermost: fix public links not working (API changes)
|
||||
|
||||
# v0.8.1
|
||||
## Bugfix
|
||||
* general: when using samechannelgateway NickFormat get doubled by the NICK #77
|
||||
* irc: fix !users command #78
|
||||
|
||||
# v0.8.0
|
||||
Release because of breaking mattermost API changes
|
||||
## New features
|
||||
* Supports mattermost v3.5.0
|
||||
|
||||
# v0.7.1
|
||||
## Bugfix
|
||||
* general: when using samechannelgateway NickFormat get doubled by the NICK #77
|
||||
* irc: fix !users command #78
|
||||
|
||||
# v0.7.0
|
||||
## Breaking config changes from 0.6 to 0.7
|
||||
Matterbridge now uses TOML configuration (https://github.com/toml-lang/toml)
|
||||
See matterbridge.toml.sample for an example
|
||||
@ -32,6 +78,7 @@ See matterbridge.toml.sample for an example
|
||||
# v0.6.1
|
||||
## New features
|
||||
* Slack support added. See matterbridge.conf.sample for more information
|
||||
|
||||
## Bugfix
|
||||
* Fix 100% CPU bug on incorrect closed connections
|
||||
|
||||
|
@ -11,55 +11,74 @@ import (
|
||||
|
||||
type Gateway struct {
|
||||
*config.Config
|
||||
MyConfig *config.Gateway
|
||||
Bridges []bridge.Bridge
|
||||
ChannelsOut map[string][]string
|
||||
ChannelsIn map[string][]string
|
||||
ignoreNicks map[string][]string
|
||||
Name string
|
||||
MyConfig *config.Gateway
|
||||
//Bridges []*bridge.Bridge
|
||||
Bridges map[string]*bridge.Bridge
|
||||
ChannelsOut map[string][]string
|
||||
ChannelsIn map[string][]string
|
||||
ignoreNicks map[string][]string
|
||||
ChannelOptions map[string]config.ChannelOptions
|
||||
Name string
|
||||
Message chan config.Message
|
||||
}
|
||||
|
||||
func New(cfg *config.Config, gateway *config.Gateway) error {
|
||||
c := make(chan config.Message)
|
||||
func New(cfg *config.Config, gateway *config.Gateway) *Gateway {
|
||||
gw := &Gateway{}
|
||||
gw.Name = gateway.Name
|
||||
gw.Config = cfg
|
||||
gw.MyConfig = gateway
|
||||
exists := make(map[string]bool)
|
||||
for _, br := range append(gateway.In, gateway.Out...) {
|
||||
if exists[br.Account] {
|
||||
continue
|
||||
}
|
||||
log.Infof("Starting bridge: %s channel: %s", br.Account, br.Channel)
|
||||
gw.Bridges = append(gw.Bridges, bridge.New(cfg, &br, c))
|
||||
exists[br.Account] = true
|
||||
}
|
||||
gw.mapChannels()
|
||||
//TODO fix mapIgnores
|
||||
//gw.mapIgnores()
|
||||
exists = make(map[string]bool)
|
||||
gw.Message = make(chan config.Message)
|
||||
gw.Bridges = make(map[string]*bridge.Bridge)
|
||||
return gw
|
||||
}
|
||||
|
||||
func (gw *Gateway) AddBridge(cfg *config.Bridge) error {
|
||||
for _, br := range gw.Bridges {
|
||||
err := br.Connect()
|
||||
if err != nil {
|
||||
log.Fatalf("Bridge %s failed to start: %v", br.FullOrigin(), err)
|
||||
}
|
||||
for _, channel := range append(gw.ChannelsOut[br.FullOrigin()], gw.ChannelsIn[br.FullOrigin()]...) {
|
||||
if exists[br.FullOrigin()+channel] {
|
||||
continue
|
||||
}
|
||||
log.Infof("%s: joining %s", br.FullOrigin(), channel)
|
||||
br.JoinChannel(channel)
|
||||
exists[br.FullOrigin()+channel] = true
|
||||
if br.Account == cfg.Account {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
log.Infof("Starting bridge: %s ", cfg.Account)
|
||||
br := bridge.New(gw.Config, cfg, gw.Message)
|
||||
gw.Bridges[cfg.Account] = br
|
||||
err := br.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Bridge %s failed to start: %v", br.Account, err)
|
||||
}
|
||||
exists := make(map[string]bool)
|
||||
for _, channel := range append(gw.ChannelsOut[br.Account], gw.ChannelsIn[br.Account]...) {
|
||||
if !exists[br.Account+channel] {
|
||||
mychannel := channel
|
||||
log.Infof("%s: joining %s", br.Account, channel)
|
||||
if br.Protocol == "irc" && gw.ChannelOptions[br.Account+channel].Key != "" {
|
||||
log.Debugf("using key %s for channel %s", gw.ChannelOptions[br.Account+channel].Key, channel)
|
||||
mychannel = mychannel + " " + gw.ChannelOptions[br.Account+channel].Key
|
||||
}
|
||||
br.JoinChannel(mychannel)
|
||||
exists[br.Account+channel] = true
|
||||
}
|
||||
}
|
||||
gw.handleReceive(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gw *Gateway) handleReceive(c chan config.Message) {
|
||||
func (gw *Gateway) Start() error {
|
||||
gw.mapChannels()
|
||||
for _, br := range append(gw.MyConfig.In, append(gw.MyConfig.InOut, gw.MyConfig.Out...)...) {
|
||||
err := gw.AddBridge(&br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
//TODO fix mapIgnores
|
||||
//gw.mapIgnores()
|
||||
go gw.handleReceive()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gw *Gateway) handleReceive() {
|
||||
for {
|
||||
select {
|
||||
case msg := <-c:
|
||||
case msg := <-gw.Message:
|
||||
for _, br := range gw.Bridges {
|
||||
gw.handleMessage(msg, br)
|
||||
}
|
||||
@ -68,17 +87,26 @@ func (gw *Gateway) handleReceive(c chan config.Message) {
|
||||
}
|
||||
|
||||
func (gw *Gateway) mapChannels() error {
|
||||
options := make(map[string]config.ChannelOptions)
|
||||
m := make(map[string][]string)
|
||||
for _, br := range gw.MyConfig.Out {
|
||||
m[br.Account] = append(m[br.Account], br.Channel)
|
||||
options[br.Account+br.Channel] = br.Options
|
||||
}
|
||||
gw.ChannelsOut = m
|
||||
m = nil
|
||||
m = make(map[string][]string)
|
||||
for _, br := range gw.MyConfig.In {
|
||||
m[br.Account] = append(m[br.Account], br.Channel)
|
||||
options[br.Account+br.Channel] = br.Options
|
||||
}
|
||||
gw.ChannelsIn = m
|
||||
for _, br := range gw.MyConfig.InOut {
|
||||
gw.ChannelsIn[br.Account] = append(gw.ChannelsIn[br.Account], br.Channel)
|
||||
gw.ChannelsOut[br.Account] = append(gw.ChannelsOut[br.Account], br.Channel)
|
||||
options[br.Account+br.Channel] = br.Options
|
||||
}
|
||||
gw.ChannelOptions = options
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -92,7 +120,11 @@ func (gw *Gateway) mapIgnores() {
|
||||
}
|
||||
|
||||
func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string {
|
||||
channels := gw.ChannelsIn[msg.FullOrigin]
|
||||
channels := gw.ChannelsIn[msg.Account]
|
||||
// broadcast to every out channel (irc QUIT)
|
||||
if msg.Event == config.EVENT_JOIN_LEAVE && msg.Channel == "" {
|
||||
return gw.ChannelsOut[dest]
|
||||
}
|
||||
for _, channel := range channels {
|
||||
if channel == msg.Channel {
|
||||
return gw.ChannelsOut[dest]
|
||||
@ -101,15 +133,19 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (gw *Gateway) handleMessage(msg config.Message, dest bridge.Bridge) {
|
||||
func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) {
|
||||
if gw.ignoreMessage(&msg) {
|
||||
return
|
||||
}
|
||||
// only relay join/part when configged
|
||||
if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart {
|
||||
return
|
||||
}
|
||||
originchannel := msg.Channel
|
||||
channels := gw.getDestChannel(&msg, dest.FullOrigin())
|
||||
channels := gw.getDestChannel(&msg, dest.Account)
|
||||
for _, channel := range channels {
|
||||
// do not send the message to the bridge we come from if also the channel is the same
|
||||
if msg.FullOrigin == dest.FullOrigin() && channel == originchannel {
|
||||
if msg.Account == dest.Account && channel == originchannel {
|
||||
continue
|
||||
}
|
||||
msg.Channel = channel
|
||||
@ -117,7 +153,8 @@ func (gw *Gateway) handleMessage(msg config.Message, dest bridge.Bridge) {
|
||||
log.Debug("empty channel")
|
||||
return
|
||||
}
|
||||
log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.FullOrigin, originchannel, dest.FullOrigin(), channel)
|
||||
log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, originchannel, dest.Account, channel)
|
||||
gw.modifyUsername(&msg, dest)
|
||||
err := dest.Send(msg)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@ -127,7 +164,7 @@ func (gw *Gateway) handleMessage(msg config.Message, dest bridge.Bridge) {
|
||||
|
||||
func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
|
||||
// should we discard messages ?
|
||||
for _, entry := range gw.ignoreNicks[msg.FullOrigin] {
|
||||
for _, entry := range gw.ignoreNicks[msg.Account] {
|
||||
if msg.Username == entry {
|
||||
return true
|
||||
}
|
||||
@ -135,17 +172,29 @@ func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (gw *Gateway) modifyMessage(msg *config.Message, dest bridge.Bridge) {
|
||||
func (gw *Gateway) modifyMessage(msg *config.Message, dest *bridge.Bridge) {
|
||||
val := reflect.ValueOf(gw.Config).Elem()
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
typeField := val.Type().Field(i)
|
||||
// look for the protocol map (both lowercase)
|
||||
if strings.ToLower(typeField.Name) == dest.Protocol() {
|
||||
if strings.ToLower(typeField.Name) == dest.Protocol {
|
||||
// get the Protocol struct from the map
|
||||
protoCfg := val.Field(i).MapIndex(reflect.ValueOf(dest.Origin()))
|
||||
protoCfg := val.Field(i).MapIndex(reflect.ValueOf(dest.Name))
|
||||
//config.SetNickFormat(msg, protoCfg.Interface().(config.Protocol))
|
||||
val.Field(i).SetMapIndex(reflect.ValueOf(dest.Origin()), protoCfg)
|
||||
val.Field(i).SetMapIndex(reflect.ValueOf(dest.Name), protoCfg)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gw *Gateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) {
|
||||
br := gw.Bridges[msg.Account]
|
||||
nick := gw.Config.General.RemoteNickFormat
|
||||
if nick == "" {
|
||||
nick = dest.Config.RemoteNickFormat
|
||||
}
|
||||
nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
|
||||
nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1)
|
||||
nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1)
|
||||
msg.Username = nick
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
type SameChannelGateway struct {
|
||||
*config.Config
|
||||
MyConfig *config.SameChannelGateway
|
||||
Bridges []bridge.Bridge
|
||||
Bridges map[string]*bridge.Bridge
|
||||
Channels []string
|
||||
ignoreNicks map[string][]string
|
||||
Name string
|
||||
@ -19,6 +19,7 @@ type SameChannelGateway struct {
|
||||
func New(cfg *config.Config, gateway *config.SameChannelGateway) error {
|
||||
c := make(chan config.Message)
|
||||
gw := &SameChannelGateway{}
|
||||
gw.Bridges = make(map[string]*bridge.Bridge)
|
||||
gw.Name = gateway.Name
|
||||
gw.Config = cfg
|
||||
gw.MyConfig = gateway
|
||||
@ -26,15 +27,15 @@ func New(cfg *config.Config, gateway *config.SameChannelGateway) error {
|
||||
for _, account := range gateway.Accounts {
|
||||
br := config.Bridge{Account: account}
|
||||
log.Infof("Starting bridge: %s", account)
|
||||
gw.Bridges = append(gw.Bridges, bridge.New(cfg, &br, c))
|
||||
gw.Bridges[account] = bridge.New(cfg, &br, c)
|
||||
}
|
||||
for _, br := range gw.Bridges {
|
||||
err := br.Connect()
|
||||
if err != nil {
|
||||
log.Fatalf("Bridge %s failed to start: %v", br.FullOrigin(), err)
|
||||
log.Fatalf("Bridge %s failed to start: %v", br.Account, err)
|
||||
}
|
||||
for _, channel := range gw.Channels {
|
||||
log.Infof("%s: joining %s", br.FullOrigin(), channel)
|
||||
log.Infof("%s: joining %s", br.Account, channel)
|
||||
br.JoinChannel(channel)
|
||||
}
|
||||
}
|
||||
@ -53,44 +54,33 @@ func (gw *SameChannelGateway) handleReceive(c chan config.Message) {
|
||||
}
|
||||
}
|
||||
|
||||
func (gw *SameChannelGateway) handleMessage(msg config.Message, dest bridge.Bridge) {
|
||||
func (gw *SameChannelGateway) handleMessage(msg config.Message, dest *bridge.Bridge) {
|
||||
// is this a configured channel
|
||||
if !gw.validChannel(msg.Channel) {
|
||||
return
|
||||
}
|
||||
// do not send the message to the bridge we come from if also the channel is the same
|
||||
if msg.FullOrigin == dest.FullOrigin() {
|
||||
if msg.Account == dest.Account {
|
||||
return
|
||||
}
|
||||
gw.modifyMessage(&msg, dest)
|
||||
log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.FullOrigin, msg.Channel, dest.FullOrigin(), msg.Channel)
|
||||
gw.modifyUsername(&msg, dest)
|
||||
log.Debugf("Sending %#v from %s (%s) to %s (%s)", msg, msg.Account, msg.Channel, dest.Account, msg.Channel)
|
||||
err := dest.Send(msg)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setNickFormat(msg *config.Message, format string) {
|
||||
if format == "" {
|
||||
msg.Username = msg.Protocol + "." + msg.Origin + "-" + msg.Username + ": "
|
||||
return
|
||||
}
|
||||
msg.Username = strings.Replace(format, "{NICK}", msg.Username, -1)
|
||||
msg.Username = strings.Replace(msg.Username, "{BRIDGE}", msg.Origin, -1)
|
||||
msg.Username = strings.Replace(msg.Username, "{PROTOCOL}", msg.Protocol, -1)
|
||||
}
|
||||
|
||||
func (gw *SameChannelGateway) modifyMessage(msg *config.Message, dest bridge.Bridge) {
|
||||
switch dest.Protocol() {
|
||||
case "irc":
|
||||
setNickFormat(msg, gw.Config.IRC[dest.Origin()].RemoteNickFormat)
|
||||
case "mattermost":
|
||||
setNickFormat(msg, gw.Config.Mattermost[dest.Origin()].RemoteNickFormat)
|
||||
case "slack":
|
||||
setNickFormat(msg, gw.Config.Slack[dest.Origin()].RemoteNickFormat)
|
||||
case "discord":
|
||||
setNickFormat(msg, gw.Config.Discord[dest.Origin()].RemoteNickFormat)
|
||||
func (gw *SameChannelGateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) {
|
||||
br := gw.Bridges[msg.Account]
|
||||
nick := gw.Config.General.RemoteNickFormat
|
||||
if nick == "" {
|
||||
nick = dest.Config.RemoteNickFormat
|
||||
}
|
||||
nick = strings.Replace(nick, "{NICK}", msg.Username, -1)
|
||||
nick = strings.Replace(nick, "{BRIDGE}", br.Name, -1)
|
||||
nick = strings.Replace(nick, "{PROTOCOL}", br.Protocol, -1)
|
||||
msg.Username = nick
|
||||
}
|
||||
|
||||
func (gw *SameChannelGateway) validChannel(channel string) bool {
|
||||
|
108
hook/rockethook/rockethook.go
Normal file
108
hook/rockethook/rockethook.go
Normal file
@ -0,0 +1,108 @@
|
||||
package rockethook
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Message for rocketchat outgoing webhook.
|
||||
type Message struct {
|
||||
Token string `json:"token"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
ChannelName string `json:"channel_name"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
UserID string `json:"user_id"`
|
||||
UserName string `json:"user_name"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// Client for Rocketchat.
|
||||
type Client struct {
|
||||
In chan Message
|
||||
httpclient *http.Client
|
||||
Config
|
||||
}
|
||||
|
||||
// Config for client.
|
||||
type Config struct {
|
||||
BindAddress string // Address to listen on
|
||||
Token string // Only allow this token from Rocketchat. (Allow everything when empty)
|
||||
InsecureSkipVerify bool // disable certificate checking
|
||||
}
|
||||
|
||||
// New Rocketchat client.
|
||||
func New(url string, config Config) *Client {
|
||||
c := &Client{In: make(chan Message), Config: config}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify},
|
||||
}
|
||||
c.httpclient = &http.Client{Transport: tr}
|
||||
_, _, err := net.SplitHostPort(c.BindAddress)
|
||||
if err != nil {
|
||||
log.Fatalf("incorrect bindaddress %s", c.BindAddress)
|
||||
}
|
||||
go c.StartServer()
|
||||
return c
|
||||
}
|
||||
|
||||
// StartServer starts a webserver listening for incoming mattermost POSTS.
|
||||
func (c *Client) StartServer() {
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", c)
|
||||
log.Printf("Listening on http://%v...\n", c.BindAddress)
|
||||
if err := http.ListenAndServe(c.BindAddress, mux); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP implementation.
|
||||
func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
log.Println("invalid " + r.Method + " connection from " + r.RemoteAddr)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
msg := Message{}
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
log.Println(string(body))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
err = json.Unmarshal(body, &msg)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if msg.Token == "" {
|
||||
log.Println("no token from " + r.RemoteAddr)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
msg.ChannelName = "#" + msg.ChannelName
|
||||
if c.Token != "" {
|
||||
if msg.Token != c.Token {
|
||||
log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.In <- msg
|
||||
}
|
||||
|
||||
// Receive returns an incoming message from mattermost outgoing webhooks URL.
|
||||
func (c *Client) Receive() Message {
|
||||
for {
|
||||
select {
|
||||
case msg := <-c.In:
|
||||
return msg
|
||||
}
|
||||
}
|
||||
}
|
@ -1,287 +0,0 @@
|
||||
#This is configuration for matterbridge.
|
||||
###################################################################
|
||||
#IRC section
|
||||
###################################################################
|
||||
[IRC]
|
||||
#Enable enables this bridge
|
||||
#OPTIONAL (default false)
|
||||
Enable=true
|
||||
#irc server to connect to.
|
||||
#REQUIRED
|
||||
Server="irc.freenode.net:6667"
|
||||
|
||||
#Enable to use TLS connection to your irc server.
|
||||
#OPTIONAL (default false)
|
||||
UseTLS=false
|
||||
|
||||
#Enable SASL (PLAIN) authentication. (freenode requires this from eg AWS hosts)
|
||||
#It uses NickServNick and NickServPassword as login and password
|
||||
#OPTIONAL (default false)
|
||||
UseSASL=false
|
||||
|
||||
#Enable to not verify the certificate on your irc server. i
|
||||
#e.g. when using selfsigned certificates
|
||||
#OPTIONAL (default false)
|
||||
SkipTLSVerify=true
|
||||
|
||||
#Your nick on irc.
|
||||
#REQUIRED
|
||||
Nick="matterbot"
|
||||
|
||||
#If you registered your bot with a service like Nickserv on freenode.
|
||||
#Also being used when UseSASL=true
|
||||
#OPTIONAL
|
||||
NickServNick="nickserv"
|
||||
NickServPassword="secret"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#OPTIONAL (default {BRIDGE}-{NICK})
|
||||
RemoteNickFormat="[{BRIDGE}] <{NICK}> "
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="ircspammer1 ircspammer2"
|
||||
|
||||
###################################################################
|
||||
#XMPP section
|
||||
###################################################################
|
||||
[XMPP]
|
||||
#Enable enables this bridge
|
||||
#OPTIONAL (default false)
|
||||
Enable=true
|
||||
|
||||
#xmpp server to connect to.
|
||||
#REQUIRED
|
||||
Server="jabber.example.com:5222"
|
||||
|
||||
#Jid
|
||||
#REQUIRED
|
||||
Jid="user@example.com"
|
||||
|
||||
#Password
|
||||
#REQUIRED
|
||||
Password="yourpass"
|
||||
|
||||
#MUC
|
||||
#REQUIRED
|
||||
Muc="conference.jabber.example.com"
|
||||
|
||||
#Your nick in the rooms
|
||||
#REQUIRED
|
||||
Nick="xmppbot"
|
||||
|
||||
|
||||
###################################################################
|
||||
#mattermost section
|
||||
###################################################################
|
||||
|
||||
[mattermost]
|
||||
#Enable enables this bridge
|
||||
#OPTIONAL (default false)
|
||||
Enable=true
|
||||
|
||||
#### Settings for webhook matterbridge.
|
||||
#### These settings will not be used when using -plus switch which doesn't use
|
||||
#### webhooks.
|
||||
|
||||
#Url is your incoming webhook url as specified in mattermost.
|
||||
#See account settings - integrations - incoming webhooks on mattermost.
|
||||
#REQUIRED
|
||||
URL="https://yourdomain/hooks/yourhookkey"
|
||||
|
||||
#Address to listen on for outgoing webhook requests from mattermost.
|
||||
#See account settings - integrations - outgoing webhooks on mattermost.
|
||||
#This setting will not be used when using -plus switch which doesn't use
|
||||
#webhooks
|
||||
#REQUIRED
|
||||
BindAddress="0.0.0.0:9999"
|
||||
|
||||
#Icon that will be showed in mattermost.
|
||||
#OPTIONAL
|
||||
IconURL="http://youricon.png"
|
||||
|
||||
#### Settings for matterbridge -plus
|
||||
#### Thse settings will only be used when using the -plus switch.
|
||||
|
||||
#The mattermost hostname.
|
||||
#REQUIRED
|
||||
Server="yourmattermostserver.domain"
|
||||
|
||||
#Your team on mattermost.
|
||||
#REQUIRED
|
||||
Team="yourteam"
|
||||
|
||||
#login/pass of your bot.
|
||||
#Use a dedicated user for this and not your own!
|
||||
#REQUIRED
|
||||
Login="yourlogin"
|
||||
Password="yourpass"
|
||||
|
||||
#Enable this to make a http connection (instead of https) to your mattermost.
|
||||
#OPTIONAL (default false)
|
||||
NoTLS=false
|
||||
|
||||
#### Shared settings for matterbridge and -plus
|
||||
|
||||
#Enable to not verify the certificate on your mattermost server.
|
||||
#e.g. when using selfsigned certificates
|
||||
#OPTIONAL (default false)
|
||||
SkipTLSVerify=true
|
||||
|
||||
#Enable to show IRC joins/parts in mattermost.
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
#Whether to prefix messages from other bridges to mattermost with the sender's nick.
|
||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||
#mattermost server. If you set PrefixMessagesWithNick to true, each message
|
||||
#from bridge to Mattermost will by default be prefixed by "bridge-" + nick. You can,
|
||||
#however, modify how the messages appear, by setting (and modifying) RemoteNickFormat
|
||||
#OPTIONAL (default false)
|
||||
PrefixMessagesWithNick=false
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#OPTIONAL (default {BRIDGE}-{NICK})
|
||||
RemoteNickFormat="[{BRIDGE}] <{NICK}> "
|
||||
|
||||
#how to format the list of IRC nicks when displayed in mattermost.
|
||||
#Possible options are "table" and "plain"
|
||||
#OPTIONAL (default plain)
|
||||
NickFormatter=plain
|
||||
#How many nicks to list per row for formatters that support this.
|
||||
#OPTIONAL (default 4)
|
||||
NicksPerRow=4
|
||||
|
||||
#Nicks you want to ignore. Messages from those users will not be bridged.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="mmbot spammer2"
|
||||
|
||||
###################################################################
|
||||
#Gitter section
|
||||
#Best to make a dedicated gitter account for the bot.
|
||||
###################################################################
|
||||
[Gitter]
|
||||
#Enable enables this bridge
|
||||
#OPTIONAL (default false)
|
||||
Enable=true
|
||||
|
||||
#Token to connect with Gitter API
|
||||
#You can get your token by going to https://developer.gitter.im/docs/welcome and SIGN IN
|
||||
#REQUIRED
|
||||
Token="Yourtokenhere"
|
||||
|
||||
#Nicks you want to ignore. Messages of those users will not be bridged.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="spammer1 spammer2"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#OPTIONAL (default {BRIDGE}-{NICK})
|
||||
RemoteNickFormat="[{BRIDGE}] <{NICK}> "
|
||||
|
||||
###################################################################
|
||||
#slack section
|
||||
###################################################################
|
||||
|
||||
[slack]
|
||||
#Enable enables this bridge
|
||||
#OPTIONAL (default false)
|
||||
Enable=true
|
||||
|
||||
#### Settings for webhook matterbridge.
|
||||
#### These settings will not be used when useAPI is enabled
|
||||
|
||||
#Url is your incoming webhook url as specified in slack
|
||||
#See account settings - integrations - incoming webhooks on slack
|
||||
#REQUIRED (unless useAPI=true)
|
||||
URL="https://hooks.slack.com/services/yourhook"
|
||||
|
||||
#Address to listen on for outgoing webhook requests from slack
|
||||
#See account settings - integrations - outgoing webhooks on slack
|
||||
#This setting will not be used when useAPI is eanbled
|
||||
#webhooks
|
||||
#REQUIRED (unless useAPI=true)
|
||||
BindAddress="0.0.0.0:9999"
|
||||
|
||||
#Icon that will be showed in slack
|
||||
#OPTIONAL
|
||||
IconURL="http://youricon.png"
|
||||
|
||||
#### Settings for using slack API
|
||||
#OPTIONAL
|
||||
useAPI=false
|
||||
|
||||
#Token to connect with the Slack API
|
||||
#REQUIRED (when useAPI=true)
|
||||
Token="yourslacktoken"
|
||||
|
||||
#### Shared settings for webhooks and API
|
||||
|
||||
#Whether to prefix messages from other bridges to mattermost with the sender's nick.
|
||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||
#slack server. If you set PrefixMessagesWithNick to true, each message
|
||||
#from bridge to Slack will by default be prefixed by "bridge-" + nick. You can,
|
||||
#however, modify how the messages appear, by setting (and modifying) RemoteNickFormat
|
||||
#OPTIONAL (default false)
|
||||
PrefixMessagesWithNick=false
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#OPTIONAL (default {BRIDGE}-{NICK})
|
||||
RemoteNickFormat="[{BRIDGE}] <{NICK}>
|
||||
|
||||
#how to format the list of IRC nicks when displayed in slack
|
||||
#Possible options are "table" and "plain"
|
||||
#OPTIONAL (default plain)
|
||||
NickFormatter=plain
|
||||
#How many nicks to list per row for formatters that support this.
|
||||
#OPTIONAL (default 4)
|
||||
NicksPerRow=4
|
||||
|
||||
#Nicks you want to ignore. Messages from those users will not be bridged.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="mmbot spammer2"
|
||||
|
||||
###################################################################
|
||||
#multiple channel config
|
||||
###################################################################
|
||||
#You can specify multiple channels.
|
||||
#The name is just an identifier for you.
|
||||
#REQUIRED (at least 1 channel)
|
||||
[Channel "channel1"]
|
||||
#Choose the IRC channel to send messages to.
|
||||
IRC="#off-topic"
|
||||
#Choose the mattermost channel to messages to.
|
||||
mattermost="off-topic"
|
||||
#Choose the xmpp channel to send messages to.
|
||||
xmpp="off-topic"
|
||||
#Choose the Gitter channel to send messages to.
|
||||
#Gitter channels are named "user/repo"
|
||||
gitter="42wim/matterbridge"
|
||||
#Choose the slack channel to send messages to.
|
||||
slack="general"
|
||||
|
||||
[Channel "testchannel"]
|
||||
IRC="#testing"
|
||||
mattermost="testing"
|
||||
xmpp="testing"
|
||||
gitter="user/repo"
|
||||
slack="testing"
|
||||
|
||||
###################################################################
|
||||
#general
|
||||
###################################################################
|
||||
[general]
|
||||
#request your API key on https://github.com/giphy/GiphyAPI. This is a public beta key.
|
||||
#OPTIONAL
|
||||
GiphyApiKey="dc6zaTOxFJmzC"
|
||||
|
||||
#Enabling plus means you'll use the API version instead of the webhooks one
|
||||
Plus=false
|
@ -9,7 +9,7 @@ import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
var version = "0.7.0-dev"
|
||||
var version = "0.9.1"
|
||||
|
||||
func init() {
|
||||
log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
|
||||
@ -39,7 +39,7 @@ func main() {
|
||||
go func(gw config.SameChannelGateway) {
|
||||
err := samechannelgateway.New(cfg, &gw)
|
||||
if err != nil {
|
||||
log.Debugf("starting gateway failed %#v", err)
|
||||
log.Fatalf("starting gateway failed %#v", err)
|
||||
}
|
||||
}(gw)
|
||||
}
|
||||
@ -49,12 +49,11 @@ func main() {
|
||||
continue
|
||||
}
|
||||
fmt.Printf("starting gateway %#v\n", gw.Name)
|
||||
go func(gw config.Gateway) {
|
||||
err := gateway.New(cfg, &gw)
|
||||
if err != nil {
|
||||
log.Debugf("starting gateway failed %#v", err)
|
||||
}
|
||||
}(gw)
|
||||
g := gateway.New(cfg, &gw)
|
||||
err := g.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("starting gateway failed %#v", err)
|
||||
}
|
||||
}
|
||||
select {}
|
||||
}
|
||||
|
@ -13,6 +13,10 @@
|
||||
#REQUIRED
|
||||
Server="irc.freenode.net:6667"
|
||||
|
||||
#Password for irc server (if necessary)
|
||||
#OPTIONAL (default "")
|
||||
Password=""
|
||||
|
||||
#Enable to use TLS connection to your irc server.
|
||||
#OPTIONAL (default false)
|
||||
UseTLS=false
|
||||
@ -37,18 +41,6 @@ Nick="matterbot"
|
||||
NickServNick="nickserv"
|
||||
NickServPassword="secret"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="ircspammer1 ircspammer2"
|
||||
|
||||
#Flood control
|
||||
#Delay in milliseconds between each message send to the IRC server
|
||||
#OPTIONAL (default 1300)
|
||||
@ -60,6 +52,22 @@ MessageDelay=1300
|
||||
#OPTIONAL (default 30)
|
||||
MessageQueue=30
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="ircspammer1 ircspammer2"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment)
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
###################################################################
|
||||
#XMPP section
|
||||
###################################################################
|
||||
@ -89,6 +97,70 @@ Muc="conference.jabber.example.com"
|
||||
#REQUIRED
|
||||
Nick="xmppbot"
|
||||
|
||||
#Enable to not verify the certificate on your xmpp server.
|
||||
#e.g. when using selfsigned certificates
|
||||
#OPTIONAL (default false)
|
||||
SkipTLSVerify=true
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="ircspammer1 ircspammer2"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment)
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
|
||||
###################################################################
|
||||
#hipchat section
|
||||
###################################################################
|
||||
#Go to https://www.hipchat.com/account/xmpp this will show you the necessary data
|
||||
#to fill in the section below
|
||||
[xmpp.hipchat]
|
||||
#xmpp server to connect to.
|
||||
#REQUIRED
|
||||
Server="chat.hipchat.com:5222"
|
||||
|
||||
#Jabber ID
|
||||
#REQUIRED
|
||||
Jid="12345_12345@chat.hipchat.com"
|
||||
|
||||
#Password (your hipchat password)
|
||||
#REQUIRED
|
||||
Password="yourpass"
|
||||
|
||||
#Conference (MUC) domain
|
||||
#REQUIRED
|
||||
Muc="conf.hipchat.com"
|
||||
|
||||
#Room nickname
|
||||
#REQUIRED
|
||||
Nick="yourlogin"
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="spammer1 spammer2"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}/{BRIDGE}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment)
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
|
||||
###################################################################
|
||||
#mattermost section
|
||||
@ -150,9 +222,13 @@ NoTLS=false
|
||||
#OPTIONAL (default false)
|
||||
SkipTLSVerify=true
|
||||
|
||||
#Enable to show IRC joins/parts in mattermost.
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
#how to format the list of IRC nicks when displayed in mattermost.
|
||||
#Possible options are "table" and "plain"
|
||||
#OPTIONAL (default plain)
|
||||
NickFormatter="plain"
|
||||
#How many nicks to list per row for formatters that support this.
|
||||
#OPTIONAL (default 4)
|
||||
NicksPerRow=4
|
||||
|
||||
#Whether to prefix messages from other bridges to mattermost with the sender's nick.
|
||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||
@ -162,6 +238,11 @@ ShowJoinPart=false
|
||||
#OPTIONAL (default false)
|
||||
PrefixMessagesWithNick=false
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="ircspammer1 ircspammer2"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
@ -169,17 +250,9 @@ PrefixMessagesWithNick=false
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#how to format the list of IRC nicks when displayed in mattermost.
|
||||
#Possible options are "table" and "plain"
|
||||
#OPTIONAL (default plain)
|
||||
NickFormatter="plain"
|
||||
#How many nicks to list per row for formatters that support this.
|
||||
#OPTIONAL (default 4)
|
||||
NicksPerRow=4
|
||||
|
||||
#Nicks you want to ignore. Messages from those users will not be bridged.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="mmbot spammer2"
|
||||
#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment)
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
###################################################################
|
||||
#Gitter section
|
||||
@ -197,9 +270,10 @@ IgnoreNicks="mmbot spammer2"
|
||||
#REQUIRED
|
||||
Token="Yourtokenhere"
|
||||
|
||||
#Nicks you want to ignore. Messages of those users will not be bridged.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="spammer1 spammer2"
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="ircspammer1 ircspammer2"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
@ -208,6 +282,10 @@ IgnoreNicks="spammer1 spammer2"
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment)
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
###################################################################
|
||||
#slack section
|
||||
###################################################################
|
||||
@ -237,6 +315,8 @@ BindAddress="0.0.0.0:9999"
|
||||
useAPI=false
|
||||
|
||||
#Token to connect with the Slack API
|
||||
#You'll have to use a test/api-token using a dedicated user and not a bot token.
|
||||
#See https://github.com/42wim/matterbridge/issues/75 for more info.
|
||||
#REQUIRED (when useAPI=true)
|
||||
Token="yourslacktoken"
|
||||
|
||||
@ -249,21 +329,6 @@ Token="yourslacktoken"
|
||||
#OPTIONAL
|
||||
IconURL="https://robohash.org/{NICK}.png?size=48x48"
|
||||
|
||||
#Whether to prefix messages from other bridges to mattermost with RemoteNickFormat
|
||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||
#slack server. If you set PrefixMessagesWithNick to true, each message
|
||||
#from bridge to Slack will by default be prefixed by "bridge-" + nick. You can,
|
||||
#however, modify how the messages appear, by setting (and modifying) RemoteNickFormat
|
||||
#OPTIONAL (default false)
|
||||
PrefixMessagesWithNick=false
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#how to format the list of IRC nicks when displayed in slack
|
||||
#Possible options are "table" and "plain"
|
||||
#OPTIONAL (default plain)
|
||||
@ -272,9 +337,29 @@ NickFormatter="plain"
|
||||
#OPTIONAL (default 4)
|
||||
NicksPerRow=4
|
||||
|
||||
#Nicks you want to ignore. Messages from those users will not be bridged.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="mmbot spammer2"
|
||||
#Whether to prefix messages from other bridges to mattermost with RemoteNickFormat
|
||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||
#slack server. If you set PrefixMessagesWithNick to true, each message
|
||||
#from bridge to Slack will by default be prefixed by "bridge-" + nick. You can,
|
||||
#however, modify how the messages appear, by setting (and modifying) RemoteNickFormat
|
||||
#OPTIONAL (default false)
|
||||
PrefixMessagesWithNick=false
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="ircspammer1 ircspammer2"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment)
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
###################################################################
|
||||
#discord section
|
||||
@ -288,15 +373,44 @@ IgnoreNicks="mmbot spammer2"
|
||||
#Token to connect with Discord API
|
||||
#You can get your token by following the instructions on
|
||||
#https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token
|
||||
#The "Bot" tag needs to be added before the token
|
||||
#REQUIRED
|
||||
Token="Bot Yourtokenhere"
|
||||
Token="Yourtokenhere"
|
||||
|
||||
#REQUIRED
|
||||
Server="yourservername"
|
||||
|
||||
#Nicks you want to ignore. Messages of those users will not be bridged.
|
||||
#OPTIONAL
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="ircspammer1 ircspammer2"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment)
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
###################################################################
|
||||
#telegram section
|
||||
###################################################################
|
||||
[telegram]
|
||||
|
||||
#You can configure multiple servers "[telegram.name]" or "[telegram.name2]"
|
||||
#In this example we use [telegram.secure]
|
||||
#REQUIRED
|
||||
[telegram.secure]
|
||||
#Token to connect with telegram API
|
||||
#REQUIRED
|
||||
Token="Yourtokenhere"
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="spammer1 spammer2"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
@ -306,6 +420,79 @@ IgnoreNicks="spammer1 spammer2"
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment)
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
|
||||
###################################################################
|
||||
#rocketchat section
|
||||
###################################################################
|
||||
[rocketchat]
|
||||
#You can configure multiple servers "[rocketchat.name]" or "[rocketchat.name2]"
|
||||
#In this example we use [rocketchat.work]
|
||||
#REQUIRED
|
||||
|
||||
[rocketchat.rockme]
|
||||
#Url is your incoming webhook url as specified in rocketchat
|
||||
#Read #https://rocket.chat/docs/administrator-guides/integrations/#how-to-create-a-new-incoming-webhook
|
||||
#See administration - integrations - new integration - incoming webhook
|
||||
#REQUIRED
|
||||
URL="https://yourdomain/hooks/yourhookkey"
|
||||
|
||||
#Address to listen on for outgoing webhook requests from rocketchat.
|
||||
#See administration - integrations - new integration - outgoing webhook
|
||||
#REQUIRED
|
||||
BindAddress="0.0.0.0:9999"
|
||||
|
||||
#Your nick/username as specified in your incoming webhook "Post as" setting
|
||||
#REQUIRED
|
||||
Nick="matterbot"
|
||||
|
||||
#Enable this to make a http connection (instead of https) to your rocketchat
|
||||
#OPTIONAL (default false)
|
||||
NoTLS=false
|
||||
|
||||
#Enable to not verify the certificate on your rocketchat server.
|
||||
#e.g. when using selfsigned certificates
|
||||
#OPTIONAL (default false)
|
||||
SkipTLSVerify=true
|
||||
|
||||
#Whether to prefix messages from other bridges to rocketchat with the sender's nick.
|
||||
#Useful if username overrides for incoming webhooks isn't enabled on the
|
||||
#rocketchat server. If you set PrefixMessagesWithNick to true, each message
|
||||
#from bridge to rocketchat will by default be prefixed by the RemoteNickFormat setting. i
|
||||
#OPTIONAL (default false)
|
||||
PrefixMessagesWithNick=false
|
||||
|
||||
#Nicks you want to ignore.
|
||||
#Messages from those users will not be sent to other bridges.
|
||||
#OPTIONAL
|
||||
IgnoreNicks="ircspammer1 ircspammer2"
|
||||
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment)
|
||||
#OPTIONAL (default false)
|
||||
ShowJoinPart=false
|
||||
|
||||
|
||||
###################################################################
|
||||
#General configuration
|
||||
###################################################################
|
||||
#Settings here override specific settings for each protocol
|
||||
[general]
|
||||
#RemoteNickFormat defines how remote users appear on this bridge
|
||||
#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username.
|
||||
#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge
|
||||
#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge
|
||||
#OPTIONAL (default empty)
|
||||
RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
|
||||
###################################################################
|
||||
#Gateway configuration
|
||||
@ -318,7 +505,7 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> "
|
||||
#from [[gateway.in]] to.
|
||||
#
|
||||
#Most of the time [[gateway.in]] and [[gateway.out]] are the same if you
|
||||
#want bidirectional bridging.
|
||||
#want bidirectional bridging. You can then use [[gateway.inout]]
|
||||
#
|
||||
|
||||
[[gateway]]
|
||||
@ -346,21 +533,38 @@ enable=true
|
||||
#discord - channel (without the #)
|
||||
# - ID:123456789 (where 123456789 is the channel ID)
|
||||
# (https://github.com/42wim/matterbridge/issues/57)
|
||||
#telegram - chatid (a large negative number, eg -123456789)
|
||||
#hipchat - id_channel (see https://www.hipchat.com/account/xmpp for the correct channel)
|
||||
#rocketchat - #channel (# is required)
|
||||
#REQUIRED
|
||||
channel="#testing"
|
||||
|
||||
[[gateway.in]]
|
||||
account="mattermost.work"
|
||||
channel="off-topic"
|
||||
#OPTIONAL - only used for IRC protocol at the moment
|
||||
[gateway.in.options]
|
||||
#OPTIONAL - your irc channel key
|
||||
key="yourkey"
|
||||
|
||||
|
||||
#[[gateway.out]] specifies the account and channels we will sent messages to.
|
||||
[[gateway.out]]
|
||||
account="irc.freenode"
|
||||
channel="#testing"
|
||||
|
||||
[[gateway.out]]
|
||||
#OPTIONAL - only used for IRC protocol at the moment
|
||||
[gateway.out.options]
|
||||
#OPTIONAL - your irc channel key
|
||||
key="yourkey"
|
||||
|
||||
#[[gateway.inout]] can be used when then channel will be used to receive from
|
||||
#and send messages to
|
||||
[[gateway.inout]]
|
||||
account="mattermost.work"
|
||||
channel="off-topic"
|
||||
|
||||
#OPTIONAL - only used for IRC protocol at the moment
|
||||
[gateway.inout.options]
|
||||
#OPTIONAL - your irc channel key
|
||||
key="yourkey"
|
||||
|
||||
#If you want to do a 1:1 mapping between protocols where the channelnames are the same
|
||||
#e.g. slack and mattermost you can use the samechannelgateway configuration
|
||||
|
@ -19,14 +19,14 @@ enable=true
|
||||
account="irc.freenode"
|
||||
channel="#testing"
|
||||
|
||||
[[gateway.in]]
|
||||
account="mattermost.work"
|
||||
channel="off-topic"
|
||||
|
||||
[[gateway.out]]
|
||||
account="irc.freenode"
|
||||
channel="#testing"
|
||||
|
||||
[[gateway.in]]
|
||||
account="mattermost.work"
|
||||
channel="off-topic"
|
||||
|
||||
[[gateway.out]]
|
||||
account="mattermost.work"
|
||||
channel="off-topic"
|
||||
|
@ -80,6 +80,11 @@ func (m *MMClient) SetLogLevel(level string) {
|
||||
}
|
||||
|
||||
func (m *MMClient) Login() error {
|
||||
// check if this is a first connect or a reconnection
|
||||
firstConnection := true
|
||||
if m.WsConnected == true {
|
||||
firstConnection = false
|
||||
}
|
||||
m.WsConnected = false
|
||||
if m.WsQuit {
|
||||
return nil
|
||||
@ -125,11 +130,7 @@ func (m *MMClient) Login() error {
|
||||
if appErr != nil {
|
||||
d := b.Duration()
|
||||
m.log.Debug(appErr.DetailedError)
|
||||
//TODO more generic fix needed
|
||||
if !strings.Contains(appErr.DetailedError, "connection refused") &&
|
||||
!strings.Contains(appErr.DetailedError, "invalid character") &&
|
||||
!strings.Contains(appErr.DetailedError, "connection reset by peer") &&
|
||||
!strings.Contains(appErr.DetailedError, "connection timed out") {
|
||||
if firstConnection {
|
||||
if appErr.Message == "" {
|
||||
return errors.New(appErr.DetailedError)
|
||||
}
|
||||
@ -264,7 +265,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) {
|
||||
}
|
||||
rmsg.Username = m.GetUser(data.UserId).Username
|
||||
rmsg.Channel = m.GetChannelName(data.ChannelId)
|
||||
rmsg.Team = m.GetTeamName(rmsg.Raw.TeamId)
|
||||
rmsg.Team = m.GetTeamName(rmsg.Raw.Data["team_id"].(string))
|
||||
// direct message
|
||||
if rmsg.Raw.Data["channel_type"] == "D" {
|
||||
rmsg.Channel = m.GetUser(data.UserId).Username
|
||||
@ -275,7 +276,7 @@ func (m *MMClient) parseActionPost(rmsg *Message) {
|
||||
}
|
||||
|
||||
func (m *MMClient) UpdateUsers() error {
|
||||
mmusers, err := m.Client.GetProfilesForDirectMessageList(m.Team.Id)
|
||||
mmusers, err := m.Client.GetProfiles(0, 50000, "")
|
||||
if err != nil {
|
||||
return errors.New(err.DetailedError)
|
||||
}
|
||||
@ -305,7 +306,7 @@ func (m *MMClient) GetChannelName(channelId string) string {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
for _, t := range m.OtherTeams {
|
||||
for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) {
|
||||
for _, channel := range append(*t.Channels, *t.MoreChannels...) {
|
||||
if channel.Id == channelId {
|
||||
return channel.Name
|
||||
}
|
||||
@ -322,7 +323,7 @@ func (m *MMClient) GetChannelId(name string, teamId string) string {
|
||||
}
|
||||
for _, t := range m.OtherTeams {
|
||||
if t.Id == teamId {
|
||||
for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) {
|
||||
for _, channel := range append(*t.Channels, *t.MoreChannels...) {
|
||||
if channel.Name == name {
|
||||
return channel.Id
|
||||
}
|
||||
@ -336,7 +337,7 @@ func (m *MMClient) GetChannelHeader(channelId string) string {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
for _, t := range m.OtherTeams {
|
||||
for _, channel := range append(t.Channels.Channels, t.MoreChannels.Channels...) {
|
||||
for _, channel := range append(*t.Channels, *t.MoreChannels...) {
|
||||
if channel.Id == channelId {
|
||||
return channel.Header
|
||||
}
|
||||
@ -354,7 +355,7 @@ func (m *MMClient) PostMessage(channelId string, text string) {
|
||||
func (m *MMClient) JoinChannel(channelId string) error {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
for _, c := range m.Team.Channels.Channels {
|
||||
for _, c := range *m.Team.Channels {
|
||||
if c.Id == channelId {
|
||||
m.log.Debug("Not joining ", channelId, " already joined.")
|
||||
return nil
|
||||
@ -397,7 +398,7 @@ func (m *MMClient) GetPublicLink(filename string) string {
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return res.Data.(string)
|
||||
return res
|
||||
}
|
||||
|
||||
func (m *MMClient) GetPublicLinks(filenames []string) []string {
|
||||
@ -407,7 +408,7 @@ func (m *MMClient) GetPublicLinks(filenames []string) []string {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
output = append(output, res.Data.(string))
|
||||
output = append(output, res)
|
||||
}
|
||||
return output
|
||||
}
|
||||
@ -432,15 +433,17 @@ func (m *MMClient) UpdateLastViewed(channelId string) {
|
||||
}
|
||||
|
||||
func (m *MMClient) UsernamesInChannel(channelId string) []string {
|
||||
ceiRes, err := m.Client.GetChannelExtraInfo(channelId, 5000, "")
|
||||
res, err := m.Client.GetMyChannelMembers()
|
||||
if err != nil {
|
||||
m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, err)
|
||||
return []string{}
|
||||
}
|
||||
extra := ceiRes.Data.(*model.ChannelExtra)
|
||||
members := res.Data.(*model.ChannelMembers)
|
||||
result := []string{}
|
||||
for _, member := range extra.Members {
|
||||
result = append(result, member.Username)
|
||||
for _, channel := range *members {
|
||||
if channel.ChannelId == channelId {
|
||||
result = append(result, m.GetUser(channel.UserId).Username)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -500,10 +503,10 @@ func (m *MMClient) GetChannels() []*model.Channel {
|
||||
defer m.RUnlock()
|
||||
var channels []*model.Channel
|
||||
// our primary team channels first
|
||||
channels = append(channels, m.Team.Channels.Channels...)
|
||||
channels = append(channels, *m.Team.Channels...)
|
||||
for _, t := range m.OtherTeams {
|
||||
if t.Id != m.Team.Id {
|
||||
channels = append(channels, t.Channels.Channels...)
|
||||
channels = append(channels, *t.Channels...)
|
||||
}
|
||||
}
|
||||
return channels
|
||||
@ -515,7 +518,7 @@ func (m *MMClient) GetMoreChannels() []*model.Channel {
|
||||
defer m.RUnlock()
|
||||
var channels []*model.Channel
|
||||
for _, t := range m.OtherTeams {
|
||||
channels = append(channels, t.MoreChannels.Channels...)
|
||||
channels = append(channels, *t.MoreChannels...)
|
||||
}
|
||||
return channels
|
||||
}
|
||||
@ -526,8 +529,8 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string {
|
||||
defer m.RUnlock()
|
||||
var channels []*model.Channel
|
||||
for _, t := range m.OtherTeams {
|
||||
channels = append(channels, t.Channels.Channels...)
|
||||
channels = append(channels, t.MoreChannels.Channels...)
|
||||
channels = append(channels, *t.Channels...)
|
||||
channels = append(channels, *t.MoreChannels...)
|
||||
for _, c := range channels {
|
||||
if c.Id == channelId {
|
||||
return t.Id
|
||||
@ -540,12 +543,12 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string {
|
||||
func (m *MMClient) GetLastViewedAt(channelId string) int64 {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
for _, t := range m.OtherTeams {
|
||||
if _, ok := t.Channels.Members[channelId]; ok {
|
||||
return t.Channels.Members[channelId].LastViewedAt
|
||||
}
|
||||
res, err := m.Client.GetChannel(channelId, "")
|
||||
if err != nil {
|
||||
return model.GetMillis()
|
||||
}
|
||||
return 0
|
||||
data := res.Data.(*model.ChannelData)
|
||||
return data.Member.LastViewedAt
|
||||
}
|
||||
|
||||
func (m *MMClient) GetUsers() map[string]*model.User {
|
||||
@ -579,6 +582,27 @@ func (m *MMClient) GetStatus(userId string) string {
|
||||
return "offline"
|
||||
}
|
||||
|
||||
func (m *MMClient) GetStatuses() map[string]string {
|
||||
var ok bool
|
||||
statuses := make(map[string]string)
|
||||
res, err := m.Client.GetStatuses()
|
||||
if err != nil {
|
||||
return statuses
|
||||
}
|
||||
if statuses, ok = res.Data.(map[string]string); ok {
|
||||
for userId, status := range statuses {
|
||||
statuses[userId] = "offline"
|
||||
if status == model.STATUS_AWAY {
|
||||
statuses[userId] = "away"
|
||||
}
|
||||
if status == model.STATUS_ONLINE {
|
||||
statuses[userId] = "online"
|
||||
}
|
||||
}
|
||||
}
|
||||
return statuses
|
||||
}
|
||||
|
||||
func (m *MMClient) GetTeamId() string {
|
||||
return m.Team.Id
|
||||
}
|
||||
@ -619,11 +643,20 @@ func (m *MMClient) initUser() error {
|
||||
//m.log.Debug("initUser(): loading all team data")
|
||||
for _, v := range initData.Teams {
|
||||
m.Client.SetTeamId(v.Id)
|
||||
mmusers, _ := m.Client.GetProfiles(v.Id, "")
|
||||
mmusers, err := m.Client.GetProfiles(0, 50000, "")
|
||||
if err != nil {
|
||||
return errors.New(err.DetailedError)
|
||||
}
|
||||
t := &Team{Team: v, Users: mmusers.Data.(map[string]*model.User), Id: v.Id}
|
||||
mmchannels, _ := m.Client.GetChannels("")
|
||||
mmchannels, err := m.Client.GetChannels("")
|
||||
if err != nil {
|
||||
return errors.New(err.DetailedError)
|
||||
}
|
||||
t.Channels = mmchannels.Data.(*model.ChannelList)
|
||||
mmchannels, _ = m.Client.GetMoreChannels("")
|
||||
mmchannels, err = m.Client.GetMoreChannels("")
|
||||
if err != nil {
|
||||
return errors.New(err.DetailedError)
|
||||
}
|
||||
t.MoreChannels = mmchannels.Data.(*model.ChannelList)
|
||||
m.OtherTeams = append(m.OtherTeams, t)
|
||||
if v.Name == m.Credentials.Team {
|
||||
|
21
vendor/github.com/go-telegram-bot-api/telegram-bot-api/LICENSE.txt
generated
vendored
Normal file
21
vendor/github.com/go-telegram-bot-api/telegram-bot-api/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Syfaro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
650
vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go
generated
vendored
Normal file
650
vendor/github.com/go-telegram-bot-api/telegram-bot-api/bot.go
generated
vendored
Normal file
@ -0,0 +1,650 @@
|
||||
// Package tgbotapi has functions and types used for interacting with
|
||||
// the Telegram Bot API.
|
||||
package tgbotapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/technoweenie/multipartstreamer"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BotAPI allows you to interact with the Telegram Bot API.
|
||||
type BotAPI struct {
|
||||
Token string `json:"token"`
|
||||
Debug bool `json:"debug"`
|
||||
Self User `json:"-"`
|
||||
Client *http.Client `json:"-"`
|
||||
}
|
||||
|
||||
// NewBotAPI creates a new BotAPI instance.
|
||||
//
|
||||
// It requires a token, provided by @BotFather on Telegram.
|
||||
func NewBotAPI(token string) (*BotAPI, error) {
|
||||
return NewBotAPIWithClient(token, &http.Client{})
|
||||
}
|
||||
|
||||
// NewBotAPIWithClient creates a new BotAPI instance
|
||||
// and allows you to pass a http.Client.
|
||||
//
|
||||
// It requires a token, provided by @BotFather on Telegram.
|
||||
func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) {
|
||||
bot := &BotAPI{
|
||||
Token: token,
|
||||
Client: client,
|
||||
}
|
||||
|
||||
self, err := bot.GetMe()
|
||||
if err != nil {
|
||||
return &BotAPI{}, err
|
||||
}
|
||||
|
||||
bot.Self = self
|
||||
|
||||
return bot, nil
|
||||
}
|
||||
|
||||
// MakeRequest makes a request to a specific endpoint with our token.
|
||||
func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse, error) {
|
||||
method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint)
|
||||
|
||||
resp, err := bot.Client.PostForm(method, params)
|
||||
if err != nil {
|
||||
return APIResponse{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
return APIResponse{}, errors.New(ErrAPIForbidden)
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return APIResponse{}, err
|
||||
}
|
||||
|
||||
if bot.Debug {
|
||||
log.Println(endpoint, string(bytes))
|
||||
}
|
||||
|
||||
var apiResp APIResponse
|
||||
json.Unmarshal(bytes, &apiResp)
|
||||
|
||||
if !apiResp.Ok {
|
||||
return APIResponse{}, errors.New(apiResp.Description)
|
||||
}
|
||||
|
||||
return apiResp, nil
|
||||
}
|
||||
|
||||
// makeMessageRequest makes a request to a method that returns a Message.
|
||||
func (bot *BotAPI) makeMessageRequest(endpoint string, params url.Values) (Message, error) {
|
||||
resp, err := bot.MakeRequest(endpoint, params)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
var message Message
|
||||
json.Unmarshal(resp.Result, &message)
|
||||
|
||||
bot.debugLog(endpoint, params, message)
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// UploadFile makes a request to the API with a file.
|
||||
//
|
||||
// Requires the parameter to hold the file not be in the params.
|
||||
// File should be a string to a file path, a FileBytes struct,
|
||||
// or a FileReader struct.
|
||||
//
|
||||
// Note that if your FileReader has a size set to -1, it will read
|
||||
// the file into memory to calculate a size.
|
||||
func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) {
|
||||
ms := multipartstreamer.New()
|
||||
ms.WriteFields(params)
|
||||
|
||||
switch f := file.(type) {
|
||||
case string:
|
||||
fileHandle, err := os.Open(f)
|
||||
if err != nil {
|
||||
return APIResponse{}, err
|
||||
}
|
||||
defer fileHandle.Close()
|
||||
|
||||
fi, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return APIResponse{}, err
|
||||
}
|
||||
|
||||
ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle)
|
||||
case FileBytes:
|
||||
buf := bytes.NewBuffer(f.Bytes)
|
||||
ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf)
|
||||
case FileReader:
|
||||
if f.Size != -1 {
|
||||
ms.WriteReader(fieldname, f.Name, f.Size, f.Reader)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(f.Reader)
|
||||
if err != nil {
|
||||
return APIResponse{}, err
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
ms.WriteReader(fieldname, f.Name, int64(len(data)), buf)
|
||||
default:
|
||||
return APIResponse{}, errors.New(ErrBadFileType)
|
||||
}
|
||||
|
||||
method := fmt.Sprintf(APIEndpoint, bot.Token, endpoint)
|
||||
|
||||
req, err := http.NewRequest("POST", method, nil)
|
||||
if err != nil {
|
||||
return APIResponse{}, err
|
||||
}
|
||||
|
||||
ms.SetupRequest(req)
|
||||
|
||||
res, err := bot.Client.Do(req)
|
||||
if err != nil {
|
||||
return APIResponse{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
bytes, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return APIResponse{}, err
|
||||
}
|
||||
|
||||
if bot.Debug {
|
||||
log.Println(string(bytes))
|
||||
}
|
||||
|
||||
var apiResp APIResponse
|
||||
json.Unmarshal(bytes, &apiResp)
|
||||
|
||||
if !apiResp.Ok {
|
||||
return APIResponse{}, errors.New(apiResp.Description)
|
||||
}
|
||||
|
||||
return apiResp, nil
|
||||
}
|
||||
|
||||
// GetFileDirectURL returns direct URL to file
|
||||
//
|
||||
// It requires the FileID.
|
||||
func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) {
|
||||
file, err := bot.GetFile(FileConfig{fileID})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return file.Link(bot.Token), nil
|
||||
}
|
||||
|
||||
// GetMe fetches the currently authenticated bot.
|
||||
//
|
||||
// This method is called upon creation to validate the token,
|
||||
// and so you may get this data from BotAPI.Self without the need for
|
||||
// another request.
|
||||
func (bot *BotAPI) GetMe() (User, error) {
|
||||
resp, err := bot.MakeRequest("getMe", nil)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
|
||||
var user User
|
||||
json.Unmarshal(resp.Result, &user)
|
||||
|
||||
bot.debugLog("getMe", nil, user)
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// IsMessageToMe returns true if message directed to this bot.
|
||||
//
|
||||
// It requires the Message.
|
||||
func (bot *BotAPI) IsMessageToMe(message Message) bool {
|
||||
return strings.Contains(message.Text, "@"+bot.Self.UserName)
|
||||
}
|
||||
|
||||
// Send will send a Chattable item to Telegram.
|
||||
//
|
||||
// It requires the Chattable to send.
|
||||
func (bot *BotAPI) Send(c Chattable) (Message, error) {
|
||||
switch c.(type) {
|
||||
case Fileable:
|
||||
return bot.sendFile(c.(Fileable))
|
||||
default:
|
||||
return bot.sendChattable(c)
|
||||
}
|
||||
}
|
||||
|
||||
// debugLog checks if the bot is currently running in debug mode, and if
|
||||
// so will display information about the request and response in the
|
||||
// debug log.
|
||||
func (bot *BotAPI) debugLog(context string, v url.Values, message interface{}) {
|
||||
if bot.Debug {
|
||||
log.Printf("%s req : %+v\n", context, v)
|
||||
log.Printf("%s resp: %+v\n", context, message)
|
||||
}
|
||||
}
|
||||
|
||||
// sendExisting will send a Message with an existing file to Telegram.
|
||||
func (bot *BotAPI) sendExisting(method string, config Fileable) (Message, error) {
|
||||
v, err := config.values()
|
||||
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
message, err := bot.makeMessageRequest(method, v)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// uploadAndSend will send a Message with a new file to Telegram.
|
||||
func (bot *BotAPI) uploadAndSend(method string, config Fileable) (Message, error) {
|
||||
params, err := config.params()
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
file := config.getFile()
|
||||
|
||||
resp, err := bot.UploadFile(method, params, config.name(), file)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
var message Message
|
||||
json.Unmarshal(resp.Result, &message)
|
||||
|
||||
bot.debugLog(method, nil, message)
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// sendFile determines if the file is using an existing file or uploading
|
||||
// a new file, then sends it as needed.
|
||||
func (bot *BotAPI) sendFile(config Fileable) (Message, error) {
|
||||
if config.useExistingFile() {
|
||||
return bot.sendExisting(config.method(), config)
|
||||
}
|
||||
|
||||
return bot.uploadAndSend(config.method(), config)
|
||||
}
|
||||
|
||||
// sendChattable sends a Chattable.
|
||||
func (bot *BotAPI) sendChattable(config Chattable) (Message, error) {
|
||||
v, err := config.values()
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
message, err := bot.makeMessageRequest(config.method(), v)
|
||||
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// GetUserProfilePhotos gets a user's profile photos.
|
||||
//
|
||||
// It requires UserID.
|
||||
// Offset and Limit are optional.
|
||||
func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
|
||||
v := url.Values{}
|
||||
v.Add("user_id", strconv.Itoa(config.UserID))
|
||||
if config.Offset != 0 {
|
||||
v.Add("offset", strconv.Itoa(config.Offset))
|
||||
}
|
||||
if config.Limit != 0 {
|
||||
v.Add("limit", strconv.Itoa(config.Limit))
|
||||
}
|
||||
|
||||
resp, err := bot.MakeRequest("getUserProfilePhotos", v)
|
||||
if err != nil {
|
||||
return UserProfilePhotos{}, err
|
||||
}
|
||||
|
||||
var profilePhotos UserProfilePhotos
|
||||
json.Unmarshal(resp.Result, &profilePhotos)
|
||||
|
||||
bot.debugLog("GetUserProfilePhoto", v, profilePhotos)
|
||||
|
||||
return profilePhotos, nil
|
||||
}
|
||||
|
||||
// GetFile returns a File which can download a file from Telegram.
|
||||
//
|
||||
// Requires FileID.
|
||||
func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
|
||||
v := url.Values{}
|
||||
v.Add("file_id", config.FileID)
|
||||
|
||||
resp, err := bot.MakeRequest("getFile", v)
|
||||
if err != nil {
|
||||
return File{}, err
|
||||
}
|
||||
|
||||
var file File
|
||||
json.Unmarshal(resp.Result, &file)
|
||||
|
||||
bot.debugLog("GetFile", v, file)
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// GetUpdates fetches updates.
|
||||
// If a WebHook is set, this will not return any data!
|
||||
//
|
||||
// Offset, Limit, and Timeout are optional.
|
||||
// To avoid stale items, set Offset to one higher than the previous item.
|
||||
// Set Timeout to a large number to reduce requests so you can get updates
|
||||
// instantly instead of having to wait between requests.
|
||||
func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
|
||||
v := url.Values{}
|
||||
if config.Offset != 0 {
|
||||
v.Add("offset", strconv.Itoa(config.Offset))
|
||||
}
|
||||
if config.Limit > 0 {
|
||||
v.Add("limit", strconv.Itoa(config.Limit))
|
||||
}
|
||||
if config.Timeout > 0 {
|
||||
v.Add("timeout", strconv.Itoa(config.Timeout))
|
||||
}
|
||||
|
||||
resp, err := bot.MakeRequest("getUpdates", v)
|
||||
if err != nil {
|
||||
return []Update{}, err
|
||||
}
|
||||
|
||||
var updates []Update
|
||||
json.Unmarshal(resp.Result, &updates)
|
||||
|
||||
bot.debugLog("getUpdates", v, updates)
|
||||
|
||||
return updates, nil
|
||||
}
|
||||
|
||||
// RemoveWebhook unsets the webhook.
|
||||
func (bot *BotAPI) RemoveWebhook() (APIResponse, error) {
|
||||
return bot.MakeRequest("setWebhook", url.Values{})
|
||||
}
|
||||
|
||||
// SetWebhook sets a webhook.
|
||||
//
|
||||
// If this is set, GetUpdates will not get any data!
|
||||
//
|
||||
// If you do not have a legitimate TLS certificate, you need to include
|
||||
// your self signed certificate with the config.
|
||||
func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) {
|
||||
if config.Certificate == nil {
|
||||
v := url.Values{}
|
||||
v.Add("url", config.URL.String())
|
||||
|
||||
return bot.MakeRequest("setWebhook", v)
|
||||
}
|
||||
|
||||
params := make(map[string]string)
|
||||
params["url"] = config.URL.String()
|
||||
|
||||
resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate)
|
||||
if err != nil {
|
||||
return APIResponse{}, err
|
||||
}
|
||||
|
||||
var apiResp APIResponse
|
||||
json.Unmarshal(resp.Result, &apiResp)
|
||||
|
||||
if bot.Debug {
|
||||
log.Printf("setWebhook resp: %+v\n", apiResp)
|
||||
}
|
||||
|
||||
return apiResp, nil
|
||||
}
|
||||
|
||||
// GetUpdatesChan starts and returns a channel for getting updates.
|
||||
func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (<-chan Update, error) {
|
||||
updatesChan := make(chan Update, 100)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
updates, err := bot.GetUpdates(config)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
log.Println("Failed to get updates, retrying in 3 seconds...")
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
for _, update := range updates {
|
||||
if update.UpdateID >= config.Offset {
|
||||
config.Offset = update.UpdateID + 1
|
||||
updatesChan <- update
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return updatesChan, nil
|
||||
}
|
||||
|
||||
// ListenForWebhook registers a http handler for a webhook.
|
||||
func (bot *BotAPI) ListenForWebhook(pattern string) <-chan Update {
|
||||
updatesChan := make(chan Update, 100)
|
||||
|
||||
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
|
||||
bytes, _ := ioutil.ReadAll(r.Body)
|
||||
|
||||
var update Update
|
||||
json.Unmarshal(bytes, &update)
|
||||
|
||||
updatesChan <- update
|
||||
})
|
||||
|
||||
return updatesChan
|
||||
}
|
||||
|
||||
// AnswerInlineQuery sends a response to an inline query.
|
||||
//
|
||||
// Note that you must respond to an inline query within 30 seconds.
|
||||
func (bot *BotAPI) AnswerInlineQuery(config InlineConfig) (APIResponse, error) {
|
||||
v := url.Values{}
|
||||
|
||||
v.Add("inline_query_id", config.InlineQueryID)
|
||||
v.Add("cache_time", strconv.Itoa(config.CacheTime))
|
||||
v.Add("is_personal", strconv.FormatBool(config.IsPersonal))
|
||||
v.Add("next_offset", config.NextOffset)
|
||||
data, err := json.Marshal(config.Results)
|
||||
if err != nil {
|
||||
return APIResponse{}, err
|
||||
}
|
||||
v.Add("results", string(data))
|
||||
v.Add("switch_pm_text", config.SwitchPMText)
|
||||
v.Add("switch_pm_parameter", config.SwitchPMParameter)
|
||||
|
||||
bot.debugLog("answerInlineQuery", v, nil)
|
||||
|
||||
return bot.MakeRequest("answerInlineQuery", v)
|
||||
}
|
||||
|
||||
// AnswerCallbackQuery sends a response to an inline query callback.
|
||||
func (bot *BotAPI) AnswerCallbackQuery(config CallbackConfig) (APIResponse, error) {
|
||||
v := url.Values{}
|
||||
|
||||
v.Add("callback_query_id", config.CallbackQueryID)
|
||||
v.Add("text", config.Text)
|
||||
v.Add("show_alert", strconv.FormatBool(config.ShowAlert))
|
||||
|
||||
bot.debugLog("answerCallbackQuery", v, nil)
|
||||
|
||||
return bot.MakeRequest("answerCallbackQuery", v)
|
||||
}
|
||||
|
||||
// KickChatMember kicks a user from a chat. Note that this only will work
|
||||
// in supergroups, and requires the bot to be an admin. Also note they
|
||||
// will be unable to rejoin until they are unbanned.
|
||||
func (bot *BotAPI) KickChatMember(config ChatMemberConfig) (APIResponse, error) {
|
||||
v := url.Values{}
|
||||
|
||||
if config.SuperGroupUsername == "" {
|
||||
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
|
||||
} else {
|
||||
v.Add("chat_id", config.SuperGroupUsername)
|
||||
}
|
||||
v.Add("user_id", strconv.Itoa(config.UserID))
|
||||
|
||||
bot.debugLog("kickChatMember", v, nil)
|
||||
|
||||
return bot.MakeRequest("kickChatMember", v)
|
||||
}
|
||||
|
||||
// LeaveChat makes the bot leave the chat.
|
||||
func (bot *BotAPI) LeaveChat(config ChatConfig) (APIResponse, error) {
|
||||
v := url.Values{}
|
||||
|
||||
if config.SuperGroupUsername == "" {
|
||||
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
|
||||
} else {
|
||||
v.Add("chat_id", config.SuperGroupUsername)
|
||||
}
|
||||
|
||||
bot.debugLog("leaveChat", v, nil)
|
||||
|
||||
return bot.MakeRequest("leaveChat", v)
|
||||
}
|
||||
|
||||
// GetChat gets information about a chat.
|
||||
func (bot *BotAPI) GetChat(config ChatConfig) (Chat, error) {
|
||||
v := url.Values{}
|
||||
|
||||
if config.SuperGroupUsername == "" {
|
||||
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
|
||||
} else {
|
||||
v.Add("chat_id", config.SuperGroupUsername)
|
||||
}
|
||||
|
||||
resp, err := bot.MakeRequest("getChat", v)
|
||||
if err != nil {
|
||||
return Chat{}, err
|
||||
}
|
||||
|
||||
var chat Chat
|
||||
err = json.Unmarshal(resp.Result, &chat)
|
||||
|
||||
bot.debugLog("getChat", v, chat)
|
||||
|
||||
return chat, err
|
||||
}
|
||||
|
||||
// GetChatAdministrators gets a list of administrators in the chat.
|
||||
//
|
||||
// If none have been appointed, only the creator will be returned.
|
||||
// Bots are not shown, even if they are an administrator.
|
||||
func (bot *BotAPI) GetChatAdministrators(config ChatConfig) ([]ChatMember, error) {
|
||||
v := url.Values{}
|
||||
|
||||
if config.SuperGroupUsername == "" {
|
||||
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
|
||||
} else {
|
||||
v.Add("chat_id", config.SuperGroupUsername)
|
||||
}
|
||||
|
||||
resp, err := bot.MakeRequest("getChatAdministrators", v)
|
||||
if err != nil {
|
||||
return []ChatMember{}, err
|
||||
}
|
||||
|
||||
var members []ChatMember
|
||||
err = json.Unmarshal(resp.Result, &members)
|
||||
|
||||
bot.debugLog("getChatAdministrators", v, members)
|
||||
|
||||
return members, err
|
||||
}
|
||||
|
||||
// GetChatMembersCount gets the number of users in a chat.
|
||||
func (bot *BotAPI) GetChatMembersCount(config ChatConfig) (int, error) {
|
||||
v := url.Values{}
|
||||
|
||||
if config.SuperGroupUsername == "" {
|
||||
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
|
||||
} else {
|
||||
v.Add("chat_id", config.SuperGroupUsername)
|
||||
}
|
||||
|
||||
resp, err := bot.MakeRequest("getChatMembersCount", v)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
var count int
|
||||
err = json.Unmarshal(resp.Result, &count)
|
||||
|
||||
bot.debugLog("getChatMembersCount", v, count)
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetChatMember gets a specific chat member.
|
||||
func (bot *BotAPI) GetChatMember(config ChatConfigWithUser) (ChatMember, error) {
|
||||
v := url.Values{}
|
||||
|
||||
if config.SuperGroupUsername == "" {
|
||||
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
|
||||
} else {
|
||||
v.Add("chat_id", config.SuperGroupUsername)
|
||||
}
|
||||
v.Add("user_id", strconv.Itoa(config.UserID))
|
||||
|
||||
resp, err := bot.MakeRequest("getChatMember", v)
|
||||
if err != nil {
|
||||
return ChatMember{}, err
|
||||
}
|
||||
|
||||
var member ChatMember
|
||||
err = json.Unmarshal(resp.Result, &member)
|
||||
|
||||
bot.debugLog("getChatMember", v, member)
|
||||
|
||||
return member, err
|
||||
}
|
||||
|
||||
// UnbanChatMember unbans a user from a chat. Note that this only will work
|
||||
// in supergroups, and requires the bot to be an admin.
|
||||
func (bot *BotAPI) UnbanChatMember(config ChatMemberConfig) (APIResponse, error) {
|
||||
v := url.Values{}
|
||||
|
||||
if config.SuperGroupUsername == "" {
|
||||
v.Add("chat_id", strconv.FormatInt(config.ChatID, 10))
|
||||
} else {
|
||||
v.Add("chat_id", config.SuperGroupUsername)
|
||||
}
|
||||
v.Add("user_id", strconv.Itoa(config.UserID))
|
||||
|
||||
bot.debugLog("unbanChatMember", v, nil)
|
||||
|
||||
return bot.MakeRequest("unbanChatMember", v)
|
||||
}
|
694
vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go
generated
vendored
Normal file
694
vendor/github.com/go-telegram-bot-api/telegram-bot-api/configs.go
generated
vendored
Normal file
@ -0,0 +1,694 @@
|
||||
package tgbotapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Telegram constants
|
||||
const (
|
||||
// APIEndpoint is the endpoint for all API methods,
|
||||
// with formatting for Sprintf.
|
||||
APIEndpoint = "https://api.telegram.org/bot%s/%s"
|
||||
// FileEndpoint is the endpoint for downloading a file from Telegram.
|
||||
FileEndpoint = "https://api.telegram.org/file/bot%s/%s"
|
||||
)
|
||||
|
||||
// Constant values for ChatActions
|
||||
const (
|
||||
ChatTyping = "typing"
|
||||
ChatUploadPhoto = "upload_photo"
|
||||
ChatRecordVideo = "record_video"
|
||||
ChatUploadVideo = "upload_video"
|
||||
ChatRecordAudio = "record_audio"
|
||||
ChatUploadAudio = "upload_audio"
|
||||
ChatUploadDocument = "upload_document"
|
||||
ChatFindLocation = "find_location"
|
||||
)
|
||||
|
||||
// API errors
|
||||
const (
|
||||
// ErrAPIForbidden happens when a token is bad
|
||||
ErrAPIForbidden = "forbidden"
|
||||
)
|
||||
|
||||
// Constant values for ParseMode in MessageConfig
|
||||
const (
|
||||
ModeMarkdown = "Markdown"
|
||||
ModeHTML = "HTML"
|
||||
)
|
||||
|
||||
// Library errors
|
||||
const (
|
||||
// ErrBadFileType happens when you pass an unknown type
|
||||
ErrBadFileType = "bad file type"
|
||||
ErrBadURL = "bad or empty url"
|
||||
)
|
||||
|
||||
// Chattable is any config type that can be sent.
|
||||
type Chattable interface {
|
||||
values() (url.Values, error)
|
||||
method() string
|
||||
}
|
||||
|
||||
// Fileable is any config type that can be sent that includes a file.
|
||||
type Fileable interface {
|
||||
Chattable
|
||||
params() (map[string]string, error)
|
||||
name() string
|
||||
getFile() interface{}
|
||||
useExistingFile() bool
|
||||
}
|
||||
|
||||
// BaseChat is base type for all chat config types.
|
||||
type BaseChat struct {
|
||||
ChatID int64 // required
|
||||
ChannelUsername string
|
||||
ReplyToMessageID int
|
||||
ReplyMarkup interface{}
|
||||
DisableNotification bool
|
||||
}
|
||||
|
||||
// values returns url.Values representation of BaseChat
|
||||
func (chat *BaseChat) values() (url.Values, error) {
|
||||
v := url.Values{}
|
||||
if chat.ChannelUsername != "" {
|
||||
v.Add("chat_id", chat.ChannelUsername)
|
||||
} else {
|
||||
v.Add("chat_id", strconv.FormatInt(chat.ChatID, 10))
|
||||
}
|
||||
|
||||
if chat.ReplyToMessageID != 0 {
|
||||
v.Add("reply_to_message_id", strconv.Itoa(chat.ReplyToMessageID))
|
||||
}
|
||||
|
||||
if chat.ReplyMarkup != nil {
|
||||
data, err := json.Marshal(chat.ReplyMarkup)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
v.Add("reply_markup", string(data))
|
||||
}
|
||||
|
||||
v.Add("disable_notification", strconv.FormatBool(chat.DisableNotification))
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// BaseFile is a base type for all file config types.
|
||||
type BaseFile struct {
|
||||
BaseChat
|
||||
File interface{}
|
||||
FileID string
|
||||
UseExisting bool
|
||||
MimeType string
|
||||
FileSize int
|
||||
}
|
||||
|
||||
// params returns a map[string]string representation of BaseFile.
|
||||
func (file BaseFile) params() (map[string]string, error) {
|
||||
params := make(map[string]string)
|
||||
|
||||
if file.ChannelUsername != "" {
|
||||
params["chat_id"] = file.ChannelUsername
|
||||
} else {
|
||||
params["chat_id"] = strconv.FormatInt(file.ChatID, 10)
|
||||
}
|
||||
|
||||
if file.ReplyToMessageID != 0 {
|
||||
params["reply_to_message_id"] = strconv.Itoa(file.ReplyToMessageID)
|
||||
}
|
||||
|
||||
if file.ReplyMarkup != nil {
|
||||
data, err := json.Marshal(file.ReplyMarkup)
|
||||
if err != nil {
|
||||
return params, err
|
||||
}
|
||||
|
||||
params["reply_markup"] = string(data)
|
||||
}
|
||||
|
||||
if file.MimeType != "" {
|
||||
params["mime_type"] = file.MimeType
|
||||
}
|
||||
|
||||
if file.FileSize > 0 {
|
||||
params["file_size"] = strconv.Itoa(file.FileSize)
|
||||
}
|
||||
|
||||
params["disable_notification"] = strconv.FormatBool(file.DisableNotification)
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// getFile returns the file.
|
||||
func (file BaseFile) getFile() interface{} {
|
||||
return file.File
|
||||
}
|
||||
|
||||
// useExistingFile returns if the BaseFile has already been uploaded.
|
||||
func (file BaseFile) useExistingFile() bool {
|
||||
return file.UseExisting
|
||||
}
|
||||
|
||||
// BaseEdit is base type of all chat edits.
|
||||
type BaseEdit struct {
|
||||
ChatID int64
|
||||
ChannelUsername string
|
||||
MessageID int
|
||||
InlineMessageID string
|
||||
ReplyMarkup *InlineKeyboardMarkup
|
||||
}
|
||||
|
||||
func (edit BaseEdit) values() (url.Values, error) {
|
||||
v := url.Values{}
|
||||
|
||||
if edit.InlineMessageID == "" {
|
||||
if edit.ChannelUsername != "" {
|
||||
v.Add("chat_id", edit.ChannelUsername)
|
||||
} else {
|
||||
v.Add("chat_id", strconv.FormatInt(edit.ChatID, 10))
|
||||
}
|
||||
v.Add("message_id", strconv.Itoa(edit.MessageID))
|
||||
} else {
|
||||
v.Add("inline_message_id", edit.InlineMessageID)
|
||||
}
|
||||
|
||||
if edit.ReplyMarkup != nil {
|
||||
data, err := json.Marshal(edit.ReplyMarkup)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
v.Add("reply_markup", string(data))
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// MessageConfig contains information about a SendMessage request.
|
||||
type MessageConfig struct {
|
||||
BaseChat
|
||||
Text string
|
||||
ParseMode string
|
||||
DisableWebPagePreview bool
|
||||
}
|
||||
|
||||
// values returns a url.Values representation of MessageConfig.
|
||||
func (config MessageConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
v.Add("text", config.Text)
|
||||
v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
|
||||
if config.ParseMode != "" {
|
||||
v.Add("parse_mode", config.ParseMode)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// method returns Telegram API method name for sending Message.
|
||||
func (config MessageConfig) method() string {
|
||||
return "sendMessage"
|
||||
}
|
||||
|
||||
// ForwardConfig contains information about a ForwardMessage request.
|
||||
type ForwardConfig struct {
|
||||
BaseChat
|
||||
FromChatID int64 // required
|
||||
FromChannelUsername string
|
||||
MessageID int // required
|
||||
}
|
||||
|
||||
// values returns a url.Values representation of ForwardConfig.
|
||||
func (config ForwardConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
v.Add("from_chat_id", strconv.FormatInt(config.FromChatID, 10))
|
||||
v.Add("message_id", strconv.Itoa(config.MessageID))
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// method returns Telegram API method name for sending Forward.
|
||||
func (config ForwardConfig) method() string {
|
||||
return "forwardMessage"
|
||||
}
|
||||
|
||||
// PhotoConfig contains information about a SendPhoto request.
|
||||
type PhotoConfig struct {
|
||||
BaseFile
|
||||
Caption string
|
||||
}
|
||||
|
||||
// Params returns a map[string]string representation of PhotoConfig.
|
||||
func (config PhotoConfig) params() (map[string]string, error) {
|
||||
params, _ := config.BaseFile.params()
|
||||
|
||||
if config.Caption != "" {
|
||||
params["caption"] = config.Caption
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// Values returns a url.Values representation of PhotoConfig.
|
||||
func (config PhotoConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
|
||||
v.Add(config.name(), config.FileID)
|
||||
if config.Caption != "" {
|
||||
v.Add("caption", config.Caption)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// name returns the field name for the Photo.
|
||||
func (config PhotoConfig) name() string {
|
||||
return "photo"
|
||||
}
|
||||
|
||||
// method returns Telegram API method name for sending Photo.
|
||||
func (config PhotoConfig) method() string {
|
||||
return "sendPhoto"
|
||||
}
|
||||
|
||||
// AudioConfig contains information about a SendAudio request.
|
||||
type AudioConfig struct {
|
||||
BaseFile
|
||||
Duration int
|
||||
Performer string
|
||||
Title string
|
||||
}
|
||||
|
||||
// values returns a url.Values representation of AudioConfig.
|
||||
func (config AudioConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
|
||||
v.Add(config.name(), config.FileID)
|
||||
if config.Duration != 0 {
|
||||
v.Add("duration", strconv.Itoa(config.Duration))
|
||||
}
|
||||
|
||||
if config.Performer != "" {
|
||||
v.Add("performer", config.Performer)
|
||||
}
|
||||
if config.Title != "" {
|
||||
v.Add("title", config.Title)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// params returns a map[string]string representation of AudioConfig.
|
||||
func (config AudioConfig) params() (map[string]string, error) {
|
||||
params, _ := config.BaseFile.params()
|
||||
|
||||
if config.Duration != 0 {
|
||||
params["duration"] = strconv.Itoa(config.Duration)
|
||||
}
|
||||
|
||||
if config.Performer != "" {
|
||||
params["performer"] = config.Performer
|
||||
}
|
||||
if config.Title != "" {
|
||||
params["title"] = config.Title
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// name returns the field name for the Audio.
|
||||
func (config AudioConfig) name() string {
|
||||
return "audio"
|
||||
}
|
||||
|
||||
// method returns Telegram API method name for sending Audio.
|
||||
func (config AudioConfig) method() string {
|
||||
return "sendAudio"
|
||||
}
|
||||
|
||||
// DocumentConfig contains information about a SendDocument request.
|
||||
type DocumentConfig struct {
|
||||
BaseFile
|
||||
}
|
||||
|
||||
// values returns a url.Values representation of DocumentConfig.
|
||||
func (config DocumentConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
|
||||
v.Add(config.name(), config.FileID)
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// params returns a map[string]string representation of DocumentConfig.
|
||||
func (config DocumentConfig) params() (map[string]string, error) {
|
||||
params, _ := config.BaseFile.params()
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// name returns the field name for the Document.
|
||||
func (config DocumentConfig) name() string {
|
||||
return "document"
|
||||
}
|
||||
|
||||
// method returns Telegram API method name for sending Document.
|
||||
func (config DocumentConfig) method() string {
|
||||
return "sendDocument"
|
||||
}
|
||||
|
||||
// StickerConfig contains information about a SendSticker request.
|
||||
type StickerConfig struct {
|
||||
BaseFile
|
||||
}
|
||||
|
||||
// values returns a url.Values representation of StickerConfig.
|
||||
func (config StickerConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
|
||||
v.Add(config.name(), config.FileID)
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// params returns a map[string]string representation of StickerConfig.
|
||||
func (config StickerConfig) params() (map[string]string, error) {
|
||||
params, _ := config.BaseFile.params()
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// name returns the field name for the Sticker.
|
||||
func (config StickerConfig) name() string {
|
||||
return "sticker"
|
||||
}
|
||||
|
||||
// method returns Telegram API method name for sending Sticker.
|
||||
func (config StickerConfig) method() string {
|
||||
return "sendSticker"
|
||||
}
|
||||
|
||||
// VideoConfig contains information about a SendVideo request.
|
||||
type VideoConfig struct {
|
||||
BaseFile
|
||||
Duration int
|
||||
Caption string
|
||||
}
|
||||
|
||||
// values returns a url.Values representation of VideoConfig.
|
||||
func (config VideoConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
|
||||
v.Add(config.name(), config.FileID)
|
||||
if config.Duration != 0 {
|
||||
v.Add("duration", strconv.Itoa(config.Duration))
|
||||
}
|
||||
if config.Caption != "" {
|
||||
v.Add("caption", config.Caption)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// params returns a map[string]string representation of VideoConfig.
|
||||
func (config VideoConfig) params() (map[string]string, error) {
|
||||
params, _ := config.BaseFile.params()
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// name returns the field name for the Video.
|
||||
func (config VideoConfig) name() string {
|
||||
return "video"
|
||||
}
|
||||
|
||||
// method returns Telegram API method name for sending Video.
|
||||
func (config VideoConfig) method() string {
|
||||
return "sendVideo"
|
||||
}
|
||||
|
||||
// VoiceConfig contains information about a SendVoice request.
|
||||
type VoiceConfig struct {
|
||||
BaseFile
|
||||
Duration int
|
||||
}
|
||||
|
||||
// values returns a url.Values representation of VoiceConfig.
|
||||
func (config VoiceConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
|
||||
v.Add(config.name(), config.FileID)
|
||||
if config.Duration != 0 {
|
||||
v.Add("duration", strconv.Itoa(config.Duration))
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// params returns a map[string]string representation of VoiceConfig.
|
||||
func (config VoiceConfig) params() (map[string]string, error) {
|
||||
params, _ := config.BaseFile.params()
|
||||
|
||||
if config.Duration != 0 {
|
||||
params["duration"] = strconv.Itoa(config.Duration)
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
// name returns the field name for the Voice.
|
||||
func (config VoiceConfig) name() string {
|
||||
return "voice"
|
||||
}
|
||||
|
||||
// method returns Telegram API method name for sending Voice.
|
||||
func (config VoiceConfig) method() string {
|
||||
return "sendVoice"
|
||||
}
|
||||
|
||||
// LocationConfig contains information about a SendLocation request.
|
||||
type LocationConfig struct {
|
||||
BaseChat
|
||||
Latitude float64 // required
|
||||
Longitude float64 // required
|
||||
}
|
||||
|
||||
// values returns a url.Values representation of LocationConfig.
|
||||
func (config LocationConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
|
||||
v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
|
||||
v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// method returns Telegram API method name for sending Location.
|
||||
func (config LocationConfig) method() string {
|
||||
return "sendLocation"
|
||||
}
|
||||
|
||||
// VenueConfig contains information about a SendVenue request.
|
||||
type VenueConfig struct {
|
||||
BaseChat
|
||||
Latitude float64 // required
|
||||
Longitude float64 // required
|
||||
Title string // required
|
||||
Address string // required
|
||||
FoursquareID string
|
||||
}
|
||||
|
||||
func (config VenueConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
|
||||
v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
|
||||
v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
|
||||
v.Add("title", config.Title)
|
||||
v.Add("address", config.Address)
|
||||
if config.FoursquareID != "" {
|
||||
v.Add("foursquare_id", config.FoursquareID)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (config VenueConfig) method() string {
|
||||
return "sendVenue"
|
||||
}
|
||||
|
||||
// ContactConfig allows you to send a contact.
|
||||
type ContactConfig struct {
|
||||
BaseChat
|
||||
PhoneNumber string
|
||||
FirstName string
|
||||
LastName string
|
||||
}
|
||||
|
||||
func (config ContactConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
|
||||
v.Add("phone_number", config.PhoneNumber)
|
||||
v.Add("first_name", config.FirstName)
|
||||
v.Add("last_name", config.LastName)
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (config ContactConfig) method() string {
|
||||
return "sendContact"
|
||||
}
|
||||
|
||||
// ChatActionConfig contains information about a SendChatAction request.
|
||||
type ChatActionConfig struct {
|
||||
BaseChat
|
||||
Action string // required
|
||||
}
|
||||
|
||||
// values returns a url.Values representation of ChatActionConfig.
|
||||
func (config ChatActionConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseChat.values()
|
||||
v.Add("action", config.Action)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// method returns Telegram API method name for sending ChatAction.
|
||||
func (config ChatActionConfig) method() string {
|
||||
return "sendChatAction"
|
||||
}
|
||||
|
||||
// EditMessageTextConfig allows you to modify the text in a message.
|
||||
type EditMessageTextConfig struct {
|
||||
BaseEdit
|
||||
Text string
|
||||
ParseMode string
|
||||
DisableWebPagePreview bool
|
||||
}
|
||||
|
||||
func (config EditMessageTextConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseEdit.values()
|
||||
|
||||
v.Add("text", config.Text)
|
||||
v.Add("parse_mode", config.ParseMode)
|
||||
v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (config EditMessageTextConfig) method() string {
|
||||
return "editMessageText"
|
||||
}
|
||||
|
||||
// EditMessageCaptionConfig allows you to modify the caption of a message.
|
||||
type EditMessageCaptionConfig struct {
|
||||
BaseEdit
|
||||
Caption string
|
||||
}
|
||||
|
||||
func (config EditMessageCaptionConfig) values() (url.Values, error) {
|
||||
v, _ := config.BaseEdit.values()
|
||||
|
||||
v.Add("caption", config.Caption)
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (config EditMessageCaptionConfig) method() string {
|
||||
return "editMessageCaption"
|
||||
}
|
||||
|
||||
// EditMessageReplyMarkupConfig allows you to modify the reply markup
|
||||
// of a message.
|
||||
type EditMessageReplyMarkupConfig struct {
|
||||
BaseEdit
|
||||
}
|
||||
|
||||
func (config EditMessageReplyMarkupConfig) values() (url.Values, error) {
|
||||
return config.BaseEdit.values()
|
||||
}
|
||||
|
||||
func (config EditMessageReplyMarkupConfig) method() string {
|
||||
return "editMessageReplyMarkup"
|
||||
}
|
||||
|
||||
// UserProfilePhotosConfig contains information about a
|
||||
// GetUserProfilePhotos request.
|
||||
type UserProfilePhotosConfig struct {
|
||||
UserID int
|
||||
Offset int
|
||||
Limit int
|
||||
}
|
||||
|
||||
// FileConfig has information about a file hosted on Telegram.
|
||||
type FileConfig struct {
|
||||
FileID string
|
||||
}
|
||||
|
||||
// UpdateConfig contains information about a GetUpdates request.
|
||||
type UpdateConfig struct {
|
||||
Offset int
|
||||
Limit int
|
||||
Timeout int
|
||||
}
|
||||
|
||||
// WebhookConfig contains information about a SetWebhook request.
|
||||
type WebhookConfig struct {
|
||||
URL *url.URL
|
||||
Certificate interface{}
|
||||
}
|
||||
|
||||
// FileBytes contains information about a set of bytes to upload
|
||||
// as a File.
|
||||
type FileBytes struct {
|
||||
Name string
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
// FileReader contains information about a reader to upload as a File.
|
||||
// If Size is -1, it will read the entire Reader into memory to
|
||||
// calculate a Size.
|
||||
type FileReader struct {
|
||||
Name string
|
||||
Reader io.Reader
|
||||
Size int64
|
||||
}
|
||||
|
||||
// InlineConfig contains information on making an InlineQuery response.
|
||||
type InlineConfig struct {
|
||||
InlineQueryID string `json:"inline_query_id"`
|
||||
Results []interface{} `json:"results"`
|
||||
CacheTime int `json:"cache_time"`
|
||||
IsPersonal bool `json:"is_personal"`
|
||||
NextOffset string `json:"next_offset"`
|
||||
SwitchPMText string `json:"switch_pm_text"`
|
||||
SwitchPMParameter string `json:"switch_pm_parameter"`
|
||||
}
|
||||
|
||||
// CallbackConfig contains information on making a CallbackQuery response.
|
||||
type CallbackConfig struct {
|
||||
CallbackQueryID string `json:"callback_query_id"`
|
||||
Text string `json:"text"`
|
||||
ShowAlert bool `json:"show_alert"`
|
||||
}
|
||||
|
||||
// ChatMemberConfig contains information about a user in a chat for use
|
||||
// with administrative functions such as kicking or unbanning a user.
|
||||
type ChatMemberConfig struct {
|
||||
ChatID int64
|
||||
SuperGroupUsername string
|
||||
UserID int
|
||||
}
|
||||
|
||||
// ChatConfig contains information about getting information on a chat.
|
||||
type ChatConfig struct {
|
||||
ChatID int64
|
||||
SuperGroupUsername string
|
||||
}
|
||||
|
||||
// ChatConfigWithUser contains information about getting information on
|
||||
// a specific user within a chat.
|
||||
type ChatConfigWithUser struct {
|
||||
ChatID int64
|
||||
SuperGroupUsername string
|
||||
UserID int
|
||||
}
|
598
vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go
generated
vendored
Normal file
598
vendor/github.com/go-telegram-bot-api/telegram-bot-api/helpers.go
generated
vendored
Normal file
@ -0,0 +1,598 @@
|
||||
package tgbotapi
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// NewMessage creates a new Message.
|
||||
//
|
||||
// chatID is where to send it, text is the message text.
|
||||
func NewMessage(chatID int64, text string) MessageConfig {
|
||||
return MessageConfig{
|
||||
BaseChat: BaseChat{
|
||||
ChatID: chatID,
|
||||
ReplyToMessageID: 0,
|
||||
},
|
||||
Text: text,
|
||||
DisableWebPagePreview: false,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMessageToChannel creates a new Message that is sent to a channel
|
||||
// by username.
|
||||
// username is the username of the channel, text is the message text.
|
||||
func NewMessageToChannel(username string, text string) MessageConfig {
|
||||
return MessageConfig{
|
||||
BaseChat: BaseChat{
|
||||
ChannelUsername: username,
|
||||
},
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
|
||||
// NewForward creates a new forward.
|
||||
//
|
||||
// chatID is where to send it, fromChatID is the source chat,
|
||||
// and messageID is the ID of the original message.
|
||||
func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig {
|
||||
return ForwardConfig{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FromChatID: fromChatID,
|
||||
MessageID: messageID,
|
||||
}
|
||||
}
|
||||
|
||||
// NewPhotoUpload creates a new photo uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
//
|
||||
// Note that you must send animated GIFs as a document.
|
||||
func NewPhotoUpload(chatID int64, file interface{}) PhotoConfig {
|
||||
return PhotoConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewPhotoShare shares an existing photo.
|
||||
// You may use this to reshare an existing photo without reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the file
|
||||
// already uploaded.
|
||||
func NewPhotoShare(chatID int64, fileID string) PhotoConfig {
|
||||
return PhotoConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewAudioUpload creates a new audio uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewAudioUpload(chatID int64, file interface{}) AudioConfig {
|
||||
return AudioConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewAudioShare shares an existing audio file.
|
||||
// You may use this to reshare an existing audio file without
|
||||
// reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the audio
|
||||
// already uploaded.
|
||||
func NewAudioShare(chatID int64, fileID string) AudioConfig {
|
||||
return AudioConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewDocumentUpload creates a new document uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewDocumentUpload(chatID int64, file interface{}) DocumentConfig {
|
||||
return DocumentConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewDocumentShare shares an existing document.
|
||||
// You may use this to reshare an existing document without
|
||||
// reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the document
|
||||
// already uploaded.
|
||||
func NewDocumentShare(chatID int64, fileID string) DocumentConfig {
|
||||
return DocumentConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewStickerUpload creates a new sticker uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewStickerUpload(chatID int64, file interface{}) StickerConfig {
|
||||
return StickerConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewStickerShare shares an existing sticker.
|
||||
// You may use this to reshare an existing sticker without
|
||||
// reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the sticker
|
||||
// already uploaded.
|
||||
func NewStickerShare(chatID int64, fileID string) StickerConfig {
|
||||
return StickerConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewVideoUpload creates a new video uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewVideoUpload(chatID int64, file interface{}) VideoConfig {
|
||||
return VideoConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewVideoShare shares an existing video.
|
||||
// You may use this to reshare an existing video without reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the video
|
||||
// already uploaded.
|
||||
func NewVideoShare(chatID int64, fileID string) VideoConfig {
|
||||
return VideoConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewVoiceUpload creates a new voice uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewVoiceUpload(chatID int64, file interface{}) VoiceConfig {
|
||||
return VoiceConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewVoiceShare shares an existing voice.
|
||||
// You may use this to reshare an existing voice without reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the video
|
||||
// already uploaded.
|
||||
func NewVoiceShare(chatID int64, fileID string) VoiceConfig {
|
||||
return VoiceConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewContact allows you to send a shared contact.
|
||||
func NewContact(chatID int64, phoneNumber, firstName string) ContactConfig {
|
||||
return ContactConfig{
|
||||
BaseChat: BaseChat{
|
||||
ChatID: chatID,
|
||||
},
|
||||
PhoneNumber: phoneNumber,
|
||||
FirstName: firstName,
|
||||
}
|
||||
}
|
||||
|
||||
// NewLocation shares your location.
|
||||
//
|
||||
// chatID is where to send it, latitude and longitude are coordinates.
|
||||
func NewLocation(chatID int64, latitude float64, longitude float64) LocationConfig {
|
||||
return LocationConfig{
|
||||
BaseChat: BaseChat{
|
||||
ChatID: chatID,
|
||||
},
|
||||
Latitude: latitude,
|
||||
Longitude: longitude,
|
||||
}
|
||||
}
|
||||
|
||||
// NewVenue allows you to send a venue and its location.
|
||||
func NewVenue(chatID int64, title, address string, latitude, longitude float64) VenueConfig {
|
||||
return VenueConfig{
|
||||
BaseChat: BaseChat{
|
||||
ChatID: chatID,
|
||||
},
|
||||
Title: title,
|
||||
Address: address,
|
||||
Latitude: latitude,
|
||||
Longitude: longitude,
|
||||
}
|
||||
}
|
||||
|
||||
// NewChatAction sets a chat action.
|
||||
// Actions last for 5 seconds, or until your next action.
|
||||
//
|
||||
// chatID is where to send it, action should be set via Chat constants.
|
||||
func NewChatAction(chatID int64, action string) ChatActionConfig {
|
||||
return ChatActionConfig{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
Action: action,
|
||||
}
|
||||
}
|
||||
|
||||
// NewUserProfilePhotos gets user profile photos.
|
||||
//
|
||||
// userID is the ID of the user you wish to get profile photos from.
|
||||
func NewUserProfilePhotos(userID int) UserProfilePhotosConfig {
|
||||
return UserProfilePhotosConfig{
|
||||
UserID: userID,
|
||||
Offset: 0,
|
||||
Limit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// NewUpdate gets updates since the last Offset.
|
||||
//
|
||||
// offset is the last Update ID to include.
|
||||
// You likely want to set this to the last Update ID plus 1.
|
||||
func NewUpdate(offset int) UpdateConfig {
|
||||
return UpdateConfig{
|
||||
Offset: offset,
|
||||
Limit: 0,
|
||||
Timeout: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// NewWebhook creates a new webhook.
|
||||
//
|
||||
// link is the url parsable link you wish to get the updates.
|
||||
func NewWebhook(link string) WebhookConfig {
|
||||
u, _ := url.Parse(link)
|
||||
|
||||
return WebhookConfig{
|
||||
URL: u,
|
||||
}
|
||||
}
|
||||
|
||||
// NewWebhookWithCert creates a new webhook with a certificate.
|
||||
//
|
||||
// link is the url you wish to get webhooks,
|
||||
// file contains a string to a file, FileReader, or FileBytes.
|
||||
func NewWebhookWithCert(link string, file interface{}) WebhookConfig {
|
||||
u, _ := url.Parse(link)
|
||||
|
||||
return WebhookConfig{
|
||||
URL: u,
|
||||
Certificate: file,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultArticle creates a new inline query article.
|
||||
func NewInlineQueryResultArticle(id, title, messageText string) InlineQueryResultArticle {
|
||||
return InlineQueryResultArticle{
|
||||
Type: "article",
|
||||
ID: id,
|
||||
Title: title,
|
||||
InputMessageContent: InputTextMessageContent{
|
||||
Text: messageText,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultArticleMarkdown creates a new inline query article with Markdown parsing.
|
||||
func NewInlineQueryResultArticleMarkdown(id, title, messageText string) InlineQueryResultArticle {
|
||||
return InlineQueryResultArticle{
|
||||
Type: "article",
|
||||
ID: id,
|
||||
Title: title,
|
||||
InputMessageContent: InputTextMessageContent{
|
||||
Text: messageText,
|
||||
ParseMode: "Markdown",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultArticleHTML creates a new inline query article with HTML parsing.
|
||||
func NewInlineQueryResultArticleHTML(id, title, messageText string) InlineQueryResultArticle {
|
||||
return InlineQueryResultArticle{
|
||||
Type: "article",
|
||||
ID: id,
|
||||
Title: title,
|
||||
InputMessageContent: InputTextMessageContent{
|
||||
Text: messageText,
|
||||
ParseMode: "HTML",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultGIF creates a new inline query GIF.
|
||||
func NewInlineQueryResultGIF(id, url string) InlineQueryResultGIF {
|
||||
return InlineQueryResultGIF{
|
||||
Type: "gif",
|
||||
ID: id,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultMPEG4GIF creates a new inline query MPEG4 GIF.
|
||||
func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF {
|
||||
return InlineQueryResultMPEG4GIF{
|
||||
Type: "mpeg4_gif",
|
||||
ID: id,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultPhoto creates a new inline query photo.
|
||||
func NewInlineQueryResultPhoto(id, url string) InlineQueryResultPhoto {
|
||||
return InlineQueryResultPhoto{
|
||||
Type: "photo",
|
||||
ID: id,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultPhotoWithThumb creates a new inline query photo.
|
||||
func NewInlineQueryResultPhotoWithThumb(id, url, thumb string) InlineQueryResultPhoto {
|
||||
return InlineQueryResultPhoto{
|
||||
Type: "photo",
|
||||
ID: id,
|
||||
URL: url,
|
||||
ThumbURL: thumb,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultVideo creates a new inline query video.
|
||||
func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo {
|
||||
return InlineQueryResultVideo{
|
||||
Type: "video",
|
||||
ID: id,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultAudio creates a new inline query audio.
|
||||
func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio {
|
||||
return InlineQueryResultAudio{
|
||||
Type: "audio",
|
||||
ID: id,
|
||||
URL: url,
|
||||
Title: title,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultVoice creates a new inline query voice.
|
||||
func NewInlineQueryResultVoice(id, url, title string) InlineQueryResultVoice {
|
||||
return InlineQueryResultVoice{
|
||||
Type: "voice",
|
||||
ID: id,
|
||||
URL: url,
|
||||
Title: title,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultDocument creates a new inline query document.
|
||||
func NewInlineQueryResultDocument(id, url, title, mimeType string) InlineQueryResultDocument {
|
||||
return InlineQueryResultDocument{
|
||||
Type: "document",
|
||||
ID: id,
|
||||
URL: url,
|
||||
Title: title,
|
||||
MimeType: mimeType,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineQueryResultLocation creates a new inline query location.
|
||||
func NewInlineQueryResultLocation(id, title string, latitude, longitude float64) InlineQueryResultLocation {
|
||||
return InlineQueryResultLocation{
|
||||
Type: "location",
|
||||
ID: id,
|
||||
Title: title,
|
||||
Latitude: latitude,
|
||||
Longitude: longitude,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEditMessageText allows you to edit the text of a message.
|
||||
func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTextConfig {
|
||||
return EditMessageTextConfig{
|
||||
BaseEdit: BaseEdit{
|
||||
ChatID: chatID,
|
||||
MessageID: messageID,
|
||||
},
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEditMessageCaption allows you to edit the caption of a message.
|
||||
func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig {
|
||||
return EditMessageCaptionConfig{
|
||||
BaseEdit: BaseEdit{
|
||||
ChatID: chatID,
|
||||
MessageID: messageID,
|
||||
},
|
||||
Caption: caption,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEditMessageReplyMarkup allows you to edit the inline
|
||||
// keyboard markup.
|
||||
func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKeyboardMarkup) EditMessageReplyMarkupConfig {
|
||||
return EditMessageReplyMarkupConfig{
|
||||
BaseEdit: BaseEdit{
|
||||
ChatID: chatID,
|
||||
MessageID: messageID,
|
||||
ReplyMarkup: &replyMarkup,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewHideKeyboard hides the keyboard, with the option for being selective
|
||||
// or hiding for everyone.
|
||||
func NewHideKeyboard(selective bool) ReplyKeyboardHide {
|
||||
return ReplyKeyboardHide{
|
||||
HideKeyboard: true,
|
||||
Selective: selective,
|
||||
}
|
||||
}
|
||||
|
||||
// NewKeyboardButton creates a regular keyboard button.
|
||||
func NewKeyboardButton(text string) KeyboardButton {
|
||||
return KeyboardButton{
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
|
||||
// NewKeyboardButtonContact creates a keyboard button that requests
|
||||
// user contact information upon click.
|
||||
func NewKeyboardButtonContact(text string) KeyboardButton {
|
||||
return KeyboardButton{
|
||||
Text: text,
|
||||
RequestContact: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewKeyboardButtonLocation creates a keyboard button that requests
|
||||
// user location information upon click.
|
||||
func NewKeyboardButtonLocation(text string) KeyboardButton {
|
||||
return KeyboardButton{
|
||||
Text: text,
|
||||
RequestLocation: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewKeyboardButtonRow creates a row of keyboard buttons.
|
||||
func NewKeyboardButtonRow(buttons ...KeyboardButton) []KeyboardButton {
|
||||
var row []KeyboardButton
|
||||
|
||||
row = append(row, buttons...)
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
// NewReplyKeyboard creates a new regular keyboard with sane defaults.
|
||||
func NewReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup {
|
||||
var keyboard [][]KeyboardButton
|
||||
|
||||
keyboard = append(keyboard, rows...)
|
||||
|
||||
return ReplyKeyboardMarkup{
|
||||
ResizeKeyboard: true,
|
||||
Keyboard: keyboard,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineKeyboardButtonData creates an inline keyboard button with text
|
||||
// and data for a callback.
|
||||
func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton {
|
||||
return InlineKeyboardButton{
|
||||
Text: text,
|
||||
CallbackData: &data,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineKeyboardButtonURL creates an inline keyboard button with text
|
||||
// which goes to a URL.
|
||||
func NewInlineKeyboardButtonURL(text, url string) InlineKeyboardButton {
|
||||
return InlineKeyboardButton{
|
||||
Text: text,
|
||||
URL: &url,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineKeyboardButtonSwitch creates an inline keyboard button with
|
||||
// text which allows the user to switch to a chat or return to a chat.
|
||||
func NewInlineKeyboardButtonSwitch(text, sw string) InlineKeyboardButton {
|
||||
return InlineKeyboardButton{
|
||||
Text: text,
|
||||
SwitchInlineQuery: &sw,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInlineKeyboardRow creates an inline keyboard row with buttons.
|
||||
func NewInlineKeyboardRow(buttons ...InlineKeyboardButton) []InlineKeyboardButton {
|
||||
var row []InlineKeyboardButton
|
||||
|
||||
row = append(row, buttons...)
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
// NewInlineKeyboardMarkup creates a new inline keyboard.
|
||||
func NewInlineKeyboardMarkup(rows ...[]InlineKeyboardButton) InlineKeyboardMarkup {
|
||||
var keyboard [][]InlineKeyboardButton
|
||||
|
||||
keyboard = append(keyboard, rows...)
|
||||
|
||||
return InlineKeyboardMarkup{
|
||||
InlineKeyboard: keyboard,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCallback creates a new callback message.
|
||||
func NewCallback(id, text string) CallbackConfig {
|
||||
return CallbackConfig{
|
||||
CallbackQueryID: id,
|
||||
Text: text,
|
||||
ShowAlert: false,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCallbackWithAlert creates a new callback message that alerts
|
||||
// the user.
|
||||
func NewCallbackWithAlert(id, text string) CallbackConfig {
|
||||
return CallbackConfig{
|
||||
CallbackQueryID: id,
|
||||
Text: text,
|
||||
ShowAlert: true,
|
||||
}
|
||||
}
|
550
vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go
generated
vendored
Normal file
550
vendor/github.com/go-telegram-bot-api/telegram-bot-api/types.go
generated
vendored
Normal file
@ -0,0 +1,550 @@
|
||||
package tgbotapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// APIResponse is a response from the Telegram API with the result
|
||||
// stored raw.
|
||||
type APIResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
ErrorCode int `json:"error_code"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// Update is an update response, from GetUpdates.
|
||||
type Update struct {
|
||||
UpdateID int `json:"update_id"`
|
||||
Message *Message `json:"message"`
|
||||
EditedMessage *Message `json:"edited_message"`
|
||||
InlineQuery *InlineQuery `json:"inline_query"`
|
||||
ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"`
|
||||
CallbackQuery *CallbackQuery `json:"callback_query"`
|
||||
}
|
||||
|
||||
// User is a user on Telegram.
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"` // optional
|
||||
UserName string `json:"username"` // optional
|
||||
}
|
||||
|
||||
// String displays a simple text version of a user.
|
||||
//
|
||||
// It is normally a user's username, but falls back to a first/last
|
||||
// name as available.
|
||||
func (u *User) String() string {
|
||||
if u.UserName != "" {
|
||||
return u.UserName
|
||||
}
|
||||
|
||||
name := u.FirstName
|
||||
if u.LastName != "" {
|
||||
name += " " + u.LastName
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// GroupChat is a group chat.
|
||||
type GroupChat struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// Chat contains information about the place a message was sent.
|
||||
type Chat struct {
|
||||
ID int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"` // optional
|
||||
UserName string `json:"username"` // optional
|
||||
FirstName string `json:"first_name"` // optional
|
||||
LastName string `json:"last_name"` // optional
|
||||
}
|
||||
|
||||
// IsPrivate returns if the Chat is a private conversation.
|
||||
func (c Chat) IsPrivate() bool {
|
||||
return c.Type == "private"
|
||||
}
|
||||
|
||||
// IsGroup returns if the Chat is a group.
|
||||
func (c Chat) IsGroup() bool {
|
||||
return c.Type == "group"
|
||||
}
|
||||
|
||||
// IsSuperGroup returns if the Chat is a supergroup.
|
||||
func (c Chat) IsSuperGroup() bool {
|
||||
return c.Type == "supergroup"
|
||||
}
|
||||
|
||||
// IsChannel returns if the Chat is a channel.
|
||||
func (c Chat) IsChannel() bool {
|
||||
return c.Type == "channel"
|
||||
}
|
||||
|
||||
// ChatConfig returns a ChatConfig struct for chat related methods.
|
||||
func (c Chat) ChatConfig() ChatConfig {
|
||||
return ChatConfig{ChatID: c.ID}
|
||||
}
|
||||
|
||||
// Message is returned by almost every request, and contains data about
|
||||
// almost anything.
|
||||
type Message struct {
|
||||
MessageID int `json:"message_id"`
|
||||
From *User `json:"from"` // optional
|
||||
Date int `json:"date"`
|
||||
Chat *Chat `json:"chat"`
|
||||
ForwardFrom *User `json:"forward_from"` // optional
|
||||
ForwardFromChat *Chat `json:"forward_from_chat"` // optional
|
||||
ForwardDate int `json:"forward_date"` // optional
|
||||
ReplyToMessage *Message `json:"reply_to_message"` // optional
|
||||
EditDate int `json:"edit_date"` // optional
|
||||
Text string `json:"text"` // optional
|
||||
Entities *[]MessageEntity `json:"entities"` // optional
|
||||
Audio *Audio `json:"audio"` // optional
|
||||
Document *Document `json:"document"` // optional
|
||||
Photo *[]PhotoSize `json:"photo"` // optional
|
||||
Sticker *Sticker `json:"sticker"` // optional
|
||||
Video *Video `json:"video"` // optional
|
||||
Voice *Voice `json:"voice"` // optional
|
||||
Caption string `json:"caption"` // optional
|
||||
Contact *Contact `json:"contact"` // optional
|
||||
Location *Location `json:"location"` // optional
|
||||
Venue *Venue `json:"venue"` // optional
|
||||
NewChatMember *User `json:"new_chat_member"` // optional
|
||||
LeftChatMember *User `json:"left_chat_member"` // optional
|
||||
NewChatTitle string `json:"new_chat_title"` // optional
|
||||
NewChatPhoto *[]PhotoSize `json:"new_chat_photo"` // optional
|
||||
DeleteChatPhoto bool `json:"delete_chat_photo"` // optional
|
||||
GroupChatCreated bool `json:"group_chat_created"` // optional
|
||||
SuperGroupChatCreated bool `json:"supergroup_chat_created"` // optional
|
||||
ChannelChatCreated bool `json:"channel_chat_created"` // optional
|
||||
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
|
||||
MigrateFromChatID int64 `json:"migrate_from_chat_id"` // optional
|
||||
PinnedMessage *Message `json:"pinned_message"` // optional
|
||||
}
|
||||
|
||||
// Time converts the message timestamp into a Time.
|
||||
func (m *Message) Time() time.Time {
|
||||
return time.Unix(int64(m.Date), 0)
|
||||
}
|
||||
|
||||
// IsCommand returns true if message starts with '/'.
|
||||
func (m *Message) IsCommand() bool {
|
||||
return m.Text != "" && m.Text[0] == '/'
|
||||
}
|
||||
|
||||
// Command checks if the message was a command and if it was, returns the
|
||||
// command. If the Message was not a command, it returns an empty string.
|
||||
//
|
||||
// If the command contains the at bot syntax, it removes the bot name.
|
||||
func (m *Message) Command() string {
|
||||
if !m.IsCommand() {
|
||||
return ""
|
||||
}
|
||||
|
||||
command := strings.SplitN(m.Text, " ", 2)[0][1:]
|
||||
|
||||
if i := strings.Index(command, "@"); i != -1 {
|
||||
command = command[:i]
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
// CommandArguments checks if the message was a command and if it was,
|
||||
// returns all text after the command name. If the Message was not a
|
||||
// command, it returns an empty string.
|
||||
func (m *Message) CommandArguments() string {
|
||||
if !m.IsCommand() {
|
||||
return ""
|
||||
}
|
||||
|
||||
split := strings.SplitN(m.Text, " ", 2)
|
||||
if len(split) != 2 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.SplitN(m.Text, " ", 2)[1]
|
||||
}
|
||||
|
||||
// MessageEntity contains information about data in a Message.
|
||||
type MessageEntity struct {
|
||||
Type string `json:"type"`
|
||||
Offset int `json:"offset"`
|
||||
Length int `json:"length"`
|
||||
URL string `json:"url"` // optional
|
||||
User *User `json:"user"` // optional
|
||||
}
|
||||
|
||||
// ParseURL attempts to parse a URL contained within a MessageEntity.
|
||||
func (entity MessageEntity) ParseURL() (*url.URL, error) {
|
||||
if entity.URL == "" {
|
||||
return nil, errors.New(ErrBadURL)
|
||||
}
|
||||
|
||||
return url.Parse(entity.URL)
|
||||
}
|
||||
|
||||
// PhotoSize contains information about photos.
|
||||
type PhotoSize struct {
|
||||
FileID string `json:"file_id"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
FileSize int `json:"file_size"` // optional
|
||||
}
|
||||
|
||||
// Audio contains information about audio.
|
||||
type Audio struct {
|
||||
FileID string `json:"file_id"`
|
||||
Duration int `json:"duration"`
|
||||
Performer string `json:"performer"` // optional
|
||||
Title string `json:"title"` // optional
|
||||
MimeType string `json:"mime_type"` // optional
|
||||
FileSize int `json:"file_size"` // optional
|
||||
}
|
||||
|
||||
// Document contains information about a document.
|
||||
type Document struct {
|
||||
FileID string `json:"file_id"`
|
||||
Thumbnail *PhotoSize `json:"thumb"` // optional
|
||||
FileName string `json:"file_name"` // optional
|
||||
MimeType string `json:"mime_type"` // optional
|
||||
FileSize int `json:"file_size"` // optional
|
||||
}
|
||||
|
||||
// Sticker contains information about a sticker.
|
||||
type Sticker struct {
|
||||
FileID string `json:"file_id"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Thumbnail *PhotoSize `json:"thumb"` // optional
|
||||
Emoji string `json:"emoji"` // optional
|
||||
FileSize int `json:"file_size"` // optional
|
||||
}
|
||||
|
||||
// Video contains information about a video.
|
||||
type Video struct {
|
||||
FileID string `json:"file_id"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Duration int `json:"duration"`
|
||||
Thumbnail *PhotoSize `json:"thumb"` // optional
|
||||
MimeType string `json:"mime_type"` // optional
|
||||
FileSize int `json:"file_size"` // optional
|
||||
}
|
||||
|
||||
// Voice contains information about a voice.
|
||||
type Voice struct {
|
||||
FileID string `json:"file_id"`
|
||||
Duration int `json:"duration"`
|
||||
MimeType string `json:"mime_type"` // optional
|
||||
FileSize int `json:"file_size"` // optional
|
||||
}
|
||||
|
||||
// Contact contains information about a contact.
|
||||
//
|
||||
// Note that LastName and UserID may be empty.
|
||||
type Contact struct {
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"` // optional
|
||||
UserID int `json:"user_id"` // optional
|
||||
}
|
||||
|
||||
// Location contains information about a place.
|
||||
type Location struct {
|
||||
Longitude float64 `json:"longitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
}
|
||||
|
||||
// Venue contains information about a venue, including its Location.
|
||||
type Venue struct {
|
||||
Location Location `json:"location"`
|
||||
Title string `json:"title"`
|
||||
Address string `json:"address"`
|
||||
FoursquareID string `json:"foursquare_id"` // optional
|
||||
}
|
||||
|
||||
// UserProfilePhotos contains a set of user profile photos.
|
||||
type UserProfilePhotos struct {
|
||||
TotalCount int `json:"total_count"`
|
||||
Photos [][]PhotoSize `json:"photos"`
|
||||
}
|
||||
|
||||
// File contains information about a file to download from Telegram.
|
||||
type File struct {
|
||||
FileID string `json:"file_id"`
|
||||
FileSize int `json:"file_size"` // optional
|
||||
FilePath string `json:"file_path"` // optional
|
||||
}
|
||||
|
||||
// Link returns a full path to the download URL for a File.
|
||||
//
|
||||
// It requires the Bot Token to create the link.
|
||||
func (f *File) Link(token string) string {
|
||||
return fmt.Sprintf(FileEndpoint, token, f.FilePath)
|
||||
}
|
||||
|
||||
// ReplyKeyboardMarkup allows the Bot to set a custom keyboard.
|
||||
type ReplyKeyboardMarkup struct {
|
||||
Keyboard [][]KeyboardButton `json:"keyboard"`
|
||||
ResizeKeyboard bool `json:"resize_keyboard"` // optional
|
||||
OneTimeKeyboard bool `json:"one_time_keyboard"` // optional
|
||||
Selective bool `json:"selective"` // optional
|
||||
}
|
||||
|
||||
// KeyboardButton is a button within a custom keyboard.
|
||||
type KeyboardButton struct {
|
||||
Text string `json:"text"`
|
||||
RequestContact bool `json:"request_contact"`
|
||||
RequestLocation bool `json:"request_location"`
|
||||
}
|
||||
|
||||
// ReplyKeyboardHide allows the Bot to hide a custom keyboard.
|
||||
type ReplyKeyboardHide struct {
|
||||
HideKeyboard bool `json:"hide_keyboard"`
|
||||
Selective bool `json:"selective"` // optional
|
||||
}
|
||||
|
||||
// InlineKeyboardMarkup is a custom keyboard presented for an inline bot.
|
||||
type InlineKeyboardMarkup struct {
|
||||
InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"`
|
||||
}
|
||||
|
||||
// InlineKeyboardButton is a button within a custom keyboard for
|
||||
// inline query responses.
|
||||
//
|
||||
// Note that some values are references as even an empty string
|
||||
// will change behavior.
|
||||
type InlineKeyboardButton struct {
|
||||
Text string `json:"text"`
|
||||
URL *string `json:"url,omitempty"` // optional
|
||||
CallbackData *string `json:"callback_data,omitempty"` // optional
|
||||
SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional
|
||||
}
|
||||
|
||||
// CallbackQuery is data sent when a keyboard button with callback data
|
||||
// is clicked.
|
||||
type CallbackQuery struct {
|
||||
ID string `json:"id"`
|
||||
From *User `json:"from"`
|
||||
Message *Message `json:"message"` // optional
|
||||
InlineMessageID string `json:"inline_message_id"` // optional
|
||||
Data string `json:"data"` // optional
|
||||
}
|
||||
|
||||
// ForceReply allows the Bot to have users directly reply to it without
|
||||
// additional interaction.
|
||||
type ForceReply struct {
|
||||
ForceReply bool `json:"force_reply"`
|
||||
Selective bool `json:"selective"` // optional
|
||||
}
|
||||
|
||||
// ChatMember is information about a member in a chat.
|
||||
type ChatMember struct {
|
||||
User *User `json:"user"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// IsCreator returns if the ChatMember was the creator of the chat.
|
||||
func (chat ChatMember) IsCreator() bool { return chat.Status == "creator" }
|
||||
|
||||
// IsAdministrator returns if the ChatMember is a chat administrator.
|
||||
func (chat ChatMember) IsAdministrator() bool { return chat.Status == "administrator" }
|
||||
|
||||
// IsMember returns if the ChatMember is a current member of the chat.
|
||||
func (chat ChatMember) IsMember() bool { return chat.Status == "member" }
|
||||
|
||||
// HasLeft returns if the ChatMember left the chat.
|
||||
func (chat ChatMember) HasLeft() bool { return chat.Status == "left" }
|
||||
|
||||
// WasKicked returns if the ChatMember was kicked from the chat.
|
||||
func (chat ChatMember) WasKicked() bool { return chat.Status == "kicked" }
|
||||
|
||||
// InlineQuery is a Query from Telegram for an inline request.
|
||||
type InlineQuery struct {
|
||||
ID string `json:"id"`
|
||||
From *User `json:"from"`
|
||||
Location *Location `json:"location"` // optional
|
||||
Query string `json:"query"`
|
||||
Offset string `json:"offset"`
|
||||
}
|
||||
|
||||
// InlineQueryResultArticle is an inline query response article.
|
||||
type InlineQueryResultArticle struct {
|
||||
Type string `json:"type"` // required
|
||||
ID string `json:"id"` // required
|
||||
Title string `json:"title"` // required
|
||||
InputMessageContent interface{} `json:"input_message_content,omitempty"` // required
|
||||
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
||||
URL string `json:"url"`
|
||||
HideURL bool `json:"hide_url"`
|
||||
Description string `json:"description"`
|
||||
ThumbURL string `json:"thumb_url"`
|
||||
ThumbWidth int `json:"thumb_width"`
|
||||
ThumbHeight int `json:"thumb_height"`
|
||||
}
|
||||
|
||||
// InlineQueryResultPhoto is an inline query response photo.
|
||||
type InlineQueryResultPhoto struct {
|
||||
Type string `json:"type"` // required
|
||||
ID string `json:"id"` // required
|
||||
URL string `json:"photo_url"` // required
|
||||
MimeType string `json:"mime_type"`
|
||||
Width int `json:"photo_width"`
|
||||
Height int `json:"photo_height"`
|
||||
ThumbURL string `json:"thumb_url"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Caption string `json:"caption"`
|
||||
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
||||
InputMessageContent interface{} `json:"input_message_content,omitempty"`
|
||||
}
|
||||
|
||||
// InlineQueryResultGIF is an inline query response GIF.
|
||||
type InlineQueryResultGIF struct {
|
||||
Type string `json:"type"` // required
|
||||
ID string `json:"id"` // required
|
||||
URL string `json:"gif_url"` // required
|
||||
Width int `json:"gif_width"`
|
||||
Height int `json:"gif_height"`
|
||||
ThumbURL string `json:"thumb_url"`
|
||||
Title string `json:"title"`
|
||||
Caption string `json:"caption"`
|
||||
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
||||
InputMessageContent interface{} `json:"input_message_content,omitempty"`
|
||||
}
|
||||
|
||||
// InlineQueryResultMPEG4GIF is an inline query response MPEG4 GIF.
|
||||
type InlineQueryResultMPEG4GIF struct {
|
||||
Type string `json:"type"` // required
|
||||
ID string `json:"id"` // required
|
||||
URL string `json:"mpeg4_url"` // required
|
||||
Width int `json:"mpeg4_width"`
|
||||
Height int `json:"mpeg4_height"`
|
||||
ThumbURL string `json:"thumb_url"`
|
||||
Title string `json:"title"`
|
||||
Caption string `json:"caption"`
|
||||
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
||||
InputMessageContent interface{} `json:"input_message_content,omitempty"`
|
||||
}
|
||||
|
||||
// InlineQueryResultVideo is an inline query response video.
|
||||
type InlineQueryResultVideo struct {
|
||||
Type string `json:"type"` // required
|
||||
ID string `json:"id"` // required
|
||||
URL string `json:"video_url"` // required
|
||||
MimeType string `json:"mime_type"` // required
|
||||
ThumbURL string `json:"thumb_url"`
|
||||
Title string `json:"title"`
|
||||
Caption string `json:"caption"`
|
||||
Width int `json:"video_width"`
|
||||
Height int `json:"video_height"`
|
||||
Duration int `json:"video_duration"`
|
||||
Description string `json:"description"`
|
||||
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
||||
InputMessageContent interface{} `json:"input_message_content,omitempty"`
|
||||
}
|
||||
|
||||
// InlineQueryResultAudio is an inline query response audio.
|
||||
type InlineQueryResultAudio struct {
|
||||
Type string `json:"type"` // required
|
||||
ID string `json:"id"` // required
|
||||
URL string `json:"audio_url"` // required
|
||||
Title string `json:"title"` // required
|
||||
Performer string `json:"performer"`
|
||||
Duration int `json:"audio_duration"`
|
||||
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
||||
InputMessageContent interface{} `json:"input_message_content,omitempty"`
|
||||
}
|
||||
|
||||
// InlineQueryResultVoice is an inline query response voice.
|
||||
type InlineQueryResultVoice struct {
|
||||
Type string `json:"type"` // required
|
||||
ID string `json:"id"` // required
|
||||
URL string `json:"voice_url"` // required
|
||||
Title string `json:"title"` // required
|
||||
Duration int `json:"voice_duration"`
|
||||
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
||||
InputMessageContent interface{} `json:"input_message_content,omitempty"`
|
||||
}
|
||||
|
||||
// InlineQueryResultDocument is an inline query response document.
|
||||
type InlineQueryResultDocument struct {
|
||||
Type string `json:"type"` // required
|
||||
ID string `json:"id"` // required
|
||||
Title string `json:"title"` // required
|
||||
Caption string `json:"caption"`
|
||||
URL string `json:"document_url"` // required
|
||||
MimeType string `json:"mime_type"` // required
|
||||
Description string `json:"description"`
|
||||
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
||||
InputMessageContent interface{} `json:"input_message_content,omitempty"`
|
||||
ThumbURL string `json:"thumb_url"`
|
||||
ThumbWidth int `json:"thumb_width"`
|
||||
ThumbHeight int `json:"thumb_height"`
|
||||
}
|
||||
|
||||
// InlineQueryResultLocation is an inline query response location.
|
||||
type InlineQueryResultLocation struct {
|
||||
Type string `json:"type"` // required
|
||||
ID string `json:"id"` // required
|
||||
Latitude float64 `json:"latitude"` // required
|
||||
Longitude float64 `json:"longitude"` // required
|
||||
Title string `json:"title"` // required
|
||||
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
|
||||
InputMessageContent interface{} `json:"input_message_content,omitempty"`
|
||||
ThumbURL string `json:"thumb_url"`
|
||||
ThumbWidth int `json:"thumb_width"`
|
||||
ThumbHeight int `json:"thumb_height"`
|
||||
}
|
||||
|
||||
// ChosenInlineResult is an inline query result chosen by a User
|
||||
type ChosenInlineResult struct {
|
||||
ResultID string `json:"result_id"`
|
||||
From *User `json:"from"`
|
||||
Location *Location `json:"location"`
|
||||
InlineMessageID string `json:"inline_message_id"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
// InputTextMessageContent contains text for displaying
|
||||
// as an inline query result.
|
||||
type InputTextMessageContent struct {
|
||||
Text string `json:"message_text"`
|
||||
ParseMode string `json:"parse_mode"`
|
||||
DisableWebPagePreview bool `json:"disable_web_page_preview"`
|
||||
}
|
||||
|
||||
// InputLocationMessageContent contains a location for displaying
|
||||
// as an inline query result.
|
||||
type InputLocationMessageContent struct {
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
}
|
||||
|
||||
// InputVenueMessageContent contains a venue for displaying
|
||||
// as an inline query result.
|
||||
type InputVenueMessageContent struct {
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Title string `json:"title"`
|
||||
Address string `json:"address"`
|
||||
FoursquareID string `json:"foursquare_id"`
|
||||
}
|
||||
|
||||
// InputContactMessageContent contains a contact for displaying
|
||||
// as an inline query result.
|
||||
type InputContactMessageContent struct {
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
}
|
3
vendor/github.com/mattermost/platform/einterfaces/cluster.go
generated
vendored
3
vendor/github.com/mattermost/platform/einterfaces/cluster.go
generated
vendored
@ -11,14 +11,17 @@ type ClusterInterface interface {
|
||||
StartInterNodeCommunication()
|
||||
StopInterNodeCommunication()
|
||||
GetClusterInfos() []*model.ClusterInfo
|
||||
GetClusterStats() ([]*model.ClusterStats, *model.AppError)
|
||||
RemoveAllSessionsForUserId(userId string)
|
||||
InvalidateCacheForUser(userId string)
|
||||
InvalidateCacheForChannel(channelId string)
|
||||
InvalidateCacheForChannelPosts(channelId string)
|
||||
Publish(event *model.WebSocketEvent)
|
||||
UpdateStatus(status *model.Status)
|
||||
GetLogs() ([]string, *model.AppError)
|
||||
GetClusterId() string
|
||||
ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError
|
||||
InvalidateAllCaches() *model.AppError
|
||||
}
|
||||
|
||||
var theClusterInterface ClusterInterface
|
||||
|
41
vendor/github.com/mattermost/platform/einterfaces/metrics.go
generated
vendored
Normal file
41
vendor/github.com/mattermost/platform/einterfaces/metrics.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package einterfaces
|
||||
|
||||
type MetricsInterface interface {
|
||||
StartServer()
|
||||
StopServer()
|
||||
|
||||
IncrementPostCreate()
|
||||
IncrementPostSentEmail()
|
||||
IncrementPostSentPush()
|
||||
IncrementPostBroadcast()
|
||||
IncrementPostFileAttachment(count int)
|
||||
|
||||
IncrementHttpRequest()
|
||||
IncrementHttpError()
|
||||
ObserveHttpRequestDuration(elapsed float64)
|
||||
|
||||
IncrementLogin()
|
||||
IncrementLoginFail()
|
||||
|
||||
IncrementEtagHitCounter(route string)
|
||||
IncrementEtagMissCounter(route string)
|
||||
|
||||
IncrementMemCacheHitCounter(cacheName string)
|
||||
IncrementMemCacheMissCounter(cacheName string)
|
||||
|
||||
AddMemCacheHitCounter(cacheName string, amount float64)
|
||||
AddMemCacheMissCounter(cacheName string, amount float64)
|
||||
}
|
||||
|
||||
var theMetricsInterface MetricsInterface
|
||||
|
||||
func RegisterMetricsInterface(newInterface MetricsInterface) {
|
||||
theMetricsInterface = newInterface
|
||||
}
|
||||
|
||||
func GetMetricsInterface() MetricsInterface {
|
||||
return theMetricsInterface
|
||||
}
|
2
vendor/github.com/mattermost/platform/einterfaces/mfa.go
generated
vendored
2
vendor/github.com/mattermost/platform/einterfaces/mfa.go
generated
vendored
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
type MfaInterface interface {
|
||||
GenerateQrCode(user *model.User) ([]byte, *model.AppError)
|
||||
GenerateSecret(user *model.User) (string, []byte, *model.AppError)
|
||||
Activate(user *model.User, token string) *model.AppError
|
||||
Deactivate(userId string) *model.AppError
|
||||
ValidateToken(secret, token string) (bool, *model.AppError)
|
||||
|
372
vendor/github.com/mattermost/platform/model/authorization.go
generated
vendored
Normal file
372
vendor/github.com/mattermost/platform/model/authorization.go
generated
vendored
Normal file
@ -0,0 +1,372 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
type Permission struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Permissions []string `json:"permissions"`
|
||||
}
|
||||
|
||||
var PERMISSION_INVITE_USER *Permission
|
||||
var PERMISSION_ADD_USER_TO_TEAM *Permission
|
||||
var PERMISSION_USE_SLASH_COMMANDS *Permission
|
||||
var PERMISSION_MANAGE_SLASH_COMMANDS *Permission
|
||||
var PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS *Permission
|
||||
var PERMISSION_CREATE_PUBLIC_CHANNEL *Permission
|
||||
var PERMISSION_CREATE_PRIVATE_CHANNEL *Permission
|
||||
var PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS *Permission
|
||||
var PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS *Permission
|
||||
var PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE *Permission
|
||||
var PERMISSION_MANAGE_ROLES *Permission
|
||||
var PERMISSION_CREATE_DIRECT_CHANNEL *Permission
|
||||
var PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES *Permission
|
||||
var PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES *Permission
|
||||
var PERMISSION_LIST_TEAM_CHANNELS *Permission
|
||||
var PERMISSION_JOIN_PUBLIC_CHANNELS *Permission
|
||||
var PERMISSION_DELETE_PUBLIC_CHANNEL *Permission
|
||||
var PERMISSION_DELETE_PRIVATE_CHANNEL *Permission
|
||||
var PERMISSION_EDIT_OTHER_USERS *Permission
|
||||
var PERMISSION_READ_CHANNEL *Permission
|
||||
var PERMISSION_PERMANENT_DELETE_USER *Permission
|
||||
var PERMISSION_UPLOAD_FILE *Permission
|
||||
var PERMISSION_GET_PUBLIC_LINK *Permission
|
||||
var PERMISSION_MANAGE_WEBHOOKS *Permission
|
||||
var PERMISSION_MANAGE_OTHERS_WEBHOOKS *Permission
|
||||
var PERMISSION_MANAGE_OAUTH *Permission
|
||||
var PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH *Permission
|
||||
var PERMISSION_CREATE_POST *Permission
|
||||
var PERMISSION_EDIT_POST *Permission
|
||||
var PERMISSION_EDIT_OTHERS_POSTS *Permission
|
||||
var PERMISSION_REMOVE_USER_FROM_TEAM *Permission
|
||||
var PERMISSION_MANAGE_TEAM *Permission
|
||||
var PERMISSION_IMPORT_TEAM *Permission
|
||||
|
||||
// General permission that encompases all system admin functions
|
||||
// in the future this could be broken up to allow access to some
|
||||
// admin functions but not others
|
||||
var PERMISSION_MANAGE_SYSTEM *Permission
|
||||
|
||||
var ROLE_SYSTEM_USER *Role
|
||||
var ROLE_SYSTEM_ADMIN *Role
|
||||
|
||||
var ROLE_TEAM_USER *Role
|
||||
var ROLE_TEAM_ADMIN *Role
|
||||
|
||||
var ROLE_CHANNEL_USER *Role
|
||||
var ROLE_CHANNEL_ADMIN *Role
|
||||
var ROLE_CHANNEL_GUEST *Role
|
||||
|
||||
var BuiltInRoles map[string]*Role
|
||||
|
||||
func InitalizePermissions() {
|
||||
PERMISSION_INVITE_USER = &Permission{
|
||||
"invite_user",
|
||||
"authentication.permissions.team_invite_user.name",
|
||||
"authentication.permissions.team_invite_user.description",
|
||||
}
|
||||
PERMISSION_ADD_USER_TO_TEAM = &Permission{
|
||||
"add_user_to_team",
|
||||
"authentication.permissions.add_user_to_team.name",
|
||||
"authentication.permissions.add_user_to_team.description",
|
||||
}
|
||||
PERMISSION_USE_SLASH_COMMANDS = &Permission{
|
||||
"use_slash_commands",
|
||||
"authentication.permissions.team_use_slash_commands.name",
|
||||
"authentication.permissions.team_use_slash_commands.description",
|
||||
}
|
||||
PERMISSION_MANAGE_SLASH_COMMANDS = &Permission{
|
||||
"manage_slash_commands",
|
||||
"authentication.permissions.manage_slash_commands.name",
|
||||
"authentication.permissions.manage_slash_commands.description",
|
||||
}
|
||||
PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS = &Permission{
|
||||
"manage_others_slash_commands",
|
||||
"authentication.permissions.manage_others_slash_commands.name",
|
||||
"authentication.permissions.manage_others_slash_commands.description",
|
||||
}
|
||||
PERMISSION_CREATE_PUBLIC_CHANNEL = &Permission{
|
||||
"create_public_channel",
|
||||
"authentication.permissions.create_public_channel.name",
|
||||
"authentication.permissions.create_public_channel.description",
|
||||
}
|
||||
PERMISSION_CREATE_PRIVATE_CHANNEL = &Permission{
|
||||
"create_private_channel",
|
||||
"authentication.permissions.create_private_channel.name",
|
||||
"authentication.permissions.create_private_channel.description",
|
||||
}
|
||||
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS = &Permission{
|
||||
"manage_public_channel_members",
|
||||
"authentication.permissions.manage_public_channel_members.name",
|
||||
"authentication.permissions.manage_public_channel_members.description",
|
||||
}
|
||||
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS = &Permission{
|
||||
"manage_private_channel_members",
|
||||
"authentication.permissions.manage_private_channel_members.name",
|
||||
"authentication.permissions.manage_private_channel_members.description",
|
||||
}
|
||||
PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE = &Permission{
|
||||
"assign_system_admin_role",
|
||||
"authentication.permissions.assign_system_admin_role.name",
|
||||
"authentication.permissions.assign_system_admin_role.description",
|
||||
}
|
||||
PERMISSION_MANAGE_ROLES = &Permission{
|
||||
"manage_roles",
|
||||
"authentication.permissions.manage_roles.name",
|
||||
"authentication.permissions.manage_roles.description",
|
||||
}
|
||||
PERMISSION_MANAGE_SYSTEM = &Permission{
|
||||
"manage_system",
|
||||
"authentication.permissions.manage_system.name",
|
||||
"authentication.permissions.manage_system.description",
|
||||
}
|
||||
PERMISSION_CREATE_DIRECT_CHANNEL = &Permission{
|
||||
"create_direct_channel",
|
||||
"authentication.permissions.create_direct_channel.name",
|
||||
"authentication.permissions.create_direct_channel.description",
|
||||
}
|
||||
PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES = &Permission{
|
||||
"manage__publicchannel_properties",
|
||||
"authentication.permissions.manage_public_channel_properties.name",
|
||||
"authentication.permissions.manage_public_channel_properties.description",
|
||||
}
|
||||
PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES = &Permission{
|
||||
"manage_private_channel_properties",
|
||||
"authentication.permissions.manage_private_channel_properties.name",
|
||||
"authentication.permissions.manage_private_channel_properties.description",
|
||||
}
|
||||
PERMISSION_LIST_TEAM_CHANNELS = &Permission{
|
||||
"list_team_channels",
|
||||
"authentication.permissions.list_team_channels.name",
|
||||
"authentication.permissions.list_team_channels.description",
|
||||
}
|
||||
PERMISSION_JOIN_PUBLIC_CHANNELS = &Permission{
|
||||
"join_public_channels",
|
||||
"authentication.permissions.join_public_channels.name",
|
||||
"authentication.permissions.join_public_channels.description",
|
||||
}
|
||||
PERMISSION_DELETE_PUBLIC_CHANNEL = &Permission{
|
||||
"delete_public_channel",
|
||||
"authentication.permissions.delete_public_channel.name",
|
||||
"authentication.permissions.delete_public_channel.description",
|
||||
}
|
||||
PERMISSION_DELETE_PRIVATE_CHANNEL = &Permission{
|
||||
"delete_private_channel",
|
||||
"authentication.permissions.delete_private_channel.name",
|
||||
"authentication.permissions.delete_private_channel.description",
|
||||
}
|
||||
PERMISSION_EDIT_OTHER_USERS = &Permission{
|
||||
"edit_other_users",
|
||||
"authentication.permissions.edit_other_users.name",
|
||||
"authentication.permissions.edit_other_users.description",
|
||||
}
|
||||
PERMISSION_READ_CHANNEL = &Permission{
|
||||
"read_channel",
|
||||
"authentication.permissions.read_channel.name",
|
||||
"authentication.permissions.read_channel.description",
|
||||
}
|
||||
PERMISSION_PERMANENT_DELETE_USER = &Permission{
|
||||
"permanent_delete_user",
|
||||
"authentication.permissions.permanent_delete_user.name",
|
||||
"authentication.permissions.permanent_delete_user.description",
|
||||
}
|
||||
PERMISSION_UPLOAD_FILE = &Permission{
|
||||
"upload_file",
|
||||
"authentication.permissions.upload_file.name",
|
||||
"authentication.permissions.upload_file.description",
|
||||
}
|
||||
PERMISSION_GET_PUBLIC_LINK = &Permission{
|
||||
"get_public_link",
|
||||
"authentication.permissions.get_public_link.name",
|
||||
"authentication.permissions.get_public_link.description",
|
||||
}
|
||||
PERMISSION_MANAGE_WEBHOOKS = &Permission{
|
||||
"manage_webhooks",
|
||||
"authentication.permissions.manage_webhooks.name",
|
||||
"authentication.permissions.manage_webhooks.description",
|
||||
}
|
||||
PERMISSION_MANAGE_OTHERS_WEBHOOKS = &Permission{
|
||||
"manage_others_webhooks",
|
||||
"authentication.permissions.manage_others_webhooks.name",
|
||||
"authentication.permissions.manage_others_webhooks.description",
|
||||
}
|
||||
PERMISSION_MANAGE_OAUTH = &Permission{
|
||||
"manage_oauth",
|
||||
"authentication.permissions.manage_oauth.name",
|
||||
"authentication.permissions.manage_oauth.description",
|
||||
}
|
||||
PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH = &Permission{
|
||||
"manage_sytem_wide_oauth",
|
||||
"authentication.permissions.manage_sytem_wide_oauth.name",
|
||||
"authentication.permissions.manage_sytem_wide_oauth.description",
|
||||
}
|
||||
PERMISSION_CREATE_POST = &Permission{
|
||||
"create_post",
|
||||
"authentication.permissions.create_post.name",
|
||||
"authentication.permissions.create_post.description",
|
||||
}
|
||||
PERMISSION_EDIT_POST = &Permission{
|
||||
"edit_post",
|
||||
"authentication.permissions.edit_post.name",
|
||||
"authentication.permissions.edit_post.description",
|
||||
}
|
||||
PERMISSION_EDIT_OTHERS_POSTS = &Permission{
|
||||
"edit_others_posts",
|
||||
"authentication.permissions.edit_others_posts.name",
|
||||
"authentication.permissions.edit_others_posts.description",
|
||||
}
|
||||
PERMISSION_REMOVE_USER_FROM_TEAM = &Permission{
|
||||
"remove_user_from_team",
|
||||
"authentication.permissions.remove_user_from_team.name",
|
||||
"authentication.permissions.remove_user_from_team.description",
|
||||
}
|
||||
PERMISSION_MANAGE_TEAM = &Permission{
|
||||
"manage_team",
|
||||
"authentication.permissions.manage_team.name",
|
||||
"authentication.permissions.manage_team.description",
|
||||
}
|
||||
PERMISSION_IMPORT_TEAM = &Permission{
|
||||
"import_team",
|
||||
"authentication.permissions.import_team.name",
|
||||
"authentication.permissions.import_team.description",
|
||||
}
|
||||
}
|
||||
|
||||
func InitalizeRoles() {
|
||||
InitalizePermissions()
|
||||
BuiltInRoles = make(map[string]*Role)
|
||||
|
||||
ROLE_CHANNEL_USER = &Role{
|
||||
"channel_user",
|
||||
"authentication.roles.channel_user.name",
|
||||
"authentication.roles.channel_user.description",
|
||||
[]string{
|
||||
PERMISSION_READ_CHANNEL.Id,
|
||||
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id,
|
||||
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id,
|
||||
PERMISSION_UPLOAD_FILE.Id,
|
||||
PERMISSION_GET_PUBLIC_LINK.Id,
|
||||
PERMISSION_CREATE_POST.Id,
|
||||
PERMISSION_EDIT_POST.Id,
|
||||
PERMISSION_USE_SLASH_COMMANDS.Id,
|
||||
},
|
||||
}
|
||||
BuiltInRoles[ROLE_CHANNEL_USER.Id] = ROLE_CHANNEL_USER
|
||||
ROLE_CHANNEL_ADMIN = &Role{
|
||||
"channel_admin",
|
||||
"authentication.roles.channel_admin.name",
|
||||
"authentication.roles.channel_admin.description",
|
||||
[]string{},
|
||||
}
|
||||
BuiltInRoles[ROLE_CHANNEL_ADMIN.Id] = ROLE_CHANNEL_ADMIN
|
||||
ROLE_CHANNEL_GUEST = &Role{
|
||||
"guest",
|
||||
"authentication.roles.global_guest.name",
|
||||
"authentication.roles.global_guest.description",
|
||||
[]string{},
|
||||
}
|
||||
BuiltInRoles[ROLE_CHANNEL_GUEST.Id] = ROLE_CHANNEL_GUEST
|
||||
|
||||
ROLE_TEAM_USER = &Role{
|
||||
"team_user",
|
||||
"authentication.roles.team_user.name",
|
||||
"authentication.roles.team_user.description",
|
||||
[]string{
|
||||
PERMISSION_LIST_TEAM_CHANNELS.Id,
|
||||
PERMISSION_JOIN_PUBLIC_CHANNELS.Id,
|
||||
},
|
||||
}
|
||||
BuiltInRoles[ROLE_TEAM_USER.Id] = ROLE_TEAM_USER
|
||||
ROLE_TEAM_ADMIN = &Role{
|
||||
"team_admin",
|
||||
"authentication.roles.team_admin.name",
|
||||
"authentication.roles.team_admin.description",
|
||||
[]string{
|
||||
PERMISSION_EDIT_OTHERS_POSTS.Id,
|
||||
PERMISSION_ADD_USER_TO_TEAM.Id,
|
||||
PERMISSION_REMOVE_USER_FROM_TEAM.Id,
|
||||
PERMISSION_MANAGE_TEAM.Id,
|
||||
PERMISSION_IMPORT_TEAM.Id,
|
||||
PERMISSION_MANAGE_ROLES.Id,
|
||||
PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id,
|
||||
PERMISSION_MANAGE_SLASH_COMMANDS.Id,
|
||||
PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS.Id,
|
||||
PERMISSION_MANAGE_WEBHOOKS.Id,
|
||||
},
|
||||
}
|
||||
BuiltInRoles[ROLE_TEAM_ADMIN.Id] = ROLE_TEAM_ADMIN
|
||||
|
||||
ROLE_SYSTEM_USER = &Role{
|
||||
"system_user",
|
||||
"authentication.roles.global_user.name",
|
||||
"authentication.roles.global_user.description",
|
||||
[]string{
|
||||
PERMISSION_CREATE_DIRECT_CHANNEL.Id,
|
||||
PERMISSION_PERMANENT_DELETE_USER.Id,
|
||||
PERMISSION_MANAGE_OAUTH.Id,
|
||||
},
|
||||
}
|
||||
BuiltInRoles[ROLE_SYSTEM_USER.Id] = ROLE_SYSTEM_USER
|
||||
ROLE_SYSTEM_ADMIN = &Role{
|
||||
"system_admin",
|
||||
"authentication.roles.global_admin.name",
|
||||
"authentication.roles.global_admin.description",
|
||||
// System admins can do anything channel and team admins can do
|
||||
// plus everything members of teams and channels can do to all teams
|
||||
// and channels on the system
|
||||
append(
|
||||
append(
|
||||
append(
|
||||
append(
|
||||
[]string{
|
||||
PERMISSION_ASSIGN_SYSTEM_ADMIN_ROLE.Id,
|
||||
PERMISSION_MANAGE_SYSTEM.Id,
|
||||
PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id,
|
||||
PERMISSION_DELETE_PUBLIC_CHANNEL.Id,
|
||||
PERMISSION_CREATE_PUBLIC_CHANNEL.Id,
|
||||
PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id,
|
||||
PERMISSION_DELETE_PRIVATE_CHANNEL.Id,
|
||||
PERMISSION_CREATE_PRIVATE_CHANNEL.Id,
|
||||
PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH.Id,
|
||||
PERMISSION_MANAGE_OTHERS_WEBHOOKS.Id,
|
||||
PERMISSION_EDIT_OTHER_USERS.Id,
|
||||
PERMISSION_MANAGE_OAUTH.Id,
|
||||
PERMISSION_INVITE_USER.Id,
|
||||
},
|
||||
ROLE_TEAM_USER.Permissions...,
|
||||
),
|
||||
ROLE_CHANNEL_USER.Permissions...,
|
||||
),
|
||||
ROLE_TEAM_ADMIN.Permissions...,
|
||||
),
|
||||
ROLE_CHANNEL_ADMIN.Permissions...,
|
||||
),
|
||||
}
|
||||
BuiltInRoles[ROLE_SYSTEM_ADMIN.Id] = ROLE_SYSTEM_ADMIN
|
||||
|
||||
}
|
||||
|
||||
func RoleIdsToString(roles []string) string {
|
||||
output := ""
|
||||
for _, role := range roles {
|
||||
output += role + ", "
|
||||
}
|
||||
|
||||
if output == "" {
|
||||
return "[<NO ROLES>]"
|
||||
}
|
||||
|
||||
return output[:len(output)-1]
|
||||
}
|
||||
|
||||
func init() {
|
||||
InitalizeRoles()
|
||||
}
|
24
vendor/github.com/mattermost/platform/model/channel.go
generated
vendored
24
vendor/github.com/mattermost/platform/model/channel.go
generated
vendored
@ -10,10 +10,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
CHANNEL_OPEN = "O"
|
||||
CHANNEL_PRIVATE = "P"
|
||||
CHANNEL_DIRECT = "D"
|
||||
DEFAULT_CHANNEL = "town-square"
|
||||
CHANNEL_OPEN = "O"
|
||||
CHANNEL_PRIVATE = "P"
|
||||
CHANNEL_DIRECT = "D"
|
||||
DEFAULT_CHANNEL = "town-square"
|
||||
CHANNEL_DISPLAY_NAME_MAX_RUNES = 64
|
||||
CHANNEL_NAME_MAX_LENGTH = 64
|
||||
CHANNEL_HEADER_MAX_RUNES = 1024
|
||||
CHANNEL_PURPOSE_MAX_RUNES = 250
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
@ -57,8 +61,8 @@ func (o *Channel) Etag() string {
|
||||
return Etag(o.Id, o.UpdateAt)
|
||||
}
|
||||
|
||||
func (o *Channel) ExtraEtag(memberLimit int) string {
|
||||
return Etag(o.Id, o.ExtraUpdateAt, memberLimit)
|
||||
func (o *Channel) StatsEtag() string {
|
||||
return Etag(o.Id, o.ExtraUpdateAt)
|
||||
}
|
||||
|
||||
func (o *Channel) IsValid() *AppError {
|
||||
@ -75,11 +79,11 @@ func (o *Channel) IsValid() *AppError {
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.update_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.DisplayName) > 64 {
|
||||
if utf8.RuneCountInString(o.DisplayName) > CHANNEL_DISPLAY_NAME_MAX_RUNES {
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.display_name.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.Name) > 64 {
|
||||
if len(o.Name) > CHANNEL_NAME_MAX_LENGTH {
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.name.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
@ -91,11 +95,11 @@ func (o *Channel) IsValid() *AppError {
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.type.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.Header) > 1024 {
|
||||
if utf8.RuneCountInString(o.Header) > CHANNEL_HEADER_MAX_RUNES {
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.header.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.Purpose) > 128 {
|
||||
if utf8.RuneCountInString(o.Purpose) > CHANNEL_PURPOSE_MAX_RUNES {
|
||||
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.purpose.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
|
49
vendor/github.com/mattermost/platform/model/channel_extra.go
generated
vendored
49
vendor/github.com/mattermost/platform/model/channel_extra.go
generated
vendored
@ -1,49 +0,0 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type ExtraMember struct {
|
||||
Id string `json:"id"`
|
||||
Nickname string `json:"nickname"`
|
||||
Email string `json:"email"`
|
||||
Roles string `json:"roles"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
func (o *ExtraMember) Sanitize(options map[string]bool) {
|
||||
if len(options) == 0 || !options["email"] {
|
||||
o.Email = ""
|
||||
}
|
||||
}
|
||||
|
||||
type ChannelExtra struct {
|
||||
Id string `json:"id"`
|
||||
Members []ExtraMember `json:"members"`
|
||||
MemberCount int64 `json:"member_count"`
|
||||
}
|
||||
|
||||
func (o *ChannelExtra) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func ChannelExtraFromJson(data io.Reader) *ChannelExtra {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o ChannelExtra
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
35
vendor/github.com/mattermost/platform/model/channel_list.go
generated
vendored
35
vendor/github.com/mattermost/platform/model/channel_list.go
generated
vendored
@ -8,15 +8,11 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type ChannelList struct {
|
||||
Channels []*Channel `json:"channels"`
|
||||
Members map[string]*ChannelMember `json:"members"`
|
||||
}
|
||||
type ChannelList []*Channel
|
||||
|
||||
func (o *ChannelList) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
if b, err := json.Marshal(o); err != nil {
|
||||
return "[]"
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
@ -28,7 +24,7 @@ func (o *ChannelList) Etag() string {
|
||||
var t int64 = 0
|
||||
var delta int64 = 0
|
||||
|
||||
for _, v := range o.Channels {
|
||||
for _, v := range *o {
|
||||
if v.LastPostAt > t {
|
||||
t = v.LastPostAt
|
||||
id = v.Id
|
||||
@ -39,30 +35,9 @@ func (o *ChannelList) Etag() string {
|
||||
id = v.Id
|
||||
}
|
||||
|
||||
member := o.Members[v.Id]
|
||||
|
||||
if member != nil {
|
||||
max := v.LastPostAt
|
||||
if v.UpdateAt > max {
|
||||
max = v.UpdateAt
|
||||
}
|
||||
|
||||
delta += max - member.LastViewedAt
|
||||
|
||||
if member.LastViewedAt > t {
|
||||
t = member.LastViewedAt
|
||||
id = v.Id
|
||||
}
|
||||
|
||||
if member.LastUpdateAt > t {
|
||||
t = member.LastUpdateAt
|
||||
id = v.Id
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return Etag(id, t, delta, len(o.Channels))
|
||||
return Etag(id, t, delta, len(*o))
|
||||
}
|
||||
|
||||
func ChannelListFromJson(data io.Reader) *ChannelList {
|
||||
|
40
vendor/github.com/mattermost/platform/model/channel_member.go
generated
vendored
40
vendor/github.com/mattermost/platform/model/channel_member.go
generated
vendored
@ -10,7 +10,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
CHANNEL_ROLE_ADMIN = "admin"
|
||||
CHANNEL_NOTIFY_DEFAULT = "default"
|
||||
CHANNEL_NOTIFY_ALL = "all"
|
||||
CHANNEL_NOTIFY_MENTION = "mention"
|
||||
@ -19,6 +18,14 @@ const (
|
||||
CHANNEL_MARK_UNREAD_MENTION = "mention"
|
||||
)
|
||||
|
||||
type ChannelUnread struct {
|
||||
TeamId string
|
||||
TotalMsgCount int64
|
||||
MsgCount int64
|
||||
MentionCount int64
|
||||
NotifyProps StringMap
|
||||
}
|
||||
|
||||
type ChannelMember struct {
|
||||
ChannelId string `json:"channel_id"`
|
||||
UserId string `json:"user_id"`
|
||||
@ -30,6 +37,27 @@ type ChannelMember struct {
|
||||
LastUpdateAt int64 `json:"last_update_at"`
|
||||
}
|
||||
|
||||
type ChannelMembers []ChannelMember
|
||||
|
||||
func (o *ChannelMembers) ToJson() string {
|
||||
if b, err := json.Marshal(o); err != nil {
|
||||
return "[]"
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func ChannelMembersFromJson(data io.Reader) *ChannelMembers {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o ChannelMembers
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ChannelMember) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
@ -60,12 +88,6 @@ func (o *ChannelMember) IsValid() *AppError {
|
||||
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.user_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
for _, role := range strings.Split(o.Roles, " ") {
|
||||
if !(role == "" || role == CHANNEL_ROLE_ADMIN) {
|
||||
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.role.app_error", nil, "role="+role)
|
||||
}
|
||||
}
|
||||
|
||||
notifyLevel := o.NotifyProps["desktop"]
|
||||
if len(notifyLevel) > 20 || !IsChannelNotifyLevelValid(notifyLevel) {
|
||||
return NewLocAppError("ChannelMember.IsValid", "model.channel_member.is_valid.notify_level.app_error",
|
||||
@ -89,6 +111,10 @@ func (o *ChannelMember) PreUpdate() {
|
||||
o.LastUpdateAt = GetMillis()
|
||||
}
|
||||
|
||||
func (o *ChannelMember) GetRoles() []string {
|
||||
return strings.Fields(o.Roles)
|
||||
}
|
||||
|
||||
func IsChannelNotifyLevelValid(notifyLevel string) bool {
|
||||
return notifyLevel == CHANNEL_NOTIFY_DEFAULT ||
|
||||
notifyLevel == CHANNEL_NOTIFY_ALL ||
|
||||
|
35
vendor/github.com/mattermost/platform/model/channel_search.go
generated
vendored
Normal file
35
vendor/github.com/mattermost/platform/model/channel_search.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type ChannelSearch struct {
|
||||
Term string `json:"term"`
|
||||
}
|
||||
|
||||
// ToJson convert a Channel to a json string
|
||||
func (c *ChannelSearch) ToJson() string {
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
// ChannelSearchFromJson will decode the input and return a Channel
|
||||
func ChannelSearchFromJson(data io.Reader) *ChannelSearch {
|
||||
decoder := json.NewDecoder(data)
|
||||
var cs ChannelSearch
|
||||
err := decoder.Decode(&cs)
|
||||
if err == nil {
|
||||
return &cs
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
@ -5,32 +5,15 @@ package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type TeamSignup struct {
|
||||
Team Team `json:"team"`
|
||||
User User `json:"user"`
|
||||
Invites []string `json:"invites"`
|
||||
Data string `json:"data"`
|
||||
Hash string `json:"hash"`
|
||||
type ChannelStats struct {
|
||||
ChannelId string `json:"channel_id"`
|
||||
MemberCount int64 `json:"member_count"`
|
||||
}
|
||||
|
||||
func TeamSignupFromJson(data io.Reader) *TeamSignup {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o TeamSignup
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (o *TeamSignup) ToJson() string {
|
||||
func (o *ChannelStats) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
@ -38,3 +21,14 @@ func (o *TeamSignup) ToJson() string {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func ChannelStatsFromJson(data io.Reader) *ChannelStats {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o ChannelStats
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
34
vendor/github.com/mattermost/platform/model/channel_view.go
generated
vendored
Normal file
34
vendor/github.com/mattermost/platform/model/channel_view.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type ChannelView struct {
|
||||
ChannelId string `json:"channel_id"`
|
||||
PrevChannelId string `json:"prev_channel_id"`
|
||||
}
|
||||
|
||||
func (o *ChannelView) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func ChannelViewFromJson(data io.Reader) *ChannelView {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o ChannelView
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
623
vendor/github.com/mattermost/platform/model/client.go
generated
vendored
623
vendor/github.com/mattermost/platform/model/client.go
generated
vendored
@ -6,7 +6,6 @@ package model
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
@ -15,6 +14,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
l4g "github.com/alecthomas/log4go"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -48,6 +49,13 @@ type Result struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
type ResponseMetadata struct {
|
||||
StatusCode int
|
||||
Error *AppError
|
||||
RequestId string
|
||||
Etag string
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Url string // The location of the server like "http://localhost:8065"
|
||||
ApiUrl string // The api location of the server like "http://localhost:8065/api/v3"
|
||||
@ -108,6 +116,10 @@ func (c *Client) GetChannelRoute(channelId string) string {
|
||||
return fmt.Sprintf("/teams/%v/channels/%v", c.GetTeamId(), channelId)
|
||||
}
|
||||
|
||||
func (c *Client) GetUserRequiredRoute(userId string) string {
|
||||
return fmt.Sprintf("/users/%v", userId)
|
||||
}
|
||||
|
||||
func (c *Client) GetChannelNameRoute(channelName string) string {
|
||||
return fmt.Sprintf("/teams/%v/channels/name/%v", c.GetTeamId(), channelName)
|
||||
}
|
||||
@ -120,9 +132,14 @@ func (c *Client) GetGeneralRoute() string {
|
||||
return "/general"
|
||||
}
|
||||
|
||||
func (c *Client) GetFileRoute(fileId string) string {
|
||||
return fmt.Sprintf("/files/%v", fileId)
|
||||
}
|
||||
|
||||
func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppError) {
|
||||
rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data))
|
||||
rq.Header.Set("Content-Type", contentType)
|
||||
rq.Close = true
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||
@ -136,6 +153,7 @@ func (c *Client) DoPost(url, data, contentType string) (*http.Response, *AppErro
|
||||
|
||||
func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) {
|
||||
rq, _ := http.NewRequest("POST", c.ApiUrl+url, strings.NewReader(data))
|
||||
rq.Close = true
|
||||
|
||||
if len(c.AuthToken) > 0 {
|
||||
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
|
||||
@ -153,6 +171,7 @@ func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError)
|
||||
|
||||
func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, *AppError) {
|
||||
rq, _ := http.NewRequest("GET", c.ApiUrl+url, strings.NewReader(data))
|
||||
rq.Close = true
|
||||
|
||||
if len(etag) > 0 {
|
||||
rq.Header.Set(HEADER_ETAG_CLIENT, etag)
|
||||
@ -280,34 +299,6 @@ func (c *Client) GetPing() (map[string]string, *AppError) {
|
||||
|
||||
// Team Routes Section
|
||||
|
||||
// SignupTeam sends an email with a team sign-up link to the provided address if email
|
||||
// verification is enabled, otherwise it returns a map with a "follow_link" entry
|
||||
// containing the team sign-up link.
|
||||
func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) {
|
||||
m := make(map[string]string)
|
||||
m["email"] = email
|
||||
m["display_name"] = displayName
|
||||
if r, err := c.DoApiPost("/teams/signup", MapToJson(m)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTeamFromSignup creates a team based on the provided TeamSignup struct. On success
|
||||
// it returns the TeamSignup struct.
|
||||
func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamSignupFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTeam creates a team based on the provided Team struct. On success it returns
|
||||
// the Team struct with the Id, CreateAt and other server-decided fields populated.
|
||||
func (c *Client) CreateTeam(team *Team) (*Result, *AppError) {
|
||||
@ -489,6 +480,32 @@ func (c *Client) GetUser(id string, etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// getByUsername returns a user based on a provided username string. Must be authenticated.
|
||||
func (c *Client) GetByUsername(username string, etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/users/name/%v", username), "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getByEmail returns a user based on a provided username string. Must be authenticated.
|
||||
func (c *Client) GetByEmail(email string, etag string) (*User, *ResponseMetadata) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/users/email/%v", email), "", etag); err != nil {
|
||||
return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return UserFromJson(r.Body),
|
||||
&ResponseMetadata{
|
||||
StatusCode: r.StatusCode,
|
||||
RequestId: r.Header.Get(HEADER_REQUEST_ID),
|
||||
Etag: r.Header.Get(HEADER_ETAG_SERVER),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetMe returns the current user.
|
||||
func (c *Client) GetMe(etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/users/me", "", etag); err != nil {
|
||||
@ -500,10 +517,9 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetProfilesForDirectMessageList returns a map of users for a team that can be direct
|
||||
// messaged, using user id as the key. Must be authenticated.
|
||||
func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/users/profiles_for_dm_list/"+teamId, "", ""); err != nil {
|
||||
// GetProfiles returns a map of users using user id as the key. Must be authenticated.
|
||||
func (c *Client) GetProfiles(offset int, limit int, etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/users/%v/%v", offset, limit), "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
@ -512,10 +528,10 @@ func (c *Client) GetProfilesForDirectMessageList(teamId string) (*Result, *AppEr
|
||||
}
|
||||
}
|
||||
|
||||
// GetProfiles returns a map of users for a team using user id as the key. Must
|
||||
// GetProfilesInTeam returns a map of users for a team using user id as the key. Must
|
||||
// be authenticated.
|
||||
func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/users/profiles/"+teamId, "", etag); err != nil {
|
||||
func (c *Client) GetProfilesInTeam(teamId string, offset int, limit int, etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/users/%v/%v", teamId, offset, limit), "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
@ -524,10 +540,10 @@ func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetDirectProfiles gets a map of users that are currently shown in the sidebar,
|
||||
// using user id as the key. Must be authenticated.
|
||||
func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/users/direct_profiles", "", etag); err != nil {
|
||||
// GetProfilesInChannel returns a map of users for a channel using user id as the key. Must
|
||||
// be authenticated.
|
||||
func (c *Client) GetProfilesInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/%v/%v", offset, limit), "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
@ -536,6 +552,83 @@ func (c *Client) GetDirectProfiles(etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetProfilesNotInChannel returns a map of users not in a channel but on the team using user id as the key. Must
|
||||
// be authenticated.
|
||||
func (c *Client) GetProfilesNotInChannel(channelId string, offset int, limit int, etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf(c.GetChannelRoute(channelId)+"/users/not_in_channel/%v/%v", offset, limit), "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetProfilesByIds returns a map of users based on the user ids provided. Must
|
||||
// be authenticated.
|
||||
func (c *Client) GetProfilesByIds(userIds []string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/users/ids", ArrayToJson(userIds)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SearchUsers returns a list of users that have a username matching or similar to the search term. Must
|
||||
// be authenticated.
|
||||
func (c *Client) SearchUsers(params UserSearch) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/users/search", params.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// AutocompleteUsersInChannel returns two lists for autocompletion of users in a channel. The first list "in_channel",
|
||||
// specifies users in the channel. The second list "out_of_channel" specifies users outside of the
|
||||
// channel. Term, the string to search against, is required, channel id is also required. Must be authenticated.
|
||||
func (c *Client) AutocompleteUsersInChannel(term string, channelId string) (*Result, *AppError) {
|
||||
url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetChannelRoute(channelId), url.QueryEscape(term))
|
||||
if r, err := c.DoApiGet(url, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInChannelFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// AutocompleteUsersInTeam returns a list for autocompletion of users in a team. The list "in_team" specifies
|
||||
// the users in the team that match the provided term, matching against username, full name and
|
||||
// nickname. Must be authenticated.
|
||||
func (c *Client) AutocompleteUsersInTeam(term string) (*Result, *AppError) {
|
||||
url := fmt.Sprintf("%s/users/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term))
|
||||
if r, err := c.DoApiGet(url, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), UserAutocompleteInTeamFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// AutocompleteUsers returns a list for autocompletion of users on the system that match the provided term,
|
||||
// matching against username, full name and nickname. Must be authenticated.
|
||||
func (c *Client) AutocompleteUsers(term string) (*Result, *AppError) {
|
||||
url := fmt.Sprintf("/users/autocomplete?term=%s", url.QueryEscape(term))
|
||||
if r, err := c.DoApiGet(url, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), UserListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// LoginById authenticates a user by user id and password.
|
||||
func (c *Client) LoginById(id string, password string) (*Result, *AppError) {
|
||||
m := make(map[string]string)
|
||||
@ -622,15 +715,16 @@ func (c *Client) CheckMfa(loginId string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateMfaQrCode returns a QR code imagem containing the secret, to be scanned
|
||||
// by a multi-factor authentication mobile application. Must be authenticated.
|
||||
func (c *Client) GenerateMfaQrCode() (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/users/generate_mfa_qr", "", ""); err != nil {
|
||||
// GenerateMfaSecret returns a QR code image containing the secret, to be scanned
|
||||
// by a multi-factor authentication mobile application. It also returns the secret
|
||||
// for manual entry. Must be authenticated.
|
||||
func (c *Client) GenerateMfaSecret() (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/users/generate_mfa_secret", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), r.Body}, nil
|
||||
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -727,12 +821,9 @@ func (c *Client) EmailToLDAP(m map[string]string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Command(channelId string, command string, suggest bool) (*Result, *AppError) {
|
||||
m := make(map[string]string)
|
||||
m["command"] = command
|
||||
m["channelId"] = channelId
|
||||
m["suggest"] = strconv.FormatBool(suggest)
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", MapToJson(m)); err != nil {
|
||||
func (c *Client) Command(channelId string, command string) (*Result, *AppError) {
|
||||
args := &CommandArgs{ChannelId: channelId, Command: command}
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/execute", args.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
@ -771,6 +862,16 @@ func (c *Client) CreateCommand(cmd *Command) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) UpdateCommand(cmd *Command) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/update", cmd.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) RegenCommandToken(data map[string]string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/commands/regen_token", MapToJson(data)); err != nil {
|
||||
return nil, err
|
||||
@ -865,6 +966,16 @@ func (c *Client) ReloadConfig() (bool, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) InvalidateAllCaches() (bool, *AppError) {
|
||||
c.clearExtraProperties()
|
||||
if r, err := c.DoApiGet("/admin/invalidate_all_caches", "", ""); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
c.fillInExtraProperties(r)
|
||||
return c.CheckStatusOK(r), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SaveConfig(config *Config) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/admin/save_config", config.ToJson()); err != nil {
|
||||
return nil, err
|
||||
@ -934,6 +1045,7 @@ func (c *Client) SaveComplianceReport(job *Compliance) (*Result, *AppError) {
|
||||
func (c *Client) DownloadComplianceReport(id string) (*Result, *AppError) {
|
||||
var rq *http.Request
|
||||
rq, _ = http.NewRequest("GET", c.ApiUrl+"/admin/download_compliance_report/"+id, nil)
|
||||
rq.Close = true
|
||||
|
||||
if len(c.AuthToken) > 0 {
|
||||
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||
@ -1047,13 +1159,13 @@ func (c *Client) UpdateNotifyProps(data map[string]string) (*Result, *AppError)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetChannels(etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", etag); err != nil {
|
||||
func (c *Client) GetMyChannelMembers() (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/members", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelMembersFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -1067,6 +1179,7 @@ func (c *Client) GetChannel(id, etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// SCHEDULED FOR DEPRECATION IN 3.7 - use GetMoreChannelsPage instead
|
||||
func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/more", "", etag); err != nil {
|
||||
return nil, err
|
||||
@ -1077,6 +1190,43 @@ func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetMoreChannelsPage will return a page of open channels the user is not in based on
|
||||
// the provided offset and limit. Must be authenticated.
|
||||
func (c *Client) GetMoreChannelsPage(offset int, limit int) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf(c.GetTeamRoute()+"/channels/more/%v/%v", offset, limit), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SearchMoreChannels will return a list of open channels the user is not in, that matches
|
||||
// the search criteria provided. Must be authenticated.
|
||||
func (c *Client) SearchMoreChannels(channelSearch ChannelSearch) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/more/search", channelSearch.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// AutocompleteChannels will return a list of open channels that match the provided
|
||||
// string. Must be authenticated.
|
||||
func (c *Client) AutocompleteChannels(term string) (*Result, *AppError) {
|
||||
url := fmt.Sprintf("%s/channels/autocomplete?term=%s", c.GetTeamRoute(), url.QueryEscape(term))
|
||||
if r, err := c.DoApiGet(url, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/counts", "", etag); err != nil {
|
||||
return nil, err
|
||||
@ -1087,6 +1237,26 @@ func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetChannels(etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute()+"/channels/", "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetChannelByName(channelName string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetChannelNameRoute(channelName), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) JoinChannel(id string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(id)+"/join", ""); err != nil {
|
||||
return nil, err
|
||||
@ -1154,6 +1324,7 @@ func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) {
|
||||
// UpdateLastViewedAt will mark a channel as read.
|
||||
// The channelId indicates the channel to mark as read. If active is true, push notifications
|
||||
// will be cleared if there are unread messages. The default for active is true.
|
||||
// SCHEDULED FOR DEPRECATION IN 3.8 - use ViewChannel instead
|
||||
func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *AppError) {
|
||||
data := make(map[string]interface{})
|
||||
data["active"] = active
|
||||
@ -1166,13 +1337,52 @@ func (c *Client) UpdateLastViewedAt(channelId string, active bool) (*Result, *Ap
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetChannelExtraInfo(id string, memberLimit int, etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/extra_info/"+strconv.FormatInt(int64(memberLimit), 10), "", etag); err != nil {
|
||||
// ViewChannel performs all the actions related to viewing a channel. This includes marking
|
||||
// the channel and the previous one as read, and marking the channel as being actively viewed.
|
||||
// ChannelId is required but may be blank to indicate no channel is being viewed.
|
||||
// PrevChannelId is optional, populate to indicate a channel switch occurred.
|
||||
func (c *Client) ViewChannel(params ChannelView) (bool, *ResponseMetadata) {
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/channels/view", params.ToJson()); err != nil {
|
||||
return false, &ResponseMetadata{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
return c.CheckStatusOK(r),
|
||||
&ResponseMetadata{
|
||||
StatusCode: r.StatusCode,
|
||||
RequestId: r.Header.Get(HEADER_REQUEST_ID),
|
||||
Etag: r.Header.Get(HEADER_ETAG_SERVER),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetChannelStats(id string, etag string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetChannelRoute(id)+"/stats", "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelExtraFromJson(r.Body)}, nil
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelStatsFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetChannelMember(channelId string, userId string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/members/"+userId, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelMemberFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetChannelMembersByIds will return channel member objects as an array based on the
|
||||
// channel id and a list of user ids provided. Must be authenticated.
|
||||
func (c *Client) GetChannelMembersByIds(channelId string, userIds []string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/members/ids", ArrayToJson(userIds)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), ChannelMembersFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -1246,6 +1456,21 @@ func (c *Client) GetPost(channelId string, postId string, etag string) (*Result,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPostById returns a post and any posts in the same thread by post id
|
||||
func (c *Client) GetPostById(postId string, etag string) (*PostList, *ResponseMetadata) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute()+fmt.Sprintf("/posts/%v", postId), "", etag); err != nil {
|
||||
return nil, &ResponseMetadata{StatusCode: r.StatusCode, Error: err}
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return PostListFromJson(r.Body),
|
||||
&ResponseMetadata{
|
||||
StatusCode: r.StatusCode,
|
||||
RequestId: r.Header.Get(HEADER_REQUEST_ID),
|
||||
Etag: r.Header.Get(HEADER_ETAG_SERVER),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/delete", postId), ""); err != nil {
|
||||
return nil, err
|
||||
@ -1285,13 +1510,39 @@ func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *A
|
||||
return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType)
|
||||
}
|
||||
|
||||
func (c *Client) UploadPostAttachment(data []byte, contentType string) (*Result, *AppError) {
|
||||
return c.uploadFile(c.ApiUrl+c.GetTeamRoute()+"/files/upload", data, contentType)
|
||||
func (c *Client) UploadPostAttachment(data []byte, channelId string, filename string) (*FileUploadResponse, *AppError) {
|
||||
c.clearExtraProperties()
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
if part, err := writer.CreateFormFile("files", filename); err != nil {
|
||||
return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error())
|
||||
} else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
|
||||
return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.file.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
if part, err := writer.CreateFormField("channel_id"); err != nil {
|
||||
return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error())
|
||||
} else if _, err = io.Copy(part, strings.NewReader(channelId)); err != nil {
|
||||
return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.channel_id.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return nil, NewLocAppError("UploadPostAttachment", "model.client.upload_post_attachment.writer.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
if result, err := c.uploadFile(c.ApiUrl+c.GetTeamRoute()+"/files/upload", body.Bytes(), writer.FormDataContentType()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result.Data.(*FileUploadResponse), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) uploadFile(url string, data []byte, contentType string) (*Result, *AppError) {
|
||||
rq, _ := http.NewRequest("POST", url, bytes.NewReader(data))
|
||||
rq.Header.Set("Content-Type", contentType)
|
||||
rq.Close = true
|
||||
|
||||
if len(c.AuthToken) > 0 {
|
||||
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||
@ -1308,55 +1559,51 @@ func (c *Client) uploadFile(url string, data []byte, contentType string) (*Resul
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
|
||||
var rq *http.Request
|
||||
if isFullUrl {
|
||||
rq, _ = http.NewRequest("GET", url, nil)
|
||||
func (c *Client) GetFile(fileId string) (io.ReadCloser, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetTeamRoute()+"/files/get"+url, nil)
|
||||
}
|
||||
|
||||
if len(c.AuthToken) > 0 {
|
||||
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||
}
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||
} else if rp.StatusCode >= 300 {
|
||||
return nil, AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
defer closeBody(rp)
|
||||
return &Result{rp.Header.Get(HEADER_REQUEST_ID),
|
||||
rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil
|
||||
c.fillInExtraProperties(r)
|
||||
return r.Body, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetFileInfo(url string) (*Result, *AppError) {
|
||||
var rq *http.Request
|
||||
rq, _ = http.NewRequest("GET", c.ApiUrl+c.GetTeamRoute()+"/files/get_info"+url, nil)
|
||||
|
||||
if len(c.AuthToken) > 0 {
|
||||
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||
}
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil {
|
||||
return nil, NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error())
|
||||
} else if rp.StatusCode >= 300 {
|
||||
return nil, AppErrorFromJson(rp.Body)
|
||||
func (c *Client) GetFileThumbnail(fileId string) (io.ReadCloser, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_thumbnail", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(rp)
|
||||
return &Result{rp.Header.Get(HEADER_REQUEST_ID),
|
||||
rp.Header.Get(HEADER_ETAG_SERVER), FileInfoFromJson(rp.Body)}, nil
|
||||
c.fillInExtraProperties(r)
|
||||
return r.Body, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetPublicLink(filename string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/files/get_public_link", MapToJson(map[string]string{"filename": filename})); err != nil {
|
||||
func (c *Client) GetFilePreview(fileId string) (io.ReadCloser, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_preview", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), StringFromJson(r.Body)}, nil
|
||||
c.fillInExtraProperties(r)
|
||||
return r.Body, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetFileInfo(fileId string) (*FileInfo, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_info", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return FileInfoFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetPublicLink(fileId string) (string, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetFileRoute(fileId)+"/get_public_link", "", ""); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return StringFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -1370,8 +1617,25 @@ func (c *Client) UpdateUser(user *User) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/users/update_roles", MapToJson(data)); err != nil {
|
||||
func (c *Client) UpdateUserRoles(userId string, roles string) (*Result, *AppError) {
|
||||
data := make(map[string]string)
|
||||
data["new_roles"] = roles
|
||||
|
||||
if r, err := c.DoApiPost(c.GetUserRequiredRoute(userId)+"/update_roles", MapToJson(data)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) UpdateTeamRoles(userId string, roles string) (*Result, *AppError) {
|
||||
data := make(map[string]string)
|
||||
data["new_roles"] = roles
|
||||
data["user_id"] = userId
|
||||
|
||||
if r, err := c.DoApiPost(c.GetTeamRoute()+"/update_member_roles", MapToJson(data)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
@ -1479,9 +1743,22 @@ func (c *Client) GetStatuses() (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetStatusesByIds returns a map of string statuses using user id as the key,
|
||||
// based on the provided user ids
|
||||
func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost("/users/status/ids", ArrayToJson(userIds)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetActiveChannel sets the the channel id the user is currently viewing.
|
||||
// The channelId key is required but the value can be blank. Returns standard
|
||||
// response.
|
||||
// SCHEDULED FOR DEPRECATION IN 3.8 - use ViewChannel instead
|
||||
func (c *Client) SetActiveChannel(channelId string) (*Result, *AppError) {
|
||||
data := map[string]string{}
|
||||
data["channel_id"] = channelId
|
||||
@ -1504,8 +1781,88 @@ func (c *Client) GetMyTeam(etag string) (*Result, *AppError) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetTeamMembers(teamId string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/teams/members/"+teamId, "", ""); err != nil {
|
||||
// GetTeamMembers will return a page of team member objects as an array paged based on the
|
||||
// team id, offset and limit provided. Must be authenticated.
|
||||
func (c *Client) GetTeamMembers(teamId string, offset int, limit int) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v/%v", teamId, offset, limit), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetMyTeamMembers will return an array with team member objects that the current user
|
||||
// is a member of. Must be authenticated.
|
||||
func (c *Client) GetMyTeamMembers() (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet("/teams/members", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamMembersFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetMyTeamsUnread will return an array with TeamUnread objects that contain the amount of
|
||||
// unread messages and mentions the current user has for the teams it belongs to.
|
||||
// An optional team ID can be set to exclude that team from the results. Must be authenticated.
|
||||
func (c *Client) GetMyTeamsUnread(teamId string) (*Result, *AppError) {
|
||||
endpoint := "/teams/unread"
|
||||
|
||||
if teamId != "" {
|
||||
endpoint += fmt.Sprintf("?id=%s", url.QueryEscape(teamId))
|
||||
}
|
||||
if r, err := c.DoApiGet(endpoint, "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamsUnreadFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeamMember will return a team member object based on the team id and user id provided.
|
||||
// Must be authenticated.
|
||||
func (c *Client) GetTeamMember(teamId string, userId string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/members/%v", teamId, userId), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamMemberFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeamStats will return a team stats object containing the number of users on the team
|
||||
// based on the team id provided. Must be authenticated.
|
||||
func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/teams/%v/stats", teamId), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeamStats will return a team stats object containing the number of users on the team
|
||||
// based on the team id provided. Must be authenticated.
|
||||
func (c *Client) GetTeamByName(teamName string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiGet(fmt.Sprintf("/teams/name/%v", teamName), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
r.Header.Get(HEADER_ETAG_SERVER), TeamStatsFromJson(r.Body)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeamMembersByIds will return team member objects as an array based on the
|
||||
// team id and a list of user ids provided. Must be authenticated.
|
||||
func (c *Client) GetTeamMembersByIds(teamId string, userIds []string) (*Result, *AppError) {
|
||||
if r, err := c.DoApiPost(fmt.Sprintf("/teams/%v/members/ids", teamId), ArrayToJson(userIds)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
@ -1820,6 +2177,7 @@ func (c *Client) CreateEmoji(emoji *Emoji, image []byte, filename string) (*Emoj
|
||||
|
||||
rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetEmojiRoute()+"/create", body)
|
||||
rq.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
rq.Close = true
|
||||
|
||||
if len(c.AuthToken) > 0 {
|
||||
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||
@ -1844,6 +2202,7 @@ func (c *Client) DeleteEmoji(id string) (bool, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetEmojiRoute()+"/delete", MapToJson(data)); err != nil {
|
||||
return false, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return c.CheckStatusOK(r), nil
|
||||
}
|
||||
@ -1862,6 +2221,7 @@ func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppErro
|
||||
url := c.ApiUrl + "/admin/add_certificate"
|
||||
rq, _ := http.NewRequest("POST", url, bytes.NewReader(data))
|
||||
rq.Header.Set("Content-Type", contentType)
|
||||
rq.Close = true
|
||||
|
||||
if len(c.AuthToken) > 0 {
|
||||
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
|
||||
@ -1873,6 +2233,7 @@ func (c *Client) UploadCertificateFile(data []byte, contentType string) *AppErro
|
||||
return AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
defer closeBody(rp)
|
||||
c.fillInExtraProperties(rp)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -1884,6 +2245,7 @@ func (c *Client) RemoveCertificateFile(filename string) *AppError {
|
||||
return err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -1895,6 +2257,65 @@ func (c *Client) SamlCertificateStatus(filename string) (map[string]interface{},
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return StringInterfaceFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetWebrtcToken if Successful returns a map with a valid token, stun server and turn server with credentials to use with
|
||||
// the Mattermost WebRTC service, otherwise returns an AppError. Must be authenticated user.
|
||||
func (c *Client) GetWebrtcToken() (map[string]string, *AppError) {
|
||||
if r, err := c.DoApiPost("/webrtc/token", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return MapFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetFileInfosForPost returns a list of FileInfo objects for a given post id, if successful.
|
||||
// Otherwise, it returns an error.
|
||||
func (c *Client) GetFileInfosForPost(channelId string, postId string, etag string) ([]*FileInfo, *AppError) {
|
||||
c.clearExtraProperties()
|
||||
|
||||
if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/get_file_infos", postId), "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return FileInfosFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Saves an emoji reaction for a post in the given channel. Returns the saved reaction if successful, otherwise returns an AppError.
|
||||
func (c *Client) SaveReaction(channelId string, reaction *Reaction) (*Reaction, *AppError) {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions/save", reaction.PostId), reaction.ToJson()); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return ReactionFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Removes an emoji reaction for a post in the given channel. Returns nil if successful, otherwise returns an AppError.
|
||||
func (c *Client) DeleteReaction(channelId string, reaction *Reaction) *AppError {
|
||||
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions/delete", reaction.PostId), reaction.ToJson()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Lists all emoji reactions made for the given post in the given channel. Returns a list of Reactions if successful, otherwise returns an AppError.
|
||||
func (c *Client) ListReactions(channelId string, postId string) ([]*Reaction, *AppError) {
|
||||
if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+fmt.Sprintf("/posts/%v/reactions", postId), "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
c.fillInExtraProperties(r)
|
||||
return ReactionsFromJson(r.Body), nil
|
||||
}
|
||||
}
|
||||
|
36
vendor/github.com/mattermost/platform/model/cluster_stats.go
generated
vendored
Normal file
36
vendor/github.com/mattermost/platform/model/cluster_stats.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type ClusterStats struct {
|
||||
Id string `json:"id"`
|
||||
TotalWebsocketConnections int `json:"total_websocket_connections"`
|
||||
TotalReadDbConnections int `json:"total_read_db_connections"`
|
||||
TotalMasterDbConnections int `json:"total_master_db_connections"`
|
||||
}
|
||||
|
||||
func (me *ClusterStats) ToJson() string {
|
||||
b, err := json.Marshal(me)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func ClusterStatsFromJson(data io.Reader) *ClusterStats {
|
||||
decoder := json.NewDecoder(data)
|
||||
var me ClusterStats
|
||||
err := decoder.Decode(&me)
|
||||
if err == nil {
|
||||
return &me
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
36
vendor/github.com/mattermost/platform/model/command_args.go
generated
vendored
Normal file
36
vendor/github.com/mattermost/platform/model/command_args.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type CommandArgs struct {
|
||||
ChannelId string `json:"channel_id"`
|
||||
RootId string `json:"root_id"`
|
||||
ParentId string `json:"parent_id"`
|
||||
Command string `json:"command"`
|
||||
}
|
||||
|
||||
func (o *CommandArgs) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func CommandArgsFromJson(data io.Reader) *CommandArgs {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o CommandArgs
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
2
vendor/github.com/mattermost/platform/model/command_response.go
generated
vendored
2
vendor/github.com/mattermost/platform/model/command_response.go
generated
vendored
@ -16,6 +16,8 @@ const (
|
||||
type CommandResponse struct {
|
||||
ResponseType string `json:"response_type"`
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
IconURL string `json:"icon_url"`
|
||||
GotoLocation string `json:"goto_location"`
|
||||
Attachments interface{} `json:"attachments"`
|
||||
}
|
||||
|
6
vendor/github.com/mattermost/platform/model/compliance_post.go
generated
vendored
6
vendor/github.com/mattermost/platform/model/compliance_post.go
generated
vendored
@ -34,7 +34,7 @@ type CompliancePost struct {
|
||||
PostType string
|
||||
PostProps string
|
||||
PostHashtags string
|
||||
PostFilenames string
|
||||
PostFileIds string
|
||||
}
|
||||
|
||||
func CompliancePostHeader() []string {
|
||||
@ -60,7 +60,7 @@ func CompliancePostHeader() []string {
|
||||
"PostType",
|
||||
"PostProps",
|
||||
"PostHashtags",
|
||||
"PostFilenames",
|
||||
"PostFileIds",
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,6 +99,6 @@ func (me *CompliancePost) Row() []string {
|
||||
me.PostType,
|
||||
me.PostProps,
|
||||
me.PostHashtags,
|
||||
me.PostFilenames,
|
||||
me.PostFileIds,
|
||||
}
|
||||
}
|
||||
|
346
vendor/github.com/mattermost/platform/model/config.go
generated
vendored
346
vendor/github.com/mattermost/platform/model/config.go
generated
vendored
@ -38,9 +38,10 @@ const (
|
||||
DIRECT_MESSAGE_ANY = "any"
|
||||
DIRECT_MESSAGE_TEAM = "team"
|
||||
|
||||
PERMISSIONS_ALL = "all"
|
||||
PERMISSIONS_TEAM_ADMIN = "team_admin"
|
||||
PERMISSIONS_SYSTEM_ADMIN = "system_admin"
|
||||
PERMISSIONS_ALL = "all"
|
||||
PERMISSIONS_CHANNEL_ADMIN = "channel_admin"
|
||||
PERMISSIONS_TEAM_ADMIN = "team_admin"
|
||||
PERMISSIONS_SYSTEM_ADMIN = "system_admin"
|
||||
|
||||
FAKE_SETTING = "********************************"
|
||||
|
||||
@ -57,6 +58,14 @@ const (
|
||||
type ServiceSettings struct {
|
||||
SiteURL *string
|
||||
ListenAddress string
|
||||
ConnectionSecurity *string
|
||||
TLSCertFile *string
|
||||
TLSKeyFile *string
|
||||
UseLetsEncrypt *bool
|
||||
LetsEncryptCertificateCacheFile *string
|
||||
Forward80To443 *bool
|
||||
ReadTimeout *int
|
||||
WriteTimeout *int
|
||||
MaximumLoginAttempts int
|
||||
SegmentDeveloperKey string
|
||||
GoogleDeveloperKey string
|
||||
@ -72,6 +81,7 @@ type ServiceSettings struct {
|
||||
EnableSecurityFixAlert *bool
|
||||
EnableInsecureOutgoingConnections *bool
|
||||
EnableMultifactorAuthentication *bool
|
||||
EnforceMultifactorAuthentication *bool
|
||||
AllowCorsFrom *string
|
||||
SessionLengthWebInDays *int
|
||||
SessionLengthMobileInDays *int
|
||||
@ -90,6 +100,16 @@ type ClusterSettings struct {
|
||||
InterNodeUrls []string
|
||||
}
|
||||
|
||||
type MetricsSettings struct {
|
||||
Enable *bool
|
||||
BlockProfileRate *int
|
||||
ListenAddress *string
|
||||
}
|
||||
|
||||
type AnalyticsSettings struct {
|
||||
MaxUsersForStatistics *int
|
||||
}
|
||||
|
||||
type SSOSettings struct {
|
||||
Enable bool
|
||||
Secret string
|
||||
@ -130,26 +150,24 @@ type PasswordSettings struct {
|
||||
}
|
||||
|
||||
type FileSettings struct {
|
||||
MaxFileSize *int64
|
||||
DriverName string
|
||||
Directory string
|
||||
EnablePublicLink bool
|
||||
PublicLinkSalt *string
|
||||
ThumbnailWidth int
|
||||
ThumbnailHeight int
|
||||
PreviewWidth int
|
||||
PreviewHeight int
|
||||
ProfileWidth int
|
||||
ProfileHeight int
|
||||
InitialFont string
|
||||
AmazonS3AccessKeyId string
|
||||
AmazonS3SecretAccessKey string
|
||||
AmazonS3Bucket string
|
||||
AmazonS3Region string
|
||||
AmazonS3Endpoint string
|
||||
AmazonS3BucketEndpoint string
|
||||
AmazonS3LocationConstraint *bool
|
||||
AmazonS3LowercaseBucket *bool
|
||||
MaxFileSize *int64
|
||||
DriverName string
|
||||
Directory string
|
||||
EnablePublicLink bool
|
||||
PublicLinkSalt *string
|
||||
ThumbnailWidth int
|
||||
ThumbnailHeight int
|
||||
PreviewWidth int
|
||||
PreviewHeight int
|
||||
ProfileWidth int
|
||||
ProfileHeight int
|
||||
InitialFont string
|
||||
AmazonS3AccessKeyId string
|
||||
AmazonS3SecretAccessKey string
|
||||
AmazonS3Bucket string
|
||||
AmazonS3Region string
|
||||
AmazonS3Endpoint string
|
||||
AmazonS3SSL *bool
|
||||
}
|
||||
|
||||
type EmailSettings struct {
|
||||
@ -177,11 +195,12 @@ type EmailSettings struct {
|
||||
}
|
||||
|
||||
type RateLimitSettings struct {
|
||||
EnableRateLimiter bool
|
||||
PerSec int
|
||||
MemoryStoreSize int
|
||||
VaryByRemoteAddr bool
|
||||
VaryByHeader string
|
||||
Enable *bool
|
||||
PerSec int
|
||||
MaxBurst *int
|
||||
MemoryStoreSize int
|
||||
VaryByRemoteAddr bool
|
||||
VaryByHeader string
|
||||
}
|
||||
|
||||
type PrivacySettings struct {
|
||||
@ -205,7 +224,6 @@ type TeamSettings struct {
|
||||
EnableUserCreation bool
|
||||
EnableOpenServer *bool
|
||||
RestrictCreationToDomains string
|
||||
RestrictTeamNames *bool
|
||||
EnableCustomBrand *bool
|
||||
CustomBrandText *string
|
||||
CustomDescriptionText *string
|
||||
@ -213,7 +231,13 @@ type TeamSettings struct {
|
||||
RestrictTeamInvite *string
|
||||
RestrictPublicChannelManagement *string
|
||||
RestrictPrivateChannelManagement *string
|
||||
RestrictPublicChannelCreation *string
|
||||
RestrictPrivateChannelCreation *string
|
||||
RestrictPublicChannelDeletion *string
|
||||
RestrictPrivateChannelDeletion *string
|
||||
UserStatusAwayTimeout *int64
|
||||
MaxChannelsPerTeam *int64
|
||||
MaxNotificationsPerChannel *int64
|
||||
}
|
||||
|
||||
type LdapSettings struct {
|
||||
@ -236,6 +260,7 @@ type LdapSettings struct {
|
||||
UsernameAttribute *string
|
||||
NicknameAttribute *string
|
||||
IdAttribute *string
|
||||
PositionAttribute *string
|
||||
|
||||
// Syncronization
|
||||
SyncIntervalMinutes *int
|
||||
@ -282,6 +307,7 @@ type SamlSettings struct {
|
||||
UsernameAttribute *string
|
||||
NicknameAttribute *string
|
||||
LocaleAttribute *string
|
||||
PositionAttribute *string
|
||||
|
||||
LoginButtonText *string
|
||||
}
|
||||
@ -292,6 +318,17 @@ type NativeAppSettings struct {
|
||||
IosAppDownloadLink *string
|
||||
}
|
||||
|
||||
type WebrtcSettings struct {
|
||||
Enable *bool
|
||||
GatewayWebsocketUrl *string
|
||||
GatewayAdminUrl *string
|
||||
GatewayAdminSecret *string
|
||||
StunURI *string
|
||||
TurnURI *string
|
||||
TurnUsername *string
|
||||
TurnSharedKey *string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
ServiceSettings ServiceSettings
|
||||
TeamSettings TeamSettings
|
||||
@ -312,6 +349,9 @@ type Config struct {
|
||||
SamlSettings SamlSettings
|
||||
NativeAppSettings NativeAppSettings
|
||||
ClusterSettings ClusterSettings
|
||||
MetricsSettings MetricsSettings
|
||||
AnalyticsSettings AnalyticsSettings
|
||||
WebrtcSettings WebrtcSettings
|
||||
}
|
||||
|
||||
func (o *Config) ToJson() string {
|
||||
@ -353,6 +393,21 @@ func (o *Config) SetDefaults() {
|
||||
o.SqlSettings.AtRestEncryptKey = NewRandomString(32)
|
||||
}
|
||||
|
||||
if o.FileSettings.AmazonS3Endpoint == "" {
|
||||
// Defaults to "s3.amazonaws.com"
|
||||
o.FileSettings.AmazonS3Endpoint = "s3.amazonaws.com"
|
||||
}
|
||||
|
||||
if o.FileSettings.AmazonS3Region == "" {
|
||||
// Defaults to "us-east-1" region.
|
||||
o.FileSettings.AmazonS3Region = "us-east-1"
|
||||
}
|
||||
|
||||
if o.FileSettings.AmazonS3SSL == nil {
|
||||
o.FileSettings.AmazonS3SSL = new(bool)
|
||||
*o.FileSettings.AmazonS3SSL = true // Secure by default.
|
||||
}
|
||||
|
||||
if o.FileSettings.MaxFileSize == nil {
|
||||
o.FileSettings.MaxFileSize = new(int64)
|
||||
*o.FileSettings.MaxFileSize = 52428800 // 50 MB
|
||||
@ -363,14 +418,9 @@ func (o *Config) SetDefaults() {
|
||||
*o.FileSettings.PublicLinkSalt = NewRandomString(32)
|
||||
}
|
||||
|
||||
if o.FileSettings.AmazonS3LocationConstraint == nil {
|
||||
o.FileSettings.AmazonS3LocationConstraint = new(bool)
|
||||
*o.FileSettings.AmazonS3LocationConstraint = false
|
||||
}
|
||||
|
||||
if o.FileSettings.AmazonS3LowercaseBucket == nil {
|
||||
o.FileSettings.AmazonS3LowercaseBucket = new(bool)
|
||||
*o.FileSettings.AmazonS3LowercaseBucket = false
|
||||
if o.FileSettings.InitialFont == "" {
|
||||
// Defaults to "luximbi.ttf"
|
||||
o.FileSettings.InitialFont = "luximbi.ttf"
|
||||
}
|
||||
|
||||
if len(o.EmailSettings.InviteSalt) == 0 {
|
||||
@ -406,6 +456,11 @@ func (o *Config) SetDefaults() {
|
||||
*o.ServiceSettings.EnableMultifactorAuthentication = false
|
||||
}
|
||||
|
||||
if o.ServiceSettings.EnforceMultifactorAuthentication == nil {
|
||||
o.ServiceSettings.EnforceMultifactorAuthentication = new(bool)
|
||||
*o.ServiceSettings.EnforceMultifactorAuthentication = false
|
||||
}
|
||||
|
||||
if o.PasswordSettings.MinimumLength == nil {
|
||||
o.PasswordSettings.MinimumLength = new(int)
|
||||
*o.PasswordSettings.MinimumLength = PASSWORD_MINIMUM_LENGTH
|
||||
@ -431,11 +486,6 @@ func (o *Config) SetDefaults() {
|
||||
*o.PasswordSettings.Symbol = false
|
||||
}
|
||||
|
||||
if o.TeamSettings.RestrictTeamNames == nil {
|
||||
o.TeamSettings.RestrictTeamNames = new(bool)
|
||||
*o.TeamSettings.RestrictTeamNames = true
|
||||
}
|
||||
|
||||
if o.TeamSettings.EnableCustomBrand == nil {
|
||||
o.TeamSettings.EnableCustomBrand = new(bool)
|
||||
*o.TeamSettings.EnableCustomBrand = false
|
||||
@ -476,11 +526,45 @@ func (o *Config) SetDefaults() {
|
||||
*o.TeamSettings.RestrictPrivateChannelManagement = PERMISSIONS_ALL
|
||||
}
|
||||
|
||||
if o.TeamSettings.RestrictPublicChannelCreation == nil {
|
||||
o.TeamSettings.RestrictPublicChannelCreation = new(string)
|
||||
// If this setting does not exist, assume migration from <3.6, so use management setting as default.
|
||||
*o.TeamSettings.RestrictPublicChannelCreation = *o.TeamSettings.RestrictPublicChannelManagement
|
||||
}
|
||||
|
||||
if o.TeamSettings.RestrictPrivateChannelCreation == nil {
|
||||
o.TeamSettings.RestrictPrivateChannelCreation = new(string)
|
||||
// If this setting does not exist, assume migration from <3.6, so use management setting as default.
|
||||
*o.TeamSettings.RestrictPrivateChannelCreation = *o.TeamSettings.RestrictPrivateChannelManagement
|
||||
}
|
||||
|
||||
if o.TeamSettings.RestrictPublicChannelDeletion == nil {
|
||||
o.TeamSettings.RestrictPublicChannelDeletion = new(string)
|
||||
// If this setting does not exist, assume migration from <3.6, so use management setting as default.
|
||||
*o.TeamSettings.RestrictPublicChannelDeletion = *o.TeamSettings.RestrictPublicChannelManagement
|
||||
}
|
||||
|
||||
if o.TeamSettings.RestrictPrivateChannelDeletion == nil {
|
||||
o.TeamSettings.RestrictPrivateChannelDeletion = new(string)
|
||||
// If this setting does not exist, assume migration from <3.6, so use management setting as default.
|
||||
*o.TeamSettings.RestrictPrivateChannelDeletion = *o.TeamSettings.RestrictPrivateChannelManagement
|
||||
}
|
||||
|
||||
if o.TeamSettings.UserStatusAwayTimeout == nil {
|
||||
o.TeamSettings.UserStatusAwayTimeout = new(int64)
|
||||
*o.TeamSettings.UserStatusAwayTimeout = 300
|
||||
}
|
||||
|
||||
if o.TeamSettings.MaxChannelsPerTeam == nil {
|
||||
o.TeamSettings.MaxChannelsPerTeam = new(int64)
|
||||
*o.TeamSettings.MaxChannelsPerTeam = 2000
|
||||
}
|
||||
|
||||
if o.TeamSettings.MaxNotificationsPerChannel == nil {
|
||||
o.TeamSettings.MaxNotificationsPerChannel = new(int64)
|
||||
*o.TeamSettings.MaxNotificationsPerChannel = 1000
|
||||
}
|
||||
|
||||
if o.EmailSettings.EnableSignInWithEmail == nil {
|
||||
o.EmailSettings.EnableSignInWithEmail = new(bool)
|
||||
|
||||
@ -651,6 +735,11 @@ func (o *Config) SetDefaults() {
|
||||
*o.LdapSettings.IdAttribute = ""
|
||||
}
|
||||
|
||||
if o.LdapSettings.PositionAttribute == nil {
|
||||
o.LdapSettings.PositionAttribute = new(string)
|
||||
*o.LdapSettings.PositionAttribute = ""
|
||||
}
|
||||
|
||||
if o.LdapSettings.SyncIntervalMinutes == nil {
|
||||
o.LdapSettings.SyncIntervalMinutes = new(int)
|
||||
*o.LdapSettings.SyncIntervalMinutes = 60
|
||||
@ -752,6 +841,21 @@ func (o *Config) SetDefaults() {
|
||||
o.ClusterSettings.InterNodeUrls = []string{}
|
||||
}
|
||||
|
||||
if o.MetricsSettings.ListenAddress == nil {
|
||||
o.MetricsSettings.ListenAddress = new(string)
|
||||
*o.MetricsSettings.ListenAddress = ":8067"
|
||||
}
|
||||
|
||||
if o.MetricsSettings.Enable == nil {
|
||||
o.MetricsSettings.Enable = new(bool)
|
||||
*o.MetricsSettings.Enable = false
|
||||
}
|
||||
|
||||
if o.AnalyticsSettings.MaxUsersForStatistics == nil {
|
||||
o.AnalyticsSettings.MaxUsersForStatistics = new(int)
|
||||
*o.AnalyticsSettings.MaxUsersForStatistics = 2500
|
||||
}
|
||||
|
||||
if o.ComplianceSettings.Enable == nil {
|
||||
o.ComplianceSettings.Enable = new(bool)
|
||||
*o.ComplianceSettings.Enable = false
|
||||
@ -862,6 +966,11 @@ func (o *Config) SetDefaults() {
|
||||
*o.SamlSettings.NicknameAttribute = ""
|
||||
}
|
||||
|
||||
if o.SamlSettings.PositionAttribute == nil {
|
||||
o.SamlSettings.PositionAttribute = new(string)
|
||||
*o.SamlSettings.PositionAttribute = ""
|
||||
}
|
||||
|
||||
if o.SamlSettings.LocaleAttribute == nil {
|
||||
o.SamlSettings.LocaleAttribute = new(string)
|
||||
*o.SamlSettings.LocaleAttribute = ""
|
||||
@ -881,6 +990,63 @@ func (o *Config) SetDefaults() {
|
||||
o.NativeAppSettings.IosAppDownloadLink = new(string)
|
||||
*o.NativeAppSettings.IosAppDownloadLink = "https://about.mattermost.com/mattermost-ios-app/"
|
||||
}
|
||||
|
||||
if o.RateLimitSettings.Enable == nil {
|
||||
o.RateLimitSettings.Enable = new(bool)
|
||||
*o.RateLimitSettings.Enable = false
|
||||
}
|
||||
|
||||
if o.RateLimitSettings.MaxBurst == nil {
|
||||
o.RateLimitSettings.MaxBurst = new(int)
|
||||
*o.RateLimitSettings.MaxBurst = 100
|
||||
}
|
||||
|
||||
if o.ServiceSettings.ConnectionSecurity == nil {
|
||||
o.ServiceSettings.ConnectionSecurity = new(string)
|
||||
*o.ServiceSettings.ConnectionSecurity = ""
|
||||
}
|
||||
|
||||
if o.ServiceSettings.TLSKeyFile == nil {
|
||||
o.ServiceSettings.TLSKeyFile = new(string)
|
||||
*o.ServiceSettings.TLSKeyFile = ""
|
||||
}
|
||||
|
||||
if o.ServiceSettings.TLSCertFile == nil {
|
||||
o.ServiceSettings.TLSCertFile = new(string)
|
||||
*o.ServiceSettings.TLSCertFile = ""
|
||||
}
|
||||
|
||||
if o.ServiceSettings.UseLetsEncrypt == nil {
|
||||
o.ServiceSettings.UseLetsEncrypt = new(bool)
|
||||
*o.ServiceSettings.UseLetsEncrypt = false
|
||||
}
|
||||
|
||||
if o.ServiceSettings.LetsEncryptCertificateCacheFile == nil {
|
||||
o.ServiceSettings.LetsEncryptCertificateCacheFile = new(string)
|
||||
*o.ServiceSettings.LetsEncryptCertificateCacheFile = "./config/letsencrypt.cache"
|
||||
}
|
||||
|
||||
if o.ServiceSettings.ReadTimeout == nil {
|
||||
o.ServiceSettings.ReadTimeout = new(int)
|
||||
*o.ServiceSettings.ReadTimeout = 300
|
||||
}
|
||||
|
||||
if o.ServiceSettings.WriteTimeout == nil {
|
||||
o.ServiceSettings.WriteTimeout = new(int)
|
||||
*o.ServiceSettings.WriteTimeout = 300
|
||||
}
|
||||
|
||||
if o.ServiceSettings.Forward80To443 == nil {
|
||||
o.ServiceSettings.Forward80To443 = new(bool)
|
||||
*o.ServiceSettings.Forward80To443 = false
|
||||
}
|
||||
|
||||
if o.MetricsSettings.BlockProfileRate == nil {
|
||||
o.MetricsSettings.BlockProfileRate = new(int)
|
||||
*o.MetricsSettings.BlockProfileRate = 0
|
||||
}
|
||||
|
||||
o.defaultWebrtcSettings()
|
||||
}
|
||||
|
||||
func (o *Config) IsValid() *AppError {
|
||||
@ -911,6 +1077,14 @@ func (o *Config) IsValid() *AppError {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.max_users.app_error", nil, "")
|
||||
}
|
||||
|
||||
if *o.TeamSettings.MaxChannelsPerTeam <= 0 {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.max_channels.app_error", nil, "")
|
||||
}
|
||||
|
||||
if *o.TeamSettings.MaxNotificationsPerChannel <= 0 {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.max_notify_per_channel.app_error", nil, "")
|
||||
}
|
||||
|
||||
if !(*o.TeamSettings.RestrictDirectMessage == DIRECT_MESSAGE_ANY || *o.TeamSettings.RestrictDirectMessage == DIRECT_MESSAGE_TEAM) {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.restrict_direct_message.app_error", nil, "")
|
||||
}
|
||||
@ -1083,6 +1257,26 @@ func (o *Config) IsValid() *AppError {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.sitename_length.app_error", map[string]interface{}{"MaxLength": SITENAME_MAX_LENGTH}, "")
|
||||
}
|
||||
|
||||
if *o.RateLimitSettings.MaxBurst <= 0 {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.max_burst.app_error", nil, "")
|
||||
}
|
||||
|
||||
if err := o.isValidWebrtcSettings(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !(*o.ServiceSettings.ConnectionSecurity == CONN_SECURITY_NONE || *o.ServiceSettings.ConnectionSecurity == CONN_SECURITY_TLS) {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.webserver_security.app_error", nil, "")
|
||||
}
|
||||
|
||||
if *o.ServiceSettings.ReadTimeout <= 0 {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.read_timeout.app_error", nil, "")
|
||||
}
|
||||
|
||||
if *o.ServiceSettings.WriteTimeout <= 0 {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.write_timeout.app_error", nil, "")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1121,3 +1315,71 @@ func (o *Config) Sanitize() {
|
||||
o.SqlSettings.DataSourceReplicas[i] = FAKE_SETTING
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Config) defaultWebrtcSettings() {
|
||||
if o.WebrtcSettings.Enable == nil {
|
||||
o.WebrtcSettings.Enable = new(bool)
|
||||
*o.WebrtcSettings.Enable = false
|
||||
}
|
||||
|
||||
if o.WebrtcSettings.GatewayWebsocketUrl == nil {
|
||||
o.WebrtcSettings.GatewayWebsocketUrl = new(string)
|
||||
*o.WebrtcSettings.GatewayWebsocketUrl = ""
|
||||
}
|
||||
|
||||
if o.WebrtcSettings.GatewayAdminUrl == nil {
|
||||
o.WebrtcSettings.GatewayAdminUrl = new(string)
|
||||
*o.WebrtcSettings.GatewayAdminUrl = ""
|
||||
}
|
||||
|
||||
if o.WebrtcSettings.GatewayAdminSecret == nil {
|
||||
o.WebrtcSettings.GatewayAdminSecret = new(string)
|
||||
*o.WebrtcSettings.GatewayAdminSecret = ""
|
||||
}
|
||||
|
||||
if o.WebrtcSettings.StunURI == nil {
|
||||
o.WebrtcSettings.StunURI = new(string)
|
||||
*o.WebrtcSettings.StunURI = ""
|
||||
}
|
||||
|
||||
if o.WebrtcSettings.TurnURI == nil {
|
||||
o.WebrtcSettings.TurnURI = new(string)
|
||||
*o.WebrtcSettings.TurnURI = ""
|
||||
}
|
||||
|
||||
if o.WebrtcSettings.TurnUsername == nil {
|
||||
o.WebrtcSettings.TurnUsername = new(string)
|
||||
*o.WebrtcSettings.TurnUsername = ""
|
||||
}
|
||||
|
||||
if o.WebrtcSettings.TurnSharedKey == nil {
|
||||
o.WebrtcSettings.TurnSharedKey = new(string)
|
||||
*o.WebrtcSettings.TurnSharedKey = ""
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Config) isValidWebrtcSettings() *AppError {
|
||||
if *o.WebrtcSettings.Enable {
|
||||
if len(*o.WebrtcSettings.GatewayWebsocketUrl) == 0 || !IsValidWebsocketUrl(*o.WebrtcSettings.GatewayWebsocketUrl) {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_ws_url.app_error", nil, "")
|
||||
} else if len(*o.WebrtcSettings.GatewayAdminUrl) == 0 || !IsValidHttpUrl(*o.WebrtcSettings.GatewayAdminUrl) {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_url.app_error", nil, "")
|
||||
} else if len(*o.WebrtcSettings.GatewayAdminSecret) == 0 {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_gateway_admin_secret.app_error", nil, "")
|
||||
} else if len(*o.WebrtcSettings.StunURI) != 0 && !IsValidTurnOrStunServer(*o.WebrtcSettings.StunURI) {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_stun_uri.app_error", nil, "")
|
||||
} else if len(*o.WebrtcSettings.TurnURI) != 0 {
|
||||
if !IsValidTurnOrStunServer(*o.WebrtcSettings.TurnURI) {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_uri.app_error", nil, "")
|
||||
}
|
||||
if len(*o.WebrtcSettings.TurnUsername) == 0 {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_username.app_error", nil, "")
|
||||
} else if len(*o.WebrtcSettings.TurnSharedKey) == 0 {
|
||||
return NewLocAppError("Config.IsValid", "model.config.is_valid.webrtc_turn_shared_key.app_error", nil, "")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
4
vendor/github.com/mattermost/platform/model/file.go
generated
vendored
4
vendor/github.com/mattermost/platform/model/file.go
generated
vendored
@ -14,8 +14,8 @@ var (
|
||||
)
|
||||
|
||||
type FileUploadResponse struct {
|
||||
Filenames []string `json:"filenames"`
|
||||
ClientIds []string `json:"client_ids"`
|
||||
FileInfos []*FileInfo `json:"file_infos"`
|
||||
ClientIds []string `json:"client_ids"`
|
||||
}
|
||||
|
||||
func FileUploadResponseFromJson(data io.Reader) *FileUploadResponse {
|
||||
|
175
vendor/github.com/mattermost/platform/model/file_info.go
generated
vendored
175
vendor/github.com/mattermost/platform/model/file_info.go
generated
vendored
@ -6,54 +6,31 @@ package model
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"image"
|
||||
"image/gif"
|
||||
"io"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type FileInfo struct {
|
||||
Filename string `json:"filename"`
|
||||
Size int `json:"size"`
|
||||
Id string `json:"id"`
|
||||
CreatorId string `json:"user_id"`
|
||||
PostId string `json:"post_id,omitempty"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
Path string `json:"-"` // not sent back to the client
|
||||
ThumbnailPath string `json:"-"` // not sent back to the client
|
||||
PreviewPath string `json:"-"` // not sent back to the client
|
||||
Name string `json:"name"`
|
||||
Extension string `json:"extension"`
|
||||
Size int64 `json:"size"`
|
||||
MimeType string `json:"mime_type"`
|
||||
HasPreviewImage bool `json:"has_preview_image"`
|
||||
}
|
||||
|
||||
func GetInfoForBytes(filename string, data []byte) (*FileInfo, *AppError) {
|
||||
size := len(data)
|
||||
|
||||
var mimeType string
|
||||
extension := filepath.Ext(filename)
|
||||
isImage := IsFileExtImage(extension)
|
||||
if isImage {
|
||||
mimeType = GetImageMimeType(extension)
|
||||
} else {
|
||||
mimeType = mime.TypeByExtension(extension)
|
||||
}
|
||||
|
||||
if extension != "" && extension[0] == '.' {
|
||||
// the client expects a file extension without the leading period
|
||||
extension = extension[1:]
|
||||
}
|
||||
|
||||
hasPreviewImage := isImage
|
||||
if mimeType == "image/gif" {
|
||||
// just show the gif itself instead of a preview image for animated gifs
|
||||
if gifImage, err := gif.DecodeAll(bytes.NewReader(data)); err != nil {
|
||||
return nil, NewLocAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "filename="+filename)
|
||||
} else {
|
||||
hasPreviewImage = len(gifImage.Image) == 1
|
||||
}
|
||||
}
|
||||
|
||||
return &FileInfo{
|
||||
Filename: filename,
|
||||
Size: size,
|
||||
Extension: extension,
|
||||
MimeType: mimeType,
|
||||
HasPreviewImage: hasPreviewImage,
|
||||
}, nil
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
HasPreviewImage bool `json:"has_preview_image,omitempty"`
|
||||
}
|
||||
|
||||
func (info *FileInfo) ToJson() string {
|
||||
@ -75,3 +52,123 @@ func FileInfoFromJson(data io.Reader) *FileInfo {
|
||||
return &info
|
||||
}
|
||||
}
|
||||
|
||||
func FileInfosToJson(infos []*FileInfo) string {
|
||||
b, err := json.Marshal(infos)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func FileInfosFromJson(data io.Reader) []*FileInfo {
|
||||
decoder := json.NewDecoder(data)
|
||||
|
||||
var infos []*FileInfo
|
||||
if err := decoder.Decode(&infos); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
return infos
|
||||
}
|
||||
}
|
||||
|
||||
func (o *FileInfo) PreSave() {
|
||||
if o.Id == "" {
|
||||
o.Id = NewId()
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
o.CreateAt = GetMillis()
|
||||
o.UpdateAt = o.CreateAt
|
||||
}
|
||||
}
|
||||
|
||||
func (o *FileInfo) IsValid() *AppError {
|
||||
if len(o.Id) != 26 {
|
||||
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if len(o.CreatorId) != 26 {
|
||||
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if len(o.PostId) != 0 && len(o.PostId) != 26 {
|
||||
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.post_id.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.create_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if o.UpdateAt == 0 {
|
||||
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.update_at.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if o.Path == "" {
|
||||
return NewLocAppError("FileInfo.IsValid", "model.file_info.is_valid.path.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *FileInfo) IsImage() bool {
|
||||
return strings.HasPrefix(o.MimeType, "image")
|
||||
}
|
||||
|
||||
func GetInfoForBytes(name string, data []byte) (*FileInfo, *AppError) {
|
||||
info := &FileInfo{
|
||||
Name: name,
|
||||
Size: int64(len(data)),
|
||||
}
|
||||
var err *AppError
|
||||
|
||||
extension := strings.ToLower(filepath.Ext(name))
|
||||
info.MimeType = mime.TypeByExtension(extension)
|
||||
|
||||
if extension != "" && extension[0] == '.' {
|
||||
// The client expects a file extension without the leading period
|
||||
info.Extension = extension[1:]
|
||||
} else {
|
||||
info.Extension = extension
|
||||
}
|
||||
|
||||
if info.IsImage() {
|
||||
// Only set the width and height if it's actually an image that we can understand
|
||||
if config, _, err := image.DecodeConfig(bytes.NewReader(data)); err == nil {
|
||||
info.Width = config.Width
|
||||
info.Height = config.Height
|
||||
|
||||
if info.MimeType == "image/gif" {
|
||||
// Just show the gif itself instead of a preview image for animated gifs
|
||||
if gifConfig, err := gif.DecodeAll(bytes.NewReader(data)); err != nil {
|
||||
// Still return the rest of the info even though it doesn't appear to be an actual gif
|
||||
info.HasPreviewImage = true
|
||||
err = NewLocAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "name="+name)
|
||||
} else {
|
||||
info.HasPreviewImage = len(gifConfig.Image) == 1
|
||||
}
|
||||
} else {
|
||||
info.HasPreviewImage = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info, err
|
||||
}
|
||||
|
||||
func GetEtagForFileInfos(infos []*FileInfo) string {
|
||||
if len(infos) == 0 {
|
||||
return Etag()
|
||||
}
|
||||
|
||||
var maxUpdateAt int64
|
||||
|
||||
for _, info := range infos {
|
||||
if info.UpdateAt > maxUpdateAt {
|
||||
maxUpdateAt = info.UpdateAt
|
||||
}
|
||||
}
|
||||
|
||||
return Etag(infos[0].PostId, maxUpdateAt)
|
||||
}
|
||||
|
3
vendor/github.com/mattermost/platform/model/incoming_webhook.go
generated
vendored
3
vendor/github.com/mattermost/platform/model/incoming_webhook.go
generated
vendored
@ -6,6 +6,7 @@ package model
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -233,7 +234,7 @@ func expandAnnouncements(i *IncomingWebhookRequest) {
|
||||
for _, field := range fields {
|
||||
f := field.(map[string]interface{})
|
||||
if f["value"] != nil {
|
||||
f["value"] = expandAnnouncement(f["value"].(string))
|
||||
f["value"] = expandAnnouncement(fmt.Sprintf("%v", f["value"]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
vendor/github.com/mattermost/platform/model/initial_load.go
generated
vendored
15
vendor/github.com/mattermost/platform/model/initial_load.go
generated
vendored
@ -9,14 +9,13 @@ import (
|
||||
)
|
||||
|
||||
type InitialLoad struct {
|
||||
User *User `json:"user"`
|
||||
TeamMembers []*TeamMember `json:"team_members"`
|
||||
Teams []*Team `json:"teams"`
|
||||
DirectProfiles map[string]*User `json:"direct_profiles"`
|
||||
Preferences Preferences `json:"preferences"`
|
||||
ClientCfg map[string]string `json:"client_cfg"`
|
||||
LicenseCfg map[string]string `json:"license_cfg"`
|
||||
NoAccounts bool `json:"no_accounts"`
|
||||
User *User `json:"user"`
|
||||
TeamMembers []*TeamMember `json:"team_members"`
|
||||
Teams []*Team `json:"teams"`
|
||||
Preferences Preferences `json:"preferences"`
|
||||
ClientCfg map[string]string `json:"client_cfg"`
|
||||
LicenseCfg map[string]string `json:"license_cfg"`
|
||||
NoAccounts bool `json:"no_accounts"`
|
||||
}
|
||||
|
||||
func (me *InitialLoad) ToJson() string {
|
||||
|
10
vendor/github.com/mattermost/platform/model/license.go
generated
vendored
10
vendor/github.com/mattermost/platform/model/license.go
generated
vendored
@ -39,11 +39,13 @@ type Features struct {
|
||||
Office365OAuth *bool `json:"office365_oauth"`
|
||||
Compliance *bool `json:"compliance"`
|
||||
Cluster *bool `json:"cluster"`
|
||||
Metrics *bool `json:"metrics"`
|
||||
CustomBrand *bool `json:"custom_brand"`
|
||||
MHPNS *bool `json:"mhpns"`
|
||||
SAML *bool `json:"saml"`
|
||||
PasswordRequirements *bool `json:"password_requirements"`
|
||||
FutureFeatures *bool `json:"future_features"`
|
||||
// after we enabled more features for webrtc we'll need to control them with this
|
||||
FutureFeatures *bool `json:"future_features"`
|
||||
}
|
||||
|
||||
func (f *Features) ToMap() map[string]interface{} {
|
||||
@ -54,6 +56,7 @@ func (f *Features) ToMap() map[string]interface{} {
|
||||
"office365": *f.Office365OAuth,
|
||||
"compliance": *f.Compliance,
|
||||
"cluster": *f.Cluster,
|
||||
"metrics": *f.Metrics,
|
||||
"custom_brand": *f.CustomBrand,
|
||||
"mhpns": *f.MHPNS,
|
||||
"saml": *f.SAML,
|
||||
@ -103,6 +106,11 @@ func (f *Features) SetDefaults() {
|
||||
*f.Cluster = *f.FutureFeatures
|
||||
}
|
||||
|
||||
if f.Metrics == nil {
|
||||
f.Metrics = new(bool)
|
||||
*f.Metrics = *f.FutureFeatures
|
||||
}
|
||||
|
||||
if f.CustomBrand == nil {
|
||||
f.CustomBrand = new(bool)
|
||||
*f.CustomBrand = *f.FutureFeatures
|
||||
|
33
vendor/github.com/mattermost/platform/model/post.go
generated
vendored
33
vendor/github.com/mattermost/platform/model/post.go
generated
vendored
@ -17,8 +17,14 @@ const (
|
||||
POST_JOIN_LEAVE = "system_join_leave"
|
||||
POST_ADD_REMOVE = "system_add_remove"
|
||||
POST_HEADER_CHANGE = "system_header_change"
|
||||
POST_DISPLAYNAME_CHANGE = "system_displayname_change"
|
||||
POST_CHANNEL_DELETED = "system_channel_deleted"
|
||||
POST_EPHEMERAL = "system_ephemeral"
|
||||
POST_FILEIDS_MAX_RUNES = 150
|
||||
POST_FILENAMES_MAX_RUNES = 4000
|
||||
POST_HASHTAGS_MAX_RUNES = 1000
|
||||
POST_MESSAGE_MAX_RUNES = 4000
|
||||
POST_PROPS_MAX_RUNES = 8000
|
||||
)
|
||||
|
||||
type Post struct {
|
||||
@ -35,8 +41,10 @@ type Post struct {
|
||||
Type string `json:"type"`
|
||||
Props StringInterface `json:"props"`
|
||||
Hashtags string `json:"hashtags"`
|
||||
Filenames StringArray `json:"filenames"`
|
||||
Filenames StringArray `json:"filenames,omitempty"` // Deprecated, do not use this field any more
|
||||
FileIds StringArray `json:"file_ids,omitempty"`
|
||||
PendingPostId string `json:"pending_post_id" db:"-"`
|
||||
HasReactions bool `json:"has_reactions,omitempty"`
|
||||
}
|
||||
|
||||
func (o *Post) ToJson() string {
|
||||
@ -101,24 +109,30 @@ func (o *Post) IsValid() *AppError {
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.original_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.Message) > 4000 {
|
||||
if utf8.RuneCountInString(o.Message) > POST_MESSAGE_MAX_RUNES {
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.msg.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.Hashtags) > 1000 {
|
||||
if utf8.RuneCountInString(o.Hashtags) > POST_HASHTAGS_MAX_RUNES {
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.hashtags.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
// should be removed once more message types are supported
|
||||
if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_ADD_REMOVE || o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE) {
|
||||
if !(o.Type == POST_DEFAULT || o.Type == POST_JOIN_LEAVE || o.Type == POST_ADD_REMOVE ||
|
||||
o.Type == POST_SLACK_ATTACHMENT || o.Type == POST_HEADER_CHANGE ||
|
||||
o.Type == POST_DISPLAYNAME_CHANGE || o.Type == POST_CHANNEL_DELETED) {
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.type.app_error", nil, "id="+o.Type)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(ArrayToJson(o.Filenames)) > 4000 {
|
||||
if utf8.RuneCountInString(ArrayToJson(o.Filenames)) > POST_FILENAMES_MAX_RUNES {
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.filenames.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(StringInterfaceToJson(o.Props)) > 8000 {
|
||||
if utf8.RuneCountInString(ArrayToJson(o.FileIds)) > POST_FILEIDS_MAX_RUNES {
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.file_ids.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(StringInterfaceToJson(o.Props)) > POST_PROPS_MAX_RUNES {
|
||||
return NewLocAppError("Post.IsValid", "model.post.is_valid.props.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
@ -145,15 +159,16 @@ func (o *Post) PreSave() {
|
||||
if o.Filenames == nil {
|
||||
o.Filenames = []string{}
|
||||
}
|
||||
|
||||
if o.FileIds == nil {
|
||||
o.FileIds = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Post) MakeNonNil() {
|
||||
if o.Props == nil {
|
||||
o.Props = make(map[string]interface{})
|
||||
}
|
||||
if o.Filenames == nil {
|
||||
o.Filenames = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Post) AddProp(key string, value interface{}) {
|
||||
|
1
vendor/github.com/mattermost/platform/model/preference.go
generated
vendored
1
vendor/github.com/mattermost/platform/model/preference.go
generated
vendored
@ -33,6 +33,7 @@ const (
|
||||
|
||||
PREFERENCE_CATEGORY_LAST = "last"
|
||||
PREFERENCE_NAME_LAST_CHANNEL = "channel"
|
||||
PREFERENCE_NAME_LAST_TEAM = "team"
|
||||
|
||||
PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications"
|
||||
PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval"
|
||||
|
1
vendor/github.com/mattermost/platform/model/push_notification.go
generated
vendored
1
vendor/github.com/mattermost/platform/model/push_notification.go
generated
vendored
@ -30,6 +30,7 @@ type PushNotification struct {
|
||||
Message string `json:"message"`
|
||||
Badge int `json:"badge"`
|
||||
ContentAvailable int `json:"cont_ava"`
|
||||
TeamId string `json:"team_id"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
ChannelName string `json:"channel_name"`
|
||||
Type string `json:"type"`
|
||||
|
78
vendor/github.com/mattermost/platform/model/reaction.go
generated
vendored
Normal file
78
vendor/github.com/mattermost/platform/model/reaction.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Reaction struct {
|
||||
UserId string `json:"user_id"`
|
||||
PostId string `json:"post_id"`
|
||||
EmojiName string `json:"emoji_name"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
}
|
||||
|
||||
func (o *Reaction) ToJson() string {
|
||||
if b, err := json.Marshal(o); err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func ReactionFromJson(data io.Reader) *Reaction {
|
||||
var o Reaction
|
||||
|
||||
if err := json.NewDecoder(data).Decode(&o); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
return &o
|
||||
}
|
||||
}
|
||||
|
||||
func ReactionsToJson(o []*Reaction) string {
|
||||
if b, err := json.Marshal(o); err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func ReactionsFromJson(data io.Reader) []*Reaction {
|
||||
var o []*Reaction
|
||||
|
||||
if err := json.NewDecoder(data).Decode(&o); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
return o
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Reaction) IsValid() *AppError {
|
||||
if len(o.UserId) != 26 {
|
||||
return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.user_id.app_error", nil, "user_id="+o.UserId)
|
||||
}
|
||||
|
||||
if len(o.PostId) != 26 {
|
||||
return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.post_id.app_error", nil, "post_id="+o.PostId)
|
||||
}
|
||||
|
||||
if len(o.EmojiName) == 0 || len(o.EmojiName) > 64 {
|
||||
return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.emoji_name.app_error", nil, "emoji_name="+o.EmojiName)
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.create_at.app_error", nil, "")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Reaction) PreSave() {
|
||||
if o.CreateAt == 0 {
|
||||
o.CreateAt = GetMillis()
|
||||
}
|
||||
}
|
6
vendor/github.com/mattermost/platform/model/session.go
generated
vendored
6
vendor/github.com/mattermost/platform/model/session.go
generated
vendored
@ -11,7 +11,7 @@ import (
|
||||
|
||||
const (
|
||||
SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
|
||||
SESSION_CACHE_SIZE = 10000
|
||||
SESSION_CACHE_SIZE = 25000
|
||||
SESSION_PROP_PLATFORM = "platform"
|
||||
SESSION_PROP_OS = "os"
|
||||
SESSION_PROP_BROWSER = "browser"
|
||||
@ -115,6 +115,10 @@ func (me *Session) IsMobileApp() bool {
|
||||
(strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_APPLE+":") || strings.HasPrefix(me.DeviceId, PUSH_NOTIFY_ANDROID+":"))
|
||||
}
|
||||
|
||||
func (me *Session) GetUserRoles() []string {
|
||||
return strings.Fields(me.Roles)
|
||||
}
|
||||
|
||||
func SessionsToJson(o []*Session) string {
|
||||
if b, err := json.Marshal(o); err != nil {
|
||||
return "[]"
|
||||
|
18
vendor/github.com/mattermost/platform/model/status.go
generated
vendored
18
vendor/github.com/mattermost/platform/model/status.go
generated
vendored
@ -12,8 +12,9 @@ const (
|
||||
STATUS_OFFLINE = "offline"
|
||||
STATUS_AWAY = "away"
|
||||
STATUS_ONLINE = "online"
|
||||
STATUS_CACHE_SIZE = 10000
|
||||
STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds
|
||||
STATUS_CACHE_SIZE = 25000
|
||||
STATUS_CHANNEL_TIMEOUT = 20000 // 20 seconds
|
||||
STATUS_MIN_UPDATE_TIME = 120000 // 2 minutes
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
@ -21,7 +22,7 @@ type Status struct {
|
||||
Status string `json:"status"`
|
||||
Manual bool `json:"manual"`
|
||||
LastActivityAt int64 `json:"last_activity_at"`
|
||||
ActiveChannel string `json:"active_channel"`
|
||||
ActiveChannel string `json:"active_channel" db:"-"`
|
||||
}
|
||||
|
||||
func (o *Status) ToJson() string {
|
||||
@ -43,3 +44,14 @@ func StatusFromJson(data io.Reader) *Status {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func StatusMapToInterfaceMap(statusMap map[string]*Status) map[string]interface{} {
|
||||
interfaceMap := map[string]interface{}{}
|
||||
for _, s := range statusMap {
|
||||
// Omitted statues mean offline
|
||||
if s.Status != STATUS_OFFLINE {
|
||||
interfaceMap[s.UserId] = s.Status
|
||||
}
|
||||
}
|
||||
return interfaceMap
|
||||
}
|
||||
|
11
vendor/github.com/mattermost/platform/model/team.go
generated
vendored
11
vendor/github.com/mattermost/platform/model/team.go
generated
vendored
@ -24,6 +24,7 @@ type Team struct {
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Email string `json:"email"`
|
||||
Type string `json:"type"`
|
||||
CompanyName string `json:"company_name"`
|
||||
@ -100,7 +101,7 @@ func (o *Team) Etag() string {
|
||||
return Etag(o.Id, o.UpdateAt)
|
||||
}
|
||||
|
||||
func (o *Team) IsValid(restrictTeamNames bool) *AppError {
|
||||
func (o *Team) IsValid() *AppError {
|
||||
|
||||
if len(o.Id) != 26 {
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.id.app_error", nil, "")
|
||||
@ -130,7 +131,11 @@ func (o *Team) IsValid(restrictTeamNames bool) *AppError {
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.url.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if restrictTeamNames && IsReservedTeamName(o.Name) {
|
||||
if len(o.Description) > 255 {
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.description.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
if IsReservedTeamName(o.Name) {
|
||||
return NewLocAppError("Team.IsValid", "model.team.is_valid.reserved.app_error", nil, "id="+o.Id)
|
||||
}
|
||||
|
||||
@ -188,7 +193,7 @@ func IsValidTeamName(s string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(s) <= 3 {
|
||||
if len(s) <= 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
|
72
vendor/github.com/mattermost/platform/model/team_member.go
generated
vendored
72
vendor/github.com/mattermost/platform/model/team_member.go
generated
vendored
@ -9,10 +9,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ROLE_TEAM_ADMIN = "admin"
|
||||
)
|
||||
|
||||
type TeamMember struct {
|
||||
TeamId string `json:"team_id"`
|
||||
UserId string `json:"user_id"`
|
||||
@ -20,6 +16,12 @@ type TeamMember struct {
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
}
|
||||
|
||||
type TeamUnread struct {
|
||||
TeamId string `json:"team_id"`
|
||||
MsgCount int64 `json:"msg_count"`
|
||||
MentionCount int64 `json:"mention_count"`
|
||||
}
|
||||
|
||||
func (o *TeamMember) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
@ -59,46 +61,23 @@ func TeamMembersFromJson(data io.Reader) []*TeamMember {
|
||||
}
|
||||
}
|
||||
|
||||
func IsValidTeamRoles(teamRoles string) bool {
|
||||
|
||||
roles := strings.Split(teamRoles, " ")
|
||||
|
||||
for _, r := range roles {
|
||||
if !isValidTeamRole(r) {
|
||||
return false
|
||||
}
|
||||
func TeamsUnreadToJson(o []*TeamUnread) string {
|
||||
if b, err := json.Marshal(o); err != nil {
|
||||
return "[]"
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isValidTeamRole(role string) bool {
|
||||
if role == "" {
|
||||
return true
|
||||
func TeamsUnreadFromJson(data io.Reader) []*TeamUnread {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o []*TeamUnread
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if role == ROLE_TEAM_ADMIN {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IsInTeamRole(teamRoles string, inRole string) bool {
|
||||
roles := strings.Split(teamRoles, " ")
|
||||
|
||||
for _, r := range roles {
|
||||
if r == inRole {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *TeamMember) IsTeamAdmin() bool {
|
||||
return IsInTeamRole(o.Roles, ROLE_TEAM_ADMIN)
|
||||
}
|
||||
|
||||
func (o *TeamMember) IsValid() *AppError {
|
||||
@ -111,11 +90,12 @@ func (o *TeamMember) IsValid() *AppError {
|
||||
return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.user_id.app_error", nil, "")
|
||||
}
|
||||
|
||||
for _, role := range strings.Split(o.Roles, " ") {
|
||||
if !(role == "" || role == ROLE_TEAM_ADMIN) {
|
||||
return NewLocAppError("TeamMember.IsValid", "model.team_member.is_valid.role.app_error", nil, "role="+role)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *TeamMember) PreUpdate() {
|
||||
}
|
||||
|
||||
func (o *TeamMember) GetRoles() []string {
|
||||
return strings.Fields(o.Roles)
|
||||
}
|
||||
|
35
vendor/github.com/mattermost/platform/model/team_stats.go
generated
vendored
Normal file
35
vendor/github.com/mattermost/platform/model/team_stats.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type TeamStats struct {
|
||||
TeamId string `json:"team_id"`
|
||||
TotalMemberCount int64 `json:"total_member_count"`
|
||||
ActiveMemberCount int64 `json:"active_member_count"`
|
||||
}
|
||||
|
||||
func (o *TeamStats) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func TeamStatsFromJson(data io.Reader) *TeamStats {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o TeamStats
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
59
vendor/github.com/mattermost/platform/model/user.go
generated
vendored
59
vendor/github.com/mattermost/platform/model/user.go
generated
vendored
@ -15,7 +15,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ROLE_SYSTEM_ADMIN = "system_admin"
|
||||
USER_NOTIFY_ALL = "all"
|
||||
USER_NOTIFY_MENTION = "mention"
|
||||
USER_NOTIFY_NONE = "none"
|
||||
@ -38,6 +37,7 @@ type User struct {
|
||||
Nickname string `json:"nickname"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Position string `json:"position"`
|
||||
Roles string `json:"roles"`
|
||||
AllowMarketing bool `json:"allow_marketing,omitempty"`
|
||||
Props StringMap `json:"props,omitempty"`
|
||||
@ -79,6 +79,10 @@ func (u *User) IsValid() *AppError {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.nickname.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(u.Position) > 35 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.position.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(u.FirstName) > 64 {
|
||||
return NewLocAppError("User.IsValid", "model.user.is_valid.first_name.app_error", nil, "user_id="+u.Id)
|
||||
}
|
||||
@ -233,14 +237,15 @@ func (u *User) Sanitize(options map[string]bool) {
|
||||
if len(options) != 0 && !options["passwordupdate"] {
|
||||
u.LastPasswordUpdate = 0
|
||||
}
|
||||
if len(options) != 0 && !options["authservice"] {
|
||||
u.AuthService = ""
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) ClearNonProfileFields() {
|
||||
u.Password = ""
|
||||
u.AuthData = new(string)
|
||||
*u.AuthData = ""
|
||||
u.AuthService = ""
|
||||
u.MfaActive = false
|
||||
u.MfaSecret = ""
|
||||
u.EmailVerified = false
|
||||
u.AllowMarketing = false
|
||||
@ -319,9 +324,17 @@ func (u *User) GetDisplayNameForPreference(nameFormat string) string {
|
||||
return displayName
|
||||
}
|
||||
|
||||
func (u *User) GetRoles() []string {
|
||||
return strings.Fields(u.Roles)
|
||||
}
|
||||
|
||||
func (u *User) GetRawRoles() string {
|
||||
return u.Roles
|
||||
}
|
||||
|
||||
func IsValidUserRoles(userRoles string) bool {
|
||||
|
||||
roles := strings.Split(userRoles, " ")
|
||||
roles := strings.Fields(userRoles)
|
||||
|
||||
for _, r := range roles {
|
||||
if !isValidRole(r) {
|
||||
@ -329,19 +342,17 @@ func IsValidUserRoles(userRoles string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude just the system_admin role explicitly to prevent mistakes
|
||||
if len(roles) == 1 && roles[0] == "system_admin" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func isValidRole(role string) bool {
|
||||
if role == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
if role == ROLE_SYSTEM_ADMIN {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
func isValidRole(roleId string) bool {
|
||||
_, ok := BuiltInRoles[roleId]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Make sure you acually want to use this function. In context.go there are functions to check permissions
|
||||
@ -411,6 +422,26 @@ func UserMapFromJson(data io.Reader) map[string]*User {
|
||||
}
|
||||
}
|
||||
|
||||
func UserListToJson(u []*User) string {
|
||||
b, err := json.Marshal(u)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func UserListFromJson(data io.Reader) []*User {
|
||||
decoder := json.NewDecoder(data)
|
||||
var users []*User
|
||||
err := decoder.Decode(&users)
|
||||
if err == nil {
|
||||
return users
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// HashPassword generates a hash using the bcrypt.GenerateFromPassword
|
||||
func HashPassword(password string) string {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
|
||||
|
58
vendor/github.com/mattermost/platform/model/user_autocomplete.go
generated
vendored
Normal file
58
vendor/github.com/mattermost/platform/model/user_autocomplete.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type UserAutocompleteInChannel struct {
|
||||
InChannel []*User `json:"in_channel"`
|
||||
OutOfChannel []*User `json:"out_of_channel"`
|
||||
}
|
||||
|
||||
type UserAutocompleteInTeam struct {
|
||||
InTeam []*User `json:"in_team"`
|
||||
}
|
||||
|
||||
func (o *UserAutocompleteInChannel) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func UserAutocompleteInChannelFromJson(data io.Reader) *UserAutocompleteInChannel {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o UserAutocompleteInChannel
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (o *UserAutocompleteInTeam) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func UserAutocompleteInTeamFromJson(data io.Reader) *UserAutocompleteInTeam {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o UserAutocompleteInTeam
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
39
vendor/github.com/mattermost/platform/model/user_search.go
generated
vendored
Normal file
39
vendor/github.com/mattermost/platform/model/user_search.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type UserSearch struct {
|
||||
Term string `json:"term"`
|
||||
TeamId string `json:"team_id"`
|
||||
InChannelId string `json:"in_channel_id"`
|
||||
NotInChannelId string `json:"not_in_channel_id"`
|
||||
AllowInactive bool `json:"allow_inactive"`
|
||||
}
|
||||
|
||||
// ToJson convert a User to a json string
|
||||
func (u *UserSearch) ToJson() string {
|
||||
b, err := json.Marshal(u)
|
||||
if err != nil {
|
||||
return ""
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
// UserSearchFromJson will decode the input and return a User
|
||||
func UserSearchFromJson(data io.Reader) *UserSearch {
|
||||
decoder := json.NewDecoder(data)
|
||||
var us UserSearch
|
||||
err := decoder.Decode(&us)
|
||||
if err == nil {
|
||||
return &us
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
103
vendor/github.com/mattermost/platform/model/utils.go
generated
vendored
103
vendor/github.com/mattermost/platform/model/utils.go
generated
vendored
@ -10,6 +10,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"regexp"
|
||||
@ -74,13 +75,21 @@ func (er *AppError) ToJson() string {
|
||||
|
||||
// AppErrorFromJson will decode the input and return an AppError
|
||||
func AppErrorFromJson(data io.Reader) *AppError {
|
||||
decoder := json.NewDecoder(data)
|
||||
str := ""
|
||||
bytes, rerr := ioutil.ReadAll(data)
|
||||
if rerr != nil {
|
||||
str = rerr.Error()
|
||||
} else {
|
||||
str = string(bytes)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(strings.NewReader(str))
|
||||
var er AppError
|
||||
err := decoder.Decode(&er)
|
||||
if err == nil {
|
||||
return &er
|
||||
} else {
|
||||
return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, err.Error())
|
||||
return NewLocAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str)
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,6 +175,23 @@ func ArrayFromJson(data io.Reader) []string {
|
||||
}
|
||||
}
|
||||
|
||||
func ArrayFromInterface(data interface{}) []string {
|
||||
stringArray := []string{}
|
||||
|
||||
dataArray, ok := data.([]interface{})
|
||||
if !ok {
|
||||
return stringArray
|
||||
}
|
||||
|
||||
for _, v := range dataArray {
|
||||
if str, ok := v.(string); ok {
|
||||
stringArray = append(stringArray, str)
|
||||
}
|
||||
}
|
||||
|
||||
return stringArray
|
||||
}
|
||||
|
||||
func StringInterfaceToJson(objmap map[string]interface{}) string {
|
||||
if b, err := json.Marshal(objmap); err != nil {
|
||||
return ""
|
||||
@ -227,58 +253,15 @@ func IsValidEmail(email string) bool {
|
||||
}
|
||||
|
||||
var reservedName = []string{
|
||||
"www",
|
||||
"web",
|
||||
"signup",
|
||||
"login",
|
||||
"admin",
|
||||
"support",
|
||||
"notify",
|
||||
"test",
|
||||
"demo",
|
||||
"mail",
|
||||
"team",
|
||||
"channel",
|
||||
"internal",
|
||||
"localhost",
|
||||
"dockerhost",
|
||||
"stag",
|
||||
"post",
|
||||
"cluster",
|
||||
"api",
|
||||
"oauth",
|
||||
}
|
||||
|
||||
var wwwStart = regexp.MustCompile(`^www`)
|
||||
var betaStart = regexp.MustCompile(`^beta`)
|
||||
var ciStart = regexp.MustCompile(`^ci`)
|
||||
|
||||
func GetSubDomain(s string) (string, string) {
|
||||
s = strings.Replace(s, "http://", "", 1)
|
||||
s = strings.Replace(s, "https://", "", 1)
|
||||
|
||||
match := wwwStart.MatchString(s)
|
||||
if match {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
match = betaStart.MatchString(s)
|
||||
if match {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
match = ciStart.MatchString(s)
|
||||
if match {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
parts := strings.Split(s, ".")
|
||||
|
||||
if len(parts) != 3 {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
|
||||
func IsValidChannelIdentifier(s string) bool {
|
||||
|
||||
if !IsValidAlphaNum(s, true) {
|
||||
@ -321,7 +304,7 @@ func Etag(parts ...interface{}) string {
|
||||
return etag
|
||||
}
|
||||
|
||||
var validHashtag = regexp.MustCompile(`^(#[A-Za-zäöüÄÖÜß]+[A-Za-z0-9äöüÄÖÜß_\-]*[A-Za-z0-9äöüÄÖÜß])$`)
|
||||
var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`)
|
||||
var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`)
|
||||
var hashtagStart = regexp.MustCompile(`^#{2,}`)
|
||||
var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`)
|
||||
@ -413,6 +396,18 @@ func IsValidHttpsUrl(rawUrl string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func IsValidTurnOrStunServer(rawUri string) bool {
|
||||
if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := url.ParseRequestURI(rawUri); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func IsSafeLink(link *string) bool {
|
||||
if link != nil {
|
||||
if IsValidHttpUrl(*link) {
|
||||
@ -426,3 +421,15 @@ func IsSafeLink(link *string) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func IsValidWebsocketUrl(rawUrl string) bool {
|
||||
if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := url.ParseRequestURI(rawUrl); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
2
vendor/github.com/mattermost/platform/model/version.go
generated
vendored
2
vendor/github.com/mattermost/platform/model/version.go
generated
vendored
@ -13,6 +13,8 @@ import (
|
||||
// It should be maitained in chronological order with most current
|
||||
// release at the front of the list.
|
||||
var versions = []string{
|
||||
"3.6.0",
|
||||
"3.5.0",
|
||||
"3.4.0",
|
||||
"3.3.0",
|
||||
"3.2.0",
|
||||
|
21
vendor/github.com/mattermost/platform/model/webrtc.go
generated
vendored
Normal file
21
vendor/github.com/mattermost/platform/model/webrtc.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type GatewayResponse struct {
|
||||
Status string `json:"janus"`
|
||||
}
|
||||
|
||||
func GatewayResponseFromJson(data io.Reader) *GatewayResponse {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o GatewayResponse
|
||||
err := decoder.Decode(&o)
|
||||
if err == nil {
|
||||
return &o
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
45
vendor/github.com/mattermost/platform/model/websocket_client.go
generated
vendored
45
vendor/github.com/mattermost/platform/model/websocket_client.go
generated
vendored
@ -6,7 +6,6 @@ package model
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type WebSocketClient struct {
|
||||
@ -17,19 +16,18 @@ type WebSocketClient struct {
|
||||
Sequence int64 // The ever-incrementing sequence attached to each WebSocket action
|
||||
EventChannel chan *WebSocketEvent
|
||||
ResponseChannel chan *WebSocketResponse
|
||||
ListenError *AppError
|
||||
}
|
||||
|
||||
// NewWebSocketClient constructs a new WebSocket client with convienence
|
||||
// methods for talking to the server.
|
||||
func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) {
|
||||
header := http.Header{}
|
||||
header.Set(HEADER_AUTH, "BEARER "+authToken)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", header)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(url+API_URL_SUFFIX+"/users/websocket", nil)
|
||||
if err != nil {
|
||||
return nil, NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
return &WebSocketClient{
|
||||
client := &WebSocketClient{
|
||||
url,
|
||||
url + API_URL_SUFFIX,
|
||||
conn,
|
||||
@ -37,19 +35,26 @@ func NewWebSocketClient(url, authToken string) (*WebSocketClient, *AppError) {
|
||||
1,
|
||||
make(chan *WebSocketEvent, 100),
|
||||
make(chan *WebSocketResponse, 100),
|
||||
}, nil
|
||||
nil,
|
||||
}
|
||||
|
||||
client.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": authToken})
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (wsc *WebSocketClient) Connect() *AppError {
|
||||
header := http.Header{}
|
||||
header.Set(HEADER_AUTH, "BEARER "+wsc.AuthToken)
|
||||
|
||||
var err error
|
||||
wsc.Conn, _, err = websocket.DefaultDialer.Dial(wsc.ApiUrl+"/users/websocket", header)
|
||||
wsc.Conn, _, err = websocket.DefaultDialer.Dial(wsc.ApiUrl+"/users/websocket", nil)
|
||||
if err != nil {
|
||||
return NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
wsc.EventChannel = make(chan *WebSocketEvent, 100)
|
||||
wsc.ResponseChannel = make(chan *WebSocketResponse, 100)
|
||||
|
||||
wsc.SendMessage(WEBSOCKET_AUTHENTICATION_CHALLENGE, map[string]interface{}{"token": wsc.AuthToken})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -59,10 +64,20 @@ func (wsc *WebSocketClient) Close() {
|
||||
|
||||
func (wsc *WebSocketClient) Listen() {
|
||||
go func() {
|
||||
defer func() {
|
||||
wsc.Conn.Close()
|
||||
close(wsc.EventChannel)
|
||||
close(wsc.ResponseChannel)
|
||||
}()
|
||||
|
||||
for {
|
||||
var rawMsg json.RawMessage
|
||||
var err error
|
||||
if _, rawMsg, err = wsc.Conn.ReadMessage(); err != nil {
|
||||
if !websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
|
||||
wsc.ListenError = NewLocAppError("NewWebSocketClient", "model.websocket_client.connect_fail.app_error", nil, err.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -77,6 +92,7 @@ func (wsc *WebSocketClient) Listen() {
|
||||
wsc.ResponseChannel <- &response
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -107,3 +123,12 @@ func (wsc *WebSocketClient) UserTyping(channelId, parentId string) {
|
||||
func (wsc *WebSocketClient) GetStatuses() {
|
||||
wsc.SendMessage("get_statuses", nil)
|
||||
}
|
||||
|
||||
// GetStatusesByIds will fetch certain user statuses based on ids and return
|
||||
// a map of string statuses using user id as the key
|
||||
func (wsc *WebSocketClient) GetStatusesByIds(userIds []string) {
|
||||
data := map[string]interface{}{
|
||||
"user_ids": userIds,
|
||||
}
|
||||
wsc.SendMessage("get_statuses_by_ids", data)
|
||||
}
|
||||
|
72
vendor/github.com/mattermost/platform/model/websocket_message.go
generated
vendored
72
vendor/github.com/mattermost/platform/model/websocket_message.go
generated
vendored
@ -18,6 +18,7 @@ const (
|
||||
WEBSOCKET_EVENT_DIRECT_ADDED = "direct_added"
|
||||
WEBSOCKET_EVENT_NEW_USER = "new_user"
|
||||
WEBSOCKET_EVENT_LEAVE_TEAM = "leave_team"
|
||||
WEBSOCKET_EVENT_UPDATE_TEAM = "update_team"
|
||||
WEBSOCKET_EVENT_USER_ADDED = "user_added"
|
||||
WEBSOCKET_EVENT_USER_UPDATED = "user_updated"
|
||||
WEBSOCKET_EVENT_USER_REMOVED = "user_removed"
|
||||
@ -25,33 +26,64 @@ const (
|
||||
WEBSOCKET_EVENT_EPHEMERAL_MESSAGE = "ephemeral_message"
|
||||
WEBSOCKET_EVENT_STATUS_CHANGE = "status_change"
|
||||
WEBSOCKET_EVENT_HELLO = "hello"
|
||||
WEBSOCKET_EVENT_WEBRTC = "webrtc"
|
||||
WEBSOCKET_AUTHENTICATION_CHALLENGE = "authentication_challenge"
|
||||
WEBSOCKET_EVENT_REACTION_ADDED = "reaction_added"
|
||||
WEBSOCKET_EVENT_REACTION_REMOVED = "reaction_removed"
|
||||
)
|
||||
|
||||
type WebSocketMessage interface {
|
||||
ToJson() string
|
||||
IsValid() bool
|
||||
DoPreComputeJson()
|
||||
GetPreComputeJson() []byte
|
||||
EventType() string
|
||||
}
|
||||
|
||||
type WebsocketBroadcast struct {
|
||||
OmitUsers map[string]bool `json:"omit_users"` // broadcast is omitted for users listed here
|
||||
UserId string `json:"user_id"` // broadcast only occurs for this user
|
||||
ChannelId string `json:"channel_id"` // broadcast only occurs for users in this channel
|
||||
TeamId string `json:"team_id"` // broadcast only occurs for users in this team
|
||||
}
|
||||
|
||||
type WebSocketEvent struct {
|
||||
TeamId string `json:"team_id"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
UserId string `json:"user_id"`
|
||||
Event string `json:"event"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
Event string `json:"event"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
Broadcast *WebsocketBroadcast `json:"broadcast"`
|
||||
PreComputeJson []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *WebSocketEvent) Add(key string, value interface{}) {
|
||||
m.Data[key] = value
|
||||
}
|
||||
|
||||
func NewWebSocketEvent(teamId string, channelId string, userId string, event string) *WebSocketEvent {
|
||||
return &WebSocketEvent{TeamId: teamId, ChannelId: channelId, UserId: userId, Event: event, Data: make(map[string]interface{})}
|
||||
func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[string]bool) *WebSocketEvent {
|
||||
return &WebSocketEvent{Event: event, Data: make(map[string]interface{}),
|
||||
Broadcast: &WebsocketBroadcast{TeamId: teamId, ChannelId: channelId, UserId: userId, OmitUsers: omitUsers}}
|
||||
}
|
||||
|
||||
func (o *WebSocketEvent) IsValid() bool {
|
||||
return o.Event != ""
|
||||
}
|
||||
|
||||
func (o *WebSocketEvent) EventType() string {
|
||||
return o.Event
|
||||
}
|
||||
|
||||
func (o *WebSocketEvent) DoPreComputeJson() {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
o.PreComputeJson = []byte("")
|
||||
} else {
|
||||
o.PreComputeJson = b
|
||||
}
|
||||
}
|
||||
|
||||
func (o *WebSocketEvent) GetPreComputeJson() []byte {
|
||||
return o.PreComputeJson
|
||||
}
|
||||
|
||||
func (o *WebSocketEvent) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
@ -73,10 +105,11 @@ func WebSocketEventFromJson(data io.Reader) *WebSocketEvent {
|
||||
}
|
||||
|
||||
type WebSocketResponse struct {
|
||||
Status string `json:"status"`
|
||||
SeqReply int64 `json:"seq_reply,omitempty"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
Error *AppError `json:"error,omitempty"`
|
||||
Status string `json:"status"`
|
||||
SeqReply int64 `json:"seq_reply,omitempty"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
Error *AppError `json:"error,omitempty"`
|
||||
PreComputeJson []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *WebSocketResponse) Add(key string, value interface{}) {
|
||||
@ -95,6 +128,10 @@ func (o *WebSocketResponse) IsValid() bool {
|
||||
return o.Status != ""
|
||||
}
|
||||
|
||||
func (o *WebSocketResponse) EventType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (o *WebSocketResponse) ToJson() string {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
@ -104,6 +141,19 @@ func (o *WebSocketResponse) ToJson() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (o *WebSocketResponse) DoPreComputeJson() {
|
||||
b, err := json.Marshal(o)
|
||||
if err != nil {
|
||||
o.PreComputeJson = []byte("")
|
||||
} else {
|
||||
o.PreComputeJson = b
|
||||
}
|
||||
}
|
||||
|
||||
func (o *WebSocketResponse) GetPreComputeJson() []byte {
|
||||
return o.PreComputeJson
|
||||
}
|
||||
|
||||
func WebSocketResponseFromJson(data io.Reader) *WebSocketResponse {
|
||||
decoder := json.NewDecoder(data)
|
||||
var o WebSocketResponse
|
||||
|
67
vendor/github.com/mattn/go-xmpp/xmpp.go
generated
vendored
67
vendor/github.com/mattn/go-xmpp/xmpp.go
generated
vendored
@ -536,12 +536,13 @@ func (c *Client) IsEncrypted() bool {
|
||||
|
||||
// Chat is an incoming or outgoing XMPP chat message.
|
||||
type Chat struct {
|
||||
Remote string
|
||||
Type string
|
||||
Text string
|
||||
Roster Roster
|
||||
Other []string
|
||||
Stamp time.Time
|
||||
Remote string
|
||||
Type string
|
||||
Text string
|
||||
Roster Roster
|
||||
Other []string
|
||||
OtherElem []XMLElement
|
||||
Stamp time.Time
|
||||
}
|
||||
|
||||
type Roster []Contact
|
||||
@ -584,11 +585,12 @@ func (c *Client) Recv() (stanza interface{}, err error) {
|
||||
v.Delay.Stamp,
|
||||
)
|
||||
chat := Chat{
|
||||
Remote: v.From,
|
||||
Type: v.Type,
|
||||
Text: v.Body,
|
||||
Other: v.Other,
|
||||
Stamp: stamp,
|
||||
Remote: v.From,
|
||||
Type: v.Type,
|
||||
Text: v.Body,
|
||||
Other: v.OtherStrings(),
|
||||
OtherElem: v.Other,
|
||||
Stamp: stamp,
|
||||
}
|
||||
return chat, nil
|
||||
case *clientQuery:
|
||||
@ -600,6 +602,12 @@ func (c *Client) Recv() (stanza interface{}, err error) {
|
||||
case *clientPresence:
|
||||
return Presence{v.From, v.To, v.Type, v.Show, v.Status}, nil
|
||||
case *clientIQ:
|
||||
if bytes.Equal(v.Query, []byte(`<ping xmlns='urn:xmpp:ping'/>`)) {
|
||||
err := c.SendResultPing(v.ID, v.From)
|
||||
if err != nil {
|
||||
return Chat{}, err
|
||||
}
|
||||
}
|
||||
return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: v.Query}, nil
|
||||
}
|
||||
}
|
||||
@ -714,11 +722,46 @@ type clientMessage struct {
|
||||
Thread string `xml:"thread"`
|
||||
|
||||
// Any hasn't matched element
|
||||
Other []string `xml:",any"`
|
||||
Other []XMLElement `xml:",any"`
|
||||
|
||||
Delay Delay `xml:"delay"`
|
||||
}
|
||||
|
||||
func (m *clientMessage) OtherStrings() []string {
|
||||
a := make([]string, len(m.Other))
|
||||
for i, e := range m.Other {
|
||||
a[i] = e.String()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
type XMLElement struct {
|
||||
XMLName xml.Name
|
||||
InnerXML string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
func (e *XMLElement) String() string {
|
||||
r := bytes.NewReader([]byte(e.InnerXML))
|
||||
d := xml.NewDecoder(r)
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
tok, err := d.Token()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch v := tok.(type) {
|
||||
case xml.StartElement:
|
||||
err = d.Skip()
|
||||
case xml.CharData:
|
||||
_, err = buf.Write(v)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type Delay struct {
|
||||
Stamp string `xml:"stamp,attr"`
|
||||
}
|
||||
|
6
vendor/github.com/mattn/go-xmpp/xmpp_ping.go
generated
vendored
6
vendor/github.com/mattn/go-xmpp/xmpp_ping.go
generated
vendored
@ -25,3 +25,9 @@ func (c *Client) PingS2S(fromServer, toServer string) error {
|
||||
xmlEscape(fromServer), xmlEscape(toServer))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) SendResultPing(id, toServer string) error {
|
||||
_, err := fmt.Fprintf(c.conn, "<iq type='result' to='%s' id='%s'/>",
|
||||
xmlEscape(toServer), xmlEscape(id))
|
||||
return err
|
||||
}
|
||||
|
29
vendor/github.com/russross/blackfriday/LICENSE.txt
generated
vendored
Normal file
29
vendor/github.com/russross/blackfriday/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
Blackfriday is distributed under the Simplified BSD License:
|
||||
|
||||
> Copyright © 2011 Russ Ross
|
||||
> All rights reserved.
|
||||
>
|
||||
> Redistribution and use in source and binary forms, with or without
|
||||
> modification, are permitted provided that the following conditions
|
||||
> are met:
|
||||
>
|
||||
> 1. Redistributions of source code must retain the above copyright
|
||||
> notice, this list of conditions and the following disclaimer.
|
||||
>
|
||||
> 2. Redistributions in binary form must reproduce the above
|
||||
> copyright notice, this list of conditions and the following
|
||||
> disclaimer in the documentation and/or other materials provided with
|
||||
> the distribution.
|
||||
>
|
||||
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
> POSSIBILITY OF SUCH DAMAGE.
|
1430
vendor/github.com/russross/blackfriday/block.go
generated
vendored
Normal file
1430
vendor/github.com/russross/blackfriday/block.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
949
vendor/github.com/russross/blackfriday/html.go
generated
vendored
Normal file
949
vendor/github.com/russross/blackfriday/html.go
generated
vendored
Normal file
@ -0,0 +1,949 @@
|
||||
//
|
||||
// Blackfriday Markdown Processor
|
||||
// Available at http://github.com/russross/blackfriday
|
||||
//
|
||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||
// Distributed under the Simplified BSD License.
|
||||
// See README.md for details.
|
||||
//
|
||||
|
||||
//
|
||||
//
|
||||
// HTML rendering backend
|
||||
//
|
||||
//
|
||||
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Html renderer configuration options.
|
||||
const (
|
||||
HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks
|
||||
HTML_SKIP_STYLE // skip embedded <style> elements
|
||||
HTML_SKIP_IMAGES // skip embedded images
|
||||
HTML_SKIP_LINKS // skip all links
|
||||
HTML_SAFELINK // only link to trusted protocols
|
||||
HTML_NOFOLLOW_LINKS // only link with rel="nofollow"
|
||||
HTML_NOREFERRER_LINKS // only link with rel="noreferrer"
|
||||
HTML_HREF_TARGET_BLANK // add a blank target
|
||||
HTML_TOC // generate a table of contents
|
||||
HTML_OMIT_CONTENTS // skip the main contents (for a standalone table of contents)
|
||||
HTML_COMPLETE_PAGE // generate a complete HTML page
|
||||
HTML_USE_XHTML // generate XHTML output instead of HTML
|
||||
HTML_USE_SMARTYPANTS // enable smart punctuation substitutions
|
||||
HTML_SMARTYPANTS_FRACTIONS // enable smart fractions (with HTML_USE_SMARTYPANTS)
|
||||
HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS)
|
||||
HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
|
||||
HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
|
||||
HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source
|
||||
)
|
||||
|
||||
var (
|
||||
alignments = []string{
|
||||
"left",
|
||||
"right",
|
||||
"center",
|
||||
}
|
||||
|
||||
// TODO: improve this regexp to catch all possible entities:
|
||||
htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`)
|
||||
)
|
||||
|
||||
type HtmlRendererParameters struct {
|
||||
// Prepend this text to each relative URL.
|
||||
AbsolutePrefix string
|
||||
// Add this text to each footnote anchor, to ensure uniqueness.
|
||||
FootnoteAnchorPrefix string
|
||||
// Show this text inside the <a> tag for a footnote return link, if the
|
||||
// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
|
||||
// <sup>[return]</sup> is used.
|
||||
FootnoteReturnLinkContents string
|
||||
// If set, add this text to the front of each Header ID, to ensure
|
||||
// uniqueness.
|
||||
HeaderIDPrefix string
|
||||
// If set, add this text to the back of each Header ID, to ensure uniqueness.
|
||||
HeaderIDSuffix string
|
||||
}
|
||||
|
||||
// Html is a type that implements the Renderer interface for HTML output.
|
||||
//
|
||||
// Do not create this directly, instead use the HtmlRenderer function.
|
||||
type Html struct {
|
||||
flags int // HTML_* options
|
||||
closeTag string // how to end singleton tags: either " />" or ">"
|
||||
title string // document title
|
||||
css string // optional css file url (used with HTML_COMPLETE_PAGE)
|
||||
|
||||
parameters HtmlRendererParameters
|
||||
|
||||
// table of contents data
|
||||
tocMarker int
|
||||
headerCount int
|
||||
currentLevel int
|
||||
toc *bytes.Buffer
|
||||
|
||||
// Track header IDs to prevent ID collision in a single generation.
|
||||
headerIDs map[string]int
|
||||
|
||||
smartypants *smartypantsRenderer
|
||||
}
|
||||
|
||||
const (
|
||||
xhtmlClose = " />"
|
||||
htmlClose = ">"
|
||||
)
|
||||
|
||||
// HtmlRenderer creates and configures an Html object, which
|
||||
// satisfies the Renderer interface.
|
||||
//
|
||||
// flags is a set of HTML_* options ORed together.
|
||||
// title is the title of the document, and css is a URL for the document's
|
||||
// stylesheet.
|
||||
// title and css are only used when HTML_COMPLETE_PAGE is selected.
|
||||
func HtmlRenderer(flags int, title string, css string) Renderer {
|
||||
return HtmlRendererWithParameters(flags, title, css, HtmlRendererParameters{})
|
||||
}
|
||||
|
||||
func HtmlRendererWithParameters(flags int, title string,
|
||||
css string, renderParameters HtmlRendererParameters) Renderer {
|
||||
// configure the rendering engine
|
||||
closeTag := htmlClose
|
||||
if flags&HTML_USE_XHTML != 0 {
|
||||
closeTag = xhtmlClose
|
||||
}
|
||||
|
||||
if renderParameters.FootnoteReturnLinkContents == "" {
|
||||
renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>`
|
||||
}
|
||||
|
||||
return &Html{
|
||||
flags: flags,
|
||||
closeTag: closeTag,
|
||||
title: title,
|
||||
css: css,
|
||||
parameters: renderParameters,
|
||||
|
||||
headerCount: 0,
|
||||
currentLevel: 0,
|
||||
toc: new(bytes.Buffer),
|
||||
|
||||
headerIDs: make(map[string]int),
|
||||
|
||||
smartypants: smartypants(flags),
|
||||
}
|
||||
}
|
||||
|
||||
// Using if statements is a bit faster than a switch statement. As the compiler
|
||||
// improves, this should be unnecessary this is only worthwhile because
|
||||
// attrEscape is the single largest CPU user in normal use.
|
||||
// Also tried using map, but that gave a ~3x slowdown.
|
||||
func escapeSingleChar(char byte) (string, bool) {
|
||||
if char == '"' {
|
||||
return """, true
|
||||
}
|
||||
if char == '&' {
|
||||
return "&", true
|
||||
}
|
||||
if char == '<' {
|
||||
return "<", true
|
||||
}
|
||||
if char == '>' {
|
||||
return ">", true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func attrEscape(out *bytes.Buffer, src []byte) {
|
||||
org := 0
|
||||
for i, ch := range src {
|
||||
if entity, ok := escapeSingleChar(ch); ok {
|
||||
if i > org {
|
||||
// copy all the normal characters since the last escape
|
||||
out.Write(src[org:i])
|
||||
}
|
||||
org = i + 1
|
||||
out.WriteString(entity)
|
||||
}
|
||||
}
|
||||
if org < len(src) {
|
||||
out.Write(src[org:])
|
||||
}
|
||||
}
|
||||
|
||||
func entityEscapeWithSkip(out *bytes.Buffer, src []byte, skipRanges [][]int) {
|
||||
end := 0
|
||||
for _, rang := range skipRanges {
|
||||
attrEscape(out, src[end:rang[0]])
|
||||
out.Write(src[rang[0]:rang[1]])
|
||||
end = rang[1]
|
||||
}
|
||||
attrEscape(out, src[end:])
|
||||
}
|
||||
|
||||
func (options *Html) GetFlags() int {
|
||||
return options.flags
|
||||
}
|
||||
|
||||
func (options *Html) TitleBlock(out *bytes.Buffer, text []byte) {
|
||||
text = bytes.TrimPrefix(text, []byte("% "))
|
||||
text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1)
|
||||
out.WriteString("<h1 class=\"title\">")
|
||||
out.Write(text)
|
||||
out.WriteString("\n</h1>")
|
||||
}
|
||||
|
||||
func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
||||
marker := out.Len()
|
||||
doubleSpace(out)
|
||||
|
||||
if id == "" && options.flags&HTML_TOC != 0 {
|
||||
id = fmt.Sprintf("toc_%d", options.headerCount)
|
||||
}
|
||||
|
||||
if id != "" {
|
||||
id = options.ensureUniqueHeaderID(id)
|
||||
|
||||
if options.parameters.HeaderIDPrefix != "" {
|
||||
id = options.parameters.HeaderIDPrefix + id
|
||||
}
|
||||
|
||||
if options.parameters.HeaderIDSuffix != "" {
|
||||
id = id + options.parameters.HeaderIDSuffix
|
||||
}
|
||||
|
||||
out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id))
|
||||
} else {
|
||||
out.WriteString(fmt.Sprintf("<h%d>", level))
|
||||
}
|
||||
|
||||
tocMarker := out.Len()
|
||||
if !text() {
|
||||
out.Truncate(marker)
|
||||
return
|
||||
}
|
||||
|
||||
// are we building a table of contents?
|
||||
if options.flags&HTML_TOC != 0 {
|
||||
options.TocHeaderWithAnchor(out.Bytes()[tocMarker:], level, id)
|
||||
}
|
||||
|
||||
out.WriteString(fmt.Sprintf("</h%d>\n", level))
|
||||
}
|
||||
|
||||
func (options *Html) BlockHtml(out *bytes.Buffer, text []byte) {
|
||||
if options.flags&HTML_SKIP_HTML != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
doubleSpace(out)
|
||||
out.Write(text)
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *Html) HRule(out *bytes.Buffer) {
|
||||
doubleSpace(out)
|
||||
out.WriteString("<hr")
|
||||
out.WriteString(options.closeTag)
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||
doubleSpace(out)
|
||||
|
||||
// parse out the language names/classes
|
||||
count := 0
|
||||
for _, elt := range strings.Fields(lang) {
|
||||
if elt[0] == '.' {
|
||||
elt = elt[1:]
|
||||
}
|
||||
if len(elt) == 0 {
|
||||
continue
|
||||
}
|
||||
if count == 0 {
|
||||
out.WriteString("<pre><code class=\"language-")
|
||||
} else {
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
attrEscape(out, []byte(elt))
|
||||
count++
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
out.WriteString("<pre><code>")
|
||||
} else {
|
||||
out.WriteString("\">")
|
||||
}
|
||||
|
||||
attrEscape(out, text)
|
||||
out.WriteString("</code></pre>\n")
|
||||
}
|
||||
|
||||
func (options *Html) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||
doubleSpace(out)
|
||||
out.WriteString("<blockquote>\n")
|
||||
out.Write(text)
|
||||
out.WriteString("</blockquote>\n")
|
||||
}
|
||||
|
||||
func (options *Html) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
|
||||
doubleSpace(out)
|
||||
out.WriteString("<table>\n<thead>\n")
|
||||
out.Write(header)
|
||||
out.WriteString("</thead>\n\n<tbody>\n")
|
||||
out.Write(body)
|
||||
out.WriteString("</tbody>\n</table>\n")
|
||||
}
|
||||
|
||||
func (options *Html) TableRow(out *bytes.Buffer, text []byte) {
|
||||
doubleSpace(out)
|
||||
out.WriteString("<tr>\n")
|
||||
out.Write(text)
|
||||
out.WriteString("\n</tr>\n")
|
||||
}
|
||||
|
||||
func (options *Html) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
|
||||
doubleSpace(out)
|
||||
switch align {
|
||||
case TABLE_ALIGNMENT_LEFT:
|
||||
out.WriteString("<th align=\"left\">")
|
||||
case TABLE_ALIGNMENT_RIGHT:
|
||||
out.WriteString("<th align=\"right\">")
|
||||
case TABLE_ALIGNMENT_CENTER:
|
||||
out.WriteString("<th align=\"center\">")
|
||||
default:
|
||||
out.WriteString("<th>")
|
||||
}
|
||||
|
||||
out.Write(text)
|
||||
out.WriteString("</th>")
|
||||
}
|
||||
|
||||
func (options *Html) TableCell(out *bytes.Buffer, text []byte, align int) {
|
||||
doubleSpace(out)
|
||||
switch align {
|
||||
case TABLE_ALIGNMENT_LEFT:
|
||||
out.WriteString("<td align=\"left\">")
|
||||
case TABLE_ALIGNMENT_RIGHT:
|
||||
out.WriteString("<td align=\"right\">")
|
||||
case TABLE_ALIGNMENT_CENTER:
|
||||
out.WriteString("<td align=\"center\">")
|
||||
default:
|
||||
out.WriteString("<td>")
|
||||
}
|
||||
|
||||
out.Write(text)
|
||||
out.WriteString("</td>")
|
||||
}
|
||||
|
||||
func (options *Html) Footnotes(out *bytes.Buffer, text func() bool) {
|
||||
out.WriteString("<div class=\"footnotes\">\n")
|
||||
options.HRule(out)
|
||||
options.List(out, text, LIST_TYPE_ORDERED)
|
||||
out.WriteString("</div>\n")
|
||||
}
|
||||
|
||||
func (options *Html) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
|
||||
if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
|
||||
doubleSpace(out)
|
||||
}
|
||||
slug := slugify(name)
|
||||
out.WriteString(`<li id="`)
|
||||
out.WriteString(`fn:`)
|
||||
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||
out.Write(slug)
|
||||
out.WriteString(`">`)
|
||||
out.Write(text)
|
||||
if options.flags&HTML_FOOTNOTE_RETURN_LINKS != 0 {
|
||||
out.WriteString(` <a class="footnote-return" href="#`)
|
||||
out.WriteString(`fnref:`)
|
||||
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||
out.Write(slug)
|
||||
out.WriteString(`">`)
|
||||
out.WriteString(options.parameters.FootnoteReturnLinkContents)
|
||||
out.WriteString(`</a>`)
|
||||
}
|
||||
out.WriteString("</li>\n")
|
||||
}
|
||||
|
||||
func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||
marker := out.Len()
|
||||
doubleSpace(out)
|
||||
|
||||
if flags&LIST_TYPE_DEFINITION != 0 {
|
||||
out.WriteString("<dl>")
|
||||
} else if flags&LIST_TYPE_ORDERED != 0 {
|
||||
out.WriteString("<ol>")
|
||||
} else {
|
||||
out.WriteString("<ul>")
|
||||
}
|
||||
if !text() {
|
||||
out.Truncate(marker)
|
||||
return
|
||||
}
|
||||
if flags&LIST_TYPE_DEFINITION != 0 {
|
||||
out.WriteString("</dl>\n")
|
||||
} else if flags&LIST_TYPE_ORDERED != 0 {
|
||||
out.WriteString("</ol>\n")
|
||||
} else {
|
||||
out.WriteString("</ul>\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) ||
|
||||
flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
|
||||
doubleSpace(out)
|
||||
}
|
||||
if flags&LIST_TYPE_TERM != 0 {
|
||||
out.WriteString("<dt>")
|
||||
} else if flags&LIST_TYPE_DEFINITION != 0 {
|
||||
out.WriteString("<dd>")
|
||||
} else {
|
||||
out.WriteString("<li>")
|
||||
}
|
||||
out.Write(text)
|
||||
if flags&LIST_TYPE_TERM != 0 {
|
||||
out.WriteString("</dt>\n")
|
||||
} else if flags&LIST_TYPE_DEFINITION != 0 {
|
||||
out.WriteString("</dd>\n")
|
||||
} else {
|
||||
out.WriteString("</li>\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||
marker := out.Len()
|
||||
doubleSpace(out)
|
||||
|
||||
out.WriteString("<p>")
|
||||
if !text() {
|
||||
out.Truncate(marker)
|
||||
return
|
||||
}
|
||||
out.WriteString("</p>\n")
|
||||
}
|
||||
|
||||
func (options *Html) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
||||
skipRanges := htmlEntity.FindAllIndex(link, -1)
|
||||
if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) && kind != LINK_TYPE_EMAIL {
|
||||
// mark it but don't link it if it is not a safe link: no smartypants
|
||||
out.WriteString("<tt>")
|
||||
entityEscapeWithSkip(out, link, skipRanges)
|
||||
out.WriteString("</tt>")
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteString("<a href=\"")
|
||||
if kind == LINK_TYPE_EMAIL {
|
||||
out.WriteString("mailto:")
|
||||
} else {
|
||||
options.maybeWriteAbsolutePrefix(out, link)
|
||||
}
|
||||
|
||||
entityEscapeWithSkip(out, link, skipRanges)
|
||||
|
||||
var relAttrs []string
|
||||
if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
|
||||
relAttrs = append(relAttrs, "nofollow")
|
||||
}
|
||||
if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
|
||||
relAttrs = append(relAttrs, "noreferrer")
|
||||
}
|
||||
if len(relAttrs) > 0 {
|
||||
out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
|
||||
}
|
||||
|
||||
// blank target only add to external link
|
||||
if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
|
||||
out.WriteString("\" target=\"_blank")
|
||||
}
|
||||
|
||||
out.WriteString("\">")
|
||||
|
||||
// Pretty print: if we get an email address as
|
||||
// an actual URI, e.g. `mailto:foo@bar.com`, we don't
|
||||
// want to print the `mailto:` prefix
|
||||
switch {
|
||||
case bytes.HasPrefix(link, []byte("mailto://")):
|
||||
attrEscape(out, link[len("mailto://"):])
|
||||
case bytes.HasPrefix(link, []byte("mailto:")):
|
||||
attrEscape(out, link[len("mailto:"):])
|
||||
default:
|
||||
entityEscapeWithSkip(out, link, skipRanges)
|
||||
}
|
||||
|
||||
out.WriteString("</a>")
|
||||
}
|
||||
|
||||
func (options *Html) CodeSpan(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("<code>")
|
||||
attrEscape(out, text)
|
||||
out.WriteString("</code>")
|
||||
}
|
||||
|
||||
func (options *Html) DoubleEmphasis(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("<strong>")
|
||||
out.Write(text)
|
||||
out.WriteString("</strong>")
|
||||
}
|
||||
|
||||
func (options *Html) Emphasis(out *bytes.Buffer, text []byte) {
|
||||
if len(text) == 0 {
|
||||
return
|
||||
}
|
||||
out.WriteString("<em>")
|
||||
out.Write(text)
|
||||
out.WriteString("</em>")
|
||||
}
|
||||
|
||||
func (options *Html) maybeWriteAbsolutePrefix(out *bytes.Buffer, link []byte) {
|
||||
if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
|
||||
out.WriteString(options.parameters.AbsolutePrefix)
|
||||
if link[0] != '/' {
|
||||
out.WriteByte('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (options *Html) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||
if options.flags&HTML_SKIP_IMAGES != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteString("<img src=\"")
|
||||
options.maybeWriteAbsolutePrefix(out, link)
|
||||
attrEscape(out, link)
|
||||
out.WriteString("\" alt=\"")
|
||||
if len(alt) > 0 {
|
||||
attrEscape(out, alt)
|
||||
}
|
||||
if len(title) > 0 {
|
||||
out.WriteString("\" title=\"")
|
||||
attrEscape(out, title)
|
||||
}
|
||||
|
||||
out.WriteByte('"')
|
||||
out.WriteString(options.closeTag)
|
||||
}
|
||||
|
||||
func (options *Html) LineBreak(out *bytes.Buffer) {
|
||||
out.WriteString("<br")
|
||||
out.WriteString(options.closeTag)
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *Html) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
|
||||
if options.flags&HTML_SKIP_LINKS != 0 {
|
||||
// write the link text out but don't link it, just mark it with typewriter font
|
||||
out.WriteString("<tt>")
|
||||
attrEscape(out, content)
|
||||
out.WriteString("</tt>")
|
||||
return
|
||||
}
|
||||
|
||||
if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) {
|
||||
// write the link text out but don't link it, just mark it with typewriter font
|
||||
out.WriteString("<tt>")
|
||||
attrEscape(out, content)
|
||||
out.WriteString("</tt>")
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteString("<a href=\"")
|
||||
options.maybeWriteAbsolutePrefix(out, link)
|
||||
attrEscape(out, link)
|
||||
if len(title) > 0 {
|
||||
out.WriteString("\" title=\"")
|
||||
attrEscape(out, title)
|
||||
}
|
||||
var relAttrs []string
|
||||
if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
|
||||
relAttrs = append(relAttrs, "nofollow")
|
||||
}
|
||||
if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
|
||||
relAttrs = append(relAttrs, "noreferrer")
|
||||
}
|
||||
if len(relAttrs) > 0 {
|
||||
out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
|
||||
}
|
||||
|
||||
// blank target only add to external link
|
||||
if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
|
||||
out.WriteString("\" target=\"_blank")
|
||||
}
|
||||
|
||||
out.WriteString("\">")
|
||||
out.Write(content)
|
||||
out.WriteString("</a>")
|
||||
return
|
||||
}
|
||||
|
||||
func (options *Html) RawHtmlTag(out *bytes.Buffer, text []byte) {
|
||||
if options.flags&HTML_SKIP_HTML != 0 {
|
||||
return
|
||||
}
|
||||
if options.flags&HTML_SKIP_STYLE != 0 && isHtmlTag(text, "style") {
|
||||
return
|
||||
}
|
||||
if options.flags&HTML_SKIP_LINKS != 0 && isHtmlTag(text, "a") {
|
||||
return
|
||||
}
|
||||
if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") {
|
||||
return
|
||||
}
|
||||
out.Write(text)
|
||||
}
|
||||
|
||||
func (options *Html) TripleEmphasis(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("<strong><em>")
|
||||
out.Write(text)
|
||||
out.WriteString("</em></strong>")
|
||||
}
|
||||
|
||||
func (options *Html) StrikeThrough(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("<del>")
|
||||
out.Write(text)
|
||||
out.WriteString("</del>")
|
||||
}
|
||||
|
||||
func (options *Html) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
|
||||
slug := slugify(ref)
|
||||
out.WriteString(`<sup class="footnote-ref" id="`)
|
||||
out.WriteString(`fnref:`)
|
||||
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||
out.Write(slug)
|
||||
out.WriteString(`"><a rel="footnote" href="#`)
|
||||
out.WriteString(`fn:`)
|
||||
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||
out.Write(slug)
|
||||
out.WriteString(`">`)
|
||||
out.WriteString(strconv.Itoa(id))
|
||||
out.WriteString(`</a></sup>`)
|
||||
}
|
||||
|
||||
func (options *Html) Entity(out *bytes.Buffer, entity []byte) {
|
||||
out.Write(entity)
|
||||
}
|
||||
|
||||
func (options *Html) NormalText(out *bytes.Buffer, text []byte) {
|
||||
if options.flags&HTML_USE_SMARTYPANTS != 0 {
|
||||
options.Smartypants(out, text)
|
||||
} else {
|
||||
attrEscape(out, text)
|
||||
}
|
||||
}
|
||||
|
||||
func (options *Html) Smartypants(out *bytes.Buffer, text []byte) {
|
||||
smrt := smartypantsData{false, false}
|
||||
|
||||
// first do normal entity escaping
|
||||
var escaped bytes.Buffer
|
||||
attrEscape(&escaped, text)
|
||||
text = escaped.Bytes()
|
||||
|
||||
mark := 0
|
||||
for i := 0; i < len(text); i++ {
|
||||
if action := options.smartypants[text[i]]; action != nil {
|
||||
if i > mark {
|
||||
out.Write(text[mark:i])
|
||||
}
|
||||
|
||||
previousChar := byte(0)
|
||||
if i > 0 {
|
||||
previousChar = text[i-1]
|
||||
}
|
||||
i += action(out, &smrt, previousChar, text[i:])
|
||||
mark = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
if mark < len(text) {
|
||||
out.Write(text[mark:])
|
||||
}
|
||||
}
|
||||
|
||||
func (options *Html) DocumentHeader(out *bytes.Buffer) {
|
||||
if options.flags&HTML_COMPLETE_PAGE == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ending := ""
|
||||
if options.flags&HTML_USE_XHTML != 0 {
|
||||
out.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
|
||||
out.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
|
||||
out.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
|
||||
ending = " /"
|
||||
} else {
|
||||
out.WriteString("<!DOCTYPE html>\n")
|
||||
out.WriteString("<html>\n")
|
||||
}
|
||||
out.WriteString("<head>\n")
|
||||
out.WriteString(" <title>")
|
||||
options.NormalText(out, []byte(options.title))
|
||||
out.WriteString("</title>\n")
|
||||
out.WriteString(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
|
||||
out.WriteString(VERSION)
|
||||
out.WriteString("\"")
|
||||
out.WriteString(ending)
|
||||
out.WriteString(">\n")
|
||||
out.WriteString(" <meta charset=\"utf-8\"")
|
||||
out.WriteString(ending)
|
||||
out.WriteString(">\n")
|
||||
if options.css != "" {
|
||||
out.WriteString(" <link rel=\"stylesheet\" type=\"text/css\" href=\"")
|
||||
attrEscape(out, []byte(options.css))
|
||||
out.WriteString("\"")
|
||||
out.WriteString(ending)
|
||||
out.WriteString(">\n")
|
||||
}
|
||||
out.WriteString("</head>\n")
|
||||
out.WriteString("<body>\n")
|
||||
|
||||
options.tocMarker = out.Len()
|
||||
}
|
||||
|
||||
func (options *Html) DocumentFooter(out *bytes.Buffer) {
|
||||
// finalize and insert the table of contents
|
||||
if options.flags&HTML_TOC != 0 {
|
||||
options.TocFinalize()
|
||||
|
||||
// now we have to insert the table of contents into the document
|
||||
var temp bytes.Buffer
|
||||
|
||||
// start by making a copy of everything after the document header
|
||||
temp.Write(out.Bytes()[options.tocMarker:])
|
||||
|
||||
// now clear the copied material from the main output buffer
|
||||
out.Truncate(options.tocMarker)
|
||||
|
||||
// corner case spacing issue
|
||||
if options.flags&HTML_COMPLETE_PAGE != 0 {
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
// insert the table of contents
|
||||
out.WriteString("<nav>\n")
|
||||
out.Write(options.toc.Bytes())
|
||||
out.WriteString("</nav>\n")
|
||||
|
||||
// corner case spacing issue
|
||||
if options.flags&HTML_COMPLETE_PAGE == 0 && options.flags&HTML_OMIT_CONTENTS == 0 {
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
// write out everything that came after it
|
||||
if options.flags&HTML_OMIT_CONTENTS == 0 {
|
||||
out.Write(temp.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
if options.flags&HTML_COMPLETE_PAGE != 0 {
|
||||
out.WriteString("\n</body>\n")
|
||||
out.WriteString("</html>\n")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (options *Html) TocHeaderWithAnchor(text []byte, level int, anchor string) {
|
||||
for level > options.currentLevel {
|
||||
switch {
|
||||
case bytes.HasSuffix(options.toc.Bytes(), []byte("</li>\n")):
|
||||
// this sublist can nest underneath a header
|
||||
size := options.toc.Len()
|
||||
options.toc.Truncate(size - len("</li>\n"))
|
||||
|
||||
case options.currentLevel > 0:
|
||||
options.toc.WriteString("<li>")
|
||||
}
|
||||
if options.toc.Len() > 0 {
|
||||
options.toc.WriteByte('\n')
|
||||
}
|
||||
options.toc.WriteString("<ul>\n")
|
||||
options.currentLevel++
|
||||
}
|
||||
|
||||
for level < options.currentLevel {
|
||||
options.toc.WriteString("</ul>")
|
||||
if options.currentLevel > 1 {
|
||||
options.toc.WriteString("</li>\n")
|
||||
}
|
||||
options.currentLevel--
|
||||
}
|
||||
|
||||
options.toc.WriteString("<li><a href=\"#")
|
||||
if anchor != "" {
|
||||
options.toc.WriteString(anchor)
|
||||
} else {
|
||||
options.toc.WriteString("toc_")
|
||||
options.toc.WriteString(strconv.Itoa(options.headerCount))
|
||||
}
|
||||
options.toc.WriteString("\">")
|
||||
options.headerCount++
|
||||
|
||||
options.toc.Write(text)
|
||||
|
||||
options.toc.WriteString("</a></li>\n")
|
||||
}
|
||||
|
||||
func (options *Html) TocHeader(text []byte, level int) {
|
||||
options.TocHeaderWithAnchor(text, level, "")
|
||||
}
|
||||
|
||||
func (options *Html) TocFinalize() {
|
||||
for options.currentLevel > 1 {
|
||||
options.toc.WriteString("</ul></li>\n")
|
||||
options.currentLevel--
|
||||
}
|
||||
|
||||
if options.currentLevel > 0 {
|
||||
options.toc.WriteString("</ul>\n")
|
||||
}
|
||||
}
|
||||
|
||||
func isHtmlTag(tag []byte, tagname string) bool {
|
||||
found, _ := findHtmlTagPos(tag, tagname)
|
||||
return found
|
||||
}
|
||||
|
||||
// Look for a character, but ignore it when it's in any kind of quotes, it
|
||||
// might be JavaScript
|
||||
func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
|
||||
inSingleQuote := false
|
||||
inDoubleQuote := false
|
||||
inGraveQuote := false
|
||||
i := start
|
||||
for i < len(html) {
|
||||
switch {
|
||||
case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
|
||||
return i
|
||||
case html[i] == '\'':
|
||||
inSingleQuote = !inSingleQuote
|
||||
case html[i] == '"':
|
||||
inDoubleQuote = !inDoubleQuote
|
||||
case html[i] == '`':
|
||||
inGraveQuote = !inGraveQuote
|
||||
}
|
||||
i++
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
func findHtmlTagPos(tag []byte, tagname string) (bool, int) {
|
||||
i := 0
|
||||
if i < len(tag) && tag[0] != '<' {
|
||||
return false, -1
|
||||
}
|
||||
i++
|
||||
i = skipSpace(tag, i)
|
||||
|
||||
if i < len(tag) && tag[i] == '/' {
|
||||
i++
|
||||
}
|
||||
|
||||
i = skipSpace(tag, i)
|
||||
j := 0
|
||||
for ; i < len(tag); i, j = i+1, j+1 {
|
||||
if j >= len(tagname) {
|
||||
break
|
||||
}
|
||||
|
||||
if strings.ToLower(string(tag[i]))[0] != tagname[j] {
|
||||
return false, -1
|
||||
}
|
||||
}
|
||||
|
||||
if i == len(tag) {
|
||||
return false, -1
|
||||
}
|
||||
|
||||
rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
|
||||
if rightAngle > i {
|
||||
return true, rightAngle
|
||||
}
|
||||
|
||||
return false, -1
|
||||
}
|
||||
|
||||
func skipUntilChar(text []byte, start int, char byte) int {
|
||||
i := start
|
||||
for i < len(text) && text[i] != char {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func skipSpace(tag []byte, i int) int {
|
||||
for i < len(tag) && isspace(tag[i]) {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func skipChar(data []byte, start int, char byte) int {
|
||||
i := start
|
||||
for i < len(data) && data[i] == char {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func doubleSpace(out *bytes.Buffer) {
|
||||
if out.Len() > 0 {
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
func isRelativeLink(link []byte) (yes bool) {
|
||||
// a tag begin with '#'
|
||||
if link[0] == '#' {
|
||||
return true
|
||||
}
|
||||
|
||||
// link begin with '/' but not '//', the second maybe a protocol relative link
|
||||
if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
|
||||
return true
|
||||
}
|
||||
|
||||
// only the root '/'
|
||||
if len(link) == 1 && link[0] == '/' {
|
||||
return true
|
||||
}
|
||||
|
||||
// current directory : begin with "./"
|
||||
if bytes.HasPrefix(link, []byte("./")) {
|
||||
return true
|
||||
}
|
||||
|
||||
// parent directory : begin with "../"
|
||||
if bytes.HasPrefix(link, []byte("../")) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (options *Html) ensureUniqueHeaderID(id string) string {
|
||||
for count, found := options.headerIDs[id]; found; count, found = options.headerIDs[id] {
|
||||
tmp := fmt.Sprintf("%s-%d", id, count+1)
|
||||
|
||||
if _, tmpFound := options.headerIDs[tmp]; !tmpFound {
|
||||
options.headerIDs[id] = count + 1
|
||||
id = tmp
|
||||
} else {
|
||||
id = id + "-1"
|
||||
}
|
||||
}
|
||||
|
||||
if _, found := options.headerIDs[id]; !found {
|
||||
options.headerIDs[id] = 0
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
1148
vendor/github.com/russross/blackfriday/inline.go
generated
vendored
Normal file
1148
vendor/github.com/russross/blackfriday/inline.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
332
vendor/github.com/russross/blackfriday/latex.go
generated
vendored
Normal file
332
vendor/github.com/russross/blackfriday/latex.go
generated
vendored
Normal file
@ -0,0 +1,332 @@
|
||||
//
|
||||
// Blackfriday Markdown Processor
|
||||
// Available at http://github.com/russross/blackfriday
|
||||
//
|
||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||
// Distributed under the Simplified BSD License.
|
||||
// See README.md for details.
|
||||
//
|
||||
|
||||
//
|
||||
//
|
||||
// LaTeX rendering backend
|
||||
//
|
||||
//
|
||||
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// Latex is a type that implements the Renderer interface for LaTeX output.
|
||||
//
|
||||
// Do not create this directly, instead use the LatexRenderer function.
|
||||
type Latex struct {
|
||||
}
|
||||
|
||||
// LatexRenderer creates and configures a Latex object, which
|
||||
// satisfies the Renderer interface.
|
||||
//
|
||||
// flags is a set of LATEX_* options ORed together (currently no such options
|
||||
// are defined).
|
||||
func LatexRenderer(flags int) Renderer {
|
||||
return &Latex{}
|
||||
}
|
||||
|
||||
func (options *Latex) GetFlags() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// render code chunks using verbatim, or listings if we have a language
|
||||
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||
if lang == "" {
|
||||
out.WriteString("\n\\begin{verbatim}\n")
|
||||
} else {
|
||||
out.WriteString("\n\\begin{lstlisting}[language=")
|
||||
out.WriteString(lang)
|
||||
out.WriteString("]\n")
|
||||
}
|
||||
out.Write(text)
|
||||
if lang == "" {
|
||||
out.WriteString("\n\\end{verbatim}\n")
|
||||
} else {
|
||||
out.WriteString("\n\\end{lstlisting}\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) {
|
||||
|
||||
}
|
||||
|
||||
func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("\n\\begin{quotation}\n")
|
||||
out.Write(text)
|
||||
out.WriteString("\n\\end{quotation}\n")
|
||||
}
|
||||
|
||||
func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) {
|
||||
// a pretty lame thing to do...
|
||||
out.WriteString("\n\\begin{verbatim}\n")
|
||||
out.Write(text)
|
||||
out.WriteString("\n\\end{verbatim}\n")
|
||||
}
|
||||
|
||||
func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
||||
marker := out.Len()
|
||||
|
||||
switch level {
|
||||
case 1:
|
||||
out.WriteString("\n\\section{")
|
||||
case 2:
|
||||
out.WriteString("\n\\subsection{")
|
||||
case 3:
|
||||
out.WriteString("\n\\subsubsection{")
|
||||
case 4:
|
||||
out.WriteString("\n\\paragraph{")
|
||||
case 5:
|
||||
out.WriteString("\n\\subparagraph{")
|
||||
case 6:
|
||||
out.WriteString("\n\\textbf{")
|
||||
}
|
||||
if !text() {
|
||||
out.Truncate(marker)
|
||||
return
|
||||
}
|
||||
out.WriteString("}\n")
|
||||
}
|
||||
|
||||
func (options *Latex) HRule(out *bytes.Buffer) {
|
||||
out.WriteString("\n\\HRule\n")
|
||||
}
|
||||
|
||||
func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||
marker := out.Len()
|
||||
if flags&LIST_TYPE_ORDERED != 0 {
|
||||
out.WriteString("\n\\begin{enumerate}\n")
|
||||
} else {
|
||||
out.WriteString("\n\\begin{itemize}\n")
|
||||
}
|
||||
if !text() {
|
||||
out.Truncate(marker)
|
||||
return
|
||||
}
|
||||
if flags&LIST_TYPE_ORDERED != 0 {
|
||||
out.WriteString("\n\\end{enumerate}\n")
|
||||
} else {
|
||||
out.WriteString("\n\\end{itemize}\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
out.WriteString("\n\\item ")
|
||||
out.Write(text)
|
||||
}
|
||||
|
||||
func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||
marker := out.Len()
|
||||
out.WriteString("\n")
|
||||
if !text() {
|
||||
out.Truncate(marker)
|
||||
return
|
||||
}
|
||||
out.WriteString("\n")
|
||||
}
|
||||
|
||||
func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
|
||||
out.WriteString("\n\\begin{tabular}{")
|
||||
for _, elt := range columnData {
|
||||
switch elt {
|
||||
case TABLE_ALIGNMENT_LEFT:
|
||||
out.WriteByte('l')
|
||||
case TABLE_ALIGNMENT_RIGHT:
|
||||
out.WriteByte('r')
|
||||
default:
|
||||
out.WriteByte('c')
|
||||
}
|
||||
}
|
||||
out.WriteString("}\n")
|
||||
out.Write(header)
|
||||
out.WriteString(" \\\\\n\\hline\n")
|
||||
out.Write(body)
|
||||
out.WriteString("\n\\end{tabular}\n")
|
||||
}
|
||||
|
||||
func (options *Latex) TableRow(out *bytes.Buffer, text []byte) {
|
||||
if out.Len() > 0 {
|
||||
out.WriteString(" \\\\\n")
|
||||
}
|
||||
out.Write(text)
|
||||
}
|
||||
|
||||
func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
|
||||
if out.Len() > 0 {
|
||||
out.WriteString(" & ")
|
||||
}
|
||||
out.Write(text)
|
||||
}
|
||||
|
||||
func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) {
|
||||
if out.Len() > 0 {
|
||||
out.WriteString(" & ")
|
||||
}
|
||||
out.Write(text)
|
||||
}
|
||||
|
||||
// TODO: this
|
||||
func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) {
|
||||
|
||||
}
|
||||
|
||||
func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
|
||||
|
||||
}
|
||||
|
||||
func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
||||
out.WriteString("\\href{")
|
||||
if kind == LINK_TYPE_EMAIL {
|
||||
out.WriteString("mailto:")
|
||||
}
|
||||
out.Write(link)
|
||||
out.WriteString("}{")
|
||||
out.Write(link)
|
||||
out.WriteString("}")
|
||||
}
|
||||
|
||||
func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("\\texttt{")
|
||||
escapeSpecialChars(out, text)
|
||||
out.WriteString("}")
|
||||
}
|
||||
|
||||
func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("\\textbf{")
|
||||
out.Write(text)
|
||||
out.WriteString("}")
|
||||
}
|
||||
|
||||
func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("\\textit{")
|
||||
out.Write(text)
|
||||
out.WriteString("}")
|
||||
}
|
||||
|
||||
func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||
if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) {
|
||||
// treat it like a link
|
||||
out.WriteString("\\href{")
|
||||
out.Write(link)
|
||||
out.WriteString("}{")
|
||||
out.Write(alt)
|
||||
out.WriteString("}")
|
||||
} else {
|
||||
out.WriteString("\\includegraphics{")
|
||||
out.Write(link)
|
||||
out.WriteString("}")
|
||||
}
|
||||
}
|
||||
|
||||
func (options *Latex) LineBreak(out *bytes.Buffer) {
|
||||
out.WriteString(" \\\\\n")
|
||||
}
|
||||
|
||||
func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
|
||||
out.WriteString("\\href{")
|
||||
out.Write(link)
|
||||
out.WriteString("}{")
|
||||
out.Write(content)
|
||||
out.WriteString("}")
|
||||
}
|
||||
|
||||
func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) {
|
||||
}
|
||||
|
||||
func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("\\textbf{\\textit{")
|
||||
out.Write(text)
|
||||
out.WriteString("}}")
|
||||
}
|
||||
|
||||
func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) {
|
||||
out.WriteString("\\sout{")
|
||||
out.Write(text)
|
||||
out.WriteString("}")
|
||||
}
|
||||
|
||||
// TODO: this
|
||||
func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
|
||||
|
||||
}
|
||||
|
||||
func needsBackslash(c byte) bool {
|
||||
for _, r := range []byte("_{}%$&\\~#") {
|
||||
if c == r {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func escapeSpecialChars(out *bytes.Buffer, text []byte) {
|
||||
for i := 0; i < len(text); i++ {
|
||||
// directly copy normal characters
|
||||
org := i
|
||||
|
||||
for i < len(text) && !needsBackslash(text[i]) {
|
||||
i++
|
||||
}
|
||||
if i > org {
|
||||
out.Write(text[org:i])
|
||||
}
|
||||
|
||||
// escape a character
|
||||
if i >= len(text) {
|
||||
break
|
||||
}
|
||||
out.WriteByte('\\')
|
||||
out.WriteByte(text[i])
|
||||
}
|
||||
}
|
||||
|
||||
func (options *Latex) Entity(out *bytes.Buffer, entity []byte) {
|
||||
// TODO: convert this into a unicode character or something
|
||||
out.Write(entity)
|
||||
}
|
||||
|
||||
func (options *Latex) NormalText(out *bytes.Buffer, text []byte) {
|
||||
escapeSpecialChars(out, text)
|
||||
}
|
||||
|
||||
// header and footer
|
||||
func (options *Latex) DocumentHeader(out *bytes.Buffer) {
|
||||
out.WriteString("\\documentclass{article}\n")
|
||||
out.WriteString("\n")
|
||||
out.WriteString("\\usepackage{graphicx}\n")
|
||||
out.WriteString("\\usepackage{listings}\n")
|
||||
out.WriteString("\\usepackage[margin=1in]{geometry}\n")
|
||||
out.WriteString("\\usepackage[utf8]{inputenc}\n")
|
||||
out.WriteString("\\usepackage{verbatim}\n")
|
||||
out.WriteString("\\usepackage[normalem]{ulem}\n")
|
||||
out.WriteString("\\usepackage{hyperref}\n")
|
||||
out.WriteString("\n")
|
||||
out.WriteString("\\hypersetup{colorlinks,%\n")
|
||||
out.WriteString(" citecolor=black,%\n")
|
||||
out.WriteString(" filecolor=black,%\n")
|
||||
out.WriteString(" linkcolor=black,%\n")
|
||||
out.WriteString(" urlcolor=black,%\n")
|
||||
out.WriteString(" pdfstartview=FitH,%\n")
|
||||
out.WriteString(" breaklinks=true,%\n")
|
||||
out.WriteString(" pdfauthor={Blackfriday Markdown Processor v")
|
||||
out.WriteString(VERSION)
|
||||
out.WriteString("}}\n")
|
||||
out.WriteString("\n")
|
||||
out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")
|
||||
out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n")
|
||||
out.WriteString("\\parindent=0pt\n")
|
||||
out.WriteString("\n")
|
||||
out.WriteString("\\begin{document}\n")
|
||||
}
|
||||
|
||||
func (options *Latex) DocumentFooter(out *bytes.Buffer) {
|
||||
out.WriteString("\n\\end{document}\n")
|
||||
}
|
926
vendor/github.com/russross/blackfriday/markdown.go
generated
vendored
Normal file
926
vendor/github.com/russross/blackfriday/markdown.go
generated
vendored
Normal file
@ -0,0 +1,926 @@
|
||||
//
|
||||
// Blackfriday Markdown Processor
|
||||
// Available at http://github.com/russross/blackfriday
|
||||
//
|
||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||
// Distributed under the Simplified BSD License.
|
||||
// See README.md for details.
|
||||
//
|
||||
|
||||
//
|
||||
//
|
||||
// Markdown parsing and processing
|
||||
//
|
||||
//
|
||||
|
||||
// Blackfriday markdown processor.
|
||||
//
|
||||
// Translates plain text with simple formatting rules into HTML or LaTeX.
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const VERSION = "1.5"
|
||||
|
||||
// These are the supported markdown parsing extensions.
|
||||
// OR these values together to select multiple extensions.
|
||||
const (
|
||||
EXTENSION_NO_INTRA_EMPHASIS = 1 << iota // ignore emphasis markers inside words
|
||||
EXTENSION_TABLES // render tables
|
||||
EXTENSION_FENCED_CODE // render fenced code blocks
|
||||
EXTENSION_AUTOLINK // detect embedded URLs that are not explicitly marked
|
||||
EXTENSION_STRIKETHROUGH // strikethrough text using ~~test~~
|
||||
EXTENSION_LAX_HTML_BLOCKS // loosen up HTML block parsing rules
|
||||
EXTENSION_SPACE_HEADERS // be strict about prefix header rules
|
||||
EXTENSION_HARD_LINE_BREAK // translate newlines into line breaks
|
||||
EXTENSION_TAB_SIZE_EIGHT // expand tabs to eight spaces instead of four
|
||||
EXTENSION_FOOTNOTES // Pandoc-style footnotes
|
||||
EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
|
||||
EXTENSION_HEADER_IDS // specify header IDs with {#id}
|
||||
EXTENSION_TITLEBLOCK // Titleblock ala pandoc
|
||||
EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text
|
||||
EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks
|
||||
EXTENSION_DEFINITION_LISTS // render definition lists
|
||||
|
||||
commonHtmlFlags = 0 |
|
||||
HTML_USE_XHTML |
|
||||
HTML_USE_SMARTYPANTS |
|
||||
HTML_SMARTYPANTS_FRACTIONS |
|
||||
HTML_SMARTYPANTS_DASHES |
|
||||
HTML_SMARTYPANTS_LATEX_DASHES
|
||||
|
||||
commonExtensions = 0 |
|
||||
EXTENSION_NO_INTRA_EMPHASIS |
|
||||
EXTENSION_TABLES |
|
||||
EXTENSION_FENCED_CODE |
|
||||
EXTENSION_AUTOLINK |
|
||||
EXTENSION_STRIKETHROUGH |
|
||||
EXTENSION_SPACE_HEADERS |
|
||||
EXTENSION_HEADER_IDS |
|
||||
EXTENSION_BACKSLASH_LINE_BREAK |
|
||||
EXTENSION_DEFINITION_LISTS
|
||||
)
|
||||
|
||||
// These are the possible flag values for the link renderer.
|
||||
// Only a single one of these values will be used; they are not ORed together.
|
||||
// These are mostly of interest if you are writing a new output format.
|
||||
const (
|
||||
LINK_TYPE_NOT_AUTOLINK = iota
|
||||
LINK_TYPE_NORMAL
|
||||
LINK_TYPE_EMAIL
|
||||
)
|
||||
|
||||
// These are the possible flag values for the ListItem renderer.
|
||||
// Multiple flag values may be ORed together.
|
||||
// These are mostly of interest if you are writing a new output format.
|
||||
const (
|
||||
LIST_TYPE_ORDERED = 1 << iota
|
||||
LIST_TYPE_DEFINITION
|
||||
LIST_TYPE_TERM
|
||||
LIST_ITEM_CONTAINS_BLOCK
|
||||
LIST_ITEM_BEGINNING_OF_LIST
|
||||
LIST_ITEM_END_OF_LIST
|
||||
)
|
||||
|
||||
// These are the possible flag values for the table cell renderer.
|
||||
// Only a single one of these values will be used; they are not ORed together.
|
||||
// These are mostly of interest if you are writing a new output format.
|
||||
const (
|
||||
TABLE_ALIGNMENT_LEFT = 1 << iota
|
||||
TABLE_ALIGNMENT_RIGHT
|
||||
TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT)
|
||||
)
|
||||
|
||||
// The size of a tab stop.
|
||||
const (
|
||||
TAB_SIZE_DEFAULT = 4
|
||||
TAB_SIZE_EIGHT = 8
|
||||
)
|
||||
|
||||
// blockTags is a set of tags that are recognized as HTML block tags.
|
||||
// Any of these can be included in markdown text without special escaping.
|
||||
var blockTags = map[string]struct{}{
|
||||
"blockquote": {},
|
||||
"del": {},
|
||||
"div": {},
|
||||
"dl": {},
|
||||
"fieldset": {},
|
||||
"form": {},
|
||||
"h1": {},
|
||||
"h2": {},
|
||||
"h3": {},
|
||||
"h4": {},
|
||||
"h5": {},
|
||||
"h6": {},
|
||||
"iframe": {},
|
||||
"ins": {},
|
||||
"math": {},
|
||||
"noscript": {},
|
||||
"ol": {},
|
||||
"pre": {},
|
||||
"p": {},
|
||||
"script": {},
|
||||
"style": {},
|
||||
"table": {},
|
||||
"ul": {},
|
||||
|
||||
// HTML5
|
||||
"address": {},
|
||||
"article": {},
|
||||
"aside": {},
|
||||
"canvas": {},
|
||||
"figcaption": {},
|
||||
"figure": {},
|
||||
"footer": {},
|
||||
"header": {},
|
||||
"hgroup": {},
|
||||
"main": {},
|
||||
"nav": {},
|
||||
"output": {},
|
||||
"progress": {},
|
||||
"section": {},
|
||||
"video": {},
|
||||
}
|
||||
|
||||
// Renderer is the rendering interface.
|
||||
// This is mostly of interest if you are implementing a new rendering format.
|
||||
//
|
||||
// When a byte slice is provided, it contains the (rendered) contents of the
|
||||
// element.
|
||||
//
|
||||
// When a callback is provided instead, it will write the contents of the
|
||||
// respective element directly to the output buffer and return true on success.
|
||||
// If the callback returns false, the rendering function should reset the
|
||||
// output buffer as though it had never been called.
|
||||
//
|
||||
// Currently Html and Latex implementations are provided
|
||||
type Renderer interface {
|
||||
// block-level callbacks
|
||||
BlockCode(out *bytes.Buffer, text []byte, lang string)
|
||||
BlockQuote(out *bytes.Buffer, text []byte)
|
||||
BlockHtml(out *bytes.Buffer, text []byte)
|
||||
Header(out *bytes.Buffer, text func() bool, level int, id string)
|
||||
HRule(out *bytes.Buffer)
|
||||
List(out *bytes.Buffer, text func() bool, flags int)
|
||||
ListItem(out *bytes.Buffer, text []byte, flags int)
|
||||
Paragraph(out *bytes.Buffer, text func() bool)
|
||||
Table(out *bytes.Buffer, header []byte, body []byte, columnData []int)
|
||||
TableRow(out *bytes.Buffer, text []byte)
|
||||
TableHeaderCell(out *bytes.Buffer, text []byte, flags int)
|
||||
TableCell(out *bytes.Buffer, text []byte, flags int)
|
||||
Footnotes(out *bytes.Buffer, text func() bool)
|
||||
FootnoteItem(out *bytes.Buffer, name, text []byte, flags int)
|
||||
TitleBlock(out *bytes.Buffer, text []byte)
|
||||
|
||||
// Span-level callbacks
|
||||
AutoLink(out *bytes.Buffer, link []byte, kind int)
|
||||
CodeSpan(out *bytes.Buffer, text []byte)
|
||||
DoubleEmphasis(out *bytes.Buffer, text []byte)
|
||||
Emphasis(out *bytes.Buffer, text []byte)
|
||||
Image(out *bytes.Buffer, link []byte, title []byte, alt []byte)
|
||||
LineBreak(out *bytes.Buffer)
|
||||
Link(out *bytes.Buffer, link []byte, title []byte, content []byte)
|
||||
RawHtmlTag(out *bytes.Buffer, tag []byte)
|
||||
TripleEmphasis(out *bytes.Buffer, text []byte)
|
||||
StrikeThrough(out *bytes.Buffer, text []byte)
|
||||
FootnoteRef(out *bytes.Buffer, ref []byte, id int)
|
||||
|
||||
// Low-level callbacks
|
||||
Entity(out *bytes.Buffer, entity []byte)
|
||||
NormalText(out *bytes.Buffer, text []byte)
|
||||
|
||||
// Header and footer
|
||||
DocumentHeader(out *bytes.Buffer)
|
||||
DocumentFooter(out *bytes.Buffer)
|
||||
|
||||
GetFlags() int
|
||||
}
|
||||
|
||||
// Callback functions for inline parsing. One such function is defined
|
||||
// for each character that triggers a response when parsing inline data.
|
||||
type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int
|
||||
|
||||
// Parser holds runtime state used by the parser.
|
||||
// This is constructed by the Markdown function.
|
||||
type parser struct {
|
||||
r Renderer
|
||||
refOverride ReferenceOverrideFunc
|
||||
refs map[string]*reference
|
||||
inlineCallback [256]inlineParser
|
||||
flags int
|
||||
nesting int
|
||||
maxNesting int
|
||||
insideLink bool
|
||||
|
||||
// Footnotes need to be ordered as well as available to quickly check for
|
||||
// presence. If a ref is also a footnote, it's stored both in refs and here
|
||||
// in notes. Slice is nil if footnotes not enabled.
|
||||
notes []*reference
|
||||
}
|
||||
|
||||
func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
||||
if p.refOverride != nil {
|
||||
r, overridden := p.refOverride(refid)
|
||||
if overridden {
|
||||
if r == nil {
|
||||
return nil, false
|
||||
}
|
||||
return &reference{
|
||||
link: []byte(r.Link),
|
||||
title: []byte(r.Title),
|
||||
noteId: 0,
|
||||
hasBlock: false,
|
||||
text: []byte(r.Text)}, true
|
||||
}
|
||||
}
|
||||
// refs are case insensitive
|
||||
ref, found = p.refs[strings.ToLower(refid)]
|
||||
return ref, found
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Public interface
|
||||
//
|
||||
//
|
||||
|
||||
// Reference represents the details of a link.
|
||||
// See the documentation in Options for more details on use-case.
|
||||
type Reference struct {
|
||||
// Link is usually the URL the reference points to.
|
||||
Link string
|
||||
// Title is the alternate text describing the link in more detail.
|
||||
Title string
|
||||
// Text is the optional text to override the ref with if the syntax used was
|
||||
// [refid][]
|
||||
Text string
|
||||
}
|
||||
|
||||
// ReferenceOverrideFunc is expected to be called with a reference string and
|
||||
// return either a valid Reference type that the reference string maps to or
|
||||
// nil. If overridden is false, the default reference logic will be executed.
|
||||
// See the documentation in Options for more details on use-case.
|
||||
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
|
||||
|
||||
// Options represents configurable overrides and callbacks (in addition to the
|
||||
// extension flag set) for configuring a Markdown parse.
|
||||
type Options struct {
|
||||
// Extensions is a flag set of bit-wise ORed extension bits. See the
|
||||
// EXTENSION_* flags defined in this package.
|
||||
Extensions int
|
||||
|
||||
// ReferenceOverride is an optional function callback that is called every
|
||||
// time a reference is resolved.
|
||||
//
|
||||
// In Markdown, the link reference syntax can be made to resolve a link to
|
||||
// a reference instead of an inline URL, in one of the following ways:
|
||||
//
|
||||
// * [link text][refid]
|
||||
// * [refid][]
|
||||
//
|
||||
// Usually, the refid is defined at the bottom of the Markdown document. If
|
||||
// this override function is provided, the refid is passed to the override
|
||||
// function first, before consulting the defined refids at the bottom. If
|
||||
// the override function indicates an override did not occur, the refids at
|
||||
// the bottom will be used to fill in the link details.
|
||||
ReferenceOverride ReferenceOverrideFunc
|
||||
}
|
||||
|
||||
// MarkdownBasic is a convenience function for simple rendering.
|
||||
// It processes markdown input with no extensions enabled.
|
||||
func MarkdownBasic(input []byte) []byte {
|
||||
// set up the HTML renderer
|
||||
htmlFlags := HTML_USE_XHTML
|
||||
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||
|
||||
// set up the parser
|
||||
return MarkdownOptions(input, renderer, Options{Extensions: 0})
|
||||
}
|
||||
|
||||
// Call Markdown with most useful extensions enabled
|
||||
// MarkdownCommon is a convenience function for simple rendering.
|
||||
// It processes markdown input with common extensions enabled, including:
|
||||
//
|
||||
// * Smartypants processing with smart fractions and LaTeX dashes
|
||||
//
|
||||
// * Intra-word emphasis suppression
|
||||
//
|
||||
// * Tables
|
||||
//
|
||||
// * Fenced code blocks
|
||||
//
|
||||
// * Autolinking
|
||||
//
|
||||
// * Strikethrough support
|
||||
//
|
||||
// * Strict header parsing
|
||||
//
|
||||
// * Custom Header IDs
|
||||
func MarkdownCommon(input []byte) []byte {
|
||||
// set up the HTML renderer
|
||||
renderer := HtmlRenderer(commonHtmlFlags, "", "")
|
||||
return MarkdownOptions(input, renderer, Options{
|
||||
Extensions: commonExtensions})
|
||||
}
|
||||
|
||||
// Markdown is the main rendering function.
|
||||
// It parses and renders a block of markdown-encoded text.
|
||||
// The supplied Renderer is used to format the output, and extensions dictates
|
||||
// which non-standard extensions are enabled.
|
||||
//
|
||||
// To use the supplied Html or LaTeX renderers, see HtmlRenderer and
|
||||
// LatexRenderer, respectively.
|
||||
func Markdown(input []byte, renderer Renderer, extensions int) []byte {
|
||||
return MarkdownOptions(input, renderer, Options{
|
||||
Extensions: extensions})
|
||||
}
|
||||
|
||||
// MarkdownOptions is just like Markdown but takes additional options through
|
||||
// the Options struct.
|
||||
func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
|
||||
// no point in parsing if we can't render
|
||||
if renderer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
extensions := opts.Extensions
|
||||
|
||||
// fill in the render structure
|
||||
p := new(parser)
|
||||
p.r = renderer
|
||||
p.flags = extensions
|
||||
p.refOverride = opts.ReferenceOverride
|
||||
p.refs = make(map[string]*reference)
|
||||
p.maxNesting = 16
|
||||
p.insideLink = false
|
||||
|
||||
// register inline parsers
|
||||
p.inlineCallback['*'] = emphasis
|
||||
p.inlineCallback['_'] = emphasis
|
||||
if extensions&EXTENSION_STRIKETHROUGH != 0 {
|
||||
p.inlineCallback['~'] = emphasis
|
||||
}
|
||||
p.inlineCallback['`'] = codeSpan
|
||||
p.inlineCallback['\n'] = lineBreak
|
||||
p.inlineCallback['['] = link
|
||||
p.inlineCallback['<'] = leftAngle
|
||||
p.inlineCallback['\\'] = escape
|
||||
p.inlineCallback['&'] = entity
|
||||
|
||||
if extensions&EXTENSION_AUTOLINK != 0 {
|
||||
p.inlineCallback[':'] = autoLink
|
||||
}
|
||||
|
||||
if extensions&EXTENSION_FOOTNOTES != 0 {
|
||||
p.notes = make([]*reference, 0)
|
||||
}
|
||||
|
||||
first := firstPass(p, input)
|
||||
second := secondPass(p, first)
|
||||
return second
|
||||
}
|
||||
|
||||
// first pass:
|
||||
// - normalize newlines
|
||||
// - extract references (outside of fenced code blocks)
|
||||
// - expand tabs (outside of fenced code blocks)
|
||||
// - copy everything else
|
||||
func firstPass(p *parser, input []byte) []byte {
|
||||
var out bytes.Buffer
|
||||
tabSize := TAB_SIZE_DEFAULT
|
||||
if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
|
||||
tabSize = TAB_SIZE_EIGHT
|
||||
}
|
||||
beg := 0
|
||||
lastFencedCodeBlockEnd := 0
|
||||
for beg < len(input) {
|
||||
// Find end of this line, then process the line.
|
||||
end := beg
|
||||
for end < len(input) && input[end] != '\n' && input[end] != '\r' {
|
||||
end++
|
||||
}
|
||||
|
||||
if p.flags&EXTENSION_FENCED_CODE != 0 {
|
||||
// track fenced code block boundaries to suppress tab expansion
|
||||
// and reference extraction inside them:
|
||||
if beg >= lastFencedCodeBlockEnd {
|
||||
if i := p.fencedCodeBlock(&out, input[beg:], false); i > 0 {
|
||||
lastFencedCodeBlockEnd = beg + i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the line body if present
|
||||
if end > beg {
|
||||
if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
|
||||
out.Write(input[beg:end])
|
||||
} else if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 {
|
||||
beg += refEnd
|
||||
continue
|
||||
} else {
|
||||
expandTabs(&out, input[beg:end], tabSize)
|
||||
}
|
||||
}
|
||||
|
||||
if end < len(input) && input[end] == '\r' {
|
||||
end++
|
||||
}
|
||||
if end < len(input) && input[end] == '\n' {
|
||||
end++
|
||||
}
|
||||
out.WriteByte('\n')
|
||||
|
||||
beg = end
|
||||
}
|
||||
|
||||
// empty input?
|
||||
if out.Len() == 0 {
|
||||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
// second pass: actual rendering
|
||||
func secondPass(p *parser, input []byte) []byte {
|
||||
var output bytes.Buffer
|
||||
|
||||
p.r.DocumentHeader(&output)
|
||||
p.block(&output, input)
|
||||
|
||||
if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 {
|
||||
p.r.Footnotes(&output, func() bool {
|
||||
flags := LIST_ITEM_BEGINNING_OF_LIST
|
||||
for i := 0; i < len(p.notes); i += 1 {
|
||||
ref := p.notes[i]
|
||||
var buf bytes.Buffer
|
||||
if ref.hasBlock {
|
||||
flags |= LIST_ITEM_CONTAINS_BLOCK
|
||||
p.block(&buf, ref.title)
|
||||
} else {
|
||||
p.inline(&buf, ref.title)
|
||||
}
|
||||
p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags)
|
||||
flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
p.r.DocumentFooter(&output)
|
||||
|
||||
if p.nesting != 0 {
|
||||
panic("Nesting level did not end at zero")
|
||||
}
|
||||
|
||||
return output.Bytes()
|
||||
}
|
||||
|
||||
//
|
||||
// Link references
|
||||
//
|
||||
// This section implements support for references that (usually) appear
|
||||
// as footnotes in a document, and can be referenced anywhere in the document.
|
||||
// The basic format is:
|
||||
//
|
||||
// [1]: http://www.google.com/ "Google"
|
||||
// [2]: http://www.github.com/ "Github"
|
||||
//
|
||||
// Anywhere in the document, the reference can be linked by referring to its
|
||||
// label, i.e., 1 and 2 in this example, as in:
|
||||
//
|
||||
// This library is hosted on [Github][2], a git hosting site.
|
||||
//
|
||||
// Actual footnotes as specified in Pandoc and supported by some other Markdown
|
||||
// libraries such as php-markdown are also taken care of. They look like this:
|
||||
//
|
||||
// This sentence needs a bit of further explanation.[^note]
|
||||
//
|
||||
// [^note]: This is the explanation.
|
||||
//
|
||||
// Footnotes should be placed at the end of the document in an ordered list.
|
||||
// Inline footnotes such as:
|
||||
//
|
||||
// Inline footnotes^[Not supported.] also exist.
|
||||
//
|
||||
// are not yet supported.
|
||||
|
||||
// References are parsed and stored in this struct.
|
||||
type reference struct {
|
||||
link []byte
|
||||
title []byte
|
||||
noteId int // 0 if not a footnote ref
|
||||
hasBlock bool
|
||||
text []byte
|
||||
}
|
||||
|
||||
func (r *reference) String() string {
|
||||
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteId: %d, hasBlock: %v}",
|
||||
r.link, r.title, r.text, r.noteId, r.hasBlock)
|
||||
}
|
||||
|
||||
// Check whether or not data starts with a reference link.
|
||||
// If so, it is parsed and stored in the list of references
|
||||
// (in the render struct).
|
||||
// Returns the number of bytes to skip to move past it,
|
||||
// or zero if the first line is not a reference.
|
||||
func isReference(p *parser, data []byte, tabSize int) int {
|
||||
// up to 3 optional leading spaces
|
||||
if len(data) < 4 {
|
||||
return 0
|
||||
}
|
||||
i := 0
|
||||
for i < 3 && data[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
|
||||
noteId := 0
|
||||
|
||||
// id part: anything but a newline between brackets
|
||||
if data[i] != '[' {
|
||||
return 0
|
||||
}
|
||||
i++
|
||||
if p.flags&EXTENSION_FOOTNOTES != 0 {
|
||||
if i < len(data) && data[i] == '^' {
|
||||
// we can set it to anything here because the proper noteIds will
|
||||
// be assigned later during the second pass. It just has to be != 0
|
||||
noteId = 1
|
||||
i++
|
||||
}
|
||||
}
|
||||
idOffset := i
|
||||
for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
|
||||
i++
|
||||
}
|
||||
if i >= len(data) || data[i] != ']' {
|
||||
return 0
|
||||
}
|
||||
idEnd := i
|
||||
|
||||
// spacer: colon (space | tab)* newline? (space | tab)*
|
||||
i++
|
||||
if i >= len(data) || data[i] != ':' {
|
||||
return 0
|
||||
}
|
||||
i++
|
||||
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
|
||||
i++
|
||||
if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
|
||||
i++
|
||||
}
|
||||
}
|
||||
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
if i >= len(data) {
|
||||
return 0
|
||||
}
|
||||
|
||||
var (
|
||||
linkOffset, linkEnd int
|
||||
titleOffset, titleEnd int
|
||||
lineEnd int
|
||||
raw []byte
|
||||
hasBlock bool
|
||||
)
|
||||
|
||||
if p.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 {
|
||||
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
|
||||
lineEnd = linkEnd
|
||||
} else {
|
||||
linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
|
||||
}
|
||||
if lineEnd == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// a valid ref has been found
|
||||
|
||||
ref := &reference{
|
||||
noteId: noteId,
|
||||
hasBlock: hasBlock,
|
||||
}
|
||||
|
||||
if noteId > 0 {
|
||||
// reusing the link field for the id since footnotes don't have links
|
||||
ref.link = data[idOffset:idEnd]
|
||||
// if footnote, it's not really a title, it's the contained text
|
||||
ref.title = raw
|
||||
} else {
|
||||
ref.link = data[linkOffset:linkEnd]
|
||||
ref.title = data[titleOffset:titleEnd]
|
||||
}
|
||||
|
||||
// id matches are case-insensitive
|
||||
id := string(bytes.ToLower(data[idOffset:idEnd]))
|
||||
|
||||
p.refs[id] = ref
|
||||
|
||||
return lineEnd
|
||||
}
|
||||
|
||||
func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
|
||||
// link: whitespace-free sequence, optionally between angle brackets
|
||||
if data[i] == '<' {
|
||||
i++
|
||||
}
|
||||
linkOffset = i
|
||||
if i == len(data) {
|
||||
return
|
||||
}
|
||||
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||
i++
|
||||
}
|
||||
linkEnd = i
|
||||
if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
|
||||
linkOffset++
|
||||
linkEnd--
|
||||
}
|
||||
|
||||
// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
|
||||
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
|
||||
return
|
||||
}
|
||||
|
||||
// compute end-of-line
|
||||
if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
|
||||
lineEnd = i
|
||||
}
|
||||
if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
|
||||
lineEnd++
|
||||
}
|
||||
|
||||
// optional (space|tab)* spacer after a newline
|
||||
if lineEnd > 0 {
|
||||
i = lineEnd + 1
|
||||
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// optional title: any non-newline sequence enclosed in '"() alone on its line
|
||||
if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
|
||||
i++
|
||||
titleOffset = i
|
||||
|
||||
// look for EOL
|
||||
for i < len(data) && data[i] != '\n' && data[i] != '\r' {
|
||||
i++
|
||||
}
|
||||
if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
|
||||
titleEnd = i + 1
|
||||
} else {
|
||||
titleEnd = i
|
||||
}
|
||||
|
||||
// step back
|
||||
i--
|
||||
for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
|
||||
i--
|
||||
}
|
||||
if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
|
||||
lineEnd = titleEnd
|
||||
titleEnd = i
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The first bit of this logic is the same as (*parser).listItem, but the rest
|
||||
// is much simpler. This function simply finds the entire block and shifts it
|
||||
// over by one tab if it is indeed a block (just returns the line if it's not).
|
||||
// blockEnd is the end of the section in the input buffer, and contents is the
|
||||
// extracted text that was shifted over one tab. It will need to be rendered at
|
||||
// the end of the document.
|
||||
func scanFootnote(p *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
|
||||
if i == 0 || len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// skip leading whitespace on first line
|
||||
for i < len(data) && data[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
|
||||
blockStart = i
|
||||
|
||||
// find the end of the line
|
||||
blockEnd = i
|
||||
for i < len(data) && data[i-1] != '\n' {
|
||||
i++
|
||||
}
|
||||
|
||||
// get working buffer
|
||||
var raw bytes.Buffer
|
||||
|
||||
// put the first line into the working buffer
|
||||
raw.Write(data[blockEnd:i])
|
||||
blockEnd = i
|
||||
|
||||
// process the following lines
|
||||
containsBlankLine := false
|
||||
|
||||
gatherLines:
|
||||
for blockEnd < len(data) {
|
||||
i++
|
||||
|
||||
// find the end of this line
|
||||
for i < len(data) && data[i-1] != '\n' {
|
||||
i++
|
||||
}
|
||||
|
||||
// if it is an empty line, guess that it is part of this item
|
||||
// and move on to the next line
|
||||
if p.isEmpty(data[blockEnd:i]) > 0 {
|
||||
containsBlankLine = true
|
||||
blockEnd = i
|
||||
continue
|
||||
}
|
||||
|
||||
n := 0
|
||||
if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
|
||||
// this is the end of the block.
|
||||
// we don't want to include this last line in the index.
|
||||
break gatherLines
|
||||
}
|
||||
|
||||
// if there were blank lines before this one, insert a new one now
|
||||
if containsBlankLine {
|
||||
raw.WriteByte('\n')
|
||||
containsBlankLine = false
|
||||
}
|
||||
|
||||
// get rid of that first tab, write to buffer
|
||||
raw.Write(data[blockEnd+n : i])
|
||||
hasBlock = true
|
||||
|
||||
blockEnd = i
|
||||
}
|
||||
|
||||
if data[blockEnd-1] != '\n' {
|
||||
raw.WriteByte('\n')
|
||||
}
|
||||
|
||||
contents = raw.Bytes()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Miscellaneous helper functions
|
||||
//
|
||||
//
|
||||
|
||||
// Test if a character is a punctuation symbol.
|
||||
// Taken from a private function in regexp in the stdlib.
|
||||
func ispunct(c byte) bool {
|
||||
for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
|
||||
if c == r {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Test if a character is a whitespace character.
|
||||
func isspace(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
|
||||
}
|
||||
|
||||
// Test if a character is letter.
|
||||
func isletter(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||
}
|
||||
|
||||
// Test if a character is a letter or a digit.
|
||||
// TODO: check when this is looking for ASCII alnum and when it should use unicode
|
||||
func isalnum(c byte) bool {
|
||||
return (c >= '0' && c <= '9') || isletter(c)
|
||||
}
|
||||
|
||||
// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
|
||||
// always ends output with a newline
|
||||
func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
|
||||
// first, check for common cases: no tabs, or only tabs at beginning of line
|
||||
i, prefix := 0, 0
|
||||
slowcase := false
|
||||
for i = 0; i < len(line); i++ {
|
||||
if line[i] == '\t' {
|
||||
if prefix == i {
|
||||
prefix++
|
||||
} else {
|
||||
slowcase = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no need to decode runes if all tabs are at the beginning of the line
|
||||
if !slowcase {
|
||||
for i = 0; i < prefix*tabSize; i++ {
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
out.Write(line[prefix:])
|
||||
return
|
||||
}
|
||||
|
||||
// the slow case: we need to count runes to figure out how
|
||||
// many spaces to insert for each tab
|
||||
column := 0
|
||||
i = 0
|
||||
for i < len(line) {
|
||||
start := i
|
||||
for i < len(line) && line[i] != '\t' {
|
||||
_, size := utf8.DecodeRune(line[i:])
|
||||
i += size
|
||||
column++
|
||||
}
|
||||
|
||||
if i > start {
|
||||
out.Write(line[start:i])
|
||||
}
|
||||
|
||||
if i >= len(line) {
|
||||
break
|
||||
}
|
||||
|
||||
for {
|
||||
out.WriteByte(' ')
|
||||
column++
|
||||
if column%tabSize == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// Find if a line counts as indented or not.
|
||||
// Returns number of characters the indent is (0 = not indented).
|
||||
func isIndented(data []byte, indentSize int) int {
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
if data[0] == '\t' {
|
||||
return 1
|
||||
}
|
||||
if len(data) < indentSize {
|
||||
return 0
|
||||
}
|
||||
for i := 0; i < indentSize; i++ {
|
||||
if data[i] != ' ' {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return indentSize
|
||||
}
|
||||
|
||||
// Create a url-safe slug for fragments
|
||||
func slugify(in []byte) []byte {
|
||||
if len(in) == 0 {
|
||||
return in
|
||||
}
|
||||
out := make([]byte, 0, len(in))
|
||||
sym := false
|
||||
|
||||
for _, ch := range in {
|
||||
if isalnum(ch) {
|
||||
sym = false
|
||||
out = append(out, ch)
|
||||
} else if sym {
|
||||
continue
|
||||
} else {
|
||||
out = append(out, '-')
|
||||
sym = true
|
||||
}
|
||||
}
|
||||
var a, b int
|
||||
var ch byte
|
||||
for a, ch = range out {
|
||||
if ch != '-' {
|
||||
break
|
||||
}
|
||||
}
|
||||
for b = len(out) - 1; b > 0; b-- {
|
||||
if out[b] != '-' {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out[a : b+1]
|
||||
}
|
400
vendor/github.com/russross/blackfriday/smartypants.go
generated
vendored
Normal file
400
vendor/github.com/russross/blackfriday/smartypants.go
generated
vendored
Normal file
@ -0,0 +1,400 @@
|
||||
//
|
||||
// Blackfriday Markdown Processor
|
||||
// Available at http://github.com/russross/blackfriday
|
||||
//
|
||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||
// Distributed under the Simplified BSD License.
|
||||
// See README.md for details.
|
||||
//
|
||||
|
||||
//
|
||||
//
|
||||
// SmartyPants rendering
|
||||
//
|
||||
//
|
||||
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type smartypantsData struct {
|
||||
inSingleQuote bool
|
||||
inDoubleQuote bool
|
||||
}
|
||||
|
||||
func wordBoundary(c byte) bool {
|
||||
return c == 0 || isspace(c) || ispunct(c)
|
||||
}
|
||||
|
||||
func tolower(c byte) byte {
|
||||
if c >= 'A' && c <= 'Z' {
|
||||
return c - 'A' + 'a'
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func isdigit(c byte) bool {
|
||||
return c >= '0' && c <= '9'
|
||||
}
|
||||
|
||||
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool {
|
||||
// edge of the buffer is likely to be a tag that we don't get to see,
|
||||
// so we treat it like text sometimes
|
||||
|
||||
// enumerate all sixteen possibilities for (previousChar, nextChar)
|
||||
// each can be one of {0, space, punct, other}
|
||||
switch {
|
||||
case previousChar == 0 && nextChar == 0:
|
||||
// context is not any help here, so toggle
|
||||
*isOpen = !*isOpen
|
||||
case isspace(previousChar) && nextChar == 0:
|
||||
// [ "] might be [ "<code>foo...]
|
||||
*isOpen = true
|
||||
case ispunct(previousChar) && nextChar == 0:
|
||||
// [!"] hmm... could be [Run!"] or [("<code>...]
|
||||
*isOpen = false
|
||||
case /* isnormal(previousChar) && */ nextChar == 0:
|
||||
// [a"] is probably a close
|
||||
*isOpen = false
|
||||
case previousChar == 0 && isspace(nextChar):
|
||||
// [" ] might be [...foo</code>" ]
|
||||
*isOpen = false
|
||||
case isspace(previousChar) && isspace(nextChar):
|
||||
// [ " ] context is not any help here, so toggle
|
||||
*isOpen = !*isOpen
|
||||
case ispunct(previousChar) && isspace(nextChar):
|
||||
// [!" ] is probably a close
|
||||
*isOpen = false
|
||||
case /* isnormal(previousChar) && */ isspace(nextChar):
|
||||
// [a" ] this is one of the easy cases
|
||||
*isOpen = false
|
||||
case previousChar == 0 && ispunct(nextChar):
|
||||
// ["!] hmm... could be ["$1.95] or [</code>"!...]
|
||||
*isOpen = false
|
||||
case isspace(previousChar) && ispunct(nextChar):
|
||||
// [ "!] looks more like [ "$1.95]
|
||||
*isOpen = true
|
||||
case ispunct(previousChar) && ispunct(nextChar):
|
||||
// [!"!] context is not any help here, so toggle
|
||||
*isOpen = !*isOpen
|
||||
case /* isnormal(previousChar) && */ ispunct(nextChar):
|
||||
// [a"!] is probably a close
|
||||
*isOpen = false
|
||||
case previousChar == 0 /* && isnormal(nextChar) */ :
|
||||
// ["a] is probably an open
|
||||
*isOpen = true
|
||||
case isspace(previousChar) /* && isnormal(nextChar) */ :
|
||||
// [ "a] this is one of the easy cases
|
||||
*isOpen = true
|
||||
case ispunct(previousChar) /* && isnormal(nextChar) */ :
|
||||
// [!"a] is probably an open
|
||||
*isOpen = true
|
||||
default:
|
||||
// [a'b] maybe a contraction?
|
||||
*isOpen = false
|
||||
}
|
||||
|
||||
out.WriteByte('&')
|
||||
if *isOpen {
|
||||
out.WriteByte('l')
|
||||
} else {
|
||||
out.WriteByte('r')
|
||||
}
|
||||
out.WriteByte(quote)
|
||||
out.WriteString("quo;")
|
||||
return true
|
||||
}
|
||||
|
||||
func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
if len(text) >= 2 {
|
||||
t1 := tolower(text[1])
|
||||
|
||||
if t1 == '\'' {
|
||||
nextChar := byte(0)
|
||||
if len(text) >= 3 {
|
||||
nextChar = text[2]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) {
|
||||
out.WriteString("’")
|
||||
return 0
|
||||
}
|
||||
|
||||
if len(text) >= 3 {
|
||||
t2 := tolower(text[2])
|
||||
|
||||
if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) &&
|
||||
(len(text) < 4 || wordBoundary(text[3])) {
|
||||
out.WriteString("’")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextChar := byte(0)
|
||||
if len(text) > 1 {
|
||||
nextChar = text[1]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
|
||||
return 0
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
if len(text) >= 3 {
|
||||
t1 := tolower(text[1])
|
||||
t2 := tolower(text[2])
|
||||
|
||||
if t1 == 'c' && t2 == ')' {
|
||||
out.WriteString("©")
|
||||
return 2
|
||||
}
|
||||
|
||||
if t1 == 'r' && t2 == ')' {
|
||||
out.WriteString("®")
|
||||
return 2
|
||||
}
|
||||
|
||||
if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
|
||||
out.WriteString("™")
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
if len(text) >= 2 {
|
||||
if text[1] == '-' {
|
||||
out.WriteString("—")
|
||||
return 1
|
||||
}
|
||||
|
||||
if wordBoundary(previousChar) && wordBoundary(text[1]) {
|
||||
out.WriteString("–")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
|
||||
out.WriteString("—")
|
||||
return 2
|
||||
}
|
||||
if len(text) >= 2 && text[1] == '-' {
|
||||
out.WriteString("–")
|
||||
return 1
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
||||
if bytes.HasPrefix(text, []byte(""")) {
|
||||
nextChar := byte(0)
|
||||
if len(text) >= 7 {
|
||||
nextChar = text[6]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
||||
return 5
|
||||
}
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(text, []byte("�")) {
|
||||
return 3
|
||||
}
|
||||
|
||||
out.WriteByte('&')
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
return smartAmpVariant(out, smrt, previousChar, text, 'd')
|
||||
}
|
||||
|
||||
func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
return smartAmpVariant(out, smrt, previousChar, text, 'a')
|
||||
}
|
||||
|
||||
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
|
||||
out.WriteString("…")
|
||||
return 2
|
||||
}
|
||||
|
||||
if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
|
||||
out.WriteString("…")
|
||||
return 4
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
if len(text) >= 2 && text[1] == '`' {
|
||||
nextChar := byte(0)
|
||||
if len(text) >= 3 {
|
||||
nextChar = text[2]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
||||
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
|
||||
// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8)
|
||||
// and avoid changing dates like 1/23/2005 into fractions.
|
||||
numEnd := 0
|
||||
for len(text) > numEnd && isdigit(text[numEnd]) {
|
||||
numEnd++
|
||||
}
|
||||
if numEnd == 0 {
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
denStart := numEnd + 1
|
||||
if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 {
|
||||
denStart = numEnd + 3
|
||||
} else if len(text) < numEnd+2 || text[numEnd] != '/' {
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
denEnd := denStart
|
||||
for len(text) > denEnd && isdigit(text[denEnd]) {
|
||||
denEnd++
|
||||
}
|
||||
if denEnd == denStart {
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' {
|
||||
out.WriteString("<sup>")
|
||||
out.Write(text[:numEnd])
|
||||
out.WriteString("</sup>⁄<sub>")
|
||||
out.Write(text[denStart:denEnd])
|
||||
out.WriteString("</sub>")
|
||||
return denEnd - 1
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
||||
if text[0] == '1' && text[1] == '/' && text[2] == '2' {
|
||||
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
|
||||
out.WriteString("½")
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
if text[0] == '1' && text[1] == '/' && text[2] == '4' {
|
||||
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
|
||||
out.WriteString("¼")
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
if text[0] == '3' && text[1] == '/' && text[2] == '4' {
|
||||
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
|
||||
out.WriteString("¾")
|
||||
return 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteByte(text[0])
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
||||
nextChar := byte(0)
|
||||
if len(text) > 1 {
|
||||
nextChar = text[1]
|
||||
}
|
||||
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
||||
out.WriteString(""")
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd')
|
||||
}
|
||||
|
||||
func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a')
|
||||
}
|
||||
|
||||
func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
i := 0
|
||||
|
||||
for i < len(text) && text[i] != '>' {
|
||||
i++
|
||||
}
|
||||
|
||||
out.Write(text[:i+1])
|
||||
return i
|
||||
}
|
||||
|
||||
type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int
|
||||
|
||||
type smartypantsRenderer [256]smartCallback
|
||||
|
||||
func smartypants(flags int) *smartypantsRenderer {
|
||||
r := new(smartypantsRenderer)
|
||||
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
|
||||
r['"'] = smartDoubleQuote
|
||||
r['&'] = smartAmp
|
||||
} else {
|
||||
r['"'] = smartAngledDoubleQuote
|
||||
r['&'] = smartAmpAngledQuote
|
||||
}
|
||||
r['\''] = smartSingleQuote
|
||||
r['('] = smartParens
|
||||
if flags&HTML_SMARTYPANTS_DASHES != 0 {
|
||||
if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
|
||||
r['-'] = smartDash
|
||||
} else {
|
||||
r['-'] = smartDashLatex
|
||||
}
|
||||
}
|
||||
r['.'] = smartPeriod
|
||||
if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
|
||||
r['1'] = smartNumber
|
||||
r['3'] = smartNumber
|
||||
} else {
|
||||
for ch := '1'; ch <= '9'; ch++ {
|
||||
r[ch] = smartNumberGeneric
|
||||
}
|
||||
}
|
||||
r['<'] = smartLeftAngle
|
||||
r['`'] = smartBacktick
|
||||
return r
|
||||
}
|
19
vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE
generated
vendored
Normal file
19
vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2015 Dmitri Shuralyov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
29
vendor/github.com/shurcooL/sanitized_anchor_name/main.go
generated
vendored
Normal file
29
vendor/github.com/shurcooL/sanitized_anchor_name/main.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
// Package sanitized_anchor_name provides a func to create sanitized anchor names.
|
||||
//
|
||||
// Its logic can be reused by multiple packages to create interoperable anchor names
|
||||
// and links to those anchors.
|
||||
//
|
||||
// At this time, it does not try to ensure that generated anchor names
|
||||
// are unique, that responsibility falls on the caller.
|
||||
package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name"
|
||||
|
||||
import "unicode"
|
||||
|
||||
// Create returns a sanitized anchor name for the given text.
|
||||
func Create(text string) string {
|
||||
var anchorName []rune
|
||||
var futureDash = false
|
||||
for _, r := range []rune(text) {
|
||||
switch {
|
||||
case unicode.IsLetter(r) || unicode.IsNumber(r):
|
||||
if futureDash && len(anchorName) > 0 {
|
||||
anchorName = append(anchorName, '-')
|
||||
}
|
||||
futureDash = false
|
||||
anchorName = append(anchorName, unicode.ToLower(r))
|
||||
default:
|
||||
futureDash = true
|
||||
}
|
||||
}
|
||||
return string(anchorName)
|
||||
}
|
7
vendor/github.com/technoweenie/multipartstreamer/LICENSE
generated
vendored
Normal file
7
vendor/github.com/technoweenie/multipartstreamer/LICENSE
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2013-* rick olson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
31
vendor/github.com/technoweenie/multipartstreamer/examples/multipart.go
generated
vendored
Normal file
31
vendor/github.com/technoweenie/multipartstreamer/examples/multipart.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defaultPath, _ := os.Getwd()
|
||||
defaultFile := filepath.Join(defaultPath, "streamer.go")
|
||||
fullpath := flag.String("path", defaultFile, "Path to the include in the multipart data.")
|
||||
flag.Parse()
|
||||
|
||||
buffer := bytes.NewBufferString("")
|
||||
writer := multipart.NewWriter(buffer)
|
||||
|
||||
fmt.Println("Adding the file to the multipart writer")
|
||||
fileWriter, _ := writer.CreateFormFile("file", *fullpath)
|
||||
fileData, _ := os.Open(*fullpath)
|
||||
io.Copy(fileWriter, fileData)
|
||||
writer.Close()
|
||||
|
||||
fmt.Println("Writing the multipart data to a file")
|
||||
output, _ := os.Create("multiparttest")
|
||||
io.Copy(output, buffer)
|
||||
}
|
27
vendor/github.com/technoweenie/multipartstreamer/examples/streamer.go
generated
vendored
Normal file
27
vendor/github.com/technoweenie/multipartstreamer/examples/streamer.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/technoweenie/multipartstreamer"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defaultPath, _ := os.Getwd()
|
||||
defaultFile := filepath.Join(defaultPath, "streamer.go")
|
||||
fullpath := flag.String("path", defaultFile, "Path to the include in the multipart data.")
|
||||
flag.Parse()
|
||||
|
||||
ms := multipartstreamer.New()
|
||||
|
||||
fmt.Println("Adding the file to the multipart writer")
|
||||
ms.WriteFile("file", *fullpath)
|
||||
reader := ms.GetReader()
|
||||
|
||||
fmt.Println("Writing the multipart data to a file")
|
||||
file, _ := os.Create("streamtest")
|
||||
io.Copy(file, reader)
|
||||
}
|
101
vendor/github.com/technoweenie/multipartstreamer/multipartstreamer.go
generated
vendored
Normal file
101
vendor/github.com/technoweenie/multipartstreamer/multipartstreamer.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
Package multipartstreamer helps you encode large files in MIME multipart format
|
||||
without reading the entire content into memory. It uses io.MultiReader to
|
||||
combine an inner multipart.Reader with a file handle.
|
||||
*/
|
||||
package multipartstreamer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type MultipartStreamer struct {
|
||||
ContentType string
|
||||
bodyBuffer *bytes.Buffer
|
||||
bodyWriter *multipart.Writer
|
||||
closeBuffer *bytes.Buffer
|
||||
reader io.Reader
|
||||
contentLength int64
|
||||
}
|
||||
|
||||
// New initializes a new MultipartStreamer.
|
||||
func New() (m *MultipartStreamer) {
|
||||
m = &MultipartStreamer{bodyBuffer: new(bytes.Buffer)}
|
||||
|
||||
m.bodyWriter = multipart.NewWriter(m.bodyBuffer)
|
||||
boundary := m.bodyWriter.Boundary()
|
||||
m.ContentType = "multipart/form-data; boundary=" + boundary
|
||||
|
||||
closeBoundary := fmt.Sprintf("\r\n--%s--\r\n", boundary)
|
||||
m.closeBuffer = bytes.NewBufferString(closeBoundary)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WriteFields writes multiple form fields to the multipart.Writer.
|
||||
func (m *MultipartStreamer) WriteFields(fields map[string]string) error {
|
||||
var err error
|
||||
|
||||
for key, value := range fields {
|
||||
err = m.bodyWriter.WriteField(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteReader adds an io.Reader to get the content of a file. The reader is
|
||||
// not accessed until the multipart.Reader is copied to some output writer.
|
||||
func (m *MultipartStreamer) WriteReader(key, filename string, size int64, reader io.Reader) (err error) {
|
||||
m.reader = reader
|
||||
m.contentLength = size
|
||||
|
||||
_, err = m.bodyWriter.CreateFormFile(key, filename)
|
||||
return
|
||||
}
|
||||
|
||||
// WriteFile is a shortcut for adding a local file as an io.Reader.
|
||||
func (m *MultipartStreamer) WriteFile(key, filename string) error {
|
||||
fh, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stat, err := fh.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.WriteReader(key, filepath.Base(filename), stat.Size(), fh)
|
||||
}
|
||||
|
||||
// SetupRequest sets up the http.Request body, and some crucial HTTP headers.
|
||||
func (m *MultipartStreamer) SetupRequest(req *http.Request) {
|
||||
req.Body = m.GetReader()
|
||||
req.Header.Add("Content-Type", m.ContentType)
|
||||
req.ContentLength = m.Len()
|
||||
}
|
||||
|
||||
func (m *MultipartStreamer) Boundary() string {
|
||||
return m.bodyWriter.Boundary()
|
||||
}
|
||||
|
||||
// Len calculates the byte size of the multipart content.
|
||||
func (m *MultipartStreamer) Len() int64 {
|
||||
return m.contentLength + int64(m.bodyBuffer.Len()) + int64(m.closeBuffer.Len())
|
||||
}
|
||||
|
||||
// GetReader gets an io.ReadCloser for passing to an http.Request.
|
||||
func (m *MultipartStreamer) GetReader() io.ReadCloser {
|
||||
reader := io.MultiReader(m.bodyBuffer, m.reader, m.closeBuffer)
|
||||
return ioutil.NopCloser(reader)
|
||||
}
|
26
vendor/manifest
vendored
26
vendor/manifest
vendored
@ -59,6 +59,14 @@
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/go-telegram-bot-api/telegram-bot-api",
|
||||
"repository": "https://github.com/go-telegram-bot-api/telegram-bot-api",
|
||||
"vcs": "git",
|
||||
"revision": "a7f48eb2dd301356942677e65bebe0c9aef07013",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/gorilla/schema",
|
||||
"repository": "https://github.com/gorilla/schema",
|
||||
@ -87,8 +95,8 @@
|
||||
"importpath": "github.com/mattermost/platform/einterfaces",
|
||||
"repository": "https://github.com/mattermost/platform",
|
||||
"vcs": "git",
|
||||
"revision": "57f25fa59c71821cc38fd220b133aa6a40815e12",
|
||||
"branch": "release-3.4",
|
||||
"revision": "976296cd52533ff565407e55e872339cc312a0cf",
|
||||
"branch": "release-3.6",
|
||||
"path": "/einterfaces",
|
||||
"notests": true
|
||||
},
|
||||
@ -96,8 +104,8 @@
|
||||
"importpath": "github.com/mattermost/platform/model",
|
||||
"repository": "https://github.com/mattermost/platform",
|
||||
"vcs": "git",
|
||||
"revision": "57f25fa59c71821cc38fd220b133aa6a40815e12",
|
||||
"branch": "release-3.4",
|
||||
"revision": "976296cd52533ff565407e55e872339cc312a0cf",
|
||||
"branch": "release-3.6",
|
||||
"path": "/model",
|
||||
"notests": true
|
||||
},
|
||||
@ -105,7 +113,7 @@
|
||||
"importpath": "github.com/mattn/go-xmpp",
|
||||
"repository": "https://github.com/mattn/go-xmpp",
|
||||
"vcs": "git",
|
||||
"revision": "e44d1877bb457f5c3991903e9934a31e55c3a2ad",
|
||||
"revision": "f4550b5399387339df5ce4c3f88c1ef85333bdd5",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
},
|
||||
@ -166,6 +174,14 @@
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/technoweenie/multipartstreamer",
|
||||
"repository": "https://github.com/technoweenie/multipartstreamer",
|
||||
"vcs": "git",
|
||||
"revision": "a90a01d73ae432e2611d178c18367fbaa13e0154",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/thoj/go-ircevent",
|
||||
"repository": "https://github.com/thoj/go-ircevent",
|
||||
|
Reference in New Issue
Block a user