Update vendor

This commit is contained in:
Wim
2021-10-16 23:11:32 +02:00
parent 57fce93af7
commit 20f6c05ec5
588 changed files with 119386 additions and 3424 deletions

291
vendor/github.com/matterbridge/matterclient/channels.go generated vendored Normal file
View File

@@ -0,0 +1,291 @@
package matterclient
import (
"strings"
"github.com/mattermost/mattermost-server/v6/model"
)
// GetChannels returns all channels we're members off
func (m *Client) 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
}
func (m *Client) 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 getNormalisedName(channel *model.Channel) string {
if channel.Type == model.ChannelTypeGroup {
res := strings.ReplaceAll(channel.DisplayName, ", ", "-")
res = strings.ReplaceAll(res, " ", "_")
return res
}
return channel.Name
}
func (m *Client) GetChannelID(name string, teamID string) string {
m.RLock()
defer m.RUnlock()
if teamID != "" {
return m.getChannelIDTeam(name, teamID)
}
for _, t := range m.OtherTeams {
for _, channel := range append(t.Channels, t.MoreChannels...) {
if getNormalisedName(channel) == name {
return channel.Id
}
}
}
return ""
}
func (m *Client) getChannelIDTeam(name string, teamID string) string {
for _, t := range m.OtherTeams {
if t.ID == teamID {
for _, channel := range append(t.Channels, t.MoreChannels...) {
if getNormalisedName(channel) == name {
return channel.Id
}
}
}
}
return ""
}
func (m *Client) GetChannelName(channelID string) string {
m.RLock()
defer m.RUnlock()
for _, t := range m.OtherTeams {
if t == nil {
continue
}
for _, channel := range append(t.Channels, t.MoreChannels...) {
if channel.Id == channelID {
return getNormalisedName(channel)
}
}
}
return ""
}
func (m *Client) 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 *Client) GetLastViewedAt(channelID string) int64 {
m.RLock()
defer m.RUnlock()
for {
res, resp, err := m.Client.GetChannelMember(channelID, m.User.Id, "")
if err == nil {
return res.LastViewedAt
}
if err := m.HandleRatelimit("GetChannelMember", resp); err != nil {
return model.GetMillis()
}
}
}
// GetMoreChannels returns existing channels where we're not a member off.
func (m *Client) 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 *Client) 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 {
if c.Type == model.ChannelTypeGroup {
return "G"
}
return t.ID
}
}
channels = nil
}
return ""
}
func (m *Client) JoinChannel(channelID string) error {
m.RLock()
defer m.RUnlock()
for _, c := range m.Team.Channels {
if c.Id == channelID {
m.logger.Debug("Not joining ", channelID, " already joined.")
return nil
}
}
m.logger.Debug("Joining ", channelID)
_, _, err := m.Client.AddChannelMember(channelID, m.User.Id)
if err != nil {
return err
}
return nil
}
func (m *Client) UpdateChannelsTeam(teamID string) error {
var (
mmchannels []*model.Channel
resp *model.Response
err error
)
for {
mmchannels, resp, err = m.Client.GetChannelsForTeamForUser(teamID, m.User.Id, false, "")
if err == nil {
break
}
if err = m.HandleRatelimit("GetChannelsForTeamForUser", resp); err != nil {
return err
}
}
for idx, t := range m.OtherTeams {
if t.ID == teamID {
m.Lock()
m.OtherTeams[idx].Channels = mmchannels
m.Unlock()
}
}
for {
mmchannels, resp, err = m.Client.GetPublicChannelsForTeam(teamID, 0, 5000, "")
if err == nil {
break
}
if err := m.HandleRatelimit("GetPublicChannelsForTeam", resp); err != nil {
return err
}
}
for idx, t := range m.OtherTeams {
if t.ID == teamID {
m.Lock()
m.OtherTeams[idx].MoreChannels = mmchannels
m.Unlock()
}
}
return nil
}
func (m *Client) UpdateChannels() error {
if err := m.UpdateChannelsTeam(m.Team.ID); err != nil {
return err
}
for _, t := range m.OtherTeams {
if err := m.UpdateChannelsTeam(t.ID); err != nil {
return err
}
}
return nil
}
func (m *Client) UpdateChannelHeader(channelID string, header string) {
channel := &model.Channel{Id: channelID, Header: header}
m.logger.Debugf("updating channelheader %#v, %#v", channelID, header)
_, _, err := m.Client.UpdateChannel(channel)
if err != nil {
m.logger.Error(err)
}
}
func (m *Client) UpdateLastViewed(channelID string) error {
m.logger.Debugf("posting lastview %#v", channelID)
view := &model.ChannelView{ChannelId: channelID}
for {
_, resp, err := m.Client.ViewChannel(m.User.Id, view)
if err == nil {
return nil
}
if err := m.HandleRatelimit("ViewChannel", resp); err != nil {
m.logger.Errorf("ChannelView update for %s failed: %s", channelID, err)
return err
}
}
}

