From cf13f2b5af290c69bc4f1a8184b1dbe19225b36e Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Tue, 18 Feb 2020 17:17:04 +0100 Subject: [PATCH] Handle mattermost private messages --- appservice/names.go | 16 +-- connector/mattermost/mattermost.go | 190 ++++++++++++++++++----------- 2 files changed, 131 insertions(+), 75 deletions(-) diff --git a/appservice/names.go b/appservice/names.go index 4a5d186..9ed00f8 100644 --- a/appservice/names.go +++ b/appservice/names.go @@ -8,14 +8,16 @@ import ( ) func roomAlias(protocol string, id RoomID) string { - id2 := strings.ReplaceAll(string(id), "#", "") - id2 = strings.ReplaceAll(id2, "@", "__") - - return fmt.Sprintf("_ezbr__%s__%s", id2, protocol) + return fmt.Sprintf("_ezbr__%s__%s", safeStringForId(string(id)), protocol) } func userMxId(protocol string, id UserID) string { - id2 := strings.ReplaceAll(string(id), "@", "__") - - return fmt.Sprintf("_ezbr__%s__%s", id2, protocol) + return fmt.Sprintf("_ezbr__%s__%s", safeStringForId(string(id)), protocol) +} + +func safeStringForId(in string) string { + id2 := strings.ReplaceAll(in, "#", "") + id2 = strings.ReplaceAll(id2, "@", "__") + id2 = strings.ReplaceAll(id2, ":", "_") + return id2 } diff --git a/connector/mattermost/mattermost.go b/connector/mattermost/mattermost.go index 2dbc436..ca49c99 100644 --- a/connector/mattermost/mattermost.go +++ b/connector/mattermost/mattermost.go @@ -5,9 +5,11 @@ import ( _ "os" "strings" "time" + "encoding/json" "github.com/mattermost/mattermost-server/model" "github.com/42wim/matterbridge/matterclient" + log "github.com/sirupsen/logrus" . "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector" ) @@ -24,6 +26,8 @@ type Mattermost struct { conn *matterclient.MMClient handlerStopChan chan bool + + usermap map[string]string // map username to user id } @@ -57,12 +61,18 @@ func (mm *Mattermost) Configure(c Configuration) error { return err } + notls, err := c.GetBool("no_tls", false) + if err != nil { + return err + } + password, _ := c.GetString("password", "") token, _ := c.GetString("token", "") if token != "" { password = "token=" + token } mm.conn = matterclient.New(mm.username, password, mm.team, mm.server) + mm.conn.Credentials.NoTLS = notls err = mm.conn.Login() if err != nil { return err @@ -139,7 +149,15 @@ func (mm *Mattermost) checkUserId(id UserID) (string, error) { if len(x) != 2 || x[1] != mm.server { return "", fmt.Errorf("Invalid user ID: %s", id) } - return x[0], nil + if user_id, ok := mm.usermap[x[0]]; ok { + return user_id, nil + } + u, resp := mm.conn.Client.GetUserByUsername(x[0], "") + if u == nil || resp.Error != nil { + return "", fmt.Errorf("Not found: %s (%s)", x[0], resp.Error) + } + mm.usermap[x[0]] = u.Id + return u.Id, nil } func (mm *Mattermost) SetUserInfo(info *UserInfo) error { @@ -177,6 +195,11 @@ func (mm *Mattermost) Join(roomId RoomID) error { } func (mm *Mattermost) Invite(userId UserID, roomId RoomID) error { + if roomId == "" { + _, err := mm.checkUserId(userId) + return err + } + return fmt.Errorf("Not supported: invite on mattermost") } @@ -185,26 +208,46 @@ func (mm *Mattermost) Leave(roomId RoomID) { } func (mm *Mattermost) Send(event *Event) error { - // TODO: actions // TODO: attachements + // TODO: verify private messages work + + post := &model.Post{ + Message: event.Text, + } + if event.Type == EVENT_ACTION { + post.Type = "me" + } if event.Room != "" { ch, err := mm.checkRoomId(event.Room) if err != nil { return err } - _, err = mm.conn.PostMessage(ch, event.Text, "") - return err + post.ChannelId = ch } else if event.Recipient != "" { ui, err := mm.checkUserId(event.Recipient) if err != nil { return err } - mm.conn.SendDirectMessage(ui, event.Text, "") - return nil + + _, resp := mm.conn.Client.CreateDirectChannel(mm.conn.User.Id, ui) + if resp.Error != nil { + return resp.Error + } + channelName := model.GetDMNameFromIds(ui, mm.conn.User.Id) + + err = mm.conn.UpdateChannels() + if err != nil { + return err + } + + post.ChannelId = mm.conn.GetChannelId(channelName, "") } else { return fmt.Errorf("Invalid target") } + + _, resp := mm.conn.Client.CreatePost(post) + return resp.Error } func (mm *Mattermost) Close() { @@ -217,6 +260,7 @@ func (mm *Mattermost) Close() { func (mm *Mattermost) handleConnected() { mm.handlerStopChan = make(chan bool) + mm.usermap = make(map[string]string) go mm.handleLoop(mm.conn.MessageChan, mm.handlerStopChan) fmt.Printf("Connected to mattermost\n") @@ -246,69 +290,79 @@ func (mm *Mattermost) handleLoop(msgCh chan *matterclient.Message, quitCh chan b break case msg := <-msgCh: fmt.Printf("Mattermost: %#v\n", msg) - if len(strings.Split(msg.Channel, "__")) == 2 { - // Private message - ids := strings.Split(msg.Channel, "__") - my_id := mm.conn.User.Id - var them *model.User - if ids[0] == my_id { - them = mm.conn.GetUser(ids[1]) - } else { - them = mm.conn.GetUser(ids[0]) - } - - if them != nil { - user := UserID(fmt.Sprintf("%s@%s", them.Username, mm.server)) - mm.handler.Event(&Event{ - Author: user, - Text: msg.Text, - Type: EVENT_MESSAGE, - }) - } - } else { - var room RoomID - if msg.Team == "" { - room = RoomID(fmt.Sprintf("%s@%s", msg.Channel, mm.server)) - } else { - room = RoomID(fmt.Sprintf("%s@%s@%s", msg.Channel, msg.Team, mm.server)) - } - _, err := mm.checkRoomId(room) - if err != nil { - fmt.Printf("Could not find channel: %s %s\n", msg.Channel, msg.Team) - continue - } - - user := UserID(fmt.Sprintf("%s@%s", msg.Username, mm.server)) - - if strings.Contains(msg.Text, "updated the channel header") { - splits := strings.SplitN(msg.Text, "to: ", 2) - if len(splits) == 2 { - if user == mm.User() { - user = UserID("") - } - mm.handler.RoomInfoUpdated(room, user, &RoomInfo{ - Topic: splits[1], - }) - continue - } - } - - if user == mm.User() { - continue - } - // TODO don't join everytime - mm.handler.Event(&Event{ - Author: user, - Room: room, - Type: EVENT_JOIN, - }) - mm.handler.Event(&Event{ - Author: user, - Room: room, - Text: msg.Text, - Type: EVENT_MESSAGE, - }) + fmt.Printf("Mattermost raw: %#v\n", msg.Raw) + err := mm.handlePosted(msg.Raw) + if err != nil { + log.Warnf("Mattermost error: %s", err) } } } } + +func (mm *Mattermost) handlePosted(msg *model.WebSocketEvent) error { + channel_name := msg.Data["channel_name"].(string) + post_str := msg.Data["post"].(string) + var post model.Post + err := json.Unmarshal([]byte(post_str), &post) + if err != nil { + return err + } + + // Skip self messages + if post.UserId == mm.conn.User.Id { + return nil + } + + // Find sending user + user := mm.conn.GetUser(post.UserId) + if user == nil { + return fmt.Errorf("Invalid user") + } + userId := UserID(fmt.Sprintf("%s@%s", user.Username, mm.server)) + + // Build message event + msg_ev := &Event{ + Author: userId, + Text: post.Message, + Type: EVENT_MESSAGE, + } + if post.Type == "me" { + msg_ev.Type = EVENT_ACTION + } + + // Dispatch as PM or as room message + if len(strings.Split(channel_name, "__")) == 2 { + // Private message, no need to find room id + if user.Id == mm.conn.User.Id { + // Skip self sent messages + return nil + } + + mm.handler.Event(msg_ev) + } else { + roomId := mm.reverseRoomId(post.ChannelId) + if roomId == "" { + return fmt.Errorf("Invalid channel id") + } + + // TODO don't join everytime + mm.handler.Event(&Event{ + Author: userId, + Room: roomId, + Type: EVENT_JOIN, + }) + + if post.Type == "system_header_change" { + new_header := post.Props["new_header"].(string) + mm.handler.RoomInfoUpdated(roomId, userId, &RoomInfo{ + Topic: new_header, + }) + } else if post.Type == "" || post.Type == "me" { + msg_ev.Room = roomId + mm.handler.Event(msg_ev) + } else { + return fmt.Errorf("Unhandled post type: %s", post.Type) + } + } + return nil +}