Mattermost media objects in both ways + user/team profile pictures from MM to Matrix
This commit is contained in:
parent
ddd5936fb1
commit
fd768a10be
9 changed files with 198 additions and 46 deletions
|
@ -137,7 +137,10 @@ func (a *Account) userInfoUpdatedInternal(user UserID, info *UserInfo) error {
|
|||
}
|
||||
|
||||
if info.Avatar != nil {
|
||||
err = fmt.Errorf("Avatar: not implemented")
|
||||
err2 := mx.ProfileAvatar(mx_user_id, info.Avatar)
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -182,8 +185,10 @@ func (a *Account) roomInfoUpdatedInternal(roomId RoomID, author UserID, info *Ro
|
|||
}
|
||||
|
||||
if info.Picture != nil {
|
||||
// TODO
|
||||
err = fmt.Errorf("Picture: not implemented")
|
||||
err2 := mx.RoomAvatarAs(mx_room_id, info.Picture, as_mxid)
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -254,8 +259,8 @@ func (a *Account) eventInternal(event *Event) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if event.Attachements != nil {
|
||||
for _, file := range event.Attachements {
|
||||
if event.Attachments != nil {
|
||||
for _, file := range event.Attachments {
|
||||
mxfile, err := mx.UploadMedia(file)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -270,8 +275,8 @@ func (a *Account) eventInternal(event *Event) error {
|
|||
content["info"] = map[string]interface{} {
|
||||
"mimetype": mxfile.Mimetype(),
|
||||
"size": mxfile.Size(),
|
||||
"width": sz.Width,
|
||||
"height": sz.Height,
|
||||
"w": sz.Width,
|
||||
"h": sz.Height,
|
||||
}
|
||||
} else {
|
||||
content["msgtype"] = "m.file"
|
||||
|
|
|
@ -123,6 +123,9 @@ func handleTxnEvent(e *mxlib.Event) error {
|
|||
typ := e.Content["msgtype"].(string)
|
||||
if typ == "m.emote" {
|
||||
ev.Type = connector.EVENT_MESSAGE
|
||||
} else if typ == "m.file" || typ == "m.image" {
|
||||
ev.Text = ""
|
||||
ev.Attachments = []connector.MediaObject{mx.ParseMediaInfo(e.Content)}
|
||||
}
|
||||
|
||||
if pm_room := dbIsPmRoom(e.RoomId); pm_room != nil {
|
||||
|
|
|
@ -115,7 +115,7 @@ type Event struct {
|
|||
Text string
|
||||
|
||||
// Attached files such as images
|
||||
Attachements []MediaObject
|
||||
Attachments []MediaObject
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
|
|
|
@ -205,9 +205,17 @@ func (irc *IRC) Send(event *Event) error {
|
|||
return fmt.Errorf("Invalid target")
|
||||
}
|
||||
|
||||
if event.Attachements != nil && len(event.Attachements) > 0 {
|
||||
if event.Attachments != nil && len(event.Attachments) > 0 {
|
||||
for _, at := range event.Attachments {
|
||||
url := at.URL()
|
||||
if url == "" {
|
||||
// TODO find a way to send them using some hosing of some kind
|
||||
return fmt.Errorf("Attachements not supported on IRC")
|
||||
return fmt.Errorf("Attachment without URL sent to IRC")
|
||||
} else {
|
||||
irc.conn.Cmd.Message(dest, fmt.Sprintf("%s (%s, %dkb)",
|
||||
url, at.Mimetype(), at.Size()/1024))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if event.Type == EVENT_MESSAGE {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package mattermost
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"fmt"
|
||||
_ "os"
|
||||
"strings"
|
||||
"time"
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
|
@ -210,9 +212,6 @@ func (mm *Mattermost) Leave(roomId RoomID) {
|
|||
}
|
||||
|
||||
func (mm *Mattermost) Send(event *Event) error {
|
||||
// TODO: attachements
|
||||
// TODO: verify private messages work
|
||||
|
||||
post := &model.Post{
|
||||
Message: event.Text,
|
||||
}
|
||||
|
@ -248,8 +247,30 @@ func (mm *Mattermost) Send(event *Event) error {
|
|||
return fmt.Errorf("Invalid target")
|
||||
}
|
||||
|
||||
if event.Attachments != nil {
|
||||
post.FileIds = []string{}
|
||||
for _, file := range event.Attachments {
|
||||
rdr, err := file.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rdr.Close()
|
||||
data, err := ioutil.ReadAll(rdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
up_file, err := mm.conn.UploadFile(data, post.ChannelId, file.Filename())
|
||||
if err != nil {
|
||||
log.Warnf("UploadFile error: %s", err)
|
||||
return err
|
||||
}
|
||||
post.FileIds = append(post.FileIds, up_file)
|
||||
}
|
||||
}
|
||||
|
||||
_, resp := mm.conn.Client.CreatePost(post)
|
||||
if resp.Error != nil {
|
||||
log.Warnf("CreatePost error: %s", resp.Error)
|
||||
return resp.Error
|
||||
}
|
||||
return nil
|
||||
|
@ -277,16 +298,58 @@ func (mm *Mattermost) handleConnected() {
|
|||
if len(strings.Split(ch.Name, "__")) == 2 {
|
||||
continue // This is a DM channel
|
||||
}
|
||||
|
||||
id := mm.reverseRoomId(ch.Id)
|
||||
chName := ch.DisplayName
|
||||
if teamName := mm.conn.GetTeamName(ch.TeamId); teamName != "" {
|
||||
chName = teamName + " / " + chName
|
||||
}
|
||||
mm.handler.Joined(id)
|
||||
mm.handler.RoomInfoUpdated(id, UserID(""), &RoomInfo{
|
||||
Name: chName,
|
||||
|
||||
// Update room info
|
||||
room_info := &RoomInfo{
|
||||
Name: ch.DisplayName,
|
||||
Topic: ch.Header,
|
||||
})
|
||||
}
|
||||
for _, t := range mm.conn.OtherTeams {
|
||||
if t.Id == ch.TeamId {
|
||||
if t.Team.DisplayName != "" {
|
||||
room_info.Name = t.Team.DisplayName + " / " + room_info.Name
|
||||
} else {
|
||||
room_info.Name = t.Team.Name + " / " + room_info.Name
|
||||
}
|
||||
// TODO: cache last update time so we don't do this needlessly
|
||||
if t.Team.LastTeamIconUpdate > 0 {
|
||||
team_img, resp := mm.conn.Client.GetTeamIcon(t.Id, "")
|
||||
if resp.Error == nil {
|
||||
room_info.Picture = &BlobMediaObject{
|
||||
ObjectFilename: t.Team.Name,
|
||||
ObjectMimetype: http.DetectContentType(team_img),
|
||||
ObjectData: team_img,
|
||||
}
|
||||
} else {
|
||||
log.Warnf("Could not get team image: %s", resp.Error)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
mm.handler.RoomInfoUpdated(id, UserID(""), room_info)
|
||||
|
||||
// Update member list
|
||||
members, resp := mm.conn.Client.GetChannelMembers(ch.Id, 0, 1000, "")
|
||||
if resp.Error == nil {
|
||||
for _, mem := range *members {
|
||||
if mem.UserId == mm.conn.User.Id {
|
||||
continue
|
||||
}
|
||||
user := mm.conn.GetUser(mem.UserId)
|
||||
if user != nil {
|
||||
mm.ensureJoined(user, id)
|
||||
mm.updateUserInfo(user)
|
||||
} else {
|
||||
log.Warnf("Could not find joined user: %s", mem.UserId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Warnf("Could not get channel members: %s", resp.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,6 +369,45 @@ func (mm *Mattermost) handleLoop(msgCh chan *matterclient.Message, quitCh chan b
|
|||
}
|
||||
}
|
||||
|
||||
func (mm *Mattermost) updateUserInfo(user *model.User) {
|
||||
userId := UserID(fmt.Sprintf("%s@%s", user.Username, mm.server))
|
||||
userDisp := user.GetDisplayName(model.SHOW_NICKNAME_FULLNAME)
|
||||
|
||||
if lastdn, ok := mm.userdisplaynamemap[userId]; !ok || lastdn != userDisp {
|
||||
ui := &UserInfo{
|
||||
DisplayName: userDisp,
|
||||
}
|
||||
if user.LastPictureUpdate > 0 {
|
||||
// TODO: cache last update time so we don't do this needlessly
|
||||
img, resp := mm.conn.Client.GetProfileImage(user.Id, "")
|
||||
if resp.Error == nil {
|
||||
ui.Avatar = &BlobMediaObject{
|
||||
ObjectFilename: user.Username,
|
||||
ObjectMimetype: http.DetectContentType(img),
|
||||
ObjectData: img,
|
||||
}
|
||||
} else {
|
||||
log.Warnf("Could not get profile picture: %s", resp.Error)
|
||||
}
|
||||
mm.handler.UserInfoUpdated(userId, ui)
|
||||
mm.userdisplaynamemap[userId] = userDisp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mm *Mattermost) ensureJoined(user *model.User, roomId RoomID) {
|
||||
userId := UserID(fmt.Sprintf("%s@%s", user.Username, mm.server))
|
||||
cache_key := fmt.Sprintf("%s / %s", userId, roomId)
|
||||
if _, ok := mm.sentjoinedmap[cache_key]; !ok {
|
||||
mm.handler.Event(&Event{
|
||||
Author: userId,
|
||||
Room: roomId,
|
||||
Type: EVENT_JOIN,
|
||||
})
|
||||
mm.sentjoinedmap[cache_key] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (mm *Mattermost) handlePosted(msg *model.WebSocketEvent) error {
|
||||
channel_name := msg.Data["channel_name"].(string)
|
||||
post_str := msg.Data["post"].(string)
|
||||
|
@ -326,14 +428,7 @@ func (mm *Mattermost) handlePosted(msg *model.WebSocketEvent) error {
|
|||
return fmt.Errorf("Invalid user")
|
||||
}
|
||||
userId := UserID(fmt.Sprintf("%s@%s", user.Username, mm.server))
|
||||
|
||||
userDisp := user.GetDisplayName(model.SHOW_NICKNAME_FULLNAME)
|
||||
if lastdn, ok := mm.userdisplaynamemap[userId]; !ok || lastdn != userDisp {
|
||||
mm.handler.UserInfoUpdated(userId, &UserInfo{
|
||||
DisplayName: userDisp,
|
||||
})
|
||||
mm.userdisplaynamemap[userId] = userDisp
|
||||
}
|
||||
mm.updateUserInfo(user)
|
||||
|
||||
// Build message event
|
||||
msg_ev := &Event{
|
||||
|
@ -347,7 +442,7 @@ func (mm *Mattermost) handlePosted(msg *model.WebSocketEvent) error {
|
|||
|
||||
// Handle files
|
||||
if post.FileIds != nil && len(post.FileIds) > 0 {
|
||||
msg_ev.Attachements = []MediaObject{}
|
||||
msg_ev.Attachments = []MediaObject{}
|
||||
for _, file := range post.Metadata.Files {
|
||||
blob, resp := mm.conn.Client.GetFile(file.Id)
|
||||
if resp.Error != nil {
|
||||
|
@ -355,7 +450,6 @@ func (mm *Mattermost) handlePosted(msg *model.WebSocketEvent) error {
|
|||
}
|
||||
media_object := &BlobMediaObject{
|
||||
ObjectFilename: file.Name,
|
||||
ObjectSize: file.Size,
|
||||
ObjectMimetype: file.MimeType,
|
||||
ObjectData: blob,
|
||||
}
|
||||
|
@ -365,7 +459,7 @@ func (mm *Mattermost) handlePosted(msg *model.WebSocketEvent) error {
|
|||
Height: file.Height,
|
||||
}
|
||||
}
|
||||
msg_ev.Attachements = append(msg_ev.Attachements, media_object)
|
||||
msg_ev.Attachments = append(msg_ev.Attachments, media_object)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -384,15 +478,7 @@ func (mm *Mattermost) handlePosted(msg *model.WebSocketEvent) error {
|
|||
return fmt.Errorf("Invalid channel id")
|
||||
}
|
||||
|
||||
cache_key := fmt.Sprintf("%s / %s", userId, roomId)
|
||||
if _, ok := mm.sentjoinedmap[cache_key]; !ok {
|
||||
mm.handler.Event(&Event{
|
||||
Author: userId,
|
||||
Room: roomId,
|
||||
Type: EVENT_JOIN,
|
||||
})
|
||||
mm.sentjoinedmap[cache_key] = true
|
||||
}
|
||||
mm.ensureJoined(user, roomId)
|
||||
|
||||
if post.Type == "system_header_change" {
|
||||
new_header := post.Props["new_header"].(string)
|
||||
|
|
|
@ -97,7 +97,6 @@ func (m *UrlMediaObject) URL() string {
|
|||
|
||||
type BlobMediaObject struct {
|
||||
ObjectFilename string
|
||||
ObjectSize int64
|
||||
ObjectMimetype string
|
||||
ObjectImageSize *ImageSize
|
||||
ObjectData []byte
|
||||
|
@ -108,7 +107,7 @@ func (m *BlobMediaObject) Filename() string {
|
|||
}
|
||||
|
||||
func (m *BlobMediaObject) Size() int64 {
|
||||
return m.ObjectSize
|
||||
return int64(len(m.ObjectData))
|
||||
}
|
||||
|
||||
func (m *BlobMediaObject) Mimetype() string {
|
||||
|
|
|
@ -308,6 +308,19 @@ func (xm *XMPP) Leave(roomId RoomID) {
|
|||
}
|
||||
|
||||
func (xm *XMPP) Send(event *Event) error {
|
||||
if event.Attachments != nil && len(event.Attachments) > 0 {
|
||||
for _, at := range event.Attachments {
|
||||
url := at.URL()
|
||||
if url == "" {
|
||||
// TODO find a way to send them using some hosing of some kind
|
||||
return fmt.Errorf("Attachment without URL sent to XMPP")
|
||||
} else {
|
||||
event.Text += fmt.Sprintf("\n%s (%s, %dkb)",
|
||||
url, at.Mimetype(), at.Size()/1024)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("xm *XMPP Send %#v\n", event)
|
||||
if len(event.Recipient) > 0 {
|
||||
_, err := xm.conn.Send(gxmpp.Chat{
|
||||
|
|
|
@ -137,10 +137,9 @@ func (mx *Client) ProfileAvatar(userid string, m connector.MediaObject) error {
|
|||
}
|
||||
mxc = mxm
|
||||
}
|
||||
mxc_uri := fmt.Sprintf("mxc://%s/%s", mxc.MxcServer, mxc.MxcMediaId)
|
||||
|
||||
req := ProfileAvatarUrl{
|
||||
AvatarUrl: mxc_uri,
|
||||
AvatarUrl: mxc.MxcUri(),
|
||||
}
|
||||
var rep struct{}
|
||||
err := mx.PutApiCall(fmt.Sprintf("/_matrix/client/r0/profile/%s/avatar_url?user_id=%s",
|
||||
|
@ -272,6 +271,21 @@ func (mx *Client) RoomNameAs(room string, name string, as_user string) error {
|
|||
return mx.PutStateAs(room, "m.room.name", "", content, as_user)
|
||||
}
|
||||
|
||||
func (mx *Client) RoomAvatarAs(room string, pic connector.MediaObject, as_user string) error {
|
||||
mo, err := mx.UploadMedia(pic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content := map[string]interface{}{
|
||||
"url": mo.MxcUri(),
|
||||
"info": map[string]interface{}{
|
||||
"mimetype": mo.Mimetype(),
|
||||
"size": mo.Size(),
|
||||
},
|
||||
}
|
||||
return mx.PutStateAs(room, "m.room.avatar", "", content, as_user)
|
||||
}
|
||||
|
||||
func (mx *Client) RoomTopicAs(room string, topic string, as_user string) error {
|
||||
content := map[string]interface{}{
|
||||
"topic": topic,
|
||||
|
@ -295,7 +309,7 @@ func (mx *Client) UploadMedia(m connector.MediaObject) (*MediaObject, error) {
|
|||
mx.Server+"/_matrix/media/r0/upload?filename="+url.QueryEscape(m.Filename()),
|
||||
reader)
|
||||
req.Header.Add("Content-Type", m.Mimetype())
|
||||
req.ContentLength = m.Size()
|
||||
req.ContentLength = m.Size() // TODO: this wasn't specified as mandatory in the matrix client/server spec, do a PR to fix this
|
||||
|
||||
var resp UploadResponse
|
||||
err = mx.DoAndParse(req, &resp)
|
||||
|
@ -320,3 +334,23 @@ func (mx *Client) UploadMedia(m connector.MediaObject) (*MediaObject, error) {
|
|||
return media, nil
|
||||
}
|
||||
|
||||
func (mx *Client) ParseMediaInfo(content map[string]interface{}) *MediaObject {
|
||||
// Content is an event content of type m.file or m.image
|
||||
info := content["info"].(map[string]interface{})
|
||||
mxc := strings.Split(strings.Replace(content["url"].(string), "mxc://", "", 1), "/")
|
||||
media := &MediaObject{
|
||||
mxClient: mx,
|
||||
filename: content["body"].(string),
|
||||
size: int64(info["size"].(float64)),
|
||||
mimetype: info["mimetype"].(string),
|
||||
MxcServer: mxc[0],
|
||||
MxcMediaId: mxc[1],
|
||||
}
|
||||
if content["msgtype"].(string) == "m.image" {
|
||||
media.imageSize = &connector.ImageSize{
|
||||
Width: int(info["w"].(float64)),
|
||||
Height: int(info["h"].(float64)),
|
||||
}
|
||||
}
|
||||
return media
|
||||
}
|
||||
|
|
|
@ -59,3 +59,7 @@ func (m *MediaObject) URL() string {
|
|||
return fmt.Sprintf("%s/_matrix/media/r0/download/%s/%s/%s",
|
||||
m.mxClient.Server, m.MxcServer, m.MxcMediaId, url.QueryEscape(m.filename))
|
||||
}
|
||||
|
||||
func (m *MediaObject) MxcUri() string {
|
||||
return fmt.Sprintf("mxc://%s/%s", m.MxcServer, m.MxcMediaId)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue