4
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2025-06-26 17:49:23 +00:00

Compare commits

...

42 Commits

Author SHA1 Message Date
Wim
d6ee55e35f Release v1.1.2 2017-09-09 17:06:40 +02:00
Wim
aef64eec32 Update changelog 2017-09-09 17:04:01 +02:00
Wim
c4193d5ccd Add 4.2 support (mattermost) 2017-09-09 17:00:26 +02:00
Wim
0c94186818 Add darwin-amd64 to nightly builds 2017-09-09 14:42:45 +02:00
Wim
9039720013 Send images when text is empty regression. (mattermost). Closes #254 2017-09-08 00:16:17 +02:00
Wim
a3470f8aec Send first message after connect (slack). Closes #252 2017-09-07 23:47:23 +02:00
Wim
01badde21d Add message debugging (gitter) 2017-09-07 20:35:12 +02:00
a37b232dd9 remove comment about useAPI in sample configuration (#251) 2017-09-04 15:16:58 +02:00
579ee48385 remove useAPI from sample configuration (#250) 2017-09-04 15:16:29 +02:00
Wim
dd985d1dad Fix sending direct messages with APIv4 2017-09-04 14:24:22 +02:00
Wim
d2caea70a2 Release v1.1.1 2017-09-04 13:43:30 +02:00
Wim
21143cf5ee Fix public links (mattermost) 2017-09-04 12:50:42 +02:00
Wim
dc2aed698d Release v1.1.0 2017-09-01 20:20:46 +02:00
Wim
37c350f19f Convert utf-8 back to charset (irc). #247 2017-08-30 20:59:54 +02:00
Wim
9e03fcf162 Fix private channel joining bug (mattermost). Closes #248 2017-08-30 14:01:17 +02:00
Wim
8d4521c1df Update changelog 2017-08-29 23:45:39 +02:00
Wim
9226252336 Replace mentions from other bridges. (slack). Closes #233 2017-08-29 23:34:50 +02:00
Wim
f4fb83e787 Use the detected charset (irc) 2017-08-29 21:35:36 +02:00
Wim
e7fcb25107 Add a charset option (irc). Closes #247 2017-08-29 21:31:03 +02:00
Wim
5a85258f74 Update travis to go 1.9 2017-08-29 20:34:32 +02:00
Wim
2f7df2df43 Do not add messages without ID to cache 2017-08-29 20:28:44 +02:00
Wim
ad3a753718 Remove debug message 2017-08-28 23:07:13 +02:00
Wim
e45c551880 Add support for editing messages. Remove ZWSP as loopcheck (gitter) 2017-08-28 23:07:12 +02:00
Wim
e59d338d4e Use github.com/42wim/go-gitter for now 2017-08-28 23:07:11 +02:00
Wim
7a86044f7a Add support for editing messages (telegram) 2017-08-28 23:07:03 +02:00
Wim
8b98f605bc Add support for editing messages (slack) 2017-08-28 20:29:02 +02:00
Wim
7c773ebae0 Add support for editing messages across bridges. Currently mattermost/discord.
Our Message type has an extra ID field which contains the message ID of the specific bridge.
The Send() function has been modified to return a msg ID (after the message to that specific
bridge has been created).

There is a lru cache of 5000 entries (message IDs). All in memory, so editing messages
will only work for messages the bot has seen.

Currently we go out from the idea that every message ID is unique, so we don't keep
the ID separate for each bridge. (we do for each gateway though)

If there's a new message from a bridge, we put that message ID in the LRU cache as key
and the []*BrMsgID as value (this slice contains the message ID's of each bridge that
received the new message)

If there's a new message and this message ID already exists in the cache, it must be
an updated message. The value from the cache gets checked for each bridge and if there
is a message ID for this bridge, the ID will be added to the Message{} sent to that
bridge. If the bridge sees that the ID isn't empty, it'll know it has to update the
message with that specific ID instead of creating a new message.
2017-08-28 00:33:17 +02:00
Wim
e84417430d Update PostMessage to also return and error. Add EditMessage function 2017-08-28 00:32:56 +02:00
Wim
5a8d7b5f6d Modify Send() to return also a message id 2017-08-27 22:59:37 +02:00
Wim
cfb8107138 Relay notices (matrix). Closes #243 2017-08-27 01:01:35 +02:00
Wim
43bd779fb7 Handle leave/join events (slack). Closes #246 2017-08-27 00:00:02 +02:00
Wim
7f9a400776 Add support for personal access tokens (mattermost)
* https://docs.mattermost.com/developer/personal-access-tokens.html
2017-08-23 22:49:42 +02:00
Wim
ce1c5873ac Make megacheck happy 2017-08-17 00:00:41 +02:00
Wim
85ff1995fd Use mattermost v4 api (drops support for mattermost < 3.8) 2017-08-16 23:41:35 +02:00
Wim
b963f83c6a Update mattermost vendor (3.7 => 4.1) 2017-08-16 23:37:37 +02:00
Wim
f6297ebbb0 Bump version 2017-08-16 23:28:11 +02:00
Wim
a5259f56c5 Release v1.0.1 2017-08-16 22:25:44 +02:00
Wim
3f75ed9c18 Add 4.1 support (mattermost) 2017-08-16 22:02:13 +02:00
49ece51167 Add new file_ids parameter for Mattermost outgoing webhook (#240)
* Added file_id parameter for outgoing webhook

* Typo in the new fileids field name
2017-08-16 21:27:17 +02:00
Wim
e77c3eb20a Swap token/id. Also check for default webhookURL in isWebhookID (discord) 2017-08-12 16:30:00 +02:00
Wim
59b2a5f8d0 Bump version 2017-08-12 14:54:19 +02:00
Wim
28710d0bc7 Allow a webhookurl per channel (discord). #239 2017-08-12 14:51:41 +02:00
177 changed files with 10764 additions and 1477 deletions

View File

@ -1,7 +1,7 @@
language: go
go:
#- 1.7.x
- 1.8.x
- 1.9.x
# - tip
# we have everything vendored

View File

@ -36,7 +36,7 @@ Has a REST API.
# Requirements
Accounts to one of the supported bridges
* [Mattermost](https://github.com/mattermost/platform/) 3.5.x - 3.10.x, 4.0.x
* [Mattermost](https://github.com/mattermost/platform/) 3.8.x - 3.10.x, 4.0.x - 4.2.x
* [IRC](http://www.mirc.com/servers.html)
* [XMPP](https://jabber.org)
* [Gitter](https://gitter.im)
@ -53,7 +53,7 @@ See https://github.com/42wim/matterbridge/wiki
# Installing
## Binaries
* Latest stable release [v1.0.0](https://github.com/42wim/matterbridge/releases/latest)
* Latest stable release [v1.1.2](https://github.com/42wim/matterbridge/releases/latest)
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
## Building

View File

@ -61,16 +61,16 @@ func (b *Api) Disconnect() error {
return nil
}
func (b *Api) JoinChannel(channel string) error {
func (b *Api) JoinChannel(channel config.ChannelInfo) error {
return nil
}
func (b *Api) Send(msg config.Message) error {
func (b *Api) Send(msg config.Message) (string, error) {
b.Lock()
defer b.Unlock()
b.Messages.Enqueue(&msg)
return nil
return "", nil
}
func (b *Api) handlePostMessage(c echo.Context) error {

View File

@ -19,9 +19,9 @@ import (
)
type Bridger interface {
Send(msg config.Message) error
Send(msg config.Message) (string, error)
Connect() error
JoinChannel(channel string) error
JoinChannel(channel config.ChannelInfo) error
Disconnect() error
}
@ -92,16 +92,10 @@ func (b *Bridge) JoinChannels() error {
}
func (b *Bridge) joinChannels(channels map[string]config.ChannelInfo, exists map[string]bool) error {
mychannel := ""
for ID, channel := range channels {
if !exists[ID] {
mychannel = channel.Name
log.Infof("%s: joining %s (%s)", b.Account, channel.Name, ID)
if b.Protocol == "irc" && channel.Options.Key != "" {
log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
mychannel = mychannel + " " + channel.Options.Key
}
err := b.JoinChannel(mychannel)
err := b.JoinChannel(channel)
if err != nil {
return err
}

View File

@ -27,6 +27,7 @@ type Message struct {
Protocol string `json:"protocol"`
Gateway string `json:"gateway"`
Timestamp time.Time `json:"timestamp"`
ID string `json:"id"`
}
type ChannelInfo struct {
@ -42,6 +43,7 @@ type Protocol struct {
AuthCode string // steam
BindAddress string // mattermost, slack // DEPRECATED
Buffer int // api
Charset string // irc
EditSuffix string // mattermost, slack, discord, telegram, gitter
EditDisable bool // mattermost, slack, discord, telegram, gitter
IconURL string // mattermost, slack
@ -85,7 +87,8 @@ type Protocol struct {
}
type ChannelOptions struct {
Key string // irc
Key string // irc
WebhookURL string // discord
}
type Bridge struct {

View File

@ -10,17 +10,18 @@ import (
)
type bdiscord struct {
c *discordgo.Session
Config *config.Protocol
Remote chan config.Message
Account string
Channels []*discordgo.Channel
Nick string
UseChannelID bool
userMemberMap map[string]*discordgo.Member
guildID string
webhookID string
webhookToken string
c *discordgo.Session
Config *config.Protocol
Remote chan config.Message
Account string
Channels []*discordgo.Channel
Nick string
UseChannelID bool
userMemberMap map[string]*discordgo.Member
guildID string
webhookID string
webhookToken string
channelInfoMap map[string]*config.ChannelInfo
sync.RWMutex
}
@ -37,11 +38,10 @@ func New(cfg config.Protocol, account string, c chan config.Message) *bdiscord {
b.Remote = c
b.Account = account
b.userMemberMap = make(map[string]*discordgo.Member)
b.channelInfoMap = make(map[string]*config.ChannelInfo)
if b.Config.WebhookURL != "" {
flog.Debug("Configuring Discord Incoming Webhook")
webhookURLSplit := strings.Split(b.Config.WebhookURL, "/")
b.webhookToken = webhookURLSplit[len(webhookURLSplit)-1]
b.webhookID = webhookURLSplit[len(webhookURLSplit)-2]
b.webhookID, b.webhookToken = b.splitURL(b.Config.WebhookURL)
}
return b
}
@ -99,40 +99,57 @@ func (b *bdiscord) Disconnect() error {
return nil
}
func (b *bdiscord) JoinChannel(channel string) error {
idcheck := strings.Split(channel, "ID:")
func (b *bdiscord) JoinChannel(channel config.ChannelInfo) error {
b.channelInfoMap[channel.ID] = &channel
idcheck := strings.Split(channel.Name, "ID:")
if len(idcheck) > 1 {
b.UseChannelID = true
}
return nil
}
func (b *bdiscord) Send(msg config.Message) error {
func (b *bdiscord) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg)
channelID := b.getChannelID(msg.Channel)
if channelID == "" {
flog.Errorf("Could not find channelID for %v", msg.Channel)
return nil
return "", nil
}
if msg.Event == config.EVENT_USER_ACTION {
msg.Text = "_" + msg.Text + "_"
}
if b.Config.WebhookURL == "" {
flog.Debugf("Broadcasting using token (API)")
b.c.ChannelMessageSend(channelID, msg.Username+msg.Text)
} else {
flog.Debugf("Broadcasting using Webhook")
b.c.WebhookExecute(
b.webhookID,
b.webhookToken,
true,
&discordgo.WebhookParams{
Content: msg.Text,
Username: msg.Username,
AvatarURL: msg.Avatar,
})
wID := b.webhookID
wToken := b.webhookToken
if ci, ok := b.channelInfoMap[msg.Channel+b.Account]; ok {
if ci.Options.WebhookURL != "" {
wID, wToken = b.splitURL(ci.Options.WebhookURL)
}
}
return nil
if wID == "" {
flog.Debugf("Broadcasting using token (API)")
if msg.ID != "" {
_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text)
return msg.ID, err
}
res, err := b.c.ChannelMessageSend(channelID, msg.Username+msg.Text)
if err != nil {
return "", err
}
return res.ID, err
}
flog.Debugf("Broadcasting using Webhook")
err := b.c.WebhookExecute(
wID,
wToken,
true,
&discordgo.WebhookParams{
Content: msg.Text,
Username: msg.Username,
AvatarURL: msg.Avatar,
})
return "", err
}
func (b *bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdate) {
@ -153,7 +170,7 @@ func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
return
}
// if using webhooks, do not relay if it's ours
if b.Config.WebhookURL != "" && m.Author.Bot && m.Author.ID == b.webhookID {
if b.useWebhook() && m.Author.Bot && b.isWebhookID(m.Author.ID) {
return
}
@ -175,7 +192,7 @@ func (b *bdiscord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreat
}
rmsg := config.Message{Account: b.Account, Avatar: "https://cdn.discordapp.com/avatars/" + m.Author.ID + "/" + m.Author.Avatar + ".jpg",
UserID: m.Author.ID}
UserID: m.Author.ID, ID: m.ID}
rmsg.Channel = b.getChannelName(m.ChannelID)
if b.UseChannelID {
@ -310,3 +327,41 @@ func (b *bdiscord) stripCustomoji(text string) string {
re := regexp.MustCompile("<(:.*?:)[0-9]+>")
return re.ReplaceAllString(text, `$1`)
}
// splitURL splits a webhookURL and returns the id and token
func (b *bdiscord) splitURL(url string) (string, string) {
webhookURLSplit := strings.Split(url, "/")
return webhookURLSplit[len(webhookURLSplit)-2], webhookURLSplit[len(webhookURLSplit)-1]
}
// useWebhook returns true if we have a webhook defined somewhere
func (b *bdiscord) useWebhook() bool {
if b.Config.WebhookURL != "" {
return true
}
for _, channel := range b.channelInfoMap {
if channel.Options.WebhookURL != "" {
return true
}
}
return false
}
// isWebhookID returns true if the specified id is used in a defined webhook
func (b *bdiscord) isWebhookID(id string) bool {
if b.Config.WebhookURL != "" {
wID, _ := b.splitURL(b.Config.WebhookURL)
if wID == id {
return true
}
}
for _, channel := range b.channelInfoMap {
if channel.Options.WebhookURL != "" {
wID, _ := b.splitURL(channel.Options.WebhookURL)
if wID == id {
return true
}
}
}
return false
}

View File

@ -2,9 +2,9 @@ package bgitter
import (
"fmt"
"github.com/42wim/go-gitter"
"github.com/42wim/matterbridge/bridge/config"
log "github.com/Sirupsen/logrus"
"github.com/sromku/go-gitter"
"strings"
)
@ -13,6 +13,7 @@ type Bgitter struct {
Config *config.Protocol
Remote chan config.Message
Account string
User *gitter.User
Users []gitter.User
Rooms []gitter.Room
}
@ -36,7 +37,7 @@ func (b *Bgitter) Connect() error {
var err error
flog.Info("Connecting")
b.c = gitter.New(b.Config.Token)
_, err = b.c.GetUser()
b.User, err = b.c.GetUser()
if err != nil {
flog.Debugf("%#v", err)
return err
@ -51,10 +52,10 @@ func (b *Bgitter) Disconnect() error {
}
func (b *Bgitter) JoinChannel(channel string) error {
roomID, err := b.c.GetRoomId(channel)
func (b *Bgitter) JoinChannel(channel config.ChannelInfo) error {
roomID, err := b.c.GetRoomId(channel.Name)
if err != nil {
return fmt.Errorf("Could not find roomID for %v. Please create the room on gitter.im", channel)
return fmt.Errorf("Could not find roomID for %v. Please create the room on gitter.im", channel.Name)
}
room, err := b.c.GetRoom(roomID)
if err != nil {
@ -78,15 +79,16 @@ func (b *Bgitter) JoinChannel(channel string) error {
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, "") {
if ev.Message.From.ID != b.User.ID {
flog.Debugf("Sending message from %s on %s to gateway", ev.Message.From.Username, b.Account)
rmsg := config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room,
Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username), UserID: ev.Message.From.ID}
Account: b.Account, Avatar: b.getAvatar(ev.Message.From.Username), UserID: ev.Message.From.ID,
ID: ev.Message.ID}
if strings.HasPrefix(ev.Message.Text, "@"+ev.Message.From.Username) {
rmsg.Event = config.EVENT_USER_ACTION
rmsg.Text = strings.Replace(rmsg.Text, "@"+ev.Message.From.Username+" ", "", -1)
}
flog.Debugf("Message is %#v", rmsg)
b.Remote <- rmsg
}
case *gitter.GitterConnectionClosed:
@ -97,15 +99,26 @@ func (b *Bgitter) JoinChannel(channel string) error {
return nil
}
func (b *Bgitter) Send(msg config.Message) error {
func (b *Bgitter) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg)
roomID := b.getRoomID(msg.Channel)
if roomID == "" {
flog.Errorf("Could not find roomID for %v", msg.Channel)
return nil
return "", nil
}
// add ZWSP because gitter echoes our own messages
return b.c.SendMessage(roomID, msg.Username+msg.Text+" ")
if msg.ID != "" {
flog.Debugf("updating message with id %s", msg.ID)
_, err := b.c.UpdateMessage(roomID, msg.ID, msg.Username+msg.Text)
if err != nil {
return "", err
}
return "", nil
}
resp, err := b.c.SendMessage(roomID, msg.Username+msg.Text)
if err != nil {
return "", err
}
return resp.ID, nil
}
func (b *Bgitter) getRoomID(channel string) string {

View File

@ -1,6 +1,7 @@
package birc
import (
"bytes"
"crypto/tls"
"fmt"
"github.com/42wim/go-ircevent"
@ -117,16 +118,34 @@ func (b *Birc) Disconnect() error {
return nil
}
func (b *Birc) JoinChannel(channel string) error {
b.i.Join(channel)
func (b *Birc) JoinChannel(channel config.ChannelInfo) error {
if channel.Options.Key != "" {
flog.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
b.i.Join(channel.Name + " " + channel.Options.Key)
} else {
b.i.Join(channel.Name)
}
return nil
}
func (b *Birc) Send(msg config.Message) error {
func (b *Birc) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg)
if strings.HasPrefix(msg.Text, "!") {
b.Command(&msg)
}
if b.Config.Charset != "" {
buf := new(bytes.Buffer)
w, err := charset.NewWriter(b.Config.Charset, buf)
if err != nil {
flog.Errorf("charset from utf-8 conversion failed: %s", err)
return "", err
}
fmt.Fprintf(w, msg.Text)
w.Close()
msg.Text = buf.String()
}
for _, text := range strings.Split(msg.Text, "\n") {
if len(text) > b.Config.MessageLength {
text = text[:b.Config.MessageLength] + " <message clipped>"
@ -140,7 +159,7 @@ func (b *Birc) Send(msg config.Message) error {
flog.Debugf("flooding, dropping message (queue at %d)", len(b.Local))
}
}
return nil
return "", nil
}
func (b *Birc) doSend() {
@ -260,20 +279,25 @@ func (b *Birc) handlePrivMsg(event *irc.Event) {
re := regexp.MustCompile(`[[:cntrl:]](\d+,|)\d+`)
msg = re.ReplaceAllString(msg, "")
// detect what were sending so that we convert it to utf-8
detector := chardet.NewTextDetector()
result, err := detector.DetectBest([]byte(msg))
if err != nil {
flog.Infof("detection failed for msg: %#v", msg)
return
}
flog.Debugf("detected %s confidence %#v", result.Charset, result.Confidence)
var r io.Reader
r, err = charset.NewReader(result.Charset, strings.NewReader(msg))
// if we're not sure, just pick ISO-8859-1
if result.Confidence < 80 {
r, err = charset.NewReader("ISO-8859-1", strings.NewReader(msg))
var err error
mycharset := b.Config.Charset
if mycharset == "" {
// detect what were sending so that we convert it to utf-8
detector := chardet.NewTextDetector()
result, err := detector.DetectBest([]byte(msg))
if err != nil {
flog.Infof("detection failed for msg: %#v", msg)
return
}
flog.Debugf("detected %s confidence %#v", result.Charset, result.Confidence)
mycharset = result.Charset
// if we're not sure, just pick ISO-8859-1
if result.Confidence < 80 {
mycharset = "ISO-8859-1"
}
}
r, err = charset.NewReader(mycharset, strings.NewReader(msg))
if err != nil {
flog.Errorf("charset to utf-8 conversion failed: %s", err)
return

View File

@ -63,28 +63,28 @@ func (b *Bmatrix) Disconnect() error {
return nil
}
func (b *Bmatrix) JoinChannel(channel string) error {
resp, err := b.mc.JoinRoom(channel, "", nil)
func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error {
resp, err := b.mc.JoinRoom(channel.Name, "", nil)
if err != nil {
return err
}
b.Lock()
b.RoomMap[resp.RoomID] = channel
b.RoomMap[resp.RoomID] = channel.Name
b.Unlock()
return err
}
func (b *Bmatrix) Send(msg config.Message) error {
func (b *Bmatrix) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg)
channel := b.getRoomID(msg.Channel)
flog.Debugf("Sending to channel %s", channel)
if msg.Event == config.EVENT_USER_ACTION {
b.mc.SendMessageEvent(channel, "m.room.message",
matrix.TextMessage{"m.emote", msg.Username + msg.Text})
return nil
return "", nil
}
b.mc.SendText(channel, msg.Username+msg.Text)
return nil
return "", nil
}
func (b *Bmatrix) getRoomID(channel string) string {
@ -100,7 +100,7 @@ func (b *Bmatrix) getRoomID(channel string) string {
func (b *Bmatrix) handlematrix() error {
syncer := b.mc.Syncer.(*matrix.DefaultSyncer)
syncer.OnEventType("m.room.message", func(ev *matrix.Event) {
if (ev.Content["msgtype"].(string) == "m.text" || ev.Content["msgtype"].(string) == "m.emote") && ev.Sender != b.UserID {
if (ev.Content["msgtype"].(string) == "m.text" || ev.Content["msgtype"].(string) == "m.notice" || ev.Content["msgtype"].(string) == "m.emote") && ev.Sender != b.UserID {
b.RLock()
channel, ok := b.RoomMap[ev.RoomID]
b.RUnlock()

View File

@ -2,6 +2,7 @@ package bmattermost
import (
"errors"
"fmt"
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/matterclient"
"github.com/42wim/matterbridge/matterhook"
@ -23,6 +24,7 @@ type MMMessage struct {
Channel string
Username string
UserID string
ID string
}
type Bmattermost struct {
@ -61,6 +63,12 @@ func (b *Bmattermost) Connect() error {
b.mh = matterhook.New(b.Config.WebhookURL,
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
BindAddress: b.Config.WebhookBindAddress})
} else if b.Config.Token != "" {
flog.Info("Connecting using token (sending)")
err := b.apiLogin()
if err != nil {
return err
}
} else if b.Config.Login != "" {
flog.Info("Connecting using login/password (sending)")
err := b.apiLogin()
@ -81,7 +89,14 @@ func (b *Bmattermost) Connect() error {
b.mh = matterhook.New(b.Config.WebhookURL,
matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
DisableServer: true})
if b.Config.Login != "" {
if b.Config.Token != "" {
flog.Info("Connecting using token (receiving)")
err := b.apiLogin()
if err != nil {
return err
}
go b.handleMatter()
} else if b.Config.Login != "" {
flog.Info("Connecting using login/password (receiving)")
err := b.apiLogin()
if err != nil {
@ -90,6 +105,13 @@ func (b *Bmattermost) Connect() error {
go b.handleMatter()
}
return nil
} else if b.Config.Token != "" {
flog.Info("Connecting using token (sending and receiving)")
err := b.apiLogin()
if err != nil {
return err
}
go b.handleMatter()
} else if b.Config.Login != "" {
flog.Info("Connecting using login/password (sending and receiving)")
err := b.apiLogin()
@ -98,8 +120,8 @@ func (b *Bmattermost) Connect() error {
}
go b.handleMatter()
}
if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Login == "" {
return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Login/Password/Server/Team configured.")
if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Login == "" && b.Config.Token == "" {
return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Token/Login/Password/Server/Team configured.")
}
return nil
}
@ -108,15 +130,19 @@ func (b *Bmattermost) Disconnect() error {
return nil
}
func (b *Bmattermost) JoinChannel(channel string) error {
func (b *Bmattermost) JoinChannel(channel config.ChannelInfo) error {
// we can only join channels using the API
if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" {
return b.mc.JoinChannel(b.mc.GetChannelId(channel, ""))
id := b.mc.GetChannelId(channel.Name, "")
if id == "" {
return fmt.Errorf("Could not find channel ID for channel %s", channel.Name)
}
return b.mc.JoinChannel(id)
}
return nil
}
func (b *Bmattermost) Send(msg config.Message) error {
func (b *Bmattermost) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg)
if msg.Event == config.EVENT_USER_ACTION {
msg.Text = "*" + msg.Text + "*"
@ -138,12 +164,14 @@ func (b *Bmattermost) Send(msg config.Message) error {
err := b.mh.Send(matterMessage)
if err != nil {
flog.Info(err)
return err
return "", err
}
return nil
return "", nil
}
b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message)
return nil
if msg.ID != "" {
return b.mc.EditMessage(msg.ID, message)
}
return b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message)
}
func (b *Bmattermost) handleMatter() {
@ -152,17 +180,22 @@ func (b *Bmattermost) handleMatter() {
flog.Debugf("Choosing webhooks based receiving")
go b.handleMatterHook(mchan)
} else {
flog.Debugf("Choosing login/password based receiving")
if b.Config.Token != "" {
flog.Debugf("Choosing token based receiving")
} else {
flog.Debugf("Choosing login/password based receiving")
}
go b.handleMatterClient(mchan)
}
for message := range mchan {
rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID}
rmsg := config.Message{Username: message.Username, Channel: message.Channel, Account: b.Account, UserID: message.UserID, ID: message.ID}
text, ok := b.replaceAction(message.Text)
if ok {
rmsg.Event = config.EVENT_USER_ACTION
}
rmsg.Text = text
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
flog.Debugf("Message is %#v", rmsg)
b.Remote <- rmsg
}
}
@ -194,6 +227,7 @@ func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
m.Username = message.Username
m.Channel = message.Channel
m.Text = message.Text
m.ID = message.Post.Id
if message.Raw.Event == "post_edited" && !b.Config.EditDisable {
m.Text = message.Text + b.Config.EditSuffix
}
@ -221,7 +255,12 @@ func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) {
}
func (b *Bmattermost) apiLogin() error {
b.mc = matterclient.New(b.Config.Login, b.Config.Password,
password := b.Config.Password
if b.Config.Token != "" {
password = "MMAUTHTOKEN=" + b.Config.Token
}
b.mc = matterclient.New(b.Config.Login, password,
b.Config.Team, b.Config.Server)
b.mc.SkipTLSVerify = b.Config.SkipTLSVerify
b.mc.NoTLS = b.Config.NoTLS

View File

@ -53,11 +53,11 @@ func (b *Brocketchat) Disconnect() error {
}
func (b *Brocketchat) JoinChannel(channel string) error {
func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error {
return nil
}
func (b *Brocketchat) Send(msg config.Message) error {
func (b *Brocketchat) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg)
matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
matterMessage.Channel = msg.Channel
@ -67,9 +67,9 @@ func (b *Brocketchat) Send(msg config.Message) error {
err := b.mh.Send(matterMessage)
if err != nil {
flog.Info(err)
return err
return "", err
}
return nil
return "", nil
}
func (b *Brocketchat) handleRocketHook() {

View File

@ -108,14 +108,14 @@ func (b *Bslack) Disconnect() error {
}
func (b *Bslack) JoinChannel(channel string) error {
func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
// we can only join channels using the API
if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" {
if strings.HasPrefix(b.Config.Token, "xoxb") {
// TODO check if bot has already joined channel
return nil
}
_, err := b.sc.JoinChannel(channel)
_, err := b.sc.JoinChannel(channel.Name)
if err != nil {
if err.Error() != "name_taken" {
return err
@ -125,7 +125,7 @@ func (b *Bslack) JoinChannel(channel string) error {
return nil
}
func (b *Bslack) Send(msg config.Message) error {
func (b *Bslack) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg)
if msg.Event == config.EVENT_USER_ACTION {
msg.Text = "_" + msg.Text + "_"
@ -145,13 +145,13 @@ func (b *Bslack) Send(msg config.Message) error {
err := b.mh.Send(matterMessage)
if err != nil {
flog.Info(err)
return err
return "", err
}
return nil
return "", nil
}
schannel, err := b.getChannelByName(channel)
if err != nil {
return err
return "", err
}
np := slack.NewPostMessageParameters()
if b.Config.PrefixMessagesWithNick {
@ -163,14 +163,20 @@ func (b *Bslack) Send(msg config.Message) error {
np.IconURL = msg.Avatar
}
np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge"})
b.sc.PostMessage(schannel.ID, message, np)
// replace mentions
np.LinkNames = 1
/*
newmsg := b.rtm.NewOutgoingMessage(message, schannel.ID)
b.rtm.SendMessage(newmsg)
*/
return nil
// if we have no ID it means we're creating a new message, not updating an existing one
if msg.ID != "" {
ts := strings.Fields(msg.ID)
b.sc.UpdateMessage(schannel.ID, ts[1], message)
return "", nil
}
_, id, err := b.sc.PostMessage(schannel.ID, message, np)
if err != nil {
return "", err
}
return "slack " + id, nil
}
func (b *Bslack) getAvatar(user string) string {
@ -233,75 +239,78 @@ func (b *Bslack) handleSlack() {
text = b.replaceURL(text)
text = html.UnescapeString(text)
flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
msg := config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username), UserID: message.UserID}
msg := config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username), UserID: message.UserID, ID: "slack " + message.Raw.Timestamp}
if message.Raw.SubType == "me_message" {
msg.Event = config.EVENT_USER_ACTION
}
if message.Raw.SubType == "channel_leave" || message.Raw.SubType == "channel_join" {
msg.Username = "system"
msg.Event = config.EVENT_JOIN_LEAVE
}
// edited messages have a submessage, use this timestamp
if message.Raw.SubMessage != nil {
msg.ID = "slack " + message.Raw.SubMessage.Timestamp
}
b.Remote <- msg
}
}
}
func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
count := 0
for msg := range b.rtm.IncomingEvents {
switch ev := msg.Data.(type) {
case *slack.MessageEvent:
// ignore first message
if count > 0 {
flog.Debugf("Receiving from slackclient %#v", ev)
if len(ev.Attachments) > 0 {
// skip messages we made ourselves
if ev.Attachments[0].CallbackID == "matterbridge" {
continue
}
flog.Debugf("Receiving from slackclient %#v", ev)
if len(ev.Attachments) > 0 {
// skip messages we made ourselves
if ev.Attachments[0].CallbackID == "matterbridge" {
continue
}
if !b.Config.EditDisable && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
flog.Debugf("SubMessage %#v", ev.SubMessage)
ev.User = ev.SubMessage.User
ev.Text = ev.SubMessage.Text + b.Config.EditSuffix
}
// use our own func because rtm.GetChannelInfo doesn't work for private channels
channel, err := b.getChannelByID(ev.Channel)
}
if !b.Config.EditDisable && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
flog.Debugf("SubMessage %#v", ev.SubMessage)
ev.User = ev.SubMessage.User
ev.Text = ev.SubMessage.Text + b.Config.EditSuffix
}
// use our own func because rtm.GetChannelInfo doesn't work for private channels
channel, err := b.getChannelByID(ev.Channel)
if err != nil {
continue
}
m := &MMMessage{}
if ev.BotID == "" {
user, err := b.rtm.GetUserInfo(ev.User)
if err != nil {
continue
}
m := &MMMessage{}
if ev.BotID == "" {
user, err := b.rtm.GetUserInfo(ev.User)
if err != nil {
continue
}
m.UserID = user.ID
m.Username = user.Name
}
m.Channel = channel.Name
m.Text = ev.Text
if m.Text == "" {
for _, attach := range ev.Attachments {
if attach.Text != "" {
m.Text = attach.Text
} else {
m.Text = attach.Fallback
}
}
}
m.Raw = ev
m.Text = b.replaceMention(m.Text)
// when using webhookURL we can't check if it's our webhook or not for now
if ev.BotID != "" && b.Config.WebhookURL == "" {
bot, err := b.rtm.GetBotInfo(ev.BotID)
if err != nil {
continue
}
if bot.Name != "" {
m.Username = bot.Name
m.UserID = bot.ID
}
}
mchan <- m
m.UserID = user.ID
m.Username = user.Name
}
count++
m.Channel = channel.Name
m.Text = ev.Text
if m.Text == "" {
for _, attach := range ev.Attachments {
if attach.Text != "" {
m.Text = attach.Text
} else {
m.Text = attach.Fallback
}
}
}
m.Raw = ev
m.Text = b.replaceMention(m.Text)
// when using webhookURL we can't check if it's our webhook or not for now
if ev.BotID != "" && b.Config.WebhookURL == "" {
bot, err := b.rtm.GetBotInfo(ev.BotID)
if err != nil {
continue
}
if bot.Name != "" {
m.Username = bot.Name
m.UserID = bot.ID
}
}
mchan <- m
case *slack.OutgoingErrorEvent:
flog.Debugf("%#v", ev.Error())
case *slack.ChannelJoinedEvent:

View File

@ -60,8 +60,8 @@ func (b *Bsteam) Disconnect() error {
}
func (b *Bsteam) JoinChannel(channel string) error {
id, err := steamid.NewId(channel)
func (b *Bsteam) JoinChannel(channel config.ChannelInfo) error {
id, err := steamid.NewId(channel.Name)
if err != nil {
return err
}
@ -69,13 +69,13 @@ func (b *Bsteam) JoinChannel(channel string) error {
return nil
}
func (b *Bsteam) Send(msg config.Message) error {
func (b *Bsteam) Send(msg config.Message) (string, error) {
id, err := steamid.NewId(msg.Channel)
if err != nil {
return err
return "", err
}
b.c.Social.SendMessage(id, steamlang.EChatEntryType_ChatMsg, msg.Username+msg.Text)
return nil
return "", nil
}
func (b *Bsteam) getNick(id steamid.SteamId) string {

View File

@ -53,26 +53,45 @@ func (b *Btelegram) Disconnect() error {
}
func (b *Btelegram) JoinChannel(channel string) error {
func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
return nil
}
func (b *Btelegram) Send(msg config.Message) error {
func (b *Btelegram) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg)
chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
if err != nil {
return err
return "", err
}
if b.Config.MessageFormat == "HTML" {
msg.Text = makeHTML(msg.Text)
}
// edit the message if we have a msg ID
if msg.ID != "" {
msgid, err := strconv.Atoi(msg.ID)
if err != nil {
return "", err
}
m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
_, err = b.c.Send(m)
if err != nil {
return "", err
}
return "", nil
}
m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text)
if b.Config.MessageFormat == "HTML" {
m.ParseMode = tgbotapi.ModeHTML
}
_, err = b.c.Send(m)
return err
res, err := b.c.Send(m)
if err != nil {
return "", err
}
return strconv.Itoa(res.MessageID), nil
}
func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
@ -131,7 +150,7 @@ func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
}
if text != "" {
flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
b.Remote <- config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID)}
b.Remote <- config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID)}
}
}
}

View File

@ -74,15 +74,15 @@ func (b *Bxmpp) Disconnect() error {
return nil
}
func (b *Bxmpp) JoinChannel(channel string) error {
b.xc.JoinMUCNoHistory(channel+"@"+b.Config.Muc, b.Config.Nick)
func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
b.xc.JoinMUCNoHistory(channel.Name+"@"+b.Config.Muc, b.Config.Nick)
return nil
}
func (b *Bxmpp) Send(msg config.Message) error {
func (b *Bxmpp) Send(msg config.Message) (string, error) {
flog.Debugf("Receiving %#v", msg)
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
return nil
return "", nil
}
func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {

View File

@ -1,3 +1,34 @@
# v1.1.2
* general: also build darwin binaries
* mattermost: add support for mattermost 4.2.x
* mattermost: Send images when text is empty regression. (mattermost). Closes #254
* slack: also send the first messsage after connect. #252
# v1.1.1
## Bugfix
* mattermost: fix public links
# v1.1.0
## New features
* general: Add better editing support. (actually edit the messages on bridges that support it)
(mattermost,discord,gitter,slack,telegram)
* mattermost: use API v4 (removes support for mattermost < 3.8)
* mattermost: add support for personal access tokens (since mattermost 4.1)
Use ```Token="yourtoken"``` in mattermost config
See https://docs.mattermost.com/developer/personal-access-tokens.html for more info
* matrix: Relay notices (matrix). Closes #243
* irc: Add a charset option. Closes #247
## Bugfix
* slack: Handle leave/join events (slack). Closes #246
* slack: Replace mentions from other bridges. (slack). Closes #233
* gitter: remove ZWSP after messages
# v1.0.1
## New features
* mattermost: add support for mattermost 4.1.x
* discord: allow a webhookURL per channel #239
# v1.0.0
## New features
* general: Add action support for slack,mattermost,irc,gitter,matrix,xmpp,discord. #199

View File

@ -1,10 +1,11 @@
#!/bin/bash
go version |grep go1.8 || exit
go version |grep go1.9 || exit
VERSION=$(git describe --tags)
mkdir ci/binaries
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-win64.exe
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux64
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-windows-amd64.exe
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux-amd64
GOOS=linux GOARCH=arm go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-linux-arm
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -X main.githash=$(git log --pretty=format:'%h' -n 1)" -o ci/binaries/matterbridge-$VERSION-darwin-amd64
cd ci
cat > deploy.json <<EOF
{

View File

@ -6,6 +6,7 @@ import (
"github.com/42wim/matterbridge/bridge/config"
log "github.com/Sirupsen/logrus"
// "github.com/davecgh/go-spew/spew"
"github.com/hashicorp/golang-lru"
"github.com/peterhellberg/emojilib"
"regexp"
"strings"
@ -21,11 +22,19 @@ type Gateway struct {
ChannelOptions map[string]config.ChannelOptions
Message chan config.Message
Name string
Messages *lru.Cache
}
type BrMsgID struct {
br *bridge.Bridge
ID string
}
func New(cfg config.Gateway, r *Router) *Gateway {
gw := &Gateway{Channels: make(map[string]*config.ChannelInfo), Message: r.Message,
Router: r, Bridges: make(map[string]*bridge.Bridge), Config: r.Config}
cache, _ := lru.New(5000)
gw.Messages = cache
gw.AddConfig(&cfg)
return gw
}
@ -136,15 +145,16 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest bridge.Bridge) []con
return channels
}
func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) {
func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) []*BrMsgID {
var brMsgIDs []*BrMsgID
// only relay join/part when configged
if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart {
return
return brMsgIDs
}
// broadcast to every out channel (irc QUIT)
if msg.Channel == "" && msg.Event != config.EVENT_JOIN_LEAVE {
log.Debug("empty channel")
return
return brMsgIDs
}
originchannel := msg.Channel
origmsg := msg
@ -158,15 +168,29 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) {
msg.Channel = channel.Name
msg.Avatar = gw.modifyAvatar(origmsg, dest)
msg.Username = gw.modifyUsername(origmsg, dest)
msg.ID = ""
if res, ok := gw.Messages.Get(origmsg.ID); ok {
IDs := res.([]*BrMsgID)
for _, id := range IDs {
if dest.Protocol == id.br.Protocol {
msg.ID = id.ID
}
}
}
// for api we need originchannel as channel
if dest.Protocol == "api" {
msg.Channel = originchannel
}
err := dest.Send(msg)
mID, err := dest.Send(msg)
if err != nil {
fmt.Println(err)
}
// append the message ID (mID) from this bridge (dest) to our brMsgIDs slice
if mID != "" {
brMsgIDs = append(brMsgIDs, &BrMsgID{dest, mID})
}
}
return brMsgIDs
}
func (gw *Gateway) ignoreMessage(msg *config.Message) bool {

View File

@ -94,11 +94,17 @@ func (r *Router) handleReceive() {
}
}
for _, gw := range r.Gateways {
// record all the message ID's of the different bridges
var msgIDs []*BrMsgID
if !gw.ignoreMessage(&msg) {
msg.Timestamp = time.Now()
gw.modifyMessage(&msg)
for _, br := range gw.Bridges {
gw.handleMessage(msg, br)
msgIDs = append(msgIDs, gw.handleMessage(msg, br)...)
}
// only add the message ID if it doesn't already exists
if _, ok := gw.Messages.Get(msg.ID); !ok && msg.ID != "" {
gw.Messages.Add(msg.ID, msgIDs)
}
}
}

View File

@ -11,7 +11,7 @@ import (
)
var (
version = "1.0.0"
version = "1.1.2"
githash string
)

View File

@ -32,6 +32,23 @@ UseSASL=false
#OPTIONAL (default false)
SkipTLSVerify=true
#If you know your charset, you can specify it manually.
#Otherwise it tries to detect this automatically. Select one below
# "iso-8859-2:1987", "iso-8859-9:1989", "866", "latin9", "iso-8859-10:1992", "iso-ir-109", "hebrew",
# "cp932", "iso-8859-15", "cp437", "utf-16be", "iso-8859-3:1988", "windows-1251", "utf16", "latin6",
# "latin3", "iso-8859-1:1987", "iso-8859-9", "utf-16le", "big5", "cp819", "asmo-708", "utf-8",
# "ibm437", "iso-ir-157", "iso-ir-144", "latin4", "850", "iso-8859-5", "iso-8859-5:1988", "l3",
# "windows-31j", "utf8", "iso-8859-3", "437", "greek", "iso-8859-8", "l6", "l9-iso-8859-15",
# "iso-8859-2", "latin2", "iso-ir-100", "iso-8859-6", "arabic", "iso-ir-148", "us-ascii", "x-sjis",
# "utf16be", "iso-8859-8:1988", "utf16le", "l4", "utf-16", "iso-ir-138", "iso-8859-7", "iso-8859-7:1987",
# "windows-1252", "l2", "koi8-r", "iso8859-1", "latin1", "ecma-114", "iso-ir-110", "elot-928",
# "iso-ir-126", "iso-8859-1", "iso-ir-127", "cp850", "cyrillic", "greek8", "windows-1250", "iso-latin-1",
# "l5", "ibm866", "cp866", "ms-kanji", "ibm850", "ecma-118", "iso-ir-101", "ibm819", "l1", "iso-8859-6:1987",
# "latin5", "ascii", "sjis", "iso-8859-10", "iso-8859-4", "iso-8859-4:1988", "shift-jis
# The select charset will be converted to utf-8 when sent to other bridges.
#OPTIONAL (default "")
Charset=""
#Your nick on irc.
#REQUIRED
Nick="matterbot"
@ -213,6 +230,11 @@ Team="yourteam"
Login="yourlogin"
Password="yourpass"
#personal access token of the bot.
#new feature since mattermost 4.1. See https://docs.mattermost.com/developer/personal-access-tokens.html
#OPTIONAL (you can use token instead of login/password)
#Token="abcdefghijklm"
#Enable this to make a http connection (instead of https) to your mattermost.
#OPTIONAL (default false)
NoTLS=false
@ -361,7 +383,6 @@ WebhookURL="https://hooks.slack.com/services/yourhook"
#AND DEDICATED BOT USER WHEN POSSIBLE!
#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
#OPTIONAL
WebhookBindAddress="0.0.0.0:9999"
@ -448,6 +469,7 @@ ShowEmbeds=false
UseUserName=false
#Specify WebhookURL. If given, will relay messages using the Webhook, which gives a better look to messages.
#This only works if you have one discord channel, if you have multiple discord channels you'll have to specify it in the gateway config
#OPTIONAL (default empty)
WebhookURL="Yourwebhooktokenhere"
@ -833,6 +855,14 @@ enable=true
#OPTIONAL - your irc channel key
key="yourkey"
[[gateway.inout]]
account="discord.game"
channel="mygreatgame"
#OPTIONAL - webhookurl only works for discord (it needs a different URL for each cahnnel)
[gateway.inout.options]
webhookurl=""https://discordapp.com/api/webhooks/123456789123456789/C9WPqExYWONPDZabcdef-def1434FGFjstasJX9pYht73y"
#API example
#[[gateway.inout]]
#account="api.local"

View File

@ -6,7 +6,6 @@
[mattermost]
[mattermost.work]
useAPI=true
#do not prefix it wit http:// or https://
Server="yourmattermostserver.domain"
Team="yourteam"

View File

@ -9,7 +9,6 @@ import (
"net/http"
"net/http/cookiejar"
"net/url"
"strconv"
"strings"
"sync"
"time"
@ -45,8 +44,8 @@ type Message struct {
type Team struct {
Team *model.Team
Id string
Channels *model.ChannelList
MoreChannels *model.ChannelList
Channels []*model.Channel
MoreChannels []*model.Channel
Users map[string]*model.User
}
@ -55,7 +54,7 @@ type MMClient struct {
*Credentials
Team *Team
OtherTeams []*Team
Client *model.Client
Client *model.Client4
User *model.User
Users map[string]*model.User
MessageChan chan *Message
@ -109,21 +108,21 @@ func (m *MMClient) Login() error {
uriScheme = "http://"
}
// login to mattermost
m.Client = model.NewClient(uriScheme + m.Credentials.Server)
m.Client = model.NewAPIv4Client(uriScheme + m.Credentials.Server)
m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}, Proxy: http.ProxyFromEnvironment}
m.Client.HttpClient.Timeout = time.Second * 10
for {
d := b.Duration()
// bogus call to get the serverversion
_, err := m.Client.GetClientProperties()
if err != nil {
return fmt.Errorf("%#v", err.Error())
_, resp := m.Client.Logout()
if resp.Error != nil {
return fmt.Errorf("%#v", resp.Error.Error())
}
if firstConnection && !supportedVersion(m.Client.ServerVersion) {
return fmt.Errorf("unsupported mattermost version: %s", m.Client.ServerVersion)
if firstConnection && !supportedVersion(resp.ServerVersion) {
return fmt.Errorf("unsupported mattermost version: %s", resp.ServerVersion)
}
m.ServerVersion = m.Client.ServerVersion
m.ServerVersion = resp.ServerVersion
if m.ServerVersion == "" {
m.log.Debugf("Server not up yet, reconnecting in %s", d)
time.Sleep(d)
@ -134,30 +133,33 @@ func (m *MMClient) Login() error {
}
b.Reset()
var myinfo *model.Result
var resp *model.Response
//var myinfo *model.Result
var appErr *model.AppError
var logmsg = "trying login"
for {
m.log.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server)
if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) {
m.log.Debugf(logmsg+" with %s", model.SESSION_COOKIE_TOKEN)
m.log.Debugf(logmsg + " with token")
token := strings.Split(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN+"=")
if len(token) != 2 {
return errors.New("incorrect MMAUTHTOKEN. valid input is MMAUTHTOKEN=yourtoken")
}
m.Client.HttpClient.Jar = m.createCookieJar(token[1])
m.Client.MockSession(token[1])
myinfo, appErr = m.Client.GetMe("")
if appErr != nil {
return errors.New(appErr.DetailedError)
m.Client.AuthToken = token[1]
m.Client.AuthType = model.HEADER_BEARER
m.User, resp = m.Client.GetMe("")
if resp.Error != nil {
return resp.Error
}
if myinfo.Data.(*model.User) == nil {
if m.User == nil {
m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass)
return errors.New("invalid " + model.SESSION_COOKIE_TOKEN)
}
} else {
_, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass)
m.User, resp = m.Client.Login(m.Credentials.Login, m.Credentials.Pass)
}
appErr = resp.Error
if appErr != nil {
d := b.Duration()
m.log.Debug(appErr.DetailedError)
@ -185,8 +187,6 @@ func (m *MMClient) Login() error {
if m.Team == nil {
return errors.New("team not found")
}
// set our team id as default route
m.Client.SetTeamId(m.Team.Id)
m.wsConnect()
@ -207,7 +207,7 @@ func (m *MMClient) wsConnect() {
}
// setup websocket connection
wsurl := wsScheme + m.Credentials.Server + model.API_URL_SUFFIX_V3 + "/users/websocket"
wsurl := wsScheme + m.Credentials.Server + model.API_URL_SUFFIX_V4 + "/websocket"
header := http.Header{}
header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken)
@ -241,9 +241,9 @@ func (m *MMClient) Logout() error {
m.log.Debug("Not invalidating session in logout, credential is a token")
return nil
}
_, err := m.Client.Logout()
if err != nil {
return err
_, resp := m.Client.Logout()
if resp.Error != nil {
return resp.Error
}
return nil
}
@ -277,6 +277,13 @@ func (m *MMClient) WsReceiver() {
// check if we didn't empty the message
if msg.Text != "" {
m.MessageChan <- msg
continue
}
// if we have file attached but the message is empty, also send it
if msg.Post != nil {
if msg.Text != "" || len(msg.Post.FileIds) > 0 {
m.MessageChan <- msg
}
}
continue
}
@ -349,33 +356,34 @@ func (m *MMClient) parseActionPost(rmsg *Message) {
}
func (m *MMClient) UpdateUsers() error {
mmusers, err := m.Client.GetProfiles(0, 50000, "")
if err != nil {
return errors.New(err.DetailedError)
mmusers, resp := m.Client.GetUsers(0, 50000, "")
if resp.Error != nil {
return errors.New(resp.Error.DetailedError)
}
m.Lock()
m.Users = mmusers.Data.(map[string]*model.User)
for _, user := range mmusers {
m.Users[user.Id] = user
}
m.Unlock()
return nil
}
func (m *MMClient) UpdateChannels() error {
mmchannels, err := m.Client.GetChannels("")
if err != nil {
return errors.New(err.DetailedError)
}
var mmchannels2 *model.Result
if m.mmVersion() >= 3.08 {
mmchannels2, err = m.Client.GetMoreChannelsPage(0, 5000)
} else {
mmchannels2, err = m.Client.GetMoreChannels("")
}
if err != nil {
return errors.New(err.DetailedError)
mmchannels, resp := m.Client.GetChannelsForTeamForUser(m.Team.Id, m.User.Id, "")
if resp.Error != nil {
return errors.New(resp.Error.DetailedError)
}
m.Lock()
m.Team.Channels = mmchannels.Data.(*model.ChannelList)
m.Team.MoreChannels = mmchannels2.Data.(*model.ChannelList)
m.Team.Channels = mmchannels
m.Unlock()
mmchannels, resp = m.Client.GetPublicChannelsForTeam(m.Team.Id, 0, 5000, "")
if resp.Error != nil {
return errors.New(resp.Error.DetailedError)
}
m.Lock()
m.Team.MoreChannels = mmchannels
m.Unlock()
return nil
}
@ -388,14 +396,14 @@ func (m *MMClient) GetChannelName(channelId string) string {
continue
}
if t.Channels != nil {
for _, channel := range *t.Channels {
for _, channel := range t.Channels {
if channel.Id == channelId {
return channel.Name
}
}
}
if t.MoreChannels != nil {
for _, channel := range *t.MoreChannels {
for _, channel := range t.MoreChannels {
if channel.Id == channelId {
return channel.Name
}
@ -413,7 +421,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, *t.MoreChannels...) {
for _, channel := range append(t.Channels, t.MoreChannels...) {
if channel.Name == name {
return channel.Id
}
@ -427,7 +435,7 @@ func (m *MMClient) GetChannelTeamId(id string) string {
m.RLock()
defer m.RUnlock()
for _, t := range append(m.OtherTeams, m.Team) {
for _, channel := range append(*t.Channels, *t.MoreChannels...) {
for _, channel := range append(t.Channels, t.MoreChannels...) {
if channel.Id == id {
return channel.TeamId
}
@ -440,7 +448,7 @@ func (m *MMClient) GetChannelHeader(channelId string) string {
m.RLock()
defer m.RUnlock()
for _, t := range m.OtherTeams {
for _, channel := range append(*t.Channels, *t.MoreChannels...) {
for _, channel := range append(t.Channels, t.MoreChannels...) {
if channel.Id == channelId {
return channel.Header
}
@ -450,55 +458,68 @@ func (m *MMClient) GetChannelHeader(channelId string) string {
return ""
}
func (m *MMClient) PostMessage(channelId string, text string) {
func (m *MMClient) PostMessage(channelId string, text string) (string, error) {
post := &model.Post{ChannelId: channelId, Message: text}
m.Client.CreatePost(post)
res, resp := m.Client.CreatePost(post)
if resp.Error != nil {
return "", resp.Error
}
return res.Id, nil
}
func (m *MMClient) EditMessage(postId string, text string) (string, error) {
post := &model.Post{Message: text}
res, resp := m.Client.UpdatePost(postId, post)
if resp.Error != nil {
return "", resp.Error
}
return res.Id, nil
}
func (m *MMClient) JoinChannel(channelId string) error {
m.RLock()
defer m.RUnlock()
for _, c := range *m.Team.Channels {
for _, c := range m.Team.Channels {
if c.Id == channelId {
m.log.Debug("Not joining ", channelId, " already joined.")
return nil
}
}
m.log.Debug("Joining ", channelId)
_, err := m.Client.JoinChannel(channelId)
if err != nil {
return errors.New("failed to join")
_, resp := m.Client.AddChannelMember(channelId, m.User.Id)
if resp.Error != nil {
return resp.Error
}
return nil
}
func (m *MMClient) GetPostsSince(channelId string, time int64) *model.PostList {
res, err := m.Client.GetPostsSince(channelId, time)
if err != nil {
res, resp := m.Client.GetPostsSince(channelId, time)
if resp.Error != nil {
return nil
}
return res.Data.(*model.PostList)
return res
}
func (m *MMClient) SearchPosts(query string) *model.PostList {
res, err := m.Client.SearchPosts(query, false)
if err != nil {
res, resp := m.Client.SearchPosts(m.Team.Id, query, false)
if resp.Error != nil {
return nil
}
return res.Data.(*model.PostList)
return res
}
func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList {
res, err := m.Client.GetPosts(channelId, 0, limit, "")
if err != nil {
res, resp := m.Client.GetPostsForChannel(channelId, 0, limit, "")
if resp.Error != nil {
return nil
}
return res.Data.(*model.PostList)
return res
}
func (m *MMClient) GetPublicLink(filename string) string {
res, err := m.Client.GetPublicLink(filename)
if err != nil {
res, resp := m.Client.GetFileLink(filename)
if resp.Error != nil {
return ""
}
return res
@ -507,8 +528,8 @@ func (m *MMClient) GetPublicLink(filename string) string {
func (m *MMClient) GetPublicLinks(filenames []string) []string {
var output []string
for _, f := range filenames {
res, err := m.Client.GetPublicLink(f)
if err != nil {
res, resp := m.Client.GetFileLink(f)
if resp.Error != nil {
continue
}
output = append(output, res)
@ -524,8 +545,8 @@ func (m *MMClient) GetFileLinks(filenames []string) []string {
var output []string
for _, f := range filenames {
res, err := m.Client.GetPublicLink(f)
if err != nil {
res, resp := m.Client.GetFileLink(f)
if resp.Error != nil {
// public links is probably disabled, create the link ourselves
output = append(output, uriScheme+m.Credentials.Server+model.API_URL_SUFFIX_V3+"/files/"+f+"/get")
continue
@ -536,42 +557,33 @@ func (m *MMClient) GetFileLinks(filenames []string) []string {
}
func (m *MMClient) UpdateChannelHeader(channelId string, header string) {
data := make(map[string]string)
data["channel_id"] = channelId
data["channel_header"] = header
channel := &model.Channel{Id: channelId, Header: header}
m.log.Debugf("updating channelheader %#v, %#v", channelId, header)
_, err := m.Client.UpdateChannelHeader(data)
if err != nil {
log.Error(err)
_, resp := m.Client.UpdateChannel(channel)
if resp.Error != nil {
log.Error(resp.Error)
}
}
func (m *MMClient) UpdateLastViewed(channelId string) {
m.log.Debugf("posting lastview %#v", channelId)
if m.mmVersion() >= 3.08 {
view := model.ChannelView{ChannelId: channelId}
res, _ := m.Client.ViewChannel(view)
if !res {
m.log.Errorf("ChannelView update for %s failed", channelId)
}
return
}
_, err := m.Client.UpdateLastViewedAt(channelId, true)
if err != nil {
m.log.Error(err)
view := &model.ChannelView{ChannelId: channelId}
res, _ := m.Client.ViewChannel(m.User.Id, view)
if !res {
m.log.Errorf("ChannelView update for %s failed", channelId)
}
}
func (m *MMClient) UsernamesInChannel(channelId string) []string {
res, err := m.Client.GetProfilesInChannel(channelId, 0, 50000, "")
if err != nil {
m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, err)
res, resp := m.Client.GetChannelMembers(channelId, 0, 50000, "")
if resp.Error != nil {
m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, resp.Error)
return []string{}
}
members := res.Data.(map[string]*model.User)
allusers := m.GetUsers()
result := []string{}
for _, member := range members {
result = append(result, member.Nickname)
for _, member := range *res {
result = append(result, allusers[member.UserId].Nickname)
}
return result
}
@ -595,22 +607,15 @@ func (m *MMClient) createCookieJar(token string) *cookiejar.Jar {
func (m *MMClient) SendDirectMessage(toUserId string, msg string) {
m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg)
// create DM channel (only happens on first message)
_, err := m.Client.CreateDirectChannel(toUserId)
if err != nil {
m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, err)
_, resp := m.Client.CreateDirectChannel(m.User.Id, toUserId)
if resp.Error != nil {
m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, resp.Error)
return
}
channelName := model.GetDMNameFromIds(toUserId, m.User.Id)
// update our channels
mmchannels, err := m.Client.GetChannels("")
if err != nil {
m.log.Debug("SendDirectMessage: Couldn't update channels")
return
}
m.Lock()
m.Team.Channels = mmchannels.Data.(*model.ChannelList)
m.Unlock()
m.UpdateChannels()
// build & send the message
msg = strings.Replace(msg, "\r", "", -1)
@ -636,10 +641,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 = append(channels, m.Team.Channels...)
for _, t := range m.OtherTeams {
if t.Id != m.Team.Id {
channels = append(channels, *t.Channels...)
channels = append(channels, t.Channels...)
}
}
return channels
@ -651,7 +656,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 = append(channels, t.MoreChannels...)
}
return channels
}
@ -662,9 +667,9 @@ 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 = append(channels, t.Channels...)
if t.MoreChannels != nil {
channels = append(channels, *t.MoreChannels...)
channels = append(channels, t.MoreChannels...)
}
for _, c := range channels {
if c.Id == channelId {
@ -678,12 +683,11 @@ func (m *MMClient) GetTeamFromChannel(channelId string) string {
func (m *MMClient) GetLastViewedAt(channelId string) int64 {
m.RLock()
defer m.RUnlock()
res, err := m.Client.GetChannel(channelId, "")
if err != nil {
res, resp := m.Client.GetChannelMember(channelId, m.User.Id, "")
if resp.Error != nil {
return model.GetMillis()
}
data := res.Data.(*model.ChannelData)
return data.Member.LastViewedAt
return res.LastViewedAt
}
func (m *MMClient) GetUsers() map[string]*model.User {
@ -701,12 +705,11 @@ func (m *MMClient) GetUser(userId string) *model.User {
defer m.Unlock()
_, ok := m.Users[userId]
if !ok {
res, err := m.Client.GetProfilesByIds([]string{userId})
if err != nil {
res, resp := m.Client.GetUser(userId, "")
if resp.Error != nil {
return nil
}
u := res.Data.(map[string]*model.User)[userId]
m.Users[userId] = u
m.Users[userId] = res
}
return m.Users[userId]
}
@ -720,36 +723,36 @@ func (m *MMClient) GetUserName(userId string) string {
}
func (m *MMClient) GetStatus(userId string) string {
res, err := m.Client.GetStatusesByIds([]string{userId})
if err != nil {
res, resp := m.Client.GetUserStatus(userId, "")
if resp.Error != nil {
return ""
}
status := res.Data.(map[string]string)
if status[userId] == model.STATUS_AWAY {
if res.Status == model.STATUS_AWAY {
return "away"
}
if status[userId] == model.STATUS_ONLINE {
if res.Status == model.STATUS_ONLINE {
return "online"
}
return "offline"
}
func (m *MMClient) GetStatuses() map[string]string {
var ok bool
var ids []string
statuses := make(map[string]string)
res, err := m.Client.GetStatuses()
if err != nil {
for id := range m.Users {
ids = append(ids, id)
}
res, resp := m.Client.GetUsersStatusesByIds(ids)
if resp.Error != 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"
}
for _, status := range res {
statuses[status.UserId] = "offline"
if status.Status == model.STATUS_AWAY {
statuses[status.UserId] = "away"
}
if status.Status == model.STATUS_ONLINE {
statuses[status.UserId] = "online"
}
}
return statuses
@ -800,40 +803,39 @@ func (m *MMClient) StatusLoop() {
func (m *MMClient) initUser() error {
m.Lock()
defer m.Unlock()
initLoad, err := m.Client.GetInitialLoad()
if err != nil {
return err
}
initData := initLoad.Data.(*model.InitialLoad)
m.User = initData.User
// we only load all team data on initial login.
// all other updates are for channels from our (primary) team only.
//m.log.Debug("initUser(): loading all team data")
for _, v := range initData.Teams {
m.Client.SetTeamId(v.Id)
mmusers, err := m.Client.GetProfiles(0, 50000, "")
if err != nil {
return errors.New(err.DetailedError)
teams, resp := m.Client.GetTeamsForUser(m.User.Id, "")
if resp.Error != nil {
return resp.Error
}
for _, team := range teams {
mmusers, resp := m.Client.GetUsersInTeam(team.Id, 0, 50000, "")
if resp.Error != nil {
return errors.New(resp.Error.DetailedError)
}
t := &Team{Team: v, Users: mmusers.Data.(map[string]*model.User), Id: v.Id}
mmchannels, err := m.Client.GetChannels("")
if err != nil {
return errors.New(err.DetailedError)
usermap := make(map[string]*model.User)
for _, user := range mmusers {
usermap[user.Id] = user
}
t.Channels = mmchannels.Data.(*model.ChannelList)
if m.mmVersion() >= 3.08 {
mmchannels, err = m.Client.GetMoreChannelsPage(0, 5000)
} else {
mmchannels, err = m.Client.GetMoreChannels("")
t := &Team{Team: team, Users: usermap, Id: team.Id}
mmchannels, resp := m.Client.GetChannelsForTeamForUser(team.Id, m.User.Id, "")
if resp.Error != nil {
return resp.Error
}
if err != nil {
return errors.New(err.DetailedError)
t.Channels = mmchannels
mmchannels, resp = m.Client.GetPublicChannelsForTeam(team.Id, 0, 5000, "")
if resp.Error != nil {
return resp.Error
}
t.MoreChannels = mmchannels.Data.(*model.ChannelList)
t.MoreChannels = mmchannels
m.OtherTeams = append(m.OtherTeams, t)
if v.Name == m.Credentials.Team {
if team.Name == m.Credentials.Team {
m.Team = t
m.log.Debugf("initUser(): found our team %s (id: %s)", v.Name, v.Id)
m.log.Debugf("initUser(): found our team %s (id: %s)", team.Name, team.Id)
}
// add all users
for k, v := range t.Users {
@ -854,22 +856,13 @@ func (m *MMClient) sendWSRequest(action string, data map[string]interface{}) err
return nil
}
func (m *MMClient) mmVersion() float64 {
v, _ := strconv.ParseFloat(string(m.ServerVersion[0:2])+"0"+string(m.ServerVersion[2]), 64)
if string(m.ServerVersion[4]) == "." {
v, _ = strconv.ParseFloat(m.ServerVersion[0:4], 64)
}
return v
}
func supportedVersion(version string) bool {
if strings.HasPrefix(version, "3.5.0") ||
strings.HasPrefix(version, "3.6.0") ||
strings.HasPrefix(version, "3.7.0") ||
strings.HasPrefix(version, "3.8.0") ||
if strings.HasPrefix(version, "3.8.0") ||
strings.HasPrefix(version, "3.9.0") ||
strings.HasPrefix(version, "3.10.0") ||
strings.HasPrefix(version, "4.0") {
strings.HasPrefix(version, "4.0") ||
strings.HasPrefix(version, "4.1") ||
strings.HasPrefix(version, "4.2") {
return true
}
return false

View File

@ -43,6 +43,7 @@ type IMessage struct {
ServiceId string `schema:"service_id"`
Text string `schema:"text"`
TriggerWord string `schema:"trigger_word"`
FileIDs string `schema:"file_ids"`
}
// Client for Mattermost.

View File

@ -205,17 +205,43 @@ func (gitter *Gitter) GetMessage(roomID, messageID string) (*Message, error) {
}
// SendMessage sends a message to a room
func (gitter *Gitter) SendMessage(roomID, text string) error {
func (gitter *Gitter) SendMessage(roomID, text string) (*Message, error) {
message := Message{Text: text}
body, _ := json.Marshal(message)
_, err := gitter.post(gitter.config.apiBaseURL+"rooms/"+roomID+"/chatMessages", body)
response, err := gitter.post(gitter.config.apiBaseURL+"rooms/"+roomID+"/chatMessages", body)
if err != nil {
gitter.log(err)
return err
return nil, err
}
return nil
err = json.Unmarshal(response, &message)
if err != nil {
gitter.log(err)
return nil, err
}
return &message, nil
}
// UpdateMessage updates a message in a room
func (gitter *Gitter) UpdateMessage(roomID, msgID, text string) (*Message, error) {
message := Message{Text: text}
body, _ := json.Marshal(message)
response, err := gitter.put(gitter.config.apiBaseURL+"rooms/"+roomID+"/chatMessages/"+msgID, body)
if err != nil {
gitter.log(err)
return nil, err
}
err = json.Unmarshal(response, &message)
if err != nil {
gitter.log(err)
return nil, err
}
return &message, nil
}
// JoinRoom joins a room
@ -265,7 +291,7 @@ func (gitter *Gitter) SearchRooms(room string) ([]Room, error) {
Results []Room `json:"results"`
}
response, err := gitter.get(gitter.config.apiBaseURL + "rooms?q=" + room )
response, err := gitter.get(gitter.config.apiBaseURL + "rooms?q=" + room)
if err != nil {
gitter.log(err)
@ -414,6 +440,39 @@ func (gitter *Gitter) post(url string, body []byte) ([]byte, error) {
return result, nil
}
func (gitter *Gitter) put(url string, body []byte) ([]byte, error) {
r, err := http.NewRequest("PUT", url, bytes.NewBuffer(body))
if err != nil {
gitter.log(err)
return nil, err
}
r.Header.Set("Content-Type", "application/json")
r.Header.Set("Accept", "application/json")
r.Header.Set("Authorization", "Bearer "+gitter.config.token)
resp, err := gitter.config.client.Do(r)
if err != nil {
gitter.log(err)
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err = APIError{What: fmt.Sprintf("Status code: %v", resp.StatusCode)}
gitter.log(err)
return nil, err
}
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
gitter.log(err)
return nil, err
}
return result, nil
}
func (gitter *Gitter) delete(url string) ([]byte, error) {
r, err := http.NewRequest("delete", url, nil)
if err != nil {

View File

@ -1,32 +1,32 @@
Mattermost Licensing
SOFTWARE LICENSING
SOFTWARE LICENSING
You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE
You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE
- See MIT-COMPILED-LICENSE.md included in compiled versions for details
You may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways:
1. Under the Free Software Foundations GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or
2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com
1. Under the Free Software Foundations GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or
2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com
You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/,
You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/,
webapp/client, webapp/fonts, webapp/i18n, webapp/images and all subdirectories thereof) under the Apache License v2.0.
We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not
We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not
link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and
(b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of
(b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of
a “modified version” or “work based on” Mattermost as these terms are defined in the AGPL v3.0 license.
MATTERMOST TRADEMARK GUIDELINES
Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organizations Trademark
Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions
you have about using these trademarks, please email trademark@mattermost.com
Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organizations Trademark
Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions
you have about using these trademarks, please email trademark@mattermost.com
------------------------------------------------------------------------------------------------------------------------------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces
@ -6,7 +6,7 @@ package einterfaces
import "github.com/mattermost/platform/model"
type AccountMigrationInterface interface {
MigrateToLdap(fromAuthService string, forignUserFieldNameToMatch string) *model.AppError
MigrateToLdap(fromAuthService string, forignUserFieldNameToMatch string, force bool) *model.AppError
}
var theAccountMigrationInterface AccountMigrationInterface

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces
@ -7,26 +7,19 @@ import (
"github.com/mattermost/platform/model"
)
type ClusterMessageHandler func(msg *model.ClusterMessage)
type ClusterInterface interface {
StartInterNodeCommunication()
StopInterNodeCommunication()
GetClusterInfos() []*model.ClusterInfo
GetClusterStats() ([]*model.ClusterStats, *model.AppError)
ClearSessionCacheForUser(userId string)
InvalidateCacheForUser(userId string)
InvalidateCacheForChannel(channelId string)
InvalidateCacheForChannelByName(teamId, name string)
InvalidateCacheForChannelMembers(channelId string)
InvalidateCacheForChannelMembersNotifyProps(channelId string)
InvalidateCacheForChannelPosts(channelId string)
InvalidateCacheForWebhook(webhookId string)
InvalidateCacheForReactions(postId string)
Publish(event *model.WebSocketEvent)
UpdateStatus(status *model.Status)
GetLogs() ([]string, *model.AppError)
RegisterClusterMessageHandler(event string, crm ClusterMessageHandler)
GetClusterId() string
GetClusterInfos() []*model.ClusterInfo
SendClusterMessage(cluster *model.ClusterMessage)
NotifyMsg(buf []byte)
GetClusterStats() ([]*model.ClusterStats, *model.AppError)
GetLogs(page, perPage int) ([]string, *model.AppError)
ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError
InvalidateAllCaches() *model.AppError
}
var theClusterInterface ClusterInterface

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces

View File

@ -0,0 +1,25 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces
import "github.com/mattermost/platform/model"
type ElasticsearchInterface interface {
Start() *model.AppError
IndexPost(post *model.Post, teamId string) *model.AppError
SearchPosts(channels *model.ChannelList, searchParams []*model.SearchParams) ([]string, *model.AppError)
DeletePost(post *model.Post) *model.AppError
TestConfig(cfg *model.Config) *model.AppError
PurgeIndexes() *model.AppError
}
var theElasticsearchInterface ElasticsearchInterface
func RegisterElasticsearchInterface(newInterface ElasticsearchInterface) {
theElasticsearchInterface = newInterface
}
func GetElasticsearchInterface() ElasticsearchInterface {
return theElasticsearchInterface
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces

View File

@ -0,0 +1,23 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package jobs
import (
"github.com/mattermost/platform/model"
)
type DataRetentionInterface interface {
MakeWorker() model.Worker
MakeScheduler() model.Scheduler
}
var theDataRetentionInterface DataRetentionInterface
func RegisterDataRetentionInterface(newInterface DataRetentionInterface) {
theDataRetentionInterface = newInterface
}
func GetDataRetentionInterface() DataRetentionInterface {
return theDataRetentionInterface
}

View File

@ -0,0 +1,22 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package jobs
import (
"github.com/mattermost/platform/model"
)
type ElasticsearchIndexerInterface interface {
MakeWorker() model.Worker
}
var theElasticsearchIndexerInterface ElasticsearchIndexerInterface
func RegisterElasticsearchIndexerInterface(newInterface ElasticsearchIndexerInterface) {
theElasticsearchIndexerInterface = newInterface
}
func GetElasticsearchIndexerInterface() ElasticsearchIndexerInterface {
return theElasticsearchIndexerInterface
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces
@ -33,6 +33,7 @@ type MetricsInterface interface {
IncrementMemCacheHitCounterSession()
IncrementWebsocketEvent(eventType string)
IncrementWebSocketBroadcast(eventType string)
AddMemCacheHitCounter(cacheName string, amount float64)
AddMemCacheMissCounter(cacheName string, amount float64)

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package einterfaces
@ -10,7 +10,7 @@ import (
type SamlInterface interface {
ConfigureSP() *model.AppError
BuildRequest(relayState string) (*model.SamlAuthRequest, *model.AppError)
DoLogin(encodedXML string, relayState map[string]string, siteURL string) (*model.User, *model.AppError)
DoLogin(encodedXML string, relayState map[string]string) (*model.User, *model.AppError)
GetMetadata() (string, *model.AppError)
}

View File

@ -1,32 +1,32 @@
Mattermost Licensing
SOFTWARE LICENSING
SOFTWARE LICENSING
You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE
You are licensed to use compiled versions of the Mattermost platform produced by Mattermost, Inc. under an MIT LICENSE
- See MIT-COMPILED-LICENSE.md included in compiled versions for details
You may be licensed to use source code to create compiled versions not produced by Mattermost, Inc. in one of two ways:
1. Under the Free Software Foundations GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or
2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com
1. Under the Free Software Foundations GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or
2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com
You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/,
You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/,
webapp/client, webapp/fonts, webapp/i18n, webapp/images and all subdirectories thereof) under the Apache License v2.0.
We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not
We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not
link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and
(b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of
(b) you have not modified, added to or adapted the source code of Mattermost in a way that results in the creation of
a “modified version” or “work based on” Mattermost as these terms are defined in the AGPL v3.0 license.
MATTERMOST TRADEMARK GUIDELINES
Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organizations Trademark
Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions
you have about using these trademarks, please email trademark@mattermost.com
Your use of the mark Mattermost is subject to Mattermost, Inc's prior written approval and our organizations Trademark
Standards of Use at http://www.mattermost.org/trademark-standards-of-use/. For trademark approval or any questions
you have about using these trademarks, please email trademark@mattermost.com
------------------------------------------------------------------------------------------------------------------------------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -21,6 +21,7 @@ type AccessData struct {
RefreshToken string `json:"refresh_token"`
RedirectUri string `json:"redirect_uri"`
ExpiresAt int64 `json:"expires_at"`
Scope string `json:"scope"`
}
type AccessResponse struct {
@ -51,7 +52,7 @@ func (ad *AccessData) IsValid() *AppError {
return NewLocAppError("AccessData.IsValid", "model.access.is_valid.refresh_token.app_error", nil, "")
}
if len(ad.RedirectUri) > 256 {
if len(ad.RedirectUri) == 0 || len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) {
return NewLocAppError("AccessData.IsValid", "model.access.is_valid.redirect_uri.app_error", nil, "")
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -39,6 +39,7 @@ 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_READ_PUBLIC_CHANNEL *Permission
var PERMISSION_PERMANENT_DELETE_USER *Permission
var PERMISSION_UPLOAD_FILE *Permission
var PERMISSION_GET_PUBLIC_LINK *Permission
@ -47,6 +48,7 @@ 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_CREATE_POST_PUBLIC *Permission
var PERMISSION_EDIT_POST *Permission
var PERMISSION_EDIT_OTHERS_POSTS *Permission
var PERMISSION_DELETE_POST *Permission
@ -56,6 +58,11 @@ var PERMISSION_CREATE_TEAM *Permission
var PERMISSION_MANAGE_TEAM *Permission
var PERMISSION_IMPORT_TEAM *Permission
var PERMISSION_VIEW_TEAM *Permission
var PERMISSION_LIST_USERS_WITHOUT_TEAM *Permission
var PERMISSION_MANAGE_JOBS *Permission
var PERMISSION_CREATE_USER_ACCESS_TOKEN *Permission
var PERMISSION_READ_USER_ACCESS_TOKEN *Permission
var PERMISSION_REVOKE_USER_ACCESS_TOKEN *Permission
// General permission that encompases all system admin functions
// in the future this could be broken up to allow access to some
@ -64,9 +71,14 @@ var PERMISSION_MANAGE_SYSTEM *Permission
var ROLE_SYSTEM_USER *Role
var ROLE_SYSTEM_ADMIN *Role
var ROLE_SYSTEM_POST_ALL *Role
var ROLE_SYSTEM_POST_ALL_PUBLIC *Role
var ROLE_SYSTEM_USER_ACCESS_TOKEN *Role
var ROLE_TEAM_USER *Role
var ROLE_TEAM_ADMIN *Role
var ROLE_TEAM_POST_ALL *Role
var ROLE_TEAM_POST_ALL_PUBLIC *Role
var ROLE_CHANNEL_USER *Role
var ROLE_CHANNEL_ADMIN *Role
@ -195,6 +207,11 @@ func InitalizePermissions() {
"authentication.permissions.read_channel.name",
"authentication.permissions.read_channel.description",
}
PERMISSION_READ_PUBLIC_CHANNEL = &Permission{
"read_public_channel",
"authentication.permissions.read_public_channel.name",
"authentication.permissions.read_public_channel.description",
}
PERMISSION_PERMANENT_DELETE_USER = &Permission{
"permanent_delete_user",
"authentication.permissions.permanent_delete_user.name",
@ -235,6 +252,11 @@ func InitalizePermissions() {
"authentication.permissions.create_post.name",
"authentication.permissions.create_post.description",
}
PERMISSION_CREATE_POST_PUBLIC = &Permission{
"create_post_public",
"authentication.permissions.create_post_public.name",
"authentication.permissions.create_post_public.description",
}
PERMISSION_EDIT_POST = &Permission{
"edit_post",
"authentication.permissions.edit_post.name",
@ -280,6 +302,31 @@ func InitalizePermissions() {
"authentication.permissions.view_team.name",
"authentication.permissions.view_team.description",
}
PERMISSION_LIST_USERS_WITHOUT_TEAM = &Permission{
"list_users_without_team",
"authentication.permissions.list_users_without_team.name",
"authentication.permissions.list_users_without_team.description",
}
PERMISSION_CREATE_USER_ACCESS_TOKEN = &Permission{
"create_user_access_token",
"authentication.permissions.create_user_access_token.name",
"authentication.permissions.create_user_access_token.description",
}
PERMISSION_READ_USER_ACCESS_TOKEN = &Permission{
"read_user_access_token",
"authentication.permissions.read_user_access_token.name",
"authentication.permissions.read_user_access_token.description",
}
PERMISSION_REVOKE_USER_ACCESS_TOKEN = &Permission{
"revoke_user_access_token",
"authentication.permissions.revoke_user_access_token.name",
"authentication.permissions.revoke_user_access_token.description",
}
PERMISSION_MANAGE_JOBS = &Permission{
"manage_jobs",
"authentication.permisssions.manage_jobs.name",
"authentication.permisssions.manage_jobs.description",
}
}
func InitalizeRoles() {
@ -293,7 +340,6 @@ func InitalizeRoles() {
[]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,
@ -326,17 +372,38 @@ func InitalizeRoles() {
[]string{
PERMISSION_LIST_TEAM_CHANNELS.Id,
PERMISSION_JOIN_PUBLIC_CHANNELS.Id,
PERMISSION_READ_PUBLIC_CHANNEL.Id,
PERMISSION_VIEW_TEAM.Id,
},
}
BuiltInRoles[ROLE_TEAM_USER.Id] = ROLE_TEAM_USER
ROLE_TEAM_POST_ALL = &Role{
"team_post_all",
"authentication.roles.team_post_all.name",
"authentication.roles.team_post_all.description",
[]string{
PERMISSION_CREATE_POST.Id,
},
}
BuiltInRoles[ROLE_TEAM_POST_ALL.Id] = ROLE_TEAM_POST_ALL
ROLE_TEAM_POST_ALL_PUBLIC = &Role{
"team_post_all_public",
"authentication.roles.team_post_all_public.name",
"authentication.roles.team_post_all_public.description",
[]string{
PERMISSION_CREATE_POST_PUBLIC.Id,
},
}
BuiltInRoles[ROLE_TEAM_POST_ALL_PUBLIC.Id] = ROLE_TEAM_POST_ALL_PUBLIC
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,
@ -358,10 +425,42 @@ func InitalizeRoles() {
PERMISSION_CREATE_DIRECT_CHANNEL.Id,
PERMISSION_CREATE_GROUP_CHANNEL.Id,
PERMISSION_PERMANENT_DELETE_USER.Id,
PERMISSION_MANAGE_OAUTH.Id,
},
}
BuiltInRoles[ROLE_SYSTEM_USER.Id] = ROLE_SYSTEM_USER
ROLE_SYSTEM_POST_ALL = &Role{
"system_post_all",
"authentication.roles.system_post_all.name",
"authentication.roles.system_post_all.description",
[]string{
PERMISSION_CREATE_POST.Id,
},
}
BuiltInRoles[ROLE_SYSTEM_POST_ALL.Id] = ROLE_SYSTEM_POST_ALL
ROLE_SYSTEM_POST_ALL_PUBLIC = &Role{
"system_post_all_public",
"authentication.roles.system_post_all_public.name",
"authentication.roles.system_post_all_public.description",
[]string{
PERMISSION_CREATE_POST_PUBLIC.Id,
},
}
BuiltInRoles[ROLE_SYSTEM_POST_ALL_PUBLIC.Id] = ROLE_SYSTEM_POST_ALL_PUBLIC
ROLE_SYSTEM_USER_ACCESS_TOKEN = &Role{
"system_user_access_token",
"authentication.roles.system_user_access_token.name",
"authentication.roles.system_user_access_token.description",
[]string{
PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
PERMISSION_READ_USER_ACCESS_TOKEN.Id,
PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
},
}
BuiltInRoles[ROLE_SYSTEM_USER_ACCESS_TOKEN.Id] = ROLE_SYSTEM_USER_ACCESS_TOKEN
ROLE_SYSTEM_ADMIN = &Role{
"system_admin",
"authentication.roles.global_admin.name",
@ -378,6 +477,8 @@ func InitalizeRoles() {
PERMISSION_MANAGE_SYSTEM.Id,
PERMISSION_MANAGE_ROLES.Id,
PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES.Id,
PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id,
PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id,
PERMISSION_DELETE_PUBLIC_CHANNEL.Id,
PERMISSION_CREATE_PUBLIC_CHANNEL.Id,
PERMISSION_MANAGE_PRIVATE_CHANNEL_PROPERTIES.Id,
@ -391,6 +492,13 @@ func InitalizeRoles() {
PERMISSION_DELETE_POST.Id,
PERMISSION_DELETE_OTHERS_POSTS.Id,
PERMISSION_CREATE_TEAM.Id,
PERMISSION_ADD_USER_TO_TEAM.Id,
PERMISSION_LIST_USERS_WITHOUT_TEAM.Id,
PERMISSION_MANAGE_JOBS.Id,
PERMISSION_CREATE_POST_PUBLIC.Id,
PERMISSION_CREATE_USER_ACCESS_TOKEN.Id,
PERMISSION_READ_USER_ACCESS_TOKEN.Id,
PERMISSION_REVOKE_USER_ACCESS_TOKEN.Id,
},
ROLE_TEAM_USER.Permissions...,
),

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -6,6 +6,7 @@ package model
import (
"encoding/json"
"io"
"net/http"
)
const (
@ -25,6 +26,14 @@ type AuthData struct {
Scope string `json:"scope"`
}
type AuthorizeRequest struct {
ResponseType string `json:"response_type"`
ClientId string `json:"client_id"`
RedirectUri string `json:"redirect_uri"`
Scope string `json:"scope"`
State string `json:"state"`
}
// IsValid validates the AuthData and returns an error if it isn't configured
// correctly.
func (ad *AuthData) IsValid() *AppError {
@ -49,7 +58,7 @@ func (ad *AuthData) IsValid() *AppError {
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.create_at.app_error", nil, "client_id="+ad.ClientId)
}
if len(ad.RedirectUri) > 256 {
if len(ad.RedirectUri) == 0 || len(ad.RedirectUri) > 256 || !IsValidHttpUrl(ad.RedirectUri) {
return NewLocAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ad.ClientId)
}
@ -64,6 +73,33 @@ func (ad *AuthData) IsValid() *AppError {
return nil
}
// IsValid validates the AuthorizeRequest and returns an error if it isn't configured
// correctly.
func (ar *AuthorizeRequest) IsValid() *AppError {
if len(ar.ClientId) != 26 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.client_id.app_error", nil, "", http.StatusBadRequest)
}
if len(ar.ResponseType) == 0 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.response_type.app_error", nil, "", http.StatusBadRequest)
}
if len(ar.RedirectUri) == 0 || len(ar.RedirectUri) > 256 || !IsValidHttpUrl(ar.RedirectUri) {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.redirect_uri.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
if len(ar.State) > 128 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.state.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
if len(ar.Scope) > 128 {
return NewAppError("AuthData.IsValid", "model.authorize.is_valid.scope.app_error", nil, "client_id="+ar.ClientId, http.StatusBadRequest)
}
return nil
}
func (ad *AuthData) PreSave() {
if ad.ExpiresIn == 0 {
ad.ExpiresIn = AUTHCODE_EXPIRE_TIME
@ -98,6 +134,26 @@ func AuthDataFromJson(data io.Reader) *AuthData {
}
}
func (ar *AuthorizeRequest) ToJson() string {
b, err := json.Marshal(ar)
if err != nil {
return ""
} else {
return string(b)
}
}
func AuthorizeRequestFromJson(data io.Reader) *AuthorizeRequest {
decoder := json.NewDecoder(data)
var ar AuthorizeRequest
err := decoder.Decode(&ar)
if err == nil {
return &ar
} else {
return nil
}
}
func (ad *AuthData) IsExpired() bool {
if GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000) {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -46,6 +46,13 @@ type Channel struct {
CreatorId string `json:"creator_id"`
}
type ChannelPatch struct {
DisplayName *string `json:"display_name"`
Name *string `json:"name"`
Header *string `json:"header"`
Purpose *string `json:"purpose"`
}
func (o *Channel) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
@ -55,6 +62,15 @@ func (o *Channel) ToJson() string {
}
}
func (o *ChannelPatch) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func ChannelFromJson(data io.Reader) *Channel {
decoder := json.NewDecoder(data)
var o Channel
@ -66,6 +82,17 @@ func ChannelFromJson(data io.Reader) *Channel {
}
}
func ChannelPatchFromJson(data io.Reader) *ChannelPatch {
decoder := json.NewDecoder(data)
var o ChannelPatch
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}
func (o *Channel) Etag() string {
return Etag(o.Id, o.UpdateAt)
}
@ -137,6 +164,24 @@ func (o *Channel) IsGroupOrDirect() bool {
return o.Type == CHANNEL_DIRECT || o.Type == CHANNEL_GROUP
}
func (o *Channel) Patch(patch *ChannelPatch) {
if patch.DisplayName != nil {
o.DisplayName = *patch.DisplayName
}
if patch.Name != nil {
o.Name = *patch.Name
}
if patch.Header != nil {
o.Header = *patch.Header
}
if patch.Purpose != nil {
o.Purpose = *patch.Purpose
}
}
func GetDMNameFromIds(userId1, userId2 string) string {
if userId1 > userId2 {
return userId2 + "__" + userId1

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -50,3 +50,14 @@ func ChannelListFromJson(data io.Reader) *ChannelList {
return nil
}
}
func ChannelSliceFromJson(data io.Reader) []*Channel {
decoder := json.NewDecoder(data)
var o []*Channel
err := decoder.Decode(&o)
if err == nil {
return o
} else {
return nil
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -19,11 +19,11 @@ const (
)
type ChannelUnread struct {
TeamId string
TotalMsgCount int64
MsgCount int64
MentionCount int64
NotifyProps StringMap
TeamId string `json:"team_id"`
ChannelId string `json:"channel_id"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
NotifyProps StringMap `json:"-"`
}
type ChannelMember struct {
@ -47,6 +47,15 @@ func (o *ChannelMembers) ToJson() string {
}
}
func (o *ChannelUnread) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func ChannelMembersFromJson(data io.Reader) *ChannelMembers {
decoder := json.NewDecoder(data)
var o ChannelMembers
@ -58,6 +67,17 @@ func ChannelMembersFromJson(data io.Reader) *ChannelMembers {
}
}
func ChannelUnreadFromJson(data io.Reader) *ChannelUnread {
decoder := json.NewDecoder(data)
var o ChannelUnread
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 {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -1191,17 +1191,6 @@ 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
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
}
}
// 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) {
@ -1333,22 +1322,6 @@ 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
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/update_last_viewed_at", StringInterfaceToJson(data)); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
}
}
// 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.
@ -1533,6 +1506,16 @@ func (c *Client) GetFlaggedPosts(offset int, limit int) (*Result, *AppError) {
}
}
func (c *Client) GetPinnedPosts(channelId string) (*Result, *AppError) {
if r, err := c.DoApiGet(c.GetChannelRoute(channelId)+"/pinned", "", ""); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
}
}
func (c *Client) UploadProfileFile(data []byte, contentType string) (*Result, *AppError) {
return c.uploadFile(c.ApiUrl+"/users/newimage", data, contentType)
}
@ -1782,22 +1765,6 @@ func (c *Client) GetStatusesByIds(userIds []string) (*Result, *AppError) {
}
}
// SetActiveChannel sets the the channel id the user is currently viewing.
// The channelId key is required but the value can be blank. Returns standard
// response.
// SCHEDULED FOR DEPRECATION IN 3.8 - use ViewChannel instead
func (c *Client) SetActiveChannel(channelId string) (*Result, *AppError) {
data := map[string]string{}
data["channel_id"] = channelId
if r, err := c.DoApiPost("/users/status/set_active_channel", 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) GetMyTeam(etag string) (*Result, *AppError) {
if r, err := c.DoApiGet(c.GetTeamRoute()+"/me", "", etag); err != nil {
return nil, err
@ -1874,15 +1841,14 @@ func (c *Client) GetTeamStats(teamId string) (*Result, *AppError) {
}
}
// GetTeamStats will return a team stats object containing the number of users on the team
// based on the team id provided. Must be authenticated.
// GetTeamByName will return a team object based on the team name 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
r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil
}
}
@ -2389,3 +2355,23 @@ func (c *Client) UpdateChannelRoles(channelId string, userId string, roles strin
}
}
}
func (c *Client) PinPost(channelId string, postId string) (*Result, *AppError) {
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/posts/"+postId+"/pin", ""); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil
}
}
func (c *Client) UnpinPost(channelId string, postId string) (*Result, *AppError) {
if r, err := c.DoApiPost(c.GetChannelRoute(channelId)+"/posts/"+postId+"/unpin", ""); err != nil {
return nil, err
} else {
defer closeBody(r)
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
"os"
)
const (
CDS_OFFLINE_AFTER_MILLIS = 1000 * 60 * 30 // 30 minutes
CDS_TYPE_APP = "mattermost_app"
)
type ClusterDiscovery struct {
Id string `json:"id"`
Type string `json:"type"`
ClusterName string `json:"cluster_name"`
Hostname string `json:"hostname"`
GossipPort int32 `json:"gossip_port"`
Port int32 `json:"port"`
CreateAt int64 `json:"create_at"`
LastPingAt int64 `json:"last_ping_at"`
}
func (o *ClusterDiscovery) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
if o.CreateAt == 0 {
o.CreateAt = GetMillis()
o.LastPingAt = o.CreateAt
}
}
func (o *ClusterDiscovery) AutoFillHostname() {
// attempt to set the hostname from the OS
if len(o.Hostname) == 0 {
if hn, err := os.Hostname(); err == nil {
o.Hostname = hn
}
}
}
func (o *ClusterDiscovery) AutoFillIpAddress() {
// attempt to set the hostname to the first non-local IP address
if len(o.Hostname) == 0 {
o.Hostname = GetServerIpAddress()
}
}
func (o *ClusterDiscovery) IsEqual(in *ClusterDiscovery) bool {
if in == nil {
return false
}
if o.Type != in.Type {
return false
}
if o.ClusterName != in.ClusterName {
return false
}
if o.Hostname != in.Hostname {
return false
}
return true
}
func FilterClusterDiscovery(vs []*ClusterDiscovery, f func(*ClusterDiscovery) bool) []*ClusterDiscovery {
copy := make([]*ClusterDiscovery, 0)
for _, v := range vs {
if f(v) {
copy = append(copy, v)
}
}
return copy
}
func (o *ClusterDiscovery) IsValid() *AppError {
if len(o.Id) != 26 {
return NewLocAppError("Channel.IsValid", "model.channel.is_valid.id.app_error", nil, "")
}
if len(o.ClusterName) == 0 {
return NewLocAppError("ClusterDiscovery.IsValid", "ClusterName must be set", nil, "")
}
if len(o.Type) == 0 {
return NewLocAppError("ClusterDiscovery.IsValid", "Type must be set", nil, "")
}
if len(o.Hostname) == 0 {
return NewLocAppError("ClusterDiscovery.IsValid", "Hostname must be set", nil, "")
}
if o.CreateAt == 0 {
return NewLocAppError("ClusterDiscovery.IsValid", "CreateAt must be set", nil, "")
}
if o.LastPingAt == 0 {
return NewLocAppError("ClusterDiscovery.IsValid", "LastPingAt must be set", nil, "")
}
return nil
}
func (o *ClusterDiscovery) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
}
return string(b)
}
func ClusterDiscoveryFromJson(data io.Reader) *ClusterDiscovery {
decoder := json.NewDecoder(data)
var me ClusterDiscovery
err := decoder.Decode(&me)
if err == nil {
return &me
}
return nil
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -6,16 +6,14 @@ package model
import (
"encoding/json"
"io"
"strings"
)
type ClusterInfo struct {
Id string `json:"id"`
Version string `json:"version"`
ConfigHash string `json:"config_hash"`
InterNodeUrl string `json:"internode_url"`
Hostname string `json:"hostname"`
LastSuccessfulPing int64 `json:"last_ping"`
IsAlive bool `json:"is_alive"`
Version string `json:"version"`
ConfigHash string `json:"config_hash"`
IpAddress string `json:"ipaddress"`
Hostname string `json:"hostname"`
}
func (me *ClusterInfo) ToJson() string {
@ -27,6 +25,11 @@ func (me *ClusterInfo) ToJson() string {
}
}
func (me *ClusterInfo) Copy() *ClusterInfo {
json := me.ToJson()
return ClusterInfoFromJson(strings.NewReader(json))
}
func ClusterInfoFromJson(data io.Reader) *ClusterInfo {
decoder := json.NewDecoder(data)
var me ClusterInfo
@ -38,14 +41,6 @@ func ClusterInfoFromJson(data io.Reader) *ClusterInfo {
}
}
func (me *ClusterInfo) HaveEstablishedInitialContact() bool {
if me.Id != "" {
return true
}
return false
}
func ClusterInfosToJson(objmap []*ClusterInfo) string {
if b, err := json.Marshal(objmap); err != nil {
return ""

View File

@ -0,0 +1,55 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
CLUSTER_EVENT_PUBLISH = "publish"
CLUSTER_EVENT_UPDATE_STATUS = "update_status"
CLUSTER_EVENT_INVALIDATE_ALL_CACHES = "inv_all_caches"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_REACTIONS = "inv_reactions"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_WEBHOOK = "inv_webhook"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_POSTS = "inv_channel_posts"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS_NOTIFY_PROPS = "inv_channel_members_notify_props"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_MEMBERS = "inv_channel_members"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL_BY_NAME = "inv_channel_name"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_CHANNEL = "inv_channel"
CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER = "inv_user"
CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER = "clear_session_user"
CLUSTER_SEND_BEST_EFFORT = "best_effort"
CLUSTER_SEND_RELIABLE = "reliable"
)
type ClusterMessage struct {
Event string `json:"event"`
SendType string `json:"-"`
WaitForAllToSend bool `json:"-"`
Data string `json:"data,omitempty"`
Props map[string]string `json:"props,omitempty"`
}
func (o *ClusterMessage) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func ClusterMessageFromJson(data io.Reader) *ClusterMessage {
decoder := json.NewDecoder(data)
var o ClusterMessage
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -6,13 +6,20 @@ package model
import (
"encoding/json"
"io"
goi18n "github.com/nicksnyder/go-i18n/i18n"
)
type CommandArgs struct {
ChannelId string `json:"channel_id"`
RootId string `json:"root_id"`
ParentId string `json:"parent_id"`
Command string `json:"command"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
TeamId string `json:"team_id"`
RootId string `json:"root_id"`
ParentId string `json:"parent_id"`
Command string `json:"command"`
SiteURL string `json:"-"`
T goi18n.TranslateFunc `json:"-"`
Session Session `json:"-"`
}
func (o *CommandArgs) ToJson() string {

View File

@ -1,11 +1,10 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
)
@ -40,14 +39,8 @@ func CommandResponseFromJson(data io.Reader) *CommandResponse {
return nil
}
// Ensure attachment fields are stored as strings
for _, attachment := range o.Attachments {
for _, field := range attachment.Fields {
if field.Value != nil {
field.Value = fmt.Sprintf("%v", field.Value)
}
}
}
o.Text = ExpandAnnouncement(o.Text)
o.Attachments = ProcessSlackAttachments(o.Attachments)
return &o
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,9 +1,10 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"regexp"
"time"
)
@ -64,6 +65,15 @@ func CompliancePostHeader() []string {
}
}
func cleanComplianceStrings(in string) string {
if matched, _ := regexp.MatchString("^\\s*(=|\\+|\\-)", in); matched {
return "'" + in
} else {
return in
}
}
func (me *CompliancePost) Row() []string {
postDeleteAt := ""
@ -77,15 +87,15 @@ func (me *CompliancePost) Row() []string {
}
return []string{
me.TeamName,
me.TeamDisplayName,
cleanComplianceStrings(me.TeamName),
cleanComplianceStrings(me.TeamDisplayName),
me.ChannelName,
me.ChannelDisplayName,
cleanComplianceStrings(me.ChannelName),
cleanComplianceStrings(me.ChannelDisplayName),
me.UserUsername,
me.UserEmail,
me.UserNickname,
cleanComplianceStrings(me.UserUsername),
cleanComplianceStrings(me.UserEmail),
cleanComplianceStrings(me.UserNickname),
me.PostId,
time.Unix(0, me.PostCreateAt*int64(1000*1000)).Format(time.RFC3339),
@ -95,7 +105,7 @@ func (me *CompliancePost) Row() []string {
me.PostRootId,
me.PostParentId,
me.PostOriginalId,
me.PostMessage,
cleanComplianceStrings(me.PostMessage),
me.PostType,
me.PostProps,
me.PostHashtags,

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -6,6 +6,7 @@ package model
import (
"encoding/json"
"io"
"net/http"
"net/url"
)
@ -32,12 +33,17 @@ const (
WEBSERVER_MODE_GZIP = "gzip"
WEBSERVER_MODE_DISABLED = "disabled"
GENERIC_NOTIFICATION = "generic"
FULL_NOTIFICATION = "full"
GENERIC_NO_CHANNEL_NOTIFICATION = "generic_no_channel"
GENERIC_NOTIFICATION = "generic"
FULL_NOTIFICATION = "full"
DIRECT_MESSAGE_ANY = "any"
DIRECT_MESSAGE_TEAM = "team"
SHOW_USERNAME = "username"
SHOW_NICKNAME_FULLNAME = "nickname_full_name"
SHOW_FULLNAME = "full_name"
PERMISSIONS_ALL = "all"
PERMISSIONS_CHANNEL_ADMIN = "channel_admin"
PERMISSIONS_TEAM_ADMIN = "team_admin"
@ -60,6 +66,9 @@ const (
EMAIL_BATCHING_BUFFER_SIZE = 256
EMAIL_BATCHING_INTERVAL = 30
EMAIL_NOTIFICATION_CONTENTS_FULL = "full"
EMAIL_NOTIFICATION_CONTENTS_GENERIC = "generic"
SITENAME_MAX_LENGTH = 30
SERVICE_SETTINGS_DEFAULT_SITE_URL = ""
@ -75,12 +84,15 @@ const (
EMAIL_SETTINGS_DEFAULT_FEEDBACK_ORGANIZATION = ""
SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK = "https://about.mattermost.com/default-terms/"
SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK = "https://about.mattermost.com/default-privacy-policy/"
SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK = "https://about.mattermost.com/default-about/"
SUPPORT_SETTINGS_DEFAULT_HELP_LINK = "https://about.mattermost.com/default-help/"
SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK = "https://about.mattermost.com/default-report-a-problem/"
SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL = "feedback@mattermost.com"
SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK = "https://about.mattermost.com/default-terms/"
SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK = "https://about.mattermost.com/default-privacy-policy/"
SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK = "https://about.mattermost.com/default-about/"
SUPPORT_SETTINGS_DEFAULT_HELP_LINK = "https://about.mattermost.com/default-help/"
SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK = "https://about.mattermost.com/default-report-a-problem/"
SUPPORT_SETTINGS_DEFAULT_ADMINISTRATORS_GUIDE_LINK = "https://about.mattermost.com/administrators-guide/"
SUPPORT_SETTINGS_DEFAULT_TROUBLESHOOTING_FORUM_LINK = "https://about.mattermost.com/troubleshooting-forum/"
SUPPORT_SETTINGS_DEFAULT_COMMERCIAL_SUPPORT_LINK = "https://about.mattermost.com/commercial-support/"
SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL = "feedback@mattermost.com"
LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = ""
@ -107,10 +119,20 @@ const (
WEBRTC_SETTINGS_DEFAULT_TURN_URI = ""
ANALYTICS_SETTINGS_DEFAULT_MAX_USERS_FOR_STATISTICS = 2500
ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_COLOR = "#f2a93b"
ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_TEXT_COLOR = "#333333"
ELASTICSEARCH_SETTINGS_DEFAULT_CONNECTION_URL = ""
ELASTICSEARCH_SETTINGS_DEFAULT_USERNAME = ""
ELASTICSEARCH_SETTINGS_DEFAULT_PASSWORD = ""
ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_REPLICAS = 1
ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_SHARDS = 1
)
type ServiceSettings struct {
SiteURL *string
LicenseFileLocation *string
ListenAddress string
ConnectionSecurity *string
TLSCertFile *string
@ -121,6 +143,7 @@ type ServiceSettings struct {
ReadTimeout *int
WriteTimeout *int
MaximumLoginAttempts int
GoroutineHealthThreshold *int
GoogleDeveloperKey string
EnableOAuthServiceProvider bool
EnableIncomingWebhooks bool
@ -129,6 +152,7 @@ type ServiceSettings struct {
EnableOnlyAdminIntegrations *bool
EnablePostUsernameOverride bool
EnablePostIconOverride bool
EnableAPIv3 *bool
EnableLinkPreviews *bool
EnableTesting bool
EnableDeveloper *bool
@ -136,6 +160,7 @@ type ServiceSettings struct {
EnableInsecureOutgoingConnections *bool
EnableMultifactorAuthentication *bool
EnforceMultifactorAuthentication *bool
EnableUserAccessTokens *bool
AllowCorsFrom *string
SessionLengthWebInDays *int
SessionLengthMobileInDays *int
@ -145,19 +170,28 @@ type ServiceSettings struct {
WebsocketPort *int
WebserverMode *string
EnableCustomEmoji *bool
EnableEmojiPicker *bool
RestrictCustomEmojiCreation *string
RestrictPostDelete *string
AllowEditPost *string
PostEditTimeLimit *int
TimeBetweenUserTypingUpdatesMilliseconds *int64
EnablePostSearch *bool
EnableUserTypingMessages *bool
EnableChannelViewedMessages *bool
EnableUserStatuses *bool
ClusterLogTimeoutMilliseconds *int
}
type ClusterSettings struct {
Enable *bool
InterNodeListenAddress *string
InterNodeUrls []string
Enable *bool
ClusterName *string
OverrideHostname *string
UseIpAddress *bool
UseExperimentalGossip *bool
ReadOnlyConfig *bool
GossipPort *int
StreamingPort *int
}
type MetricsSettings struct {
@ -181,13 +215,15 @@ type SSOSettings struct {
}
type SqlSettings struct {
DriverName string
DataSource string
DataSourceReplicas []string
MaxIdleConns int
MaxOpenConns int
Trace bool
AtRestEncryptKey string
DriverName string
DataSource string
DataSourceReplicas []string
DataSourceSearchReplicas []string
MaxIdleConns int
MaxOpenConns int
Trace bool
AtRestEncryptKey string
QueryTimeout *int
}
type LogSettings struct {
@ -210,17 +246,14 @@ type PasswordSettings struct {
}
type FileSettings struct {
EnableFileAttachments *bool
EnableMobileUpload *bool
EnableMobileDownload *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
@ -228,30 +261,34 @@ type FileSettings struct {
AmazonS3Region string
AmazonS3Endpoint string
AmazonS3SSL *bool
AmazonS3SignV2 *bool
AmazonS3SSE *bool
}
type EmailSettings struct {
EnableSignUpWithEmail bool
EnableSignInWithEmail *bool
EnableSignInWithUsername *bool
SendEmailNotifications bool
RequireEmailVerification bool
FeedbackName string
FeedbackEmail string
FeedbackOrganization *string
SMTPUsername string
SMTPPassword string
SMTPServer string
SMTPPort string
ConnectionSecurity string
InviteSalt string
PasswordResetSalt string
SendPushNotifications *bool
PushNotificationServer *string
PushNotificationContents *string
EnableEmailBatching *bool
EmailBatchingBufferSize *int
EmailBatchingInterval *int
EnableSignUpWithEmail bool
EnableSignInWithEmail *bool
EnableSignInWithUsername *bool
SendEmailNotifications bool
RequireEmailVerification bool
FeedbackName string
FeedbackEmail string
FeedbackOrganization *string
EnableSMTPAuth *bool
SMTPUsername string
SMTPPassword string
SMTPServer string
SMTPPort string
ConnectionSecurity string
InviteSalt string
SendPushNotifications *bool
PushNotificationServer *string
PushNotificationContents *string
EnableEmailBatching *bool
EmailBatchingBufferSize *int
EmailBatchingInterval *int
SkipServerCertificateVerification *bool
EmailNotificationContentsType *string
}
type RateLimitSettings struct {
@ -269,35 +306,48 @@ type PrivacySettings struct {
}
type SupportSettings struct {
TermsOfServiceLink *string
PrivacyPolicyLink *string
AboutLink *string
HelpLink *string
ReportAProblemLink *string
SupportEmail *string
TermsOfServiceLink *string
PrivacyPolicyLink *string
AboutLink *string
HelpLink *string
ReportAProblemLink *string
AdministratorsGuideLink *string
TroubleshootingForumLink *string
CommercialSupportLink *string
SupportEmail *string
}
type AnnouncementSettings struct {
EnableBanner *bool
BannerText *string
BannerColor *string
BannerTextColor *string
AllowBannerDismissal *bool
}
type TeamSettings struct {
SiteName string
MaxUsersPerTeam int
EnableTeamCreation bool
EnableUserCreation bool
EnableOpenServer *bool
RestrictCreationToDomains string
EnableCustomBrand *bool
CustomBrandText *string
CustomDescriptionText *string
RestrictDirectMessage *string
RestrictTeamInvite *string
RestrictPublicChannelManagement *string
RestrictPrivateChannelManagement *string
RestrictPublicChannelCreation *string
RestrictPrivateChannelCreation *string
RestrictPublicChannelDeletion *string
RestrictPrivateChannelDeletion *string
UserStatusAwayTimeout *int64
MaxChannelsPerTeam *int64
MaxNotificationsPerChannel *int64
SiteName string
MaxUsersPerTeam int
EnableTeamCreation bool
EnableUserCreation bool
EnableOpenServer *bool
RestrictCreationToDomains string
EnableCustomBrand *bool
CustomBrandText *string
CustomDescriptionText *string
RestrictDirectMessage *string
RestrictTeamInvite *string
RestrictPublicChannelManagement *string
RestrictPrivateChannelManagement *string
RestrictPublicChannelCreation *string
RestrictPrivateChannelCreation *string
RestrictPublicChannelDeletion *string
RestrictPrivateChannelDeletion *string
RestrictPrivateChannelManageMembers *string
UserStatusAwayTimeout *int64
MaxChannelsPerTeam *int64
MaxNotificationsPerChannel *int64
TeammateNameDisplay *string
}
type LdapSettings struct {
@ -389,29 +439,58 @@ type WebrtcSettings struct {
TurnSharedKey *string
}
type ElasticsearchSettings struct {
ConnectionUrl *string
Username *string
Password *string
EnableIndexing *bool
EnableSearching *bool
Sniff *bool
PostIndexReplicas *int
PostIndexShards *int
}
type DataRetentionSettings struct {
Enable *bool
}
type JobSettings struct {
RunJobs *bool
RunScheduler *bool
}
type PluginSettings struct {
Plugins map[string]interface{}
}
type Config struct {
ServiceSettings ServiceSettings
TeamSettings TeamSettings
SqlSettings SqlSettings
LogSettings LogSettings
PasswordSettings PasswordSettings
FileSettings FileSettings
EmailSettings EmailSettings
RateLimitSettings RateLimitSettings
PrivacySettings PrivacySettings
SupportSettings SupportSettings
GitLabSettings SSOSettings
GoogleSettings SSOSettings
Office365Settings SSOSettings
LdapSettings LdapSettings
ComplianceSettings ComplianceSettings
LocalizationSettings LocalizationSettings
SamlSettings SamlSettings
NativeAppSettings NativeAppSettings
ClusterSettings ClusterSettings
MetricsSettings MetricsSettings
AnalyticsSettings AnalyticsSettings
WebrtcSettings WebrtcSettings
ServiceSettings ServiceSettings
TeamSettings TeamSettings
SqlSettings SqlSettings
LogSettings LogSettings
PasswordSettings PasswordSettings
FileSettings FileSettings
EmailSettings EmailSettings
RateLimitSettings RateLimitSettings
PrivacySettings PrivacySettings
SupportSettings SupportSettings
AnnouncementSettings AnnouncementSettings
GitLabSettings SSOSettings
GoogleSettings SSOSettings
Office365Settings SSOSettings
LdapSettings LdapSettings
ComplianceSettings ComplianceSettings
LocalizationSettings LocalizationSettings
SamlSettings SamlSettings
NativeAppSettings NativeAppSettings
ClusterSettings ClusterSettings
MetricsSettings MetricsSettings
AnalyticsSettings AnalyticsSettings
WebrtcSettings WebrtcSettings
ElasticsearchSettings ElasticsearchSettings
DataRetentionSettings DataRetentionSettings
JobSettings JobSettings
PluginSettings PluginSettings
}
func (o *Config) ToJson() string {
@ -453,27 +532,52 @@ func (o *Config) SetDefaults() {
o.SqlSettings.AtRestEncryptKey = NewRandomString(32)
}
if o.SqlSettings.QueryTimeout == nil {
o.SqlSettings.QueryTimeout = new(int)
*o.SqlSettings.QueryTimeout = 30
}
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.AmazonS3SignV2 == nil {
o.FileSettings.AmazonS3SignV2 = new(bool)
// Signature v2 is not enabled by default.
}
if o.FileSettings.AmazonS3SSE == nil {
o.FileSettings.AmazonS3SSE = new(bool)
*o.FileSettings.AmazonS3SSE = false // Not Encrypted by default.
}
if o.FileSettings.EnableFileAttachments == nil {
o.FileSettings.EnableFileAttachments = new(bool)
*o.FileSettings.EnableFileAttachments = true
}
if o.FileSettings.EnableMobileUpload == nil {
o.FileSettings.EnableMobileUpload = new(bool)
*o.FileSettings.EnableMobileUpload = true
}
if o.FileSettings.EnableMobileDownload == nil {
o.FileSettings.EnableMobileDownload = new(bool)
*o.FileSettings.EnableMobileDownload = true
}
if o.FileSettings.MaxFileSize == nil {
o.FileSettings.MaxFileSize = new(int64)
*o.FileSettings.MaxFileSize = 52428800 // 50 MB
}
if len(*o.FileSettings.PublicLinkSalt) == 0 {
if o.FileSettings.PublicLinkSalt == nil || len(*o.FileSettings.PublicLinkSalt) == 0 {
o.FileSettings.PublicLinkSalt = new(string)
*o.FileSettings.PublicLinkSalt = NewRandomString(32)
}
@ -483,12 +587,12 @@ func (o *Config) SetDefaults() {
o.FileSettings.InitialFont = "luximbi.ttf"
}
if len(o.EmailSettings.InviteSalt) == 0 {
o.EmailSettings.InviteSalt = NewRandomString(32)
if o.FileSettings.Directory == "" {
o.FileSettings.Directory = "./data/"
}
if len(o.EmailSettings.PasswordResetSalt) == 0 {
o.EmailSettings.PasswordResetSalt = NewRandomString(32)
if len(o.EmailSettings.InviteSalt) == 0 {
o.EmailSettings.InviteSalt = NewRandomString(32)
}
if o.ServiceSettings.SiteURL == nil {
@ -496,6 +600,15 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.SiteURL = SERVICE_SETTINGS_DEFAULT_SITE_URL
}
if o.ServiceSettings.LicenseFileLocation == nil {
o.ServiceSettings.LicenseFileLocation = new(string)
}
if o.ServiceSettings.EnableAPIv3 == nil {
o.ServiceSettings.EnableAPIv3 = new(bool)
*o.ServiceSettings.EnableAPIv3 = true
}
if o.ServiceSettings.EnableLinkPreviews == nil {
o.ServiceSettings.EnableLinkPreviews = new(bool)
*o.ServiceSettings.EnableLinkPreviews = false
@ -526,6 +639,11 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.EnforceMultifactorAuthentication = false
}
if o.ServiceSettings.EnableUserAccessTokens == nil {
o.ServiceSettings.EnableUserAccessTokens = new(bool)
*o.ServiceSettings.EnableUserAccessTokens = false
}
if o.PasswordSettings.MinimumLength == nil {
o.PasswordSettings.MinimumLength = new(int)
*o.PasswordSettings.MinimumLength = PASSWORD_MINIMUM_LENGTH
@ -594,13 +712,21 @@ func (o *Config) SetDefaults() {
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.RestrictPublicChannelManagement == PERMISSIONS_CHANNEL_ADMIN {
*o.TeamSettings.RestrictPublicChannelCreation = PERMISSIONS_TEAM_ADMIN
} else {
*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.RestrictPrivateChannelManagement == PERMISSIONS_CHANNEL_ADMIN {
*o.TeamSettings.RestrictPrivateChannelCreation = PERMISSIONS_TEAM_ADMIN
} else {
*o.TeamSettings.RestrictPrivateChannelCreation = *o.TeamSettings.RestrictPrivateChannelManagement
}
}
if o.TeamSettings.RestrictPublicChannelDeletion == nil {
@ -615,6 +741,11 @@ func (o *Config) SetDefaults() {
*o.TeamSettings.RestrictPrivateChannelDeletion = *o.TeamSettings.RestrictPrivateChannelManagement
}
if o.TeamSettings.RestrictPrivateChannelManageMembers == nil {
o.TeamSettings.RestrictPrivateChannelManageMembers = new(string)
*o.TeamSettings.RestrictPrivateChannelManageMembers = PERMISSIONS_ALL
}
if o.TeamSettings.UserStatusAwayTimeout == nil {
o.TeamSettings.UserStatusAwayTimeout = new(int64)
*o.TeamSettings.UserStatusAwayTimeout = TEAM_SETTINGS_DEFAULT_USER_STATUS_AWAY_TIMEOUT
@ -680,8 +811,31 @@ func (o *Config) SetDefaults() {
*o.EmailSettings.EmailBatchingInterval = EMAIL_BATCHING_INTERVAL
}
if o.EmailSettings.EnableSMTPAuth == nil {
o.EmailSettings.EnableSMTPAuth = new(bool)
if o.EmailSettings.ConnectionSecurity == CONN_SECURITY_NONE {
*o.EmailSettings.EnableSMTPAuth = false
} else {
*o.EmailSettings.EnableSMTPAuth = true
}
}
if o.EmailSettings.ConnectionSecurity == CONN_SECURITY_PLAIN {
o.EmailSettings.ConnectionSecurity = CONN_SECURITY_NONE
}
if o.EmailSettings.SkipServerCertificateVerification == nil {
o.EmailSettings.SkipServerCertificateVerification = new(bool)
*o.EmailSettings.SkipServerCertificateVerification = false
}
if o.EmailSettings.EmailNotificationContentsType == nil {
o.EmailSettings.EmailNotificationContentsType = new(string)
*o.EmailSettings.EmailNotificationContentsType = EMAIL_NOTIFICATION_CONTENTS_FULL
}
if !IsSafeLink(o.SupportSettings.TermsOfServiceLink) {
o.SupportSettings.TermsOfServiceLink = nil
*o.SupportSettings.TermsOfServiceLink = SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK
}
if o.SupportSettings.TermsOfServiceLink == nil {
@ -690,7 +844,7 @@ func (o *Config) SetDefaults() {
}
if !IsSafeLink(o.SupportSettings.PrivacyPolicyLink) {
o.SupportSettings.PrivacyPolicyLink = nil
*o.SupportSettings.PrivacyPolicyLink = ""
}
if o.SupportSettings.PrivacyPolicyLink == nil {
@ -699,7 +853,7 @@ func (o *Config) SetDefaults() {
}
if !IsSafeLink(o.SupportSettings.AboutLink) {
o.SupportSettings.AboutLink = nil
*o.SupportSettings.AboutLink = ""
}
if o.SupportSettings.AboutLink == nil {
@ -708,7 +862,7 @@ func (o *Config) SetDefaults() {
}
if !IsSafeLink(o.SupportSettings.HelpLink) {
o.SupportSettings.HelpLink = nil
*o.SupportSettings.HelpLink = ""
}
if o.SupportSettings.HelpLink == nil {
@ -717,7 +871,7 @@ func (o *Config) SetDefaults() {
}
if !IsSafeLink(o.SupportSettings.ReportAProblemLink) {
o.SupportSettings.ReportAProblemLink = nil
*o.SupportSettings.ReportAProblemLink = ""
}
if o.SupportSettings.ReportAProblemLink == nil {
@ -725,11 +879,63 @@ func (o *Config) SetDefaults() {
*o.SupportSettings.ReportAProblemLink = SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK
}
if !IsSafeLink(o.SupportSettings.AdministratorsGuideLink) {
*o.SupportSettings.AdministratorsGuideLink = ""
}
if o.SupportSettings.AdministratorsGuideLink == nil {
o.SupportSettings.AdministratorsGuideLink = new(string)
*o.SupportSettings.AdministratorsGuideLink = SUPPORT_SETTINGS_DEFAULT_ADMINISTRATORS_GUIDE_LINK
}
if !IsSafeLink(o.SupportSettings.TroubleshootingForumLink) {
*o.SupportSettings.TroubleshootingForumLink = ""
}
if o.SupportSettings.TroubleshootingForumLink == nil {
o.SupportSettings.TroubleshootingForumLink = new(string)
*o.SupportSettings.TroubleshootingForumLink = SUPPORT_SETTINGS_DEFAULT_TROUBLESHOOTING_FORUM_LINK
}
if !IsSafeLink(o.SupportSettings.CommercialSupportLink) {
*o.SupportSettings.CommercialSupportLink = ""
}
if o.SupportSettings.CommercialSupportLink == nil {
o.SupportSettings.CommercialSupportLink = new(string)
*o.SupportSettings.CommercialSupportLink = SUPPORT_SETTINGS_DEFAULT_COMMERCIAL_SUPPORT_LINK
}
if o.SupportSettings.SupportEmail == nil {
o.SupportSettings.SupportEmail = new(string)
*o.SupportSettings.SupportEmail = SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL
}
if o.AnnouncementSettings.EnableBanner == nil {
o.AnnouncementSettings.EnableBanner = new(bool)
*o.AnnouncementSettings.EnableBanner = false
}
if o.AnnouncementSettings.BannerText == nil {
o.AnnouncementSettings.BannerText = new(string)
*o.AnnouncementSettings.BannerText = ""
}
if o.AnnouncementSettings.BannerColor == nil {
o.AnnouncementSettings.BannerColor = new(string)
*o.AnnouncementSettings.BannerColor = ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_COLOR
}
if o.AnnouncementSettings.BannerTextColor == nil {
o.AnnouncementSettings.BannerTextColor = new(string)
*o.AnnouncementSettings.BannerTextColor = ANNOUNCEMENT_SETTINGS_DEFAULT_BANNER_TEXT_COLOR
}
if o.AnnouncementSettings.AllowBannerDismissal == nil {
o.AnnouncementSettings.AllowBannerDismissal = new(bool)
*o.AnnouncementSettings.AllowBannerDismissal = true
}
if o.LdapSettings.Enable == nil {
o.LdapSettings.Enable = new(bool)
*o.LdapSettings.Enable = false
@ -884,7 +1090,12 @@ func (o *Config) SetDefaults() {
if o.ServiceSettings.EnableCustomEmoji == nil {
o.ServiceSettings.EnableCustomEmoji = new(bool)
*o.ServiceSettings.EnableCustomEmoji = true
*o.ServiceSettings.EnableCustomEmoji = false
}
if o.ServiceSettings.EnableEmojiPicker == nil {
o.ServiceSettings.EnableEmojiPicker = new(bool)
*o.ServiceSettings.EnableEmojiPicker = true
}
if o.ServiceSettings.RestrictCustomEmojiCreation == nil {
@ -907,18 +1118,44 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.PostEditTimeLimit = 300
}
if o.ClusterSettings.InterNodeListenAddress == nil {
o.ClusterSettings.InterNodeListenAddress = new(string)
*o.ClusterSettings.InterNodeListenAddress = ":8075"
}
if o.ClusterSettings.Enable == nil {
o.ClusterSettings.Enable = new(bool)
*o.ClusterSettings.Enable = false
}
if o.ClusterSettings.InterNodeUrls == nil {
o.ClusterSettings.InterNodeUrls = []string{}
if o.ClusterSettings.ClusterName == nil {
o.ClusterSettings.ClusterName = new(string)
*o.ClusterSettings.ClusterName = ""
}
if o.ClusterSettings.OverrideHostname == nil {
o.ClusterSettings.OverrideHostname = new(string)
*o.ClusterSettings.OverrideHostname = ""
}
if o.ClusterSettings.UseIpAddress == nil {
o.ClusterSettings.UseIpAddress = new(bool)
*o.ClusterSettings.UseIpAddress = true
}
if o.ClusterSettings.UseExperimentalGossip == nil {
o.ClusterSettings.UseExperimentalGossip = new(bool)
*o.ClusterSettings.UseExperimentalGossip = false
}
if o.ClusterSettings.ReadOnlyConfig == nil {
o.ClusterSettings.ReadOnlyConfig = new(bool)
*o.ClusterSettings.ReadOnlyConfig = true
}
if o.ClusterSettings.GossipPort == nil {
o.ClusterSettings.GossipPort = new(int)
*o.ClusterSettings.GossipPort = 8074
}
if o.ClusterSettings.StreamingPort == nil {
o.ClusterSettings.StreamingPort = new(int)
*o.ClusterSettings.StreamingPort = 8075
}
if o.MetricsSettings.ListenAddress == nil {
@ -978,12 +1215,12 @@ func (o *Config) SetDefaults() {
if o.SamlSettings.Verify == nil {
o.SamlSettings.Verify = new(bool)
*o.SamlSettings.Verify = false
*o.SamlSettings.Verify = true
}
if o.SamlSettings.Encrypt == nil {
o.SamlSettings.Encrypt = new(bool)
*o.SamlSettings.Encrypt = false
*o.SamlSettings.Encrypt = true
}
if o.SamlSettings.IdpUrl == nil {
@ -1056,6 +1293,15 @@ func (o *Config) SetDefaults() {
*o.SamlSettings.LocaleAttribute = SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE
}
if o.TeamSettings.TeammateNameDisplay == nil {
o.TeamSettings.TeammateNameDisplay = new(string)
*o.TeamSettings.TeammateNameDisplay = SHOW_USERNAME
if *o.SamlSettings.Enable || *o.LdapSettings.Enable {
*o.TeamSettings.TeammateNameDisplay = SHOW_FULLNAME
}
}
if o.NativeAppSettings.AppDownloadLink == nil {
o.NativeAppSettings.AppDownloadLink = new(string)
*o.NativeAppSettings.AppDownloadLink = NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK
@ -1076,6 +1322,11 @@ func (o *Config) SetDefaults() {
*o.RateLimitSettings.Enable = false
}
if o.ServiceSettings.GoroutineHealthThreshold == nil {
o.ServiceSettings.GoroutineHealthThreshold = new(int)
*o.ServiceSettings.GoroutineHealthThreshold = -1
}
if o.RateLimitSettings.MaxBurst == nil {
o.RateLimitSettings.MaxBurst = new(int)
*o.RateLimitSettings.MaxBurst = 100
@ -1131,16 +1382,90 @@ func (o *Config) SetDefaults() {
*o.ServiceSettings.TimeBetweenUserTypingUpdatesMilliseconds = 5000
}
if o.ServiceSettings.EnablePostSearch == nil {
o.ServiceSettings.EnablePostSearch = new(bool)
*o.ServiceSettings.EnablePostSearch = true
}
if o.ServiceSettings.EnableUserTypingMessages == nil {
o.ServiceSettings.EnableUserTypingMessages = new(bool)
*o.ServiceSettings.EnableUserTypingMessages = true
}
if o.ServiceSettings.EnableChannelViewedMessages == nil {
o.ServiceSettings.EnableChannelViewedMessages = new(bool)
*o.ServiceSettings.EnableChannelViewedMessages = true
}
if o.ServiceSettings.EnableUserStatuses == nil {
o.ServiceSettings.EnableUserStatuses = new(bool)
*o.ServiceSettings.EnableUserStatuses = true
}
if o.ServiceSettings.ClusterLogTimeoutMilliseconds == nil {
o.ServiceSettings.ClusterLogTimeoutMilliseconds = new(int)
*o.ServiceSettings.ClusterLogTimeoutMilliseconds = 2000
}
if o.ElasticsearchSettings.ConnectionUrl == nil {
o.ElasticsearchSettings.ConnectionUrl = new(string)
*o.ElasticsearchSettings.ConnectionUrl = ELASTICSEARCH_SETTINGS_DEFAULT_CONNECTION_URL
}
if o.ElasticsearchSettings.Username == nil {
o.ElasticsearchSettings.Username = new(string)
*o.ElasticsearchSettings.Username = ELASTICSEARCH_SETTINGS_DEFAULT_USERNAME
}
if o.ElasticsearchSettings.Password == nil {
o.ElasticsearchSettings.Password = new(string)
*o.ElasticsearchSettings.Password = ELASTICSEARCH_SETTINGS_DEFAULT_PASSWORD
}
if o.ElasticsearchSettings.EnableIndexing == nil {
o.ElasticsearchSettings.EnableIndexing = new(bool)
*o.ElasticsearchSettings.EnableIndexing = false
}
if o.ElasticsearchSettings.EnableSearching == nil {
o.ElasticsearchSettings.EnableSearching = new(bool)
*o.ElasticsearchSettings.EnableSearching = false
}
if o.ElasticsearchSettings.Sniff == nil {
o.ElasticsearchSettings.Sniff = new(bool)
*o.ElasticsearchSettings.Sniff = true
}
if o.ElasticsearchSettings.PostIndexReplicas == nil {
o.ElasticsearchSettings.PostIndexReplicas = new(int)
*o.ElasticsearchSettings.PostIndexReplicas = ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_REPLICAS
}
if o.ElasticsearchSettings.PostIndexShards == nil {
o.ElasticsearchSettings.PostIndexShards = new(int)
*o.ElasticsearchSettings.PostIndexShards = ELASTICSEARCH_SETTINGS_DEFAULT_POST_INDEX_SHARDS
}
if o.DataRetentionSettings.Enable == nil {
o.DataRetentionSettings.Enable = new(bool)
*o.DataRetentionSettings.Enable = false
}
if o.JobSettings.RunJobs == nil {
o.JobSettings.RunJobs = new(bool)
*o.JobSettings.RunJobs = true
}
if o.JobSettings.RunScheduler == nil {
o.JobSettings.RunScheduler = new(bool)
*o.JobSettings.RunScheduler = true
}
if o.PluginSettings.Plugins == nil {
o.PluginSettings.Plugins = make(map[string]interface{})
}
o.defaultWebrtcSettings()
}
@ -1184,6 +1509,10 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.restrict_direct_message.app_error", nil, "")
}
if !(*o.TeamSettings.TeammateNameDisplay == SHOW_FULLNAME || *o.TeamSettings.TeammateNameDisplay == SHOW_NICKNAME_FULLNAME || *o.TeamSettings.TeammateNameDisplay == SHOW_USERNAME) {
return NewLocAppError("Config.IsValid", "model.config.is_valid.teammate_name_display.app_error", nil, "")
}
if len(o.SqlSettings.AtRestEncryptKey) < 32 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.encrypt_sql.app_error", nil, "")
}
@ -1196,6 +1525,10 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_idle.app_error", nil, "")
}
if *o.SqlSettings.QueryTimeout <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.sql_query_timeout.app_error", nil, "", http.StatusBadRequest)
}
if len(o.SqlSettings.DataSource) == 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.sql_data_src.app_error", nil, "")
}
@ -1212,30 +1545,6 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_driver.app_error", nil, "")
}
if o.FileSettings.PreviewHeight < 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_preview_height.app_error", nil, "")
}
if o.FileSettings.PreviewWidth <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_preview_width.app_error", nil, "")
}
if o.FileSettings.ProfileHeight <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_profile_height.app_error", nil, "")
}
if o.FileSettings.ProfileWidth <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_profile_width.app_error", nil, "")
}
if o.FileSettings.ThumbnailHeight <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_height.app_error", nil, "")
}
if o.FileSettings.ThumbnailWidth <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_thumb_width.app_error", nil, "")
}
if len(*o.FileSettings.PublicLinkSalt) < 32 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.file_salt.app_error", nil, "")
}
@ -1248,10 +1557,6 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_salt.app_error", nil, "")
}
if len(o.EmailSettings.PasswordResetSalt) < 32 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_reset_salt.app_error", nil, "")
}
if *o.EmailSettings.EmailBatchingBufferSize <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_batching_buffer_size.app_error", nil, "")
}
@ -1260,6 +1565,10 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_batching_interval.app_error", nil, "")
}
if !(*o.EmailSettings.EmailNotificationContentsType == EMAIL_NOTIFICATION_CONTENTS_FULL || *o.EmailSettings.EmailNotificationContentsType == EMAIL_NOTIFICATION_CONTENTS_GENERIC) {
return NewLocAppError("Config.IsValid", "model.config.is_valid.email_notification_contents_type.app_error", nil, "")
}
if o.RateLimitSettings.MemoryStoreSize <= 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.rate_mem.app_error", nil, "")
}
@ -1376,6 +1685,16 @@ func (o *Config) IsValid() *AppError {
return NewLocAppError("Config.IsValid", "model.config.is_valid.time_between_user_typing.app_error", nil, "")
}
if *o.ElasticsearchSettings.EnableIndexing {
if len(*o.ElasticsearchSettings.ConnectionUrl) == 0 {
return NewLocAppError("Config.IsValid", "model.config.is_valid.elastic_search.connection_url.app_error", nil, "")
}
}
if *o.ElasticsearchSettings.EnableSearching && !*o.ElasticsearchSettings.EnableIndexing {
return NewLocAppError("Config.IsValid", "model.config.is_valid.elastic_search.enable_searching.app_error", nil, "")
}
return nil
}
@ -1398,7 +1717,6 @@ func (o *Config) Sanitize() {
}
o.EmailSettings.InviteSalt = FAKE_SETTING
o.EmailSettings.PasswordResetSalt = FAKE_SETTING
if len(o.EmailSettings.SMTPPassword) > 0 {
o.EmailSettings.SMTPPassword = FAKE_SETTING
}
@ -1413,6 +1731,12 @@ func (o *Config) Sanitize() {
for i := range o.SqlSettings.DataSourceReplicas {
o.SqlSettings.DataSourceReplicas[i] = FAKE_SETTING
}
for i := range o.SqlSettings.DataSourceSearchReplicas {
o.SqlSettings.DataSourceSearchReplicas[i] = FAKE_SETTING
}
*o.ElasticsearchSettings.Password = FAKE_SETTING
}
func (o *Config) defaultWebrtcSettings() {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -34,7 +34,7 @@ func (emoji *Emoji) IsValid() *AppError {
return NewLocAppError("Emoji.IsValid", "model.emoji.user_id.app_error", nil, "")
}
if len(emoji.Name) == 0 || len(emoji.Name) > 64 {
if len(emoji.Name) == 0 || len(emoji.Name) > 64 || !IsValidAlphaNumHyphenUnderscore(emoji.Name, false) {
return NewLocAppError("Emoji.IsValid", "model.emoji.name.app_error", nil, "")
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package oauthgitlab

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -6,10 +6,9 @@ package model
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"strings"
)
const (
@ -81,35 +80,36 @@ func IncomingWebhookListFromJson(data io.Reader) []*IncomingWebhook {
func (o *IncomingWebhook) IsValid() *AppError {
if len(o.Id) != 26 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.id.app_error", nil, "")
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.id.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.create_at.app_error", nil, "id="+o.Id)
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.UpdateAt == 0 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.update_at.app_error", nil, "id="+o.Id)
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.UserId) != 26 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "")
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ChannelId) != 26 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "")
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.TeamId) != 26 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "")
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.DisplayName) > 64 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.display_name.app_error", nil, "")
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.display_name.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Description) > 128 {
return NewLocAppError("IncomingWebhook.IsValid", "model.incoming_hook.description.app_error", nil, "")
return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.description.app_error", nil, "", http.StatusBadRequest)
}
return nil
@ -193,39 +193,6 @@ func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) {
}
}
// To mention @channel via a webhook in Slack, the message should contain
// <!channel>, as explained at the bottom of this article:
// https://get.slack.help/hc/en-us/articles/202009646-Making-announcements
func expandAnnouncement(text string) string {
c1 := "<!channel>"
c2 := "@channel"
if strings.Contains(text, c1) {
return strings.Replace(text, c1, c2, -1)
}
return text
}
// Expand announcements in incoming webhooks from Slack. Those announcements
// can be found in the text attribute, or in the pretext, text, title and value
// attributes of the attachment structure. The Slack attachment structure is
// documented here: https://api.slack.com/docs/attachments
func expandAnnouncements(i *IncomingWebhookRequest) {
i.Text = expandAnnouncement(i.Text)
for _, attachment := range i.Attachments {
attachment.Pretext = expandAnnouncement(attachment.Pretext)
attachment.Text = expandAnnouncement(attachment.Text)
attachment.Title = expandAnnouncement(attachment.Title)
for _, field := range attachment.Fields {
if field.Value != nil {
// Ensure the value is set to a string if it is set
field.Value = expandAnnouncement(fmt.Sprintf("%v", field.Value))
}
}
}
}
func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest {
buf := new(bytes.Buffer)
buf.ReadFrom(data)
@ -241,7 +208,8 @@ func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest {
}
}
expandAnnouncements(o)
o.Text = ExpandAnnouncement(o.Text)
o.Attachments = ProcessSlackAttachments(o.Attachments)
return o
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,100 +1,117 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"fmt"
"time"
"encoding/json"
"io"
"net/http"
)
type TaskFunc func()
const (
JOB_TYPE_DATA_RETENTION = "data_retention"
JOB_TYPE_ELASTICSEARCH_POST_INDEXING = "elasticsearch_post_indexing"
type ScheduledTask struct {
Name string `json:"name"`
Interval time.Duration `json:"interval"`
Recurring bool `json:"recurring"`
function TaskFunc
timer *time.Timer
JOB_STATUS_PENDING = "pending"
JOB_STATUS_IN_PROGRESS = "in_progress"
JOB_STATUS_SUCCESS = "success"
JOB_STATUS_ERROR = "error"
JOB_STATUS_CANCEL_REQUESTED = "cancel_requested"
JOB_STATUS_CANCELED = "canceled"
)
type Job struct {
Id string `json:"id"`
Type string `json:"type"`
Priority int64 `json:"priority"`
CreateAt int64 `json:"create_at"`
StartAt int64 `json:"start_at"`
LastActivityAt int64 `json:"last_activity_at"`
Status string `json:"status"`
Progress int64 `json:"progress"`
Data map[string]interface{} `json:"data"`
}
var tasks = make(map[string]*ScheduledTask)
func addTask(task *ScheduledTask) {
tasks[task.Name] = task
}
func removeTaskByName(name string) {
delete(tasks, name)
}
func GetTaskByName(name string) *ScheduledTask {
if task, ok := tasks[name]; ok {
return task
func (j *Job) IsValid() *AppError {
if len(j.Id) != 26 {
return NewAppError("Job.IsValid", "model.job.is_valid.id.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
if j.CreateAt == 0 {
return NewAppError("Job.IsValid", "model.job.is_valid.create_at.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
switch j.Type {
case JOB_TYPE_DATA_RETENTION:
case JOB_TYPE_ELASTICSEARCH_POST_INDEXING:
default:
return NewAppError("Job.IsValid", "model.job.is_valid.type.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
switch j.Status {
case JOB_STATUS_PENDING:
case JOB_STATUS_IN_PROGRESS:
case JOB_STATUS_SUCCESS:
case JOB_STATUS_ERROR:
case JOB_STATUS_CANCEL_REQUESTED:
case JOB_STATUS_CANCELED:
default:
return NewAppError("Job.IsValid", "model.job.is_valid.status.app_error", nil, "id="+j.Id, http.StatusBadRequest)
}
return nil
}
func GetAllTasks() *map[string]*ScheduledTask {
return &tasks
}
func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask {
task := &ScheduledTask{
Name: name,
Interval: timeToExecution,
Recurring: false,
function: function,
func (js *Job) ToJson() string {
if b, err := json.Marshal(js); err != nil {
return ""
} else {
return string(b)
}
}
taskRunner := func() {
go task.function()
removeTaskByName(task.Name)
func JobFromJson(data io.Reader) *Job {
var job Job
if err := json.NewDecoder(data).Decode(&job); err == nil {
return &job
} else {
return nil
}
task.timer = time.AfterFunc(timeToExecution, taskRunner)
addTask(task)
return task
}
func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
task := &ScheduledTask{
Name: name,
Interval: interval,
Recurring: true,
function: function,
func JobsToJson(jobs []*Job) string {
if b, err := json.Marshal(jobs); err != nil {
return ""
} else {
return string(b)
}
}
taskRecurer := func() {
go task.function()
task.timer.Reset(task.Interval)
func JobsFromJson(data io.Reader) []*Job {
var jobs []*Job
if err := json.NewDecoder(data).Decode(&jobs); err == nil {
return jobs
} else {
return nil
}
task.timer = time.AfterFunc(interval, taskRecurer)
addTask(task)
return task
}
func (task *ScheduledTask) Cancel() {
task.timer.Stop()
removeTaskByName(task.Name)
func (js *Job) DataToJson() string {
if b, err := json.Marshal(js.Data); err != nil {
return ""
} else {
return string(b)
}
}
// Executes the task immediatly. A recurring task will be run regularally after interval.
func (task *ScheduledTask) Execute() {
task.function()
task.timer.Reset(task.Interval)
type Worker interface {
Run()
Stop()
JobChannel() chan<- Job
}
func (task *ScheduledTask) String() string {
return fmt.Sprintf(
"%s\nInterval: %s\nRecurring: %t\n",
task.Name,
task.Interval.String(),
task.Recurring,
)
type Scheduler interface {
Run()
Stop()
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -37,36 +37,42 @@ type Customer struct {
}
type Features struct {
Users *int `json:"users"`
LDAP *bool `json:"ldap"`
MFA *bool `json:"mfa"`
GoogleOAuth *bool `json:"google_oauth"`
Office365OAuth *bool `json:"office365_oauth"`
Compliance *bool `json:"compliance"`
Cluster *bool `json:"cluster"`
Metrics *bool `json:"metrics"`
CustomBrand *bool `json:"custom_brand"`
MHPNS *bool `json:"mhpns"`
SAML *bool `json:"saml"`
PasswordRequirements *bool `json:"password_requirements"`
Users *int `json:"users"`
LDAP *bool `json:"ldap"`
MFA *bool `json:"mfa"`
GoogleOAuth *bool `json:"google_oauth"`
Office365OAuth *bool `json:"office365_oauth"`
Compliance *bool `json:"compliance"`
Cluster *bool `json:"cluster"`
Metrics *bool `json:"metrics"`
CustomBrand *bool `json:"custom_brand"`
MHPNS *bool `json:"mhpns"`
SAML *bool `json:"saml"`
PasswordRequirements *bool `json:"password_requirements"`
Elasticsearch *bool `json:"elastic_search"`
Announcement *bool `json:"announcement"`
EmailNotificationContents *bool `json:"email_notification_contents"`
// 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{} {
return map[string]interface{}{
"ldap": *f.LDAP,
"mfa": *f.MFA,
"google": *f.GoogleOAuth,
"office365": *f.Office365OAuth,
"compliance": *f.Compliance,
"cluster": *f.Cluster,
"metrics": *f.Metrics,
"custom_brand": *f.CustomBrand,
"mhpns": *f.MHPNS,
"saml": *f.SAML,
"password": *f.PasswordRequirements,
"future": *f.FutureFeatures,
"ldap": *f.LDAP,
"mfa": *f.MFA,
"google": *f.GoogleOAuth,
"office365": *f.Office365OAuth,
"compliance": *f.Compliance,
"cluster": *f.Cluster,
"metrics": *f.Metrics,
"custom_brand": *f.CustomBrand,
"mhpns": *f.MHPNS,
"saml": *f.SAML,
"password": *f.PasswordRequirements,
"elastic_search": *f.Elasticsearch,
"email_notification_contents": *f.EmailNotificationContents,
"future": *f.FutureFeatures,
}
}
@ -135,6 +141,21 @@ func (f *Features) SetDefaults() {
f.PasswordRequirements = new(bool)
*f.PasswordRequirements = *f.FutureFeatures
}
if f.Elasticsearch == nil {
f.Elasticsearch = new(bool)
*f.Elasticsearch = *f.FutureFeatures
}
if f.Announcement == nil {
f.Announcement = new(bool)
*f.Announcement = true
}
if f.EmailNotificationContents == nil {
f.EmailNotificationContents = new(bool)
*f.EmailNotificationContents = *f.FutureFeatures
}
}
func (l *License) IsExpired() bool {

View File

@ -0,0 +1,34 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type MfaSecret struct {
Secret string `json:"secret"`
QRCode string `json:"qr_code"`
}
func (me *MfaSecret) ToJson() string {
b, err := json.Marshal(me)
if err != nil {
return ""
} else {
return string(b)
}
}
func MfaSecretFromJson(data io.Reader) *MfaSecret {
decoder := json.NewDecoder(data)
var me MfaSecret
err := decoder.Decode(&me)
if err == nil {
return &me
} else {
return nil
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"io"
"net/http"
"unicode/utf8"
)
@ -15,6 +16,7 @@ const (
OAUTH_ACTION_LOGIN = "login"
OAUTH_ACTION_EMAIL_TO_SSO = "email_to_sso"
OAUTH_ACTION_SSO_TO_EMAIL = "sso_to_email"
OAUTH_ACTION_MOBILE = "mobile"
)
type OAuthApp struct {
@ -36,50 +38,50 @@ type OAuthApp struct {
func (a *OAuthApp) IsValid() *AppError {
if len(a.Id) != 26 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "")
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.app_id.app_error", nil, "", http.StatusBadRequest)
}
if a.CreateAt == 0 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id)
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.create_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if a.UpdateAt == 0 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id)
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.update_at.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if len(a.CreatorId) != 26 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id)
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.creator_id.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id)
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.client_secret.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if len(a.Name) == 0 || len(a.Name) > 64 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id)
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.name.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id)
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
for _, callback := range a.CallbackUrls {
if !IsValidHttpUrl(callback) {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "")
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "", http.StatusBadRequest)
}
}
if len(a.Homepage) == 0 || len(a.Homepage) > 256 || !IsValidHttpUrl(a.Homepage) {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id)
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.homepage.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if utf8.RuneCountInString(a.Description) > 512 {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id)
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.description.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
if len(a.IconURL) > 0 {
if len(a.IconURL) > 512 || !IsValidHttpUrl(a.IconURL) {
return NewLocAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id)
return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.icon_url.app_error", nil, "app_id="+a.Id, http.StatusBadRequest)
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -7,6 +7,7 @@ import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
@ -41,6 +42,7 @@ type OutgoingWebhookPayload struct {
PostId string `json:"post_id"`
Text string `json:"text"`
TriggerWord string `json:"trigger_word"`
FileIds string `json:"file_ids"`
}
func (o *OutgoingWebhookPayload) ToJSON() string {
@ -65,6 +67,7 @@ func (o *OutgoingWebhookPayload) ToFormValues() string {
v.Set("post_id", o.PostId)
v.Set("text", o.Text)
v.Set("trigger_word", o.TriggerWord)
v.Set("file_ids", o.FileIds)
return v.Encode()
}
@ -112,69 +115,69 @@ func OutgoingWebhookListFromJson(data io.Reader) []*OutgoingWebhook {
func (o *OutgoingWebhook) IsValid() *AppError {
if len(o.Id) != 26 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Token) != 26 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "", http.StatusBadRequest)
}
if o.CreateAt == 0 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id)
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if o.UpdateAt == 0 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id)
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}
if len(o.CreatorId) != 26 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ChannelId) != 0 && len(o.ChannelId) != 26 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.TeamId) != 26 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "", http.StatusBadRequest)
}
if len(fmt.Sprintf("%s", o.TriggerWords)) > 1024 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "", http.StatusBadRequest)
}
if len(o.TriggerWords) != 0 {
for _, triggerWord := range o.TriggerWords {
if len(triggerWord) == 0 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "", http.StatusBadRequest)
}
}
}
if len(o.CallbackURLs) == 0 || len(fmt.Sprintf("%s", o.CallbackURLs)) > 1024 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "", http.StatusBadRequest)
}
for _, callback := range o.CallbackURLs {
if !IsValidHttpUrl(callback) {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "", http.StatusBadRequest)
}
}
if len(o.DisplayName) > 64 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "", http.StatusBadRequest)
}
if len(o.Description) > 128 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "", http.StatusBadRequest)
}
if len(o.ContentType) > 128 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest)
}
if o.TriggerWhen > 1 {
return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "")
return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest)
}
return nil
@ -197,8 +200,8 @@ func (o *OutgoingWebhook) PreUpdate() {
o.UpdateAt = GetMillis()
}
func (o *OutgoingWebhook) HasTriggerWord(word string) bool {
if len(o.TriggerWords) == 0 || len(word) == 0 {
func (o *OutgoingWebhook) TriggerWordExactMatch(word string) bool {
if len(word) == 0 {
return false
}
@ -212,7 +215,7 @@ func (o *OutgoingWebhook) HasTriggerWord(word string) bool {
}
func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
if len(o.TriggerWords) == 0 || len(word) == 0 {
if len(word) == 0 {
return false
}
@ -224,3 +227,27 @@ func (o *OutgoingWebhook) TriggerWordStartsWith(word string) bool {
return false
}
func (o *OutgoingWebhook) GetTriggerWord(word string, isExactMatch bool) (triggerWord string) {
if len(word) == 0 {
return
}
if isExactMatch {
for _, trigger := range o.TriggerWords {
if trigger == word {
triggerWord = trigger
break
}
}
} else {
for _, trigger := range o.TriggerWords {
if strings.HasPrefix(word, trigger) {
triggerWord = trigger
break
}
}
}
return triggerWord
}

View File

@ -1,37 +0,0 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
const (
PASSWORD_RECOVERY_CODE_SIZE = 128
PASSWORD_RECOVER_EXPIRY_TIME = 1000 * 60 * 60 // 1 hour
)
type PasswordRecovery struct {
UserId string
Code string
CreateAt int64
}
func (p *PasswordRecovery) IsValid() *AppError {
if len(p.UserId) != 26 {
return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.user_id.app_error", nil, "")
}
if len(p.Code) != PASSWORD_RECOVERY_CODE_SIZE {
return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.code.app_error", nil, "")
}
if p.CreateAt == 0 {
return NewLocAppError("User.IsValid", "model.password_recovery.is_valid.create_at.app_error", nil, "")
}
return nil
}
func (p *PasswordRecovery) PreSave() {
p.Code = NewRandomString(PASSWORD_RECOVERY_CODE_SIZE)
p.CreateAt = GetMillis()
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -38,6 +38,7 @@ type Post struct {
UpdateAt int64 `json:"update_at"`
EditAt int64 `json:"edit_at"`
DeleteAt int64 `json:"delete_at"`
IsPinned bool `json:"is_pinned"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
RootId string `json:"root_id"`
@ -53,6 +54,20 @@ type Post struct {
HasReactions bool `json:"has_reactions,omitempty"`
}
type PostPatch struct {
IsPinned *bool `json:"is_pinned"`
Message *string `json:"message"`
Props *StringInterface `json:"props"`
FileIds *StringArray `json:"file_ids"`
HasReactions *bool `json:"has_reactions"`
}
type PostForIndexing struct {
Post
TeamId string `json:"team_id"`
ParentCreateAt *int64 `json:"parent_create_at"`
}
func (o *Post) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
@ -189,3 +204,45 @@ func (o *Post) AddProp(key string, value interface{}) {
func (o *Post) IsSystemMessage() bool {
return len(o.Type) >= len(POST_SYSTEM_MESSAGE_PREFIX) && o.Type[:len(POST_SYSTEM_MESSAGE_PREFIX)] == POST_SYSTEM_MESSAGE_PREFIX
}
func (p *Post) Patch(patch *PostPatch) {
if patch.IsPinned != nil {
p.IsPinned = *patch.IsPinned
}
if patch.Message != nil {
p.Message = *patch.Message
}
if patch.Props != nil {
p.Props = *patch.Props
}
if patch.FileIds != nil {
p.FileIds = *patch.FileIds
}
if patch.HasReactions != nil {
p.HasReactions = *patch.HasReactions
}
}
func (o *PostPatch) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
}
return string(b)
}
func PostPatchFromJson(data io.Reader) *PostPatch {
decoder := json.NewDecoder(data)
var post PostPatch
err := decoder.Decode(&post)
if err != nil {
return nil
}
return &post
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -16,14 +16,10 @@ const (
PREFERENCE_CATEGORY_TUTORIAL_STEPS = "tutorial_step"
PREFERENCE_CATEGORY_ADVANCED_SETTINGS = "advanced_settings"
PREFERENCE_CATEGORY_FLAGGED_POST = "flagged_post"
PREFERENCE_CATEGORY_FAVORITE_CHANNEL = "favorite_channel"
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
PREFERENCE_NAME_DISPLAY_NAME_FORMAT = "name_format"
PREFERENCE_VALUE_DISPLAY_NAME_NICKNAME = "nickname_full_name"
PREFERENCE_VALUE_DISPLAY_NAME_FULL = "full_name"
PREFERENCE_VALUE_DISPLAY_NAME_USERNAME = "username"
PREFERENCE_DEFAULT_DISPLAY_NAME_FORMAT = PREFERENCE_VALUE_DISPLAY_NAME_USERNAME
PREFERENCE_CATEGORY_DISPLAY_SETTINGS = "display_settings"
PREFERENCE_NAME_COLLAPSE_SETTING = "collapse_previews"
PREFERENCE_CATEGORY_THEME = "theme"
// the name for theme props is the team id
@ -37,7 +33,9 @@ const (
PREFERENCE_CATEGORY_NOTIFICATIONS = "notifications"
PREFERENCE_NAME_EMAIL_INTERVAL = "email_interval"
PREFERENCE_DEFAULT_EMAIL_INTERVAL = "30" // default to match the interval of the "immediate" setting (ie 30 seconds)
PREFERENCE_EMAIL_INTERVAL_NO_BATCHING_SECONDS = "30" // the "immediate" setting is actually 30s
PREFERENCE_EMAIL_INTERVAL_BATCHING_SECONDS = "900" // fifteen minutes is 900 seconds
)
type Preference struct {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -36,6 +36,10 @@ type PushNotification struct {
ChannelId string `json:"channel_id"`
ChannelName string `json:"channel_name"`
Type string `json:"type"`
SenderId string `json:"sender_id"`
OverrideUsername string `json:"override_username"`
OverrideIconUrl string `json:"override_icon_url"`
FromWebhook string `json:"from_webhook"`
}
func (me *PushNotification) ToJson() string {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -6,6 +6,7 @@ package model
import (
"encoding/json"
"io"
"regexp"
)
type Reaction struct {
@ -60,7 +61,9 @@ func (o *Reaction) IsValid() *AppError {
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 {
validName := regexp.MustCompile(`^[a-zA-Z0-9\-\+_]+$`)
if len(o.EmojiName) == 0 || len(o.EmojiName) > 64 || !validName.MatchString(o.EmojiName) {
return NewLocAppError("Reaction.IsValid", "model.reaction.is_valid.emoji_name.app_error", nil, "emoji_name="+o.EmojiName)
}

View File

@ -1,8 +1,13 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
USER_AUTH_SERVICE_SAML = "saml"
USER_AUTH_SERVICE_SAML_TEXT = "With SAML"
@ -16,3 +21,29 @@ type SamlAuthRequest struct {
URL string
RelayState string
}
type SamlCertificateStatus struct {
IdpCertificateFile bool `json:"idp_certificate_file"`
PrivateKeyFile bool `json:"private_key_file"`
PublicCertificateFile bool `json:"public_certificate_file"`
}
func (s *SamlCertificateStatus) ToJson() string {
b, err := json.Marshal(s)
if err != nil {
return ""
} else {
return string(b)
}
}
func SamlCertificateStatusFromJson(data io.Reader) *SamlCertificateStatus {
decoder := json.NewDecoder(data)
var status SamlCertificateStatus
err := decoder.Decode(&status)
if err == nil {
return &status
} else {
return nil
}
}

View File

@ -0,0 +1,110 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"fmt"
"sync"
"time"
)
type TaskFunc func()
type ScheduledTask struct {
Name string `json:"name"`
Interval time.Duration `json:"interval"`
Recurring bool `json:"recurring"`
function TaskFunc
timer *time.Timer
}
var taskMutex = sync.Mutex{}
var tasks = make(map[string]*ScheduledTask)
func addTask(task *ScheduledTask) {
taskMutex.Lock()
defer taskMutex.Unlock()
tasks[task.Name] = task
}
func removeTaskByName(name string) {
taskMutex.Lock()
defer taskMutex.Unlock()
delete(tasks, name)
}
func GetTaskByName(name string) *ScheduledTask {
taskMutex.Lock()
defer taskMutex.Unlock()
if task, ok := tasks[name]; ok {
return task
}
return nil
}
func GetAllTasks() *map[string]*ScheduledTask {
taskMutex.Lock()
defer taskMutex.Unlock()
return &tasks
}
func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask {
task := &ScheduledTask{
Name: name,
Interval: timeToExecution,
Recurring: false,
function: function,
}
taskRunner := func() {
go task.function()
removeTaskByName(task.Name)
}
task.timer = time.AfterFunc(timeToExecution, taskRunner)
addTask(task)
return task
}
func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
task := &ScheduledTask{
Name: name,
Interval: interval,
Recurring: true,
function: function,
}
taskRecurer := func() {
go task.function()
task.timer.Reset(task.Interval)
}
task.timer = time.AfterFunc(interval, taskRecurer)
addTask(task)
return task
}
func (task *ScheduledTask) Cancel() {
task.timer.Stop()
removeTaskByName(task.Name)
}
// Executes the task immediatly. A recurring task will be run regularally after interval.
func (task *ScheduledTask) Execute() {
task.function()
task.timer.Reset(task.Interval)
}
func (task *ScheduledTask) String() string {
return fmt.Sprintf(
"%s\nInterval: %s\nRecurring: %t\n",
task.Name,
task.Interval.String(),
task.Recurring,
)
}

View File

@ -1,9 +1,10 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"regexp"
"strings"
)
@ -19,6 +20,15 @@ type SearchParams struct {
OrTerms bool
}
func (o *SearchParams) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
var searchFlags = [...]string{"from", "channel", "in"}
func splitWordsNoQuotes(text string) []string {
@ -165,7 +175,7 @@ func ParseSearchParams(text string) []*SearchParams {
if len(plainTerms) == 0 && len(hashtagTerms) == 0 && (len(inChannels) != 0 || len(fromUsers) != 0) {
paramsList = append(paramsList, &SearchParams{
Terms: "",
IsHashtag: true,
IsHashtag: false,
InChannels: inChannels,
FromUsers: fromUsers,
})

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -10,11 +10,17 @@ import (
)
const (
SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
SESSION_CACHE_SIZE = 35000
SESSION_PROP_PLATFORM = "platform"
SESSION_PROP_OS = "os"
SESSION_PROP_BROWSER = "browser"
SESSION_COOKIE_TOKEN = "MMAUTHTOKEN"
SESSION_COOKIE_USER = "MMUSERID"
SESSION_CACHE_SIZE = 35000
SESSION_PROP_PLATFORM = "platform"
SESSION_PROP_OS = "os"
SESSION_PROP_BROWSER = "browser"
SESSION_PROP_TYPE = "type"
SESSION_PROP_USER_ACCESS_TOKEN_ID = "user_access_token_id"
SESSION_TYPE_USER_ACCESS_TOKEN = "UserAccessToken"
SESSION_ACTIVITY_TIMEOUT = 1000 * 60 * 5 // 5 minutes
SESSION_USER_ACCESS_TOKEN_EXPIRY = 100 * 365 // 100 years
)
type Session struct {
@ -56,7 +62,9 @@ func (me *Session) PreSave() {
me.Id = NewId()
}
me.Token = NewId()
if me.Token == "" {
me.Token = NewId()
}
me.CreateAt = GetMillis()
me.LastActivityAt = me.CreateAt

View File

@ -1,8 +1,13 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"fmt"
"strings"
)
type SlackAttachment struct {
Id int64 `json:"id"`
Fallback string `json:"fallback"`
@ -27,3 +32,48 @@ type SlackAttachmentField struct {
Value interface{} `json:"value"`
Short bool `json:"short"`
}
// To mention @channel via a webhook in Slack, the message should contain
// <!channel>, as explained at the bottom of this article:
// https://get.slack.help/hc/en-us/articles/202009646-Making-announcements
func ExpandAnnouncement(text string) string {
c1 := "<!channel>"
c2 := "@channel"
if strings.Contains(text, c1) {
return strings.Replace(text, c1, c2, -1)
}
return text
}
// Expand announcements in incoming webhooks from Slack. Those announcements
// can be found in the text attribute, or in the pretext, text, title and value
// attributes of the attachment structure. The Slack attachment structure is
// documented here: https://api.slack.com/docs/attachments
func ProcessSlackAttachments(a []*SlackAttachment) []*SlackAttachment {
var nonNilAttachments []*SlackAttachment
for _, attachment := range a {
if attachment == nil {
continue
}
nonNilAttachments = append(nonNilAttachments, attachment)
attachment.Pretext = ExpandAnnouncement(attachment.Pretext)
attachment.Text = ExpandAnnouncement(attachment.Text)
attachment.Title = ExpandAnnouncement(attachment.Title)
var nonNilFields []*SlackAttachmentField
for _, field := range attachment.Fields {
if field == nil {
continue
}
nonNilFields = append(nonNilFields, field)
if field.Value != nil {
// Ensure the value is set to a string if it is set
field.Value = ExpandAnnouncement(fmt.Sprintf("%v", field.Value))
}
}
attachment.Fields = nonNilFields
}
return nonNilAttachments
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
@ -22,7 +22,7 @@ type Status struct {
Status string `json:"status"`
Manual bool `json:"manual"`
LastActivityAt int64 `json:"last_activity_at"`
ActiveChannel string `json:"active_channel" db:"-"`
ActiveChannel string `json:"-" db:"-"`
}
func (o *Status) ToJson() string {
@ -45,6 +45,26 @@ func StatusFromJson(data io.Reader) *Status {
}
}
func StatusListToJson(u []*Status) string {
b, err := json.Marshal(u)
if err != nil {
return ""
} else {
return string(b)
}
}
func StatusListFromJson(data io.Reader) []*Status {
decoder := json.NewDecoder(data)
var statuses []*Status
err := decoder.Decode(&statuses)
if err == nil {
return statuses
} else {
return nil
}
}
func StatusMapToInterfaceMap(statusMap map[string]*Status) map[string]interface{} {
interfaceMap := map[string]interface{}{}
for _, s := range statusMap {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model

Some files were not shown because too many files have changed in this diff Show More