From 032a7e0c1188d3507b8d9a9571f2446a43cf775b Mon Sep 17 00:00:00 2001 From: msglm Date: Fri, 27 Oct 2023 07:08:25 -0500 Subject: apply https://github.com/42wim/matterbridge/pull/1864 --- bridge/matrix/matrix.go | 806 +++++++++++++++--------------------------------- 1 file changed, 256 insertions(+), 550 deletions(-) (limited to 'bridge/matrix/matrix.go') diff --git a/bridge/matrix/matrix.go b/bridge/matrix/matrix.go index 49fc33b3..ee2081ff 100644 --- a/bridge/matrix/matrix.go +++ b/bridge/matrix/matrix.go @@ -1,18 +1,18 @@ +// nolint: exhaustivestruct package bmatrix import ( - "bytes" "fmt" - "mime" "regexp" - "strings" "sync" - "time" + + matrix "maunium.net/go/mautrix" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" "github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/helper" - matrix "github.com/matterbridge/gomatrix" ) var ( @@ -20,25 +20,30 @@ var ( htmlReplacementTag = regexp.MustCompile("<[^>]*>") ) -type NicknameCacheEntry struct { - displayName string - lastUpdated time.Time +type EventOrigin int + +const ( + originClassicSyncer EventOrigin = iota + originAppService +) + +type RoomInfo struct { + name string + appService bool } type Bmatrix struct { - mc *matrix.Client - UserID string - NicknameMap map[string]NicknameCacheEntry - RoomMap map[string]string - rateMutex sync.RWMutex + mc *matrix.Client + UserID id.UserID + appService *AppServiceWrapper + NicknameCache *NicknameCache + RoomMap map[id.RoomID]RoomInfo + rateMutex sync.RWMutex + joinedRooms []id.RoomID sync.RWMutex *bridge.Config -} - -type httpError struct { - Errcode string `json:"errcode"` - Err string `json:"error"` - RetryAfterMs int `json:"retry_after_ms"` + stopNormalSync chan struct{} + stopNormalSyncAck chan struct{} } type matrixUsername struct { @@ -46,44 +51,12 @@ type matrixUsername struct { formatted string } -// SubTextMessage represents the new content of the message in edit messages. -type SubTextMessage struct { - MsgType string `json:"msgtype"` - Body string `json:"body"` - FormattedBody string `json:"formatted_body,omitempty"` - Format string `json:"format,omitempty"` -} - -// MessageRelation explains how the current message relates to a previous message. -// Notably used for message edits. -type MessageRelation struct { - EventID string `json:"event_id"` - Type string `json:"rel_type"` -} - -type EditedMessage struct { - NewContent SubTextMessage `json:"m.new_content"` - RelatedTo MessageRelation `json:"m.relates_to"` - matrix.TextMessage -} - -type InReplyToRelationContent struct { - EventID string `json:"event_id"` -} - -type InReplyToRelation struct { - InReplyTo InReplyToRelationContent `json:"m.in_reply_to"` -} - -type ReplyMessage struct { - RelatedTo InReplyToRelation `json:"m.relates_to"` - matrix.TextMessage -} - func New(cfg *bridge.Config) bridge.Bridger { b := &Bmatrix{Config: cfg} - b.RoomMap = make(map[string]string) - b.NicknameMap = make(map[string]NicknameCacheEntry) + b.RoomMap = make(map[id.RoomID]RoomInfo) + b.NicknameCache = NewNicknameCache() + b.stopNormalSync = make(chan struct{}, 1) + b.stopNormalSyncAck = make(chan struct{}, 1) return b } @@ -91,13 +64,13 @@ func (b *Bmatrix) Connect() error { var err error b.Log.Infof("Connecting %s", b.GetString("Server")) if b.GetString("MxID") != "" && b.GetString("Token") != "" { + b.UserID = id.UserID(b.GetString("MxID")) b.mc, err = matrix.NewClient( - b.GetString("Server"), b.GetString("MxID"), b.GetString("Token"), + b.GetString("Server"), b.UserID, b.GetString("Token"), ) if err != nil { return err } - b.UserID = b.GetString("MxID") b.Log.Info("Using existing Matrix credentials") } else { b.mc, err = matrix.NewClient(b.GetString("Server"), "", "") @@ -105,102 +78,150 @@ func (b *Bmatrix) Connect() error { return err } resp, err := b.mc.Login(&matrix.ReqLogin{ - Type: "m.login.password", - User: b.GetString("Login"), - Password: b.GetString("Password"), - Identifier: matrix.NewUserIdentifier(b.GetString("Login")), + Type: matrix.AuthTypePassword, + Password: b.GetString("Password"), + Identifier: matrix.UserIdentifier{Type: matrix.IdentifierTypeUser, User: b.GetString("Login")}, //nolint: exhaustruct + StoreCredentials: true, }) if err != nil { return err } - b.mc.SetCredentials(resp.UserID, resp.AccessToken) b.UserID = resp.UserID b.Log.Info("Connection succeeded") } - go b.handlematrix() + + b.Log.Debug("Retrieving the list of rooms we have already joined") + joinedRooms, err := b.mc.JoinedRooms() + if err != nil { + b.Log.Errorf("couldn't list the joined rooms") + + return err + } + b.joinedRooms = joinedRooms.JoinedRooms + for _, roomID := range joinedRooms.JoinedRooms { + // leave the channel name (usually a channel alias - in the matrix sense) + // unresolved for now, it will be completed when JoinChannel() is called + b.RoomMap[roomID] = RoomInfo{name: "", appService: false} + } + return nil } func (b *Bmatrix) Disconnect() error { + // tell the Sync() loop to exit + b.stopNormalSync <- struct{}{} + b.mc.StopSync() + + // wait for both the syncer and the appservice to terminate + <-b.stopNormalSyncAck + if b.appService != nil { + b.appService.stop <- struct{}{} + <-b.appService.stopAck + } + return nil } func (b *Bmatrix) JoinChannel(channel config.ChannelInfo) error { - return b.retry(func() error { - resp, err := b.mc.JoinRoom(channel.Name, "", nil) + resolvedAlias, err := b.mc.ResolveAlias(id.RoomAlias(channel.Name)) + if err != nil { + b.Log.Errorf("couldn't retrieve the room ID for the alias '%s'", channel.Name) + + return err + } + + roomInfo := RoomInfo{name: channel.Name, appService: false} + alreadyJoined := false + for _, roomID := range b.joinedRooms { + // we have already joined this room (e.g. in a previous execution of matterbridge) + // => we only update the room alias, but do not attempt to join it again + if roomID == resolvedAlias.RoomID { + alreadyJoined = true + break + } + } + + if !alreadyJoined { + err = b.retry(func() error { + _, innerErr := b.mc.JoinRoom(channel.Name, "", nil) + return innerErr + }) + if err != nil { return err } + } - b.Lock() - b.RoomMap[resp.RoomID] = channel.Name - b.Unlock() + b.Lock() + b.RoomMap[resolvedAlias.RoomID] = roomInfo + b.Unlock() - return nil - }) + return nil } -func (b *Bmatrix) Send(msg config.Message) (string, error) { - b.Log.Debugf("=> Receiving %#v", msg) - - channel := b.getRoomID(msg.Channel) - b.Log.Debugf("Channel %s maps to channel id %s", msg.Channel, channel) +func (b *Bmatrix) Start() error { + // at this point, JoinChannel() has been called on all the channels + // declared in the configuration, so we can exit every other joined room + // in order to stop receiving events from rooms we no longer follow + b.RLock() + for _, roomID := range b.joinedRooms { + if _, present := b.RoomMap[roomID]; !present { + // we deliberately ignore the return value, + // because the bridge will still work even if we couln't exit the room + _, _ = b.mc.LeaveRoom(roomID, &matrix.ReqLeave{Reason: "No longer bridged"}) + } + } + b.RUnlock() - username := newMatrixUsername(msg.Username) + go b.handlematrix() - body := username.plain + msg.Text - formattedBody := username.formatted + helper.ParseMarkdown(msg.Text) + if b.GetBool("UseAppService") { + appService, err := b.NewAppService() + if err != nil { + b.Log.Errorf("couldn't load the app service configuration: %#v", err) - if b.GetBool("SpoofUsername") { - // https://spec.matrix.org/v1.3/client-server-api/#mroommember - type stateMember struct { - AvatarURL string `json:"avatar_url,omitempty"` - DisplayName string `json:"displayname"` - Membership string `json:"membership"` + return err } - // TODO: reset username afterwards with DisplayName: null ? - m := stateMember{ - AvatarURL: "", - DisplayName: username.plain, - Membership: "join", - } + b.appService = appService + err = b.startAppService() + if err != nil { + b.Log.Errorf("couldn't start the application service: %#v", err) - _, err := b.mc.SendStateEvent(channel, "m.room.member", b.UserID, m) - if err == nil { - body = msg.Text - formattedBody = helper.ParseMarkdown(msg.Text) + return err } } - // Make a action /me of the message - if msg.Event == config.EventUserAction { - m := matrix.TextMessage{ - MsgType: "m.emote", - Body: body, - FormattedBody: formattedBody, - Format: "org.matrix.custom.html", - } + return nil +} - if b.GetBool("HTMLDisable") { - m.Format = "" - m.FormattedBody = "" - } +func (b *Bmatrix) Send(msg config.Message) (string, error) { + b.Log.Debugf("=> Sending %#v", msg) - msgID := "" + channel := b.getRoomID(msg.Channel) + if channel == "" { + return "", fmt.Errorf("got message for unknown channel '%s'", msg.Channel) + } - err := b.retry(func() error { - resp, err := b.mc.SendMessageEvent(channel, "m.room.message", m) - if err != nil { - return err - } + if msg.Event == config.EventUserTyping && b.GetBool("ShowUserTyping") { + _, err := b.mc.UserTyping(channel, true, 15000) + return "", err + } - msgID = resp.EventID + // Make a action /me of the message + if msg.Event == config.EventUserAction { + //nolint:exhaustruct + m := event.MessageEventContent{ + MsgType: event.MsgEmote, + Body: msg.Text, + } - return err - }) + if !b.GetBool("HTMLDisable") { + m.FormattedBody = helper.ParseMarkdown(msg.Text) + m.Format = event.FormatHTML + } - return msgID, err + return b.sendMessageEventWithRetries(channel, m, msg.Username) } // Delete message @@ -212,12 +233,10 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { msgID := "" err := b.retry(func() error { - resp, err := b.mc.RedactEvent(channel, msg.ID, &matrix.ReqRedact{}) - if err != nil { - return err - } + //nolint:exhaustruct + resp, err := b.mc.RedactEvent(channel, id.EventID(msg.ID), matrix.ReqRedact{}) - msgID = resp.EventID + msgID = string(resp.EventID) return err }) @@ -228,13 +247,13 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { // Upload a file if it exists if msg.Extra != nil { for _, rmsg := range helper.HandleExtra(&msg, b.General) { - rmsg := rmsg - - err := b.retry(func() error { - _, err := b.mc.SendText(channel, rmsg.Username+rmsg.Text) + //nolint:exhaustruct + m := event.MessageEventContent{ + MsgType: event.MsgText, + Body: rmsg.Text, + } - return err - }) + _, err := b.sendMessageEventWithRetries(channel, m, msg.Username) if err != nil { b.Log.Errorf("sendText failed: %s", err) } @@ -247,472 +266,159 @@ func (b *Bmatrix) Send(msg config.Message) (string, error) { // Edit message if we have an ID if msg.ID != "" { - rmsg := EditedMessage{ - TextMessage: matrix.TextMessage{ - Body: body, - MsgType: "m.text", - Format: "org.matrix.custom.html", - FormattedBody: formattedBody, - }, + //nolint:exhaustruct + rmsg := event.MessageEventContent{ + MsgType: event.MsgText, + Body: msg.Text, } - - rmsg.NewContent = SubTextMessage{ - Body: rmsg.TextMessage.Body, - FormattedBody: rmsg.TextMessage.FormattedBody, - Format: rmsg.TextMessage.Format, - MsgType: "m.text", + //nolint:exhaustruct + rmsg.NewContent = &event.MessageEventContent{ + Body: rmsg.Body, + MsgType: event.MsgText, } - if b.GetBool("HTMLDisable") { - rmsg.TextMessage.Format = "" - rmsg.TextMessage.FormattedBody = "" - rmsg.NewContent.Format = "" - rmsg.NewContent.FormattedBody = "" + rmsg.FormattedBody = "* " + msg.Text + } else { + rmsg.Format = event.FormatHTML + rmsg.FormattedBody = "* " + helper.ParseMarkdown(msg.Text) + rmsg.NewContent.Format = rmsg.Format + rmsg.NewContent.FormattedBody = rmsg.FormattedBody } - rmsg.RelatedTo = MessageRelation{ - EventID: msg.ID, - Type: "m.replace", + //nolint:exhaustruct + rmsg.RelatesTo = &event.RelatesTo{ + EventID: id.EventID(msg.ID), + Type: event.RelReplace, } - err := b.retry(func() error { - _, err := b.mc.SendMessageEvent(channel, "m.room.message", rmsg) + return b.sendMessageEventWithRetries(channel, rmsg, msg.Username) + } - return err - }) - if err != nil { - return "", err - } + //nolint:exhaustruct + m := event.MessageEventContent{ + Body: msg.Text, + } - return msg.ID, nil + if !b.GetBool("HTMLDisable") { + m.Format = event.FormatHTML + m.FormattedBody = msg.Text } // Use notices to send join/leave events if msg.Event == config.EventJoinLeave { - m := matrix.TextMessage{ - MsgType: "m.notice", - Body: body, - FormattedBody: formattedBody, - Format: "org.matrix.custom.html", - } - + m.MsgType = event.MsgNotice + } else { + m.MsgType = event.MsgText if b.GetBool("HTMLDisable") { - m.Format = "" m.FormattedBody = "" + } else { + m.FormattedBody = helper.ParseMarkdown(msg.Text) } - var ( - resp *matrix.RespSendEvent - err error - ) - - err = b.retry(func() error { - resp, err = b.mc.SendMessageEvent(channel, "m.room.message", m) - - return err - }) - if err != nil { - return "", err - } - - return resp.EventID, err - } - - if msg.ParentValid() { - m := ReplyMessage{ - TextMessage: matrix.TextMessage{ - MsgType: "m.text", - Body: body, - FormattedBody: formattedBody, - Format: "org.matrix.custom.html", - }, - } - - if b.GetBool("HTMLDisable") { - m.TextMessage.Format = "" - m.TextMessage.FormattedBody = "" - } - - m.RelatedTo = InReplyToRelation{ - InReplyTo: InReplyToRelationContent{ - EventID: msg.ParentID, - }, - } - - var ( - resp *matrix.RespSendEvent - err error - ) - - err = b.retry(func() error { - resp, err = b.mc.SendMessageEvent(channel, "m.room.message", m) - - return err - }) - if err != nil { - return "", err - } - - return resp.EventID, err - } - - if b.GetBool("HTMLDisable") { - var ( - resp *matrix.RespSendEvent - err error - ) - - err = b.retry(func() error { - resp, err = b.mc.SendText(channel, body) - - return err - }) - if err != nil { - return "", err - } - - return resp.EventID, err - } - - // Post normal message with HTML support (eg riot.im) - var ( - resp *matrix.RespSendEvent - err error - ) - - err = b.retry(func() error { - resp, err = b.mc.SendFormattedText(channel, body, formattedBody) - - return err - }) - if err != nil { - return "", err - } - - return resp.EventID, err -} - -func (b *Bmatrix) handlematrix() { - syncer := b.mc.Syncer.(*matrix.DefaultSyncer) - syncer.OnEventType("m.room.redaction", b.handleEvent) - syncer.OnEventType("m.room.message", b.handleEvent) - syncer.OnEventType("m.room.member", b.handleMemberChange) - go func() { - for { - if b == nil { - return - } - if err := b.mc.Sync(); err != nil { - b.Log.Println("Sync() returned ", err) + if msg.ParentValid() { + m.RelatesTo = &event.RelatesTo{ + EventID: "", + Type: event.RelReference, + InReplyTo: &event.InReplyTo{ + EventID: id.EventID(msg.ParentID), + }, + Key: "", } } - }() -} - -func (b *Bmatrix) handleEdit(ev *matrix.Event, rmsg config.Message) bool { - relationInterface, present := ev.Content["m.relates_to"] - newContentInterface, present2 := ev.Content["m.new_content"] - if !(present && present2) { - return false } - var relation MessageRelation - if err := interface2Struct(relationInterface, &relation); err != nil { - b.Log.Warnf("Couldn't parse 'm.relates_to' object with value %#v", relationInterface) - return false - } - - var newContent SubTextMessage - if err := interface2Struct(newContentInterface, &newContent); err != nil { - b.Log.Warnf("Couldn't parse 'm.new_content' object with value %#v", newContentInterface) - return false - } - - if relation.Type != "m.replace" { - return false - } - - rmsg.ID = relation.EventID - rmsg.Text = newContent.Body - b.Remote <- rmsg - - return true + return b.sendMessageEventWithRetries(channel, m, msg.Username) } -func (b *Bmatrix) handleReply(ev *matrix.Event, rmsg config.Message) bool { - relationInterface, present := ev.Content["m.relates_to"] - if !present { - return false - } - - var relation InReplyToRelation - if err := interface2Struct(relationInterface, &relation); err != nil { - // probably fine - return false - } - - body := rmsg.Text - - if !b.GetBool("keepquotedreply") { - for strings.HasPrefix(body, "> ") { - lineIdx := strings.IndexRune(body, '\n') - if lineIdx == -1 { - body = "" - } else { - body = body[(lineIdx + 1):] +// DontProcessOldEvents returns true if a sync event should be considered for further processing. +// We use that function to filter out events we have already read. +func (b *Bmatrix) DontProcessOldEvents(resp *matrix.RespSync, since string) bool { + // we only filter old events in the initial sync(), because subsequent sync() + // (where since != "") should only return new events + if since != "" { + return true + } + + for joinedRoom, roomData := range resp.Rooms.Join { + var readTimestamp int64 = 0 + // retrieve the timestamp of the last read receipt + // note: we're not sure some events will not be thrown away in this + // initial sync, as the server may not have received some events yet when + // the read receipt was sent: there is a mix of timestamps between + // the read receipt on the target homeserver and the timestamps when + // events were *created* on the homeserver peers + for _, evt := range roomData.Ephemeral.Events { + if evt.Type != event.EphemeralEventReceipt { + continue } - } - } - - rmsg.Text = body - rmsg.ParentID = relation.InReplyTo.EventID - b.Remote <- rmsg - - return true -} - -func (b *Bmatrix) handleMemberChange(ev *matrix.Event) { - // Update the displayname on join messages, according to https://matrix.org/docs/spec/client_server/r0.6.1#events-on-change-of-profile-information - if ev.Content["membership"] == "join" { - if dn, ok := ev.Content["displayname"].(string); ok { - b.cacheDisplayName(ev.Sender, dn) - } - } -} - -func (b *Bmatrix) handleEvent(ev *matrix.Event) { - b.Log.Debugf("== Receiving event: %#v", ev) - if ev.Sender != b.UserID { - b.RLock() - channel, ok := b.RoomMap[ev.RoomID] - b.RUnlock() - if !ok { - b.Log.Debugf("Unknown room %s", ev.RoomID) - return - } - - // Create our message - rmsg := config.Message{ - Username: b.getDisplayName(ev.Sender), - Channel: channel, - Account: b.Account, - UserID: ev.Sender, - ID: ev.ID, - Avatar: b.getAvatarURL(ev.Sender), - } - - // Remove homeserver suffix if configured - if b.GetBool("NoHomeServerSuffix") { - re := regexp.MustCompile("(.*?):.*") - rmsg.Username = re.ReplaceAllString(rmsg.Username, `$1`) - } - - // Delete event - if ev.Type == "m.room.redaction" { - rmsg.Event = config.EventMsgDelete - rmsg.ID = ev.Redacts - rmsg.Text = config.EventMsgDelete - b.Remote <- rmsg - return - } - - // Text must be a string - if rmsg.Text, ok = ev.Content["body"].(string); !ok { - b.Log.Errorf("Content[body] is not a string: %T\n%#v", - ev.Content["body"], ev.Content) - return - } - - // Do we have a /me action - if ev.Content["msgtype"].(string) == "m.emote" { - rmsg.Event = config.EventUserAction - } - - // Is it an edit? - if b.handleEdit(ev, rmsg) { - return - } - - // Is it a reply? - if b.handleReply(ev, rmsg) { - return - } - // Do we have attachments - if b.containsAttachment(ev.Content) { - err := b.handleDownloadFile(&rmsg, ev.Content) + err := evt.Content.ParseRaw(evt.Type) if err != nil { - b.Log.Errorf("download failed: %#v", err) + b.Log.Warnf("couldn't parse receipt event %#v", evt.Content) + } + receipts := *evt.Content.AsReceipt() + for _, receiptByType := range receipts { + for _, receiptsByUser := range receiptByType { + for userID, userReceipt := range receiptsByUser { + // ignore read receipts of other users + if userID != b.UserID { + continue + } + + readTimestamp = userReceipt.Timestamp.UnixNano() + } + } } } - b.Log.Debugf("<= Sending message from %s on %s to gateway", ev.Sender, b.Account) - b.Remote <- rmsg - - // not crucial, so no ratelimit check here - if err := b.mc.MarkRead(ev.RoomID, ev.ID); err != nil { - b.Log.Errorf("couldn't mark message as read %s", err.Error()) - } - } -} - -// handleDownloadFile handles file download -func (b *Bmatrix) handleDownloadFile(rmsg *config.Message, content map[string]interface{}) error { - var ( - ok bool - url, name, msgtype, mtype string - info map[string]interface{} - size float64 - ) - - rmsg.Extra = make(map[string][]interface{}) - if url, ok = content["url"].(string); !ok { - return fmt.Errorf("url isn't a %T", url) - } - url = strings.Replace(url, "mxc://", b.GetString("Server")+"/_matrix/media/v1/download/", -1) - - if info, ok = content["info"].(map[string]interface{}); !ok { - return fmt.Errorf("info isn't a %T", info) - } - if size, ok = info["size"].(float64); !ok { - return fmt.Errorf("size isn't a %T", size) - } - if name, ok = content["body"].(string); !ok { - return fmt.Errorf("name isn't a %T", name) - } - if msgtype, ok = content["msgtype"].(string); !ok { - return fmt.Errorf("msgtype isn't a %T", msgtype) - } - if mtype, ok = info["mimetype"].(string); !ok { - return fmt.Errorf("mtype isn't a %T", mtype) - } - - // check if we have an image uploaded without extension - if !strings.Contains(name, ".") { - if msgtype == "m.image" { - mext, _ := mime.ExtensionsByType(mtype) - if len(mext) > 0 { - name += mext[0] + newEventList := make([]*event.Event, 0, len(roomData.Timeline.Events)) + for _, evt := range roomData.Timeline.Events { + // remove old event, except for state changes + if evt.Timestamp > readTimestamp || evt.Type.Class == event.StateEventType { + newEventList = append(newEventList, evt) } - } else { - // just a default .png extension if we don't have mime info - name += ".png" } - } - - // check if the size is ok - err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General) - if err != nil { - return err - } - // actually download the file - data, err := helper.DownloadFile(url) - if err != nil { - return fmt.Errorf("download %s failed %#v", url, err) - } - // add the downloaded data to the message - helper.HandleDownloadData(b.Log, rmsg, name, "", url, data, b.General) - return nil -} -// handleUploadFiles handles native upload of files. -func (b *Bmatrix) handleUploadFiles(msg *config.Message, channel string) (string, error) { - for _, f := range msg.Extra["file"] { - if fi, ok := f.(config.FileInfo); ok { - b.handleUploadFile(msg, channel, &fi) - } + roomData.Timeline.Events = newEventList + resp.Rooms.Join[joinedRoom] = roomData } - return "", nil + return true } -// handleUploadFile handles native upload of a file. -func (b *Bmatrix) handleUploadFile(msg *config.Message, channel string, fi *config.FileInfo) { - username := newMatrixUsername(msg.Username) - content := bytes.NewReader(*fi.Data) - sp := strings.Split(fi.Name, ".") - mtype := mime.TypeByExtension("." + sp[len(sp)-1]) - // image and video uploads send no username, we have to do this ourself here #715 - err := b.retry(func() error { - _, err := b.mc.SendFormattedText(channel, username.plain+fi.Comment, username.formatted+fi.Comment) - - return err - }) - if err != nil { - b.Log.Errorf("file comment failed: %#v", err) - } - - b.Log.Debugf("uploading file: %s %s", fi.Name, mtype) - - var res *matrix.RespMediaUpload - - err = b.retry(func() error { - res, err = b.mc.UploadToContentRepo(content, mtype, int64(len(*fi.Data))) - - return err - }) +func (b *Bmatrix) handlematrix() { + syncer, ok := b.mc.Syncer.(*matrix.DefaultSyncer) + if !ok { + b.Log.Errorf("couldn't convert the Syncer object to a DefaultSyncer structure, the matrix bridge won't work") - if err != nil { - b.Log.Errorf("file upload failed: %#v", err) return } - switch { - case strings.Contains(mtype, "video"): - b.Log.Debugf("sendVideo %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendVideo(channel, fi.Name, res.ContentURI) + // register our custom filtering function + syncer.OnSync(b.DontProcessOldEvents) - return err + eventsTypes := []event.Type{event.EventRedaction, event.EventMessage, event.StateMember, event.EphemeralEventReceipt} + if b.GetBool("ShowUserTyping") { + eventsTypes = append(eventsTypes, event.EphemeralEventTyping) + } + for _, evType := range eventsTypes { + syncer.OnEventType(evType, func(source matrix.EventSource, ev *event.Event) { + b.handleEvent(originClassicSyncer, ev) }) - if err != nil { - b.Log.Errorf("sendVideo failed: %#v", err) - } - case strings.Contains(mtype, "image"): - b.Log.Debugf("sendImage %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendImage(channel, fi.Name, res.ContentURI) + } - return err - }) - if err != nil { - b.Log.Errorf("sendImage failed: %#v", err) - } - case strings.Contains(mtype, "audio"): - b.Log.Debugf("sendAudio %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.AudioMessage{ - MsgType: "m.audio", - Body: fi.Name, - URL: res.ContentURI, - Info: matrix.AudioInfo{ - Mimetype: mtype, - Size: uint(len(*fi.Data)), - }, - }) + go func() { + for { + select { + case <-b.stopNormalSync: + b.stopNormalSyncAck <- struct{}{} - return err - }) - if err != nil { - b.Log.Errorf("sendAudio failed: %#v", err) - } - default: - b.Log.Debugf("sendFile %s", res.ContentURI) - err = b.retry(func() error { - _, err = b.mc.SendMessageEvent(channel, "m.room.message", matrix.FileMessage{ - MsgType: "m.file", - Body: fi.Name, - URL: res.ContentURI, - Info: matrix.FileInfo{ - Mimetype: mtype, - Size: uint(len(*fi.Data)), - }, - }) + return + default: - return err - }) - if err != nil { - b.Log.Errorf("sendFile failed: %#v", err) + if err := b.mc.Sync(); err != nil { + b.Log.Warningf("Sync() returned %#v", err) + } + } } - } - b.Log.Debugf("result: %#v", res) + }() } -- cgit v1.2.3