mirror of
https://github.com/42wim/matterbridge.git
synced 2024-11-24 19:52:03 -08:00
ed01820722
Currently fully support mattermost,slack and discord. Message deleted on the bridge or received from other bridges will be deleted. Partially support for Gitter. Gitter bridge will delete messages received from other bridges. But if you delete a message on gitter, this deletion will not be sent to other bridges (this is a gitter API limitation, it doesn't propogate edits or deletes via the API)
892 lines
21 KiB
Go
892 lines
21 KiB
Go
package matterclient
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/hashicorp/golang-lru"
|
|
"github.com/jpillora/backoff"
|
|
"github.com/mattermost/platform/model"
|
|
)
|
|
|
|
type Credentials struct {
|
|
Login string
|
|
Team string
|
|
Pass string
|
|
Server string
|
|
NoTLS bool
|
|
SkipTLSVerify bool
|
|
}
|
|
|
|
type Message struct {
|
|
Raw *model.WebSocketEvent
|
|
Post *model.Post
|
|
Team string
|
|
Channel string
|
|
Username string
|
|
Text string
|
|
Type string
|
|
UserID string
|
|
}
|
|
|
|
type Team struct {
|
|
Team *model.Team
|
|
Id string
|
|
Channels []*model.Channel
|
|
MoreChannels []*model.Channel
|
|
Users map[string]*model.User
|
|
}
|
|
|
|
type MMClient struct {
|
|
sync.RWMutex
|
|
*Credentials
|
|
Team *Team
|
|
OtherTeams []*Team
|
|
Client *model.Client4
|
|
User *model.User
|
|
Users map[string]*model.User
|
|
MessageChan chan *Message
|
|
log *log.Entry
|
|
WsClient *websocket.Conn
|
|
WsQuit bool
|
|
WsAway bool
|
|
WsConnected bool
|
|
WsSequence int64
|
|
WsPingChan chan *model.WebSocketResponse
|
|
ServerVersion string
|
|
OnWsConnect func()
|
|
lruCache *lru.Cache
|
|
}
|
|
|
|
func New(login, pass, team, server string) *MMClient {
|
|
cred := &Credentials{Login: login, Pass: pass, Team: team, Server: server}
|
|
mmclient := &MMClient{Credentials: cred, MessageChan: make(chan *Message, 100), Users: make(map[string]*model.User)}
|
|
mmclient.log = log.WithFields(log.Fields{"module": "matterclient"})
|
|
log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
|
|
mmclient.lruCache, _ = lru.New(500)
|
|
return mmclient
|
|
}
|
|
|
|
func (m *MMClient) SetLogLevel(level string) {
|
|
l, err := log.ParseLevel(level)
|
|
if err != nil {
|
|
log.SetLevel(log.InfoLevel)
|
|
return
|
|
}
|
|
log.SetLevel(l)
|
|
}
|
|
|
|
func (m *MMClient) Login() error {
|
|
// check if this is a first connect or a reconnection
|
|
firstConnection := true
|
|
if m.WsConnected {
|
|
firstConnection = false
|
|
}
|
|
m.WsConnected = false
|
|
if m.WsQuit {
|
|
return nil
|
|
}
|
|
b := &backoff.Backoff{
|
|
Min: time.Second,
|
|
Max: 5 * time.Minute,
|
|
Jitter: true,
|
|
}
|
|
uriScheme := "https://"
|
|
if m.NoTLS {
|
|
uriScheme = "http://"
|
|
}
|
|
// login to mattermost
|
|
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
|
|
_, resp := m.Client.Logout()
|
|
if resp.Error != nil {
|
|
return fmt.Errorf("%#v", resp.Error.Error())
|
|
}
|
|
if firstConnection && !supportedVersion(resp.ServerVersion) {
|
|
return fmt.Errorf("unsupported mattermost version: %s", resp.ServerVersion)
|
|
}
|
|
m.ServerVersion = resp.ServerVersion
|
|
if m.ServerVersion == "" {
|
|
m.log.Debugf("Server not up yet, reconnecting in %s", d)
|
|
time.Sleep(d)
|
|
} else {
|
|
m.log.Infof("Found version %s", m.ServerVersion)
|
|
break
|
|
}
|
|
}
|
|
b.Reset()
|
|
|
|
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 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.AuthToken = token[1]
|
|
m.Client.AuthType = model.HEADER_BEARER
|
|
m.User, resp = m.Client.GetMe("")
|
|
if resp.Error != nil {
|
|
return resp.Error
|
|
}
|
|
if m.User == nil {
|
|
m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass)
|
|
return errors.New("invalid " + model.SESSION_COOKIE_TOKEN)
|
|
}
|
|
} else {
|
|
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)
|
|
if firstConnection {
|
|
if appErr.Message == "" {
|
|
return errors.New(appErr.DetailedError)
|
|
}
|
|
return errors.New(appErr.Message)
|
|
}
|
|
m.log.Debugf("LOGIN: %s, reconnecting in %s", appErr, d)
|
|
time.Sleep(d)
|
|
logmsg = "retrying login"
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
// reset timer
|
|
b.Reset()
|
|
|
|
err := m.initUser()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if m.Team == nil {
|
|
return errors.New("team not found")
|
|
}
|
|
|
|
m.wsConnect()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *MMClient) wsConnect() {
|
|
b := &backoff.Backoff{
|
|
Min: time.Second,
|
|
Max: 5 * time.Minute,
|
|
Jitter: true,
|
|
}
|
|
|
|
m.WsConnected = false
|
|
wsScheme := "wss://"
|
|
if m.NoTLS {
|
|
wsScheme = "ws://"
|
|
}
|
|
|
|
// setup websocket connection
|
|
wsurl := wsScheme + m.Credentials.Server + model.API_URL_SUFFIX_V4 + "/websocket"
|
|
header := http.Header{}
|
|
header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken)
|
|
|
|
m.log.Debugf("WsClient: making connection: %s", wsurl)
|
|
for {
|
|
wsDialer := &websocket.Dialer{Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}}
|
|
var err error
|
|
m.WsClient, _, err = wsDialer.Dial(wsurl, header)
|
|
if err != nil {
|
|
d := b.Duration()
|
|
m.log.Debugf("WSS: %s, reconnecting in %s", err, d)
|
|
time.Sleep(d)
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
|
|
m.log.Debug("WsClient: connected")
|
|
m.WsSequence = 1
|
|
m.WsPingChan = make(chan *model.WebSocketResponse)
|
|
// only start to parse WS messages when login is completely done
|
|
m.WsConnected = true
|
|
}
|
|
|
|
func (m *MMClient) Logout() error {
|
|
m.log.Debugf("logout as %s (team: %s) on %s", m.Credentials.Login, m.Credentials.Team, m.Credentials.Server)
|
|
m.WsQuit = true
|
|
m.WsClient.Close()
|
|
m.WsClient.UnderlyingConn().Close()
|
|
if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) {
|
|
m.log.Debug("Not invalidating session in logout, credential is a token")
|
|
return nil
|
|
}
|
|
_, resp := m.Client.Logout()
|
|
if resp.Error != nil {
|
|
return resp.Error
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MMClient) WsReceiver() {
|
|
for {
|
|
var rawMsg json.RawMessage
|
|
var err error
|
|
|
|
if m.WsQuit {
|
|
m.log.Debug("exiting WsReceiver")
|
|
return
|
|
}
|
|
|
|
if !m.WsConnected {
|
|
time.Sleep(time.Millisecond * 100)
|
|
continue
|
|
}
|
|
|
|
if _, rawMsg, err = m.WsClient.ReadMessage(); err != nil {
|
|
m.log.Error("error:", err)
|
|
// reconnect
|
|
m.wsConnect()
|
|
}
|
|
|
|
var event model.WebSocketEvent
|
|
if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() {
|
|
m.log.Debugf("WsReceiver event: %#v", event)
|
|
msg := &Message{Raw: &event, Team: m.Credentials.Team}
|
|
m.parseMessage(msg)
|
|
// 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
|
|
}
|
|
|
|
var response model.WebSocketResponse
|
|
if err := json.Unmarshal(rawMsg, &response); err == nil && response.IsValid() {
|
|
m.log.Debugf("WsReceiver response: %#v", response)
|
|
m.parseResponse(response)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *MMClient) parseMessage(rmsg *Message) {
|
|
switch rmsg.Raw.Event {
|
|
case model.WEBSOCKET_EVENT_POSTED, model.WEBSOCKET_EVENT_POST_EDITED, model.WEBSOCKET_EVENT_POST_DELETED:
|
|
m.parseActionPost(rmsg)
|
|
/*
|
|
case model.ACTION_USER_REMOVED:
|
|
m.handleWsActionUserRemoved(&rmsg)
|
|
case model.ACTION_USER_ADDED:
|
|
m.handleWsActionUserAdded(&rmsg)
|
|
*/
|
|
}
|
|
}
|
|
|
|
func (m *MMClient) parseResponse(rmsg model.WebSocketResponse) {
|
|
if rmsg.Data != nil {
|
|
// ping reply
|
|
if rmsg.Data["text"].(string) == "pong" {
|
|
m.WsPingChan <- &rmsg
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *MMClient) parseActionPost(rmsg *Message) {
|
|
// add post to cache, if it already exists don't relay this again.
|
|
// this should fix reposts
|
|
if ok, _ := m.lruCache.ContainsOrAdd(digestString(rmsg.Raw.Data["post"].(string)), true); ok {
|
|
m.log.Debugf("message %#v in cache, not processing again", rmsg.Raw.Data["post"].(string))
|
|
rmsg.Text = ""
|
|
return
|
|
}
|
|
data := model.PostFromJson(strings.NewReader(rmsg.Raw.Data["post"].(string)))
|
|
// we don't have the user, refresh the userlist
|
|
if m.GetUser(data.UserId) == nil {
|
|
m.log.Infof("User %s is not known, ignoring message %s", data)
|
|
return
|
|
}
|
|
rmsg.Username = m.GetUserName(data.UserId)
|
|
rmsg.Channel = m.GetChannelName(data.ChannelId)
|
|
rmsg.UserID = data.UserId
|
|
rmsg.Type = data.Type
|
|
teamid, _ := rmsg.Raw.Data["team_id"].(string)
|
|
// edit messsages have no team_id for some reason
|
|
if teamid == "" {
|
|
// we can find the team_id from the channelid
|
|
teamid = m.GetChannelTeamId(data.ChannelId)
|
|
rmsg.Raw.Data["team_id"] = teamid
|
|
}
|
|
if teamid != "" {
|
|
rmsg.Team = m.GetTeamName(teamid)
|
|
}
|
|
// direct message
|
|
if rmsg.Raw.Data["channel_type"] == "D" {
|
|
rmsg.Channel = m.GetUser(data.UserId).Username
|
|
}
|
|
rmsg.Text = data.Message
|
|
rmsg.Post = data
|
|
}
|
|
|
|
func (m *MMClient) UpdateUsers() error {
|
|
mmusers, resp := m.Client.GetUsers(0, 50000, "")
|
|
if resp.Error != nil {
|
|
return errors.New(resp.Error.DetailedError)
|
|
}
|
|
m.Lock()
|
|
for _, user := range mmusers {
|
|
m.Users[user.Id] = user
|
|
}
|
|
m.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (m *MMClient) UpdateChannels() error {
|
|
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
|
|
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
|
|
}
|
|
|
|
func (m *MMClient) GetChannelName(channelId string) string {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
for _, t := range m.OtherTeams {
|
|
if t == nil {
|
|
continue
|
|
}
|
|
if t.Channels != nil {
|
|
for _, channel := range t.Channels {
|
|
if channel.Id == channelId {
|
|
return channel.Name
|
|
}
|
|
}
|
|
}
|
|
if t.MoreChannels != nil {
|
|
for _, channel := range t.MoreChannels {
|
|
if channel.Id == channelId {
|
|
return channel.Name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (m *MMClient) GetChannelId(name string, teamId string) string {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
if teamId == "" {
|
|
teamId = m.Team.Id
|
|
}
|
|
for _, t := range m.OtherTeams {
|
|
if t.Id == teamId {
|
|
for _, channel := range append(t.Channels, t.MoreChannels...) {
|
|
if channel.Name == name {
|
|
return channel.Id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
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...) {
|
|
if channel.Id == id {
|
|
return channel.TeamId
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
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...) {
|
|
if channel.Id == channelId {
|
|
return channel.Header
|
|
}
|
|
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (m *MMClient) PostMessage(channelId string, text string) (string, error) {
|
|
post := &model.Post{ChannelId: channelId, Message: text}
|
|
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) DeleteMessage(postId string) error {
|
|
_, resp := m.Client.DeletePost(postId)
|
|
if resp.Error != nil {
|
|
return resp.Error
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MMClient) JoinChannel(channelId string) error {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
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)
|
|
_, 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, resp := m.Client.GetPostsSince(channelId, time)
|
|
if resp.Error != nil {
|
|
return nil
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (m *MMClient) SearchPosts(query string) *model.PostList {
|
|
res, resp := m.Client.SearchPosts(m.Team.Id, query, false)
|
|
if resp.Error != nil {
|
|
return nil
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList {
|
|
res, resp := m.Client.GetPostsForChannel(channelId, 0, limit, "")
|
|
if resp.Error != nil {
|
|
return nil
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (m *MMClient) GetPublicLink(filename string) string {
|
|
res, resp := m.Client.GetFileLink(filename)
|
|
if resp.Error != nil {
|
|
return ""
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (m *MMClient) GetPublicLinks(filenames []string) []string {
|
|
var output []string
|
|
for _, f := range filenames {
|
|
res, resp := m.Client.GetFileLink(f)
|
|
if resp.Error != nil {
|
|
continue
|
|
}
|
|
output = append(output, res)
|
|
}
|
|
return output
|
|
}
|
|
|
|
func (m *MMClient) GetFileLinks(filenames []string) []string {
|
|
uriScheme := "https://"
|
|
if m.NoTLS {
|
|
uriScheme = "http://"
|
|
}
|
|
|
|
var output []string
|
|
for _, f := range filenames {
|
|
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
|
|
}
|
|
output = append(output, res)
|
|
}
|
|
return output
|
|
}
|
|
|
|
func (m *MMClient) UpdateChannelHeader(channelId string, header string) {
|
|
channel := &model.Channel{Id: channelId, Header: header}
|
|
m.log.Debugf("updating channelheader %#v, %#v", channelId, header)
|
|
_, 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)
|
|
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) UpdateUserNick(nick string) error {
|
|
user := m.User
|
|
user.Nickname = nick
|
|
_, resp := m.Client.UpdateUser(user)
|
|
if resp.Error != nil {
|
|
return resp.Error
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MMClient) UsernamesInChannel(channelId string) []string {
|
|
res, resp := m.Client.GetChannelMembers(channelId, 0, 50000, "")
|
|
if resp.Error != nil {
|
|
m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelId, resp.Error)
|
|
return []string{}
|
|
}
|
|
allusers := m.GetUsers()
|
|
result := []string{}
|
|
for _, member := range *res {
|
|
result = append(result, allusers[member.UserId].Nickname)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (m *MMClient) createCookieJar(token string) *cookiejar.Jar {
|
|
var cookies []*http.Cookie
|
|
jar, _ := cookiejar.New(nil)
|
|
firstCookie := &http.Cookie{
|
|
Name: "MMAUTHTOKEN",
|
|
Value: token,
|
|
Path: "/",
|
|
Domain: m.Credentials.Server,
|
|
}
|
|
cookies = append(cookies, firstCookie)
|
|
cookieURL, _ := url.Parse("https://" + m.Credentials.Server)
|
|
jar.SetCookies(cookieURL, cookies)
|
|
return jar
|
|
}
|
|
|
|
// SendDirectMessage sends a direct message to specified user
|
|
func (m *MMClient) SendDirectMessage(toUserId string, msg string) {
|
|
m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg)
|
|
// create DM channel (only happens on first message)
|
|
_, 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
|
|
m.UpdateChannels()
|
|
|
|
// build & send the message
|
|
msg = strings.Replace(msg, "\r", "", -1)
|
|
post := &model.Post{ChannelId: m.GetChannelId(channelName, ""), Message: msg}
|
|
m.Client.CreatePost(post)
|
|
}
|
|
|
|
// GetTeamName returns the name of the specified teamId
|
|
func (m *MMClient) GetTeamName(teamId string) string {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
for _, t := range m.OtherTeams {
|
|
if t.Id == teamId {
|
|
return t.Team.Name
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetChannels returns all channels we're members off
|
|
func (m *MMClient) GetChannels() []*model.Channel {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
var channels []*model.Channel
|
|
// our primary team channels first
|
|
channels = append(channels, m.Team.Channels...)
|
|
for _, t := range m.OtherTeams {
|
|
if t.Id != m.Team.Id {
|
|
channels = append(channels, t.Channels...)
|
|
}
|
|
}
|
|
return channels
|
|
}
|
|
|
|
// GetMoreChannels returns existing channels where we're not a member off.
|
|
func (m *MMClient) GetMoreChannels() []*model.Channel {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
var channels []*model.Channel
|
|
for _, t := range m.OtherTeams {
|
|
channels = append(channels, t.MoreChannels...)
|
|
}
|
|
return channels
|
|
}
|
|
|
|
// GetTeamFromChannel returns teamId belonging to channel (DM channels have no teamId).
|
|
func (m *MMClient) GetTeamFromChannel(channelId string) string {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
var channels []*model.Channel
|
|
for _, t := range m.OtherTeams {
|
|
channels = append(channels, t.Channels...)
|
|
if t.MoreChannels != nil {
|
|
channels = append(channels, t.MoreChannels...)
|
|
}
|
|
for _, c := range channels {
|
|
if c.Id == channelId {
|
|
return t.Id
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (m *MMClient) GetLastViewedAt(channelId string) int64 {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
res, resp := m.Client.GetChannelMember(channelId, m.User.Id, "")
|
|
if resp.Error != nil {
|
|
return model.GetMillis()
|
|
}
|
|
return res.LastViewedAt
|
|
}
|
|
|
|
func (m *MMClient) GetUsers() map[string]*model.User {
|
|
users := make(map[string]*model.User)
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
for k, v := range m.Users {
|
|
users[k] = v
|
|
}
|
|
return users
|
|
}
|
|
|
|
func (m *MMClient) GetUser(userId string) *model.User {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
_, ok := m.Users[userId]
|
|
if !ok {
|
|
res, resp := m.Client.GetUser(userId, "")
|
|
if resp.Error != nil {
|
|
return nil
|
|
}
|
|
m.Users[userId] = res
|
|
}
|
|
return m.Users[userId]
|
|
}
|
|
|
|
func (m *MMClient) GetUserName(userId string) string {
|
|
user := m.GetUser(userId)
|
|
if user != nil {
|
|
return user.Username
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (m *MMClient) GetStatus(userId string) string {
|
|
res, resp := m.Client.GetUserStatus(userId, "")
|
|
if resp.Error != nil {
|
|
return ""
|
|
}
|
|
if res.Status == model.STATUS_AWAY {
|
|
return "away"
|
|
}
|
|
if res.Status == model.STATUS_ONLINE {
|
|
return "online"
|
|
}
|
|
return "offline"
|
|
}
|
|
|
|
func (m *MMClient) GetStatuses() map[string]string {
|
|
var ids []string
|
|
statuses := make(map[string]string)
|
|
for id := range m.Users {
|
|
ids = append(ids, id)
|
|
}
|
|
res, resp := m.Client.GetUsersStatusesByIds(ids)
|
|
if resp.Error != nil {
|
|
return statuses
|
|
}
|
|
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
|
|
}
|
|
|
|
func (m *MMClient) GetTeamId() string {
|
|
return m.Team.Id
|
|
}
|
|
|
|
func (m *MMClient) StatusLoop() {
|
|
retries := 0
|
|
backoff := time.Second * 60
|
|
if m.OnWsConnect != nil {
|
|
m.OnWsConnect()
|
|
}
|
|
m.log.Debug("StatusLoop:", m.OnWsConnect)
|
|
for {
|
|
if m.WsQuit {
|
|
return
|
|
}
|
|
if m.WsConnected {
|
|
m.log.Debug("WS PING")
|
|
m.sendWSRequest("ping", nil)
|
|
select {
|
|
case <-m.WsPingChan:
|
|
m.log.Debug("WS PONG received")
|
|
backoff = time.Second * 60
|
|
case <-time.After(time.Second * 5):
|
|
if retries > 3 {
|
|
m.Logout()
|
|
m.WsQuit = false
|
|
m.Login()
|
|
if m.OnWsConnect != nil {
|
|
m.OnWsConnect()
|
|
}
|
|
go m.WsReceiver()
|
|
} else {
|
|
retries++
|
|
backoff = time.Second * 5
|
|
}
|
|
}
|
|
}
|
|
time.Sleep(backoff)
|
|
}
|
|
}
|
|
|
|
// initialize user and teams
|
|
func (m *MMClient) initUser() error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
// 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")
|
|
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)
|
|
}
|
|
usermap := make(map[string]*model.User)
|
|
for _, user := range mmusers {
|
|
usermap[user.Id] = user
|
|
}
|
|
|
|
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
|
|
}
|
|
t.Channels = mmchannels
|
|
mmchannels, resp = m.Client.GetPublicChannelsForTeam(team.Id, 0, 5000, "")
|
|
if resp.Error != nil {
|
|
return resp.Error
|
|
}
|
|
t.MoreChannels = mmchannels
|
|
m.OtherTeams = append(m.OtherTeams, t)
|
|
if team.Name == m.Credentials.Team {
|
|
m.Team = t
|
|
m.log.Debugf("initUser(): found our team %s (id: %s)", team.Name, team.Id)
|
|
}
|
|
// add all users
|
|
for k, v := range t.Users {
|
|
m.Users[k] = v
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *MMClient) sendWSRequest(action string, data map[string]interface{}) error {
|
|
req := &model.WebSocketRequest{}
|
|
req.Seq = m.WsSequence
|
|
req.Action = action
|
|
req.Data = data
|
|
m.WsSequence++
|
|
m.log.Debugf("sendWsRequest %#v", req)
|
|
m.WsClient.WriteJSON(req)
|
|
return nil
|
|
}
|
|
|
|
func supportedVersion(version string) bool {
|
|
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.1") ||
|
|
strings.HasPrefix(version, "4.2") {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func digestString(s string) string {
|
|
return fmt.Sprintf("%x", md5.Sum([]byte(s)))
|
|
}
|