14
vendor/github.com/matterbridge/matterclient/go.mod generated vendored Normal file
View File

@@ -0,0 +1,14 @@
module github.com/matterbridge/matterclient
go 1.16
require (
github.com/gorilla/websocket v1.4.2
github.com/hashicorp/golang-lru v0.5.4
github.com/jpillora/backoff v1.0.0
github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba
github.com/mattermost/mattermost-server/v6 v6.0.0
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/sirupsen/logrus v1.8.1
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
)

1404
vendor/github.com/matterbridge/matterclient/go.sum generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,743 @@
package matterclient
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/http"
"net/http/cookiejar"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
lru "github.com/hashicorp/golang-lru"
"github.com/jpillora/backoff"
prefixed "github.com/matterbridge/logrus-prefixed-formatter"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/sirupsen/logrus"
)
type Credentials struct {
Login string
Team string
Pass string
Token string
CookieToken bool
Server string
NoTLS bool
SkipTLSVerify bool
SkipVersionCheck bool
MFAToken string
}
type Team struct {
Team *model.Team
ID string
Channels []*model.Channel
MoreChannels []*model.Channel
Users map[string]*model.User
}
type Message struct {
Raw *model.WebSocketEvent
Post *model.Post
Team string
Channel string
Username string
Text string
Type string
UserID string
}
type Client struct {
sync.RWMutex
*Credentials
Team *Team
OtherTeams []*Team
Client *model.Client4
User *model.User
Users map[string]*model.User
MessageChan chan *Message
WsClient *model.WebSocketClient
AntiIdle bool
AntiIdleChan string
AntiIdleIntvl int
WsQuit bool
WsConnected bool
OnWsConnect func()
reconnectBusy bool
logger *logrus.Entry
rootLogger *logrus.Logger
lruCache *lru.Cache
aliveChan chan bool
loginCancel context.CancelFunc
lastPong time.Time
}
func New(login string, pass string, team string, server string, mfatoken string) *Client {
rootLogger := logrus.New()
rootLogger.SetFormatter(&prefixed.TextFormatter{
PrefixPadding: 13,
DisableColors: true,
FullTimestamp: true,
})
cred := &Credentials{
Login: login,
Pass: pass,
Team: team,
Server: server,
MFAToken: mfatoken,
}
cache, _ := lru.New(500)
return &Client{
Credentials: cred,
MessageChan: make(chan *Message, 100),
Users: make(map[string]*model.User),
rootLogger: rootLogger,
lruCache: cache,
logger: rootLogger.WithFields(logrus.Fields{"prefix": "matterclient"}),
aliveChan: make(chan bool),
}
}
// Login tries to connect the client with the loging details with which it was initialized.
func (m *Client) 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,
}
// do initialization setup
if err := m.initClient(b); err != nil {
return err
}
if err := m.doLogin(firstConnection, b); err != nil {
return err
}
if err := m.initUser(); err != nil {
return err
}
if m.Team == nil {
validTeamNames := make([]string, len(m.OtherTeams))
for i, t := range m.OtherTeams {
validTeamNames[i] = t.Team.Name
}
return fmt.Errorf("Team '%s' not found in %v", m.Credentials.Team, validTeamNames)
}
// connect websocket
m.wsConnect()
ctx, loginCancel := context.WithCancel(context.Background())
m.loginCancel = loginCancel
m.logger.Debug("starting wsreceiver")
go m.WsReceiver(ctx)
if m.OnWsConnect != nil {
m.logger.Debug("executing OnWsConnect()")
go m.OnWsConnect()
}
go m.checkConnection(ctx)
if m.AntiIdle {
if m.AntiIdleChan == "" {
// do anti idle on town-square, every installation should have this channel
m.AntiIdleChan = "town-square"
}
channels := m.GetChannels()
for _, channel := range channels {
if channel.Name == m.AntiIdleChan {
go m.antiIdle(ctx, channel.Id, m.AntiIdleIntvl)
continue
}
}
}
return nil
}
func (m *Client) Reconnect() {
if m.reconnectBusy {
return
}
m.reconnectBusy = true
m.logger.Info("reconnect: logout")
m.reconnectLogout()
for {
m.logger.Info("reconnect: login")
err := m.Login()
if err != nil {
m.logger.Errorf("reconnect: login failed: %s, retrying in 10 seconds", err)
time.Sleep(time.Second * 10)
continue
}
break
}
m.logger.Info("reconnect successful")
m.reconnectBusy = false
}
func (m *Client) initClient(b *backoff.Backoff) error {
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, //nolint:gosec
},
Proxy: http.ProxyFromEnvironment,
}
m.Client.HTTPClient.Timeout = time.Second * 10
// handle MMAUTHTOKEN and personal token
if err := m.handleLoginToken(); err != nil {
return err
}
// check if server alive, retry until
if err := m.serverAlive(b); err != nil {
return err
}
return nil
}
func (m *Client) handleLoginToken() error {
switch {
case strings.Contains(m.Credentials.Pass, model.SessionCookieToken):
token := strings.Split(m.Credentials.Pass, model.SessionCookieToken+"=")
if len(token) != 2 {
return errors.New("incorrect MMAUTHTOKEN. valid input is MMAUTHTOKEN=yourtoken")
}
m.Credentials.Token = token[1]
m.Credentials.CookieToken = true
case strings.Contains(m.Credentials.Pass, "token="):
token := strings.Split(m.Credentials.Pass, "token=")
if len(token) != 2 {
return errors.New("incorrect personal token. valid input is token=yourtoken")
}
m.Credentials.Token = token[1]
}
return nil
}
func (m *Client) serverAlive(b *backoff.Backoff) error {
defer b.Reset()
for {
d := b.Duration()
// bogus call to get the serverversion
resp, err := m.Client.Logout()
if err != nil {
return err
}
if resp.ServerVersion == "" {
m.logger.Debugf("Server not up yet, reconnecting in %s", d)
time.Sleep(d)
} else {
m.logger.Infof("Found version %s", resp.ServerVersion)
return nil
}
}
}
// initialize user and teams
// nolint:funlen
func (m *Client) 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.
teams, _, err := m.Client.GetTeamsForUser(m.User.Id, "")
if err != nil {
return err
}
for _, team := range teams {
idx := 0
max := 200
usermap := make(map[string]*model.User)
mmusers, _, err := m.Client.GetUsersInTeam(team.Id, idx, max, "")
if err != nil {
return err
}
for len(mmusers) > 0 {
for _, user := range mmusers {
usermap[user.Id] = user
}
mmusers, _, err = m.Client.GetUsersInTeam(team.Id, idx, max, "")
if err != nil {
return err
}
idx++
time.Sleep(time.Millisecond * 200)
}
m.logger.Infof("found %d users in team %s", len(usermap), team.Name)
t := &Team{
Team: team,
Users: usermap,
ID: team.Id,
}
mmchannels, _, err := m.Client.GetChannelsForTeamForUser(team.Id, m.User.Id, false, "")
if err != nil {
return err
}
t.Channels = mmchannels
mmchannels, _, err = m.Client.GetPublicChannelsForTeam(team.Id, 0, 5000, "")
if err != nil {
return err
}
t.MoreChannels = mmchannels
m.OtherTeams = append(m.OtherTeams, t)
if team.Name == m.Credentials.Team {
m.Team = t
m.logger.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 *Client) doLogin(firstConnection bool, b *backoff.Backoff) error {
var (
logmsg = "trying login"
err error
user *model.User
)
for {
m.logger.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server)
switch {
case m.Credentials.Token != "":
user, _, err = m.doLoginToken()
if err != nil {
return err
}
case m.Credentials.MFAToken != "":
user, _, err = m.Client.LoginWithMFA(m.Credentials.Login, m.Credentials.Pass, m.Credentials.MFAToken)
default:
user, _, err = m.Client.Login(m.Credentials.Login, m.Credentials.Pass)
}
if err != nil {
d := b.Duration()
m.logger.Debug(err)
if firstConnection {
return err
}
m.logger.Debugf("LOGIN: %s, reconnecting in %s", err, d)
time.Sleep(d)
logmsg = "retrying login"
continue
}
m.User = user
break
}
// reset timer
b.Reset()
return nil
}
func (m *Client) doLoginToken() (*model.User, *model.Response, error) {
var (
resp *model.Response
logmsg = "trying login"
user *model.User
err error
)
m.Client.AuthType = model.HeaderBearer
m.Client.AuthToken = m.Credentials.Token
if m.Credentials.CookieToken {
m.logger.Debugf(logmsg + " with cookie (MMAUTH) token")
m.Client.HTTPClient.Jar = m.createCookieJar(m.Credentials.Token)
} else {
m.logger.Debugf(logmsg + " with personal token")
}
user, resp, err = m.Client.GetMe("")
if err != nil {
return user, resp, err
}
if user == nil {
m.logger.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass)
return user, resp, errors.New("invalid token")
}
return user, resp, nil
}
func (m *Client) 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
}
func (m *Client) 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.HeaderAuth, "BEARER "+m.Client.AuthToken)
m.logger.Debugf("WsClient: making connection: %s", wsurl)
for {
wsDialer := &websocket.Dialer{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: m.SkipTLSVerify, //nolint:gosec
},
Proxy: http.ProxyFromEnvironment,
}
var err error
m.WsClient, err = model.NewWebSocketClientWithDialer(wsDialer, wsurl, m.Client.AuthToken)
if err != nil {
d := b.Duration()
m.logger.Debugf("WSS: %s, reconnecting in %s", err, d)
time.Sleep(d)
continue
}
break
}
m.WsClient.Listen()
m.lastPong = time.Now()
m.logger.Debug("WsClient: connected")
// only start to parse WS messages when login is completely done
m.WsConnected = true
}
func (m *Client) doCheckAlive() error {
if _, _, err := m.Client.GetMe(""); err != nil {
return err
}
if m.reconnectBusy {
return nil
}
if m.WsClient.ListenError == nil {
m.WsClient.SendMessage("ping", nil)
} else {
m.logger.Errorf("got a listen error: %#v", m.WsClient.ListenError)
return m.WsClient.ListenError
}
if time.Since(m.lastPong) > 90*time.Second {
return errors.New("no pong received in 90 seconds")
}
return nil
}
func (m *Client) checkAlive(ctx context.Context) {
ticker := time.NewTicker(time.Second * 45)
for {
select {
case <-ctx.Done():
m.logger.Debugf("checkAlive: ctx.Done() triggered")
return
case <-ticker.C:
// check if session still is valid
err := m.doCheckAlive()
if err != nil {
m.logger.Errorf("connection not alive: %s", err)
m.aliveChan <- false
}
m.aliveChan <- true
}
}
}
func (m *Client) checkConnection(ctx context.Context) {
go m.checkAlive(ctx)
for {
select {
case alive := <-m.aliveChan:
if !alive {
time.Sleep(time.Second * 10)
if m.doCheckAlive() != nil {
m.Reconnect()
}
}
case <-ctx.Done():
m.logger.Debug("checkConnection: ctx.Done() triggered, exiting")
return
}
}
}
// WsReceiver implements the core loop that manages the connection to the chat server. In
// case of a disconnect it will try to reconnect. A call to this method is blocking until
// the 'WsQuite' field of the MMClient object is set to 'true'.
func (m *Client) WsReceiver(ctx context.Context) {
m.logger.Debug("starting WsReceiver")
ticker := time.NewTicker(time.Second * 10)
for {
select {
case event := <-m.WsClient.EventChannel:
if event == nil {
return
}
if !event.IsValid() {
continue
}
m.logger.Debugf("WsReceiver event: %#v", event)
msg := &Message{
Raw: event,
Team: m.Credentials.Team,
}
m.parseMessage(msg)
m.MessageChan <- msg
case response := <-m.WsClient.ResponseChannel:
if response == nil || !response.IsValid() {
continue
}
m.logger.Debugf("WsReceiver response: %#v", response)
if text, ok := response.Data["text"].(string); ok {
if text == "pong" {
m.lastPong = time.Now()
}
}
m.parseResponse(response)
case <-m.WsClient.PingTimeoutChannel:
m.logger.Error("got a ping timeout")
m.Reconnect()
return
case <-ticker.C:
if m.WsClient.ListenError != nil {
m.logger.Errorf("%#v", m.WsClient.ListenError)
m.Reconnect()
return
}
case <-ctx.Done():
m.logger.Debugf("wsReceiver: ctx.Done() triggered")
return
}
}
}
// Logout disconnects the client from the chat server.
func (m *Client) reconnectLogout() error {
err := m.Logout()
m.WsQuit = false
if err != nil {
return err
}
return nil
}
// Logout disconnects the client from the chat server.
func (m *Client) Logout() error {
m.logger.Debug("logout running loginCancel to exit goroutines")
m.loginCancel()
m.logger.Debugf("logout as %s (team: %s) on %s", m.Credentials.Login, m.Credentials.Team, m.Credentials.Server)
m.WsQuit = true
// close the websocket
m.logger.Debug("closing websocket")
m.WsClient.Close()
if strings.Contains(m.Credentials.Pass, model.SessionCookieToken) {
m.logger.Debug("Not invalidating session in logout, credential is a token")
return nil
}
// actually log out
m.logger.Debug("running m.Client.Logout")
if _, err := m.Client.Logout(); err != nil {
return err
}
m.logger.Debug("exiting Logout()")
return nil
}
// SetLogLevel tries to parse the specified level and if successful sets
// the log level accordingly. Accepted levels are: 'debug', 'info', 'warn',
// 'error', 'fatal' and 'panic'.
func (m *Client) SetLogLevel(level string) {
l, err := logrus.ParseLevel(level)
if err != nil {
m.logger.Warnf("Failed to parse specified log-level '%s': %#v", level, err)
} else {
m.rootLogger.SetLevel(l)
}
}
func (m *Client) HandleRatelimit(name string, resp *model.Response) error {
if resp.StatusCode != 429 {
return fmt.Errorf("StatusCode error: %d", resp.StatusCode)
}
waitTime, err := strconv.Atoi(resp.Header.Get("X-RateLimit-Reset"))
if err != nil {
return err
}
m.logger.Warnf("Ratelimited on %s for %d", name, waitTime)
time.Sleep(time.Duration(waitTime) * time.Second)
return nil
}
func (m *Client) antiIdle(ctx context.Context, channelID string, interval int) {
if interval == 0 {
interval = 60
}
m.logger.Debugf("starting antiIdle for %s every %d secs", channelID, interval)
ticker := time.NewTicker(time.Second * time.Duration(interval))
for {
select {
case <-ctx.Done():
m.logger.Debugf("antiIlde: ctx.Done() triggered, exiting for %s", channelID)
return
case <-ticker.C:
m.logger.Tracef("antiIdle %s", channelID)
m.UpdateLastViewed(channelID)
}
}
}

280
vendor/github.com/matterbridge/matterclient/messages.go generated vendored Normal file
View File

@@ -0,0 +1,280 @@
package matterclient
import (
"crypto/md5"
"encoding/json"
"fmt"
"strings"
"github.com/mattermost/mattermost-server/v6/model"
)
func (m *Client) parseResponse(rmsg *model.WebSocketResponse) {
m.logger.Debugf("getting response: %#v", rmsg)
}
func (m *Client) DeleteMessage(postID string) error {
_, err := m.Client.DeletePost(postID)
if err != nil {
return err
}
return nil
}
func (m *Client) EditMessage(postID string, text string) (string, error) {
post := &model.Post{Message: text, Id: postID}
res, _, err := m.Client.UpdatePost(postID, post)
if err != nil {
return "", err
}
return res.Id, nil
}
func (m *Client) GetFileLinks(filenames []string) []string {
uriScheme := "https://"
if m.NoTLS {
uriScheme = "http://"
}
var output []string
for _, f := range filenames {
res, _, err := m.Client.GetFileLink(f)
if err != nil {
// public links is probably disabled, create the link ourselves
output = append(output, uriScheme+m.Credentials.Server+model.APIURLSuffix+"/files/"+f)
continue
}
output = append(output, res)
}
return output
}
func (m *Client) GetPosts(channelID string, limit int) *model.PostList {
for {
res, resp, err := m.Client.GetPostsForChannel(channelID, 0, limit, "", true)
if err == nil {
return res
}
if err := m.HandleRatelimit("GetPostsForChannel", resp); err != nil {
return nil
}
}
}
func (m *Client) GetPostsSince(channelID string, time int64) *model.PostList {
for {
res, resp, err := m.Client.GetPostsSince(channelID, time, true)
if err == nil {
return res
}
if err := m.HandleRatelimit("GetPostsSince", resp); err != nil {
return nil
}
}
}
func (m *Client) GetPublicLink(filename string) string {
res, _, err := m.Client.GetFileLink(filename)
if err != nil {
return ""
}
return res
}
func (m *Client) GetPublicLinks(filenames []string) []string {
var output []string
for _, f := range filenames {
res, _, err := m.Client.GetFileLink(f)
if err != nil {
continue
}
output = append(output, res)
}
return output
}
func (m *Client) PostMessage(channelID string, text string, rootID string) (string, error) {
post := &model.Post{
ChannelId: channelID,
Message: text,
RootId: rootID,
}
for {
res, resp, err := m.Client.CreatePost(post)
if err == nil {
return res.Id, nil
}
if err := m.HandleRatelimit("CreatePost", resp); err != nil {
return "", err
}
}
}
func (m *Client) PostMessageWithFiles(channelID string, text string, rootID string, fileIds []string) (string, error) {
post := &model.Post{
ChannelId: channelID,
Message: text,
RootId: rootID,
FileIds: fileIds,
}
for {
res, resp, err := m.Client.CreatePost(post)
if err == nil {
return res.Id, nil
}
if err := m.HandleRatelimit("CreatePost", resp); err != nil {
return "", err
}
}
}
func (m *Client) SearchPosts(query string) *model.PostList {
res, _, err := m.Client.SearchPosts(m.Team.ID, query, false)
if err != nil {
return nil
}
return res
}
// SendDirectMessage sends a direct message to specified user
func (m *Client) SendDirectMessage(toUserID string, msg string, rootID string) error {
return m.SendDirectMessageProps(toUserID, msg, rootID, nil)
}
func (m *Client) SendDirectMessageProps(toUserID string, msg string, rootID string, props map[string]interface{}) error {
m.logger.Debugf("SendDirectMessage to %s, msg %s", toUserID, msg)
for {
// create DM channel (only happens on first message)
_, resp, err := m.Client.CreateDirectChannel(m.User.Id, toUserID)
if err == nil {
break
}
if err := m.HandleRatelimit("CreateDirectChannel", resp); err != nil {
m.logger.Debugf("SendDirectMessage to %#v failed: %s", toUserID, err)
return err
}
}
channelName := model.GetDMNameFromIds(toUserID, m.User.Id)
// update our channels
if err := m.UpdateChannels(); err != nil {
m.logger.Errorf("failed to update channels: %#v", err)
}
// build & send the message
msg = strings.ReplaceAll(msg, "\r", "")
post := &model.Post{
ChannelId: m.GetChannelID(channelName, m.Team.ID),
Message: msg,
RootId: rootID,
}
post.SetProps(props)
for {
_, resp, err := m.Client.CreatePost(post)
if err == nil {
return nil
}
if err := m.HandleRatelimit("CreatePost", resp); err != nil {
return err
}
}
}
func (m *Client) UploadFile(data []byte, channelID string, filename string) (string, error) {
f, _, err := m.Client.UploadFile(data, channelID, filename)
if err != nil {
return "", err
}
return f.FileInfos[0].Id, nil
}
func (m *Client) 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.GetData()["post"].(string)), true); ok {
m.logger.Debugf("message %#v in cache, not processing again", rmsg.Raw.GetData()["post"].(string))
rmsg.Text = ""
return
}
var data model.Post
if err := json.NewDecoder(strings.NewReader(rmsg.Raw.GetData()["post"].(string))).Decode(&data); err != nil {
return
}
// we don't have the user, refresh the userlist
if m.GetUser(data.UserId) == nil {
m.logger.Infof("User '%v' is not known, ignoring message '%#v'",
data.UserId, 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.GetData()["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.GetData()["team_id"] = teamid
}
if teamid != "" {
rmsg.Team = m.GetTeamName(teamid)
}
// direct message
if rmsg.Raw.GetData()["channel_type"] == "D" {
rmsg.Channel = m.GetUser(data.UserId).Username
}
rmsg.Text = data.Message
rmsg.Post = &data
}
func (m *Client) parseMessage(rmsg *Message) {
switch rmsg.Raw.EventType() {
case model.WebsocketEventPosted, model.WebsocketEventPostEdited, model.WebsocketEventPostDeleted:
m.parseActionPost(rmsg)
case "user_updated":
user := rmsg.Raw.GetData()["user"].(map[string]interface{})
if _, ok := user["id"].(string); ok {
m.UpdateUser(user["id"].(string))
}
case "group_added":
if err := m.UpdateChannels(); err != nil {
m.logger.Errorf("failed to update channels: %#v", err)
}
}
}
func digestString(s string) string {
return fmt.Sprintf("%x", md5.Sum([]byte(s))) //nolint:gosec
}

212
vendor/github.com/matterbridge/matterclient/users.go generated vendored Normal file
View File

@@ -0,0 +1,212 @@
package matterclient
import (
"github.com/mattermost/mattermost-server/v6/model"
)
func (m *Client) GetNickName(userID string) string {
if user := m.GetUser(userID); user != nil {
return user.Nickname
}
return ""
}
func (m *Client) GetStatus(userID string) string {
res, _, err := m.Client.GetUserStatus(userID, "")
if err != nil {
return ""
}
if res.Status == model.StatusAway {
return "away"
}
if res.Status == model.StatusOnline {
return "online"
}
return "offline"
}
func (m *Client) GetStatuses() map[string]string {
var ids []string
statuses := make(map[string]string)
for id := range m.Users {
ids = append(ids, id)
}
res, _, err := m.Client.GetUsersStatusesByIds(ids)
if err != nil {
return statuses
}
for _, status := range res {
statuses[status.UserId] = "offline"
if status.Status == model.StatusAway {
statuses[status.UserId] = "away"
}
if status.Status == model.StatusOnline {
statuses[status.UserId] = "online"
}
}
return statuses
}
func (m *Client) GetTeamID() string {
return m.Team.ID
}
// GetTeamName returns the name of the specified teamId
func (m *Client) GetTeamName(teamID string) string {
m.RLock()
defer m.RUnlock()
for _, t := range m.OtherTeams {
if t.ID == teamID {
return t.Team.Name
}
}
return ""
}
func (m *Client) GetUser(userID string) *model.User {
m.Lock()
defer m.Unlock()
_, ok := m.Users[userID]
if !ok {
res, _, err := m.Client.GetUser(userID, "")
if err != nil {
return nil
}
m.Users[userID] = res
}
return m.Users[userID]
}
func (m *Client) GetUserName(userID string) string {
if user := m.GetUser(userID); user != nil {
return user.Username
}
return ""
}
func (m *Client) 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 *Client) UpdateUsers() error {
idx := 0
max := 200
var (
mmusers []*model.User
resp *model.Response
err error
)
for {
mmusers, resp, err = m.Client.GetUsers(idx, max, "")
if err == nil {
break
}
if err = m.HandleRatelimit("GetUsers", resp); err != nil {
return err
}
}
for len(mmusers) > 0 {
m.Lock()
for _, user := range mmusers {
m.Users[user.Id] = user
}
m.Unlock()
for {
mmusers, resp, err = m.Client.GetUsers(idx, max, "")
if err == nil {
idx++
break
}
if err := m.HandleRatelimit("GetUsers", resp); err != nil {
return err
}
}
}
return nil
}
func (m *Client) UpdateUserNick(nick string) error {
user := m.User
user.Nickname = nick
_, _, err := m.Client.UpdateUser(user)
if err != nil {
return err
}
return nil
}
func (m *Client) UsernamesInChannel(channelID string) []string {
res, _, err := m.Client.GetChannelMembers(channelID, 0, 50000, "")
if err != nil {
m.logger.Errorf("UsernamesInChannel(%s) failed: %s", channelID, err)
return []string{}
}
allusers := m.GetUsers()
result := []string{}
for _, member := range res {
result = append(result, allusers[member.UserId].Nickname)
}
return result
}
func (m *Client) UpdateStatus(userID string, status string) error {
_, _, err := m.Client.UpdateUserStatus(userID, &model.Status{Status: status})
if err != nil {
return err
}
return nil
}
func (m *Client) UpdateUser(userID string) {
m.Lock()
defer m.Unlock()
res, _, err := m.Client.GetUser(userID, "")
if err != nil {
return
}
m.Users[userID] = res
}