From 5dbace10c64a8b77d36ce3b03428a1a10aa7e1be Mon Sep 17 00:00:00 2001 From: Karmanyaah Malhotra Date: Mon, 22 Feb 2021 22:02:33 -0500 Subject: [PATCH] both way sync --- ROADMAP.md | 14 +-- go.mod | 2 - portal.go | 354 ++++++++++++++++++++++++++++------------------------- 3 files changed, 193 insertions(+), 177 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index f568362..16f4941 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,12 +1,12 @@ # Features & roadmap * Matrix → GroupMe * [ ] Message content - * [ ] Plain text + * [x] Plain text * [ ] Formatted messages * [ ] Media/files * [ ] Replies * [ ] Message redactions - * [ ] Presence + * [ ] Presence - N/A * [ ] Typing notifications * [ ] Read receipts * [ ] Power level @@ -22,7 +22,7 @@ * [ ] Initial room metadata * GroupMe → Matrix * [ ] Message content - * [ ] Plain text + * [x] Plain text * [ ] Formatted messages * [ ] Media/files * [ ] Location messages @@ -30,9 +30,7 @@ * [ ] Replies * [ ] Chat types * [ ] Private chat - * [ ] Group chat - * [ ] Broadcast list[2] - * [ ] Message deletions + * [x] Group chat * [ ] Avatars * [ ] Presence * [ ] Typing notifications @@ -49,14 +47,14 @@ * [ ] Description * [ ] Initial group metadata * [ ] User metadata changes - * [ ] Display name[3] + * [x] Display name * [ ] Avatar * [ ] Initial user metadata * [ ] Display name * [ ] Avatar * Misc * [ ] Automatic portal creation - * [ ] At startup + * [x] At startup * [ ] When receiving invite * [ ] When receiving message * [ ] Private chat creation by inviting Matrix puppet of WhatsApp user to new room diff --git a/go.mod b/go.mod index 50c9779..7472a27 100644 --- a/go.mod +++ b/go.mod @@ -34,5 +34,3 @@ replace github.com/Rhymen/go-whatsapp => github.com/tulir/go-whatsapp v0.3.16 replace github.com/karmanyaahm/groupme => ../groupme replace maunium.net/go/mautrix => ../mautrix - -replace maunium.net/go/mautrix/i => ../mautrix_go/id diff --git a/portal.go b/portal.go index c38f898..f71fa7b 100644 --- a/portal.go +++ b/portal.go @@ -131,7 +131,7 @@ func (bridge *Bridge) NewManualPortal(key database.PortalKey) *Portal { bridge: bridge, log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", key)), - recentlyHandled: [recentlyHandledLength]groupme.ID{}, + recentlyHandled: [recentlyHandledLength]string{}, messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer), } @@ -146,7 +146,7 @@ func (bridge *Bridge) NewPortal(dbPortal *database.Portal) *Portal { bridge: bridge, log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", dbPortal.Key)), - recentlyHandled: [recentlyHandledLength]groupme.ID{}, + recentlyHandled: [recentlyHandledLength]string{}, messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer), } @@ -171,7 +171,7 @@ type Portal struct { roomCreateLock sync.Mutex - recentlyHandled [recentlyHandledLength]groupme.ID + recentlyHandled [recentlyHandledLength]string recentlyHandledLock sync.Mutex recentlyHandledIndex uint8 @@ -266,8 +266,9 @@ func (portal *Portal) handleMessage(msg PortalMessage) { func (portal *Portal) isRecentlyHandled(id groupme.ID) bool { start := portal.recentlyHandledIndex - for i := start; i != start; i = (i - 1) % recentlyHandledLength { - if portal.recentlyHandled[i] == id { + idStr := id.String() + for i := (start - 1) % recentlyHandledLength; i != start; i = (i - 1) % recentlyHandledLength { + if portal.recentlyHandled[i] == idStr { return true } } @@ -279,6 +280,7 @@ func (portal *Portal) isDuplicate(id groupme.ID) bool { if msg != nil { return true } + return false } @@ -287,7 +289,6 @@ func init() { } func (portal *Portal) markHandled(source *User, message *groupme.Message, mxid id.EventID) { - print("handle message") msg := portal.bridge.DB.Message.New() msg.Chat = portal.Key msg.JID = message.ID.String() @@ -311,7 +312,7 @@ func (portal *Portal) markHandled(source *User, message *groupme.Message, mxid i index := portal.recentlyHandledIndex portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength portal.recentlyHandledLock.Unlock() - portal.recentlyHandled[index] = groupme.ID(msg.JID) + portal.recentlyHandled[index] = message.ID.String() } func (portal *Portal) getMessageIntent(user *User, info *groupme.Message) *appservice.IntentAPI { @@ -704,7 +705,6 @@ func (portal *Portal) BackfillHistory(user *User, lastMessageTime uint64) error endBackfill := portal.beginBackfill() defer endBackfill() - println("hi") lastMessage := portal.bridge.DB.Message.GetLastInChat(portal.Key) fmt.Println(lastMessage) if lastMessage == nil { @@ -1239,7 +1239,7 @@ func (portal *Portal) sendMessageDirect(intent *appservice.IntentAPI, eventType if timestamp == 0 { return intent.SendMessageEvent(portal.MXID, eventType, &wrappedContent) } else { - return intent.SendMassagedMessageEvent(portal.MXID, eventType, &wrappedContent, timestamp) + return intent.SendMassagedMessageEvent(portal.MXID, eventType, &wrappedContent, timestamp*1000) //milliseconds } } @@ -1259,7 +1259,7 @@ func (portal *Portal) HandleTextMessage(source *User, message *groupme.Message) //TODO: mentions _, _ = intent.UserTyping(portal.MXID, false, 0) - resp, err := portal.sendMessage(intent, event.EventMessage, content, message.CreatedAt.ToTime().UnixNano()) + resp, err := portal.sendMessage(intent, event.EventMessage, content, message.CreatedAt.ToTime().Unix()) if err != nil { portal.log.Errorfln("Failed to handle message %s: %v", message.ID, err) return @@ -1835,151 +1835,157 @@ func (portal *Portal) addRelaybotFormat(sender *User, content *event.MessageEven return true } -func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) (*groupme.Message, *User) { - print("convertMatrixMessage") - return nil, nil - // content, ok := evt.Content.Parsed.(*event.MessageEventContent) - // if !ok { - // portal.log.Debugfln("Failed to handle event %s: unexpected parsed content type %T", evt.ID, evt.Content.Parsed) - // return nil, sender - // } +func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) ([]*groupme.Message, *User) { + content, ok := evt.Content.Parsed.(*event.MessageEventContent) + if !ok { + portal.log.Debugfln("Failed to handle event %s: unexpected parsed content type %T", evt.ID, evt.Content.Parsed) + return nil, sender + } - // ts := uint64(evt.Timestamp / 1000) - // status := waProto.WebMessageInfo_ERROR - // fromMe := true - // info := &waProto.WebMessageInfo{ - // Key: &waProto.MessageKey{ - // FromMe: &fromMe, - // Id: makeMessageID(), - // RemoteJid: &portal.Key.JID, - // }, - // MessageTimestamp: &ts, - // Message: &waProto.Message{}, - // Status: &status, - // } - // ctxInfo := &waProto.ContextInfo{} - // replyToID := content.GetReplyTo() - // if len(replyToID) > 0 { - // content.RemoveReplyFallback() - // msg := portal.bridge.DB.Message.GetByMXID(replyToID) - // if msg != nil && msg.Content != nil { - // ctxInfo.StanzaId = &msg.JID - // ctxInfo.Participant = &msg.Sender - // ctxInfo.QuotedMessage = msg.Content - // } - // } - // relaybotFormatted := false - // if sender.NeedsRelaybot(portal) { - // if !portal.HasRelaybot() { - // if sender.HasSession() { - // portal.log.Debugln("Database says", sender.MXID, "not in chat and no relaybot, but trying to send anyway") - // } else { - // portal.log.Debugln("Ignoring message from", sender.MXID, "in chat with no relaybot") - // return nil, sender - // } - // } else { - // relaybotFormatted = portal.addRelaybotFormat(sender, content) - // sender = portal.bridge.Relaybot - // } - // } - // if evt.Type == event.EventSticker { - // content.MsgType = event.MsgImage - // } else if content.MsgType == event.MsgImage && content.GetInfo().MimeType == "image/gif" { - // content.MsgType = event.MsgVideo - // } + //ts := uint64(evt.Timestamp / 1000) + //status := waProto.WebMessageInfo_ERROR + //fromMe := true + // info := &waProto.WebMessageInfo{ + // Key: &waProto.MessageKey{ + // FromMe: &fromMe, + // Id: makeMessageID(), + // RemoteJid: &portal.Key.JID, + // }, + // MessageTimestamp: &ts, + // Message: &waProto.Message{}, + // Status: &status, + // } + // + info := groupme.Message{ + SourceGUID: evt.ID.String(), //TODO Figure out for multiple messages + GroupID: groupme.ID(portal.Key.JID), + } + replyToID := content.GetReplyTo() + if len(replyToID) > 0 { + // content.RemoveReplyFallback() + // msg := portal.bridge.DB.Message.GetByMXID(replyToID) + // if msg != nil && msg.Content != nil { + // ctxInfo.StanzaId = &msg.JID + // ctxInfo.Participant = &msg.Sender + // ctxInfo.QuotedMessage = msg.Content + // } + } + relaybotFormatted := false + if sender.NeedsRelaybot(portal) { + if !portal.HasRelaybot() { + if sender.HasSession() { + portal.log.Debugln("Database says", sender.MXID, "not in chat and no relaybot, but trying to send anyway") + } else { + portal.log.Debugln("Ignoring message from", sender.MXID, "in chat with no relaybot") + return nil, sender + } + } else { + relaybotFormatted = portal.addRelaybotFormat(sender, content) + sender = portal.bridge.Relaybot + } + } + if evt.Type == event.EventSticker { + content.MsgType = event.MsgImage + } else if content.MsgType == event.MsgImage && content.GetInfo().MimeType == "image/gif" { + content.MsgType = event.MsgVideo + } - // switch content.MsgType { - // case event.MsgText, event.MsgEmote, event.MsgNotice: - // text := content.Body - // if content.Format == event.FormatHTML { - // text, ctxInfo.MentionedJid = portal.bridge.Formatter.ParseMatrix(content.FormattedBody) - // } - // if content.MsgType == event.MsgEmote && !relaybotFormatted { - // text = "/me " + text - // } - // if ctxInfo.StanzaId != nil || ctxInfo.MentionedJid != nil { - // info.Message.ExtendedTextMessage = &waProto.ExtendedTextMessage{ - // Text: &text, - // ContextInfo: ctxInfo, - // } - // } else { - // info.Message.Conversation = &text - // } - // case event.MsgImage: - // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaImage) - // if media == nil { - // return nil, sender - // } - // ctxInfo.MentionedJid = media.MentionedJIDs - // info.Message.ImageMessage = &waProto.ImageMessage{ - // ContextInfo: ctxInfo, - // Caption: &media.Caption, - // JpegThumbnail: media.Thumbnail, - // Url: &media.URL, - // MediaKey: media.MediaKey, - // Mimetype: &content.GetInfo().MimeType, - // FileEncSha256: media.FileEncSHA256, - // FileSha256: media.FileSHA256, - // FileLength: &media.FileLength, - // } - // case event.MsgVideo: - // gifPlayback := content.GetInfo().MimeType == "image/gif" - // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaVideo) - // if media == nil { - // return nil, sender - // } - // duration := uint32(content.GetInfo().Duration) - // ctxInfo.MentionedJid = media.MentionedJIDs - // info.Message.VideoMessage = &waProto.VideoMessage{ - // ContextInfo: ctxInfo, - // Caption: &media.Caption, - // JpegThumbnail: media.Thumbnail, - // Url: &media.URL, - // MediaKey: media.MediaKey, - // Mimetype: &content.GetInfo().MimeType, - // GifPlayback: &gifPlayback, - // Seconds: &duration, - // FileEncSha256: media.FileEncSHA256, - // FileSha256: media.FileSHA256, - // FileLength: &media.FileLength, - // } - // case event.MsgAudio: - // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaAudio) - // if media == nil { - // return nil, sender - // } - // duration := uint32(content.GetInfo().Duration) - // info.Message.AudioMessage = &waProto.AudioMessage{ - // ContextInfo: ctxInfo, - // Url: &media.URL, - // MediaKey: media.MediaKey, - // Mimetype: &content.GetInfo().MimeType, - // Seconds: &duration, - // FileEncSha256: media.FileEncSHA256, - // FileSha256: media.FileSHA256, - // FileLength: &media.FileLength, - // } - // case event.MsgFile: - // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaDocument) - // if media == nil { - // return nil, sender - // } - // info.Message.DocumentMessage = &waProto.DocumentMessage{ - // ContextInfo: ctxInfo, - // Url: &media.URL, - // Title: &content.Body, - // FileName: &content.Body, - // MediaKey: media.MediaKey, - // Mimetype: &content.GetInfo().MimeType, - // FileEncSha256: media.FileEncSHA256, - // FileSha256: media.FileSHA256, - // FileLength: &media.FileLength, - // } - // default: - // portal.log.Debugln("Unhandled Matrix event %s: unknown msgtype %s", evt.ID, content.MsgType) - // return nil, sender + switch content.MsgType { + case event.MsgText, event.MsgEmote, event.MsgNotice: + text := content.Body + if content.Format == event.FormatHTML { + text, _ = portal.bridge.Formatter.ParseMatrix(content.FormattedBody) + //TODO mentions + } + if content.MsgType == event.MsgEmote && !relaybotFormatted { + text = "/me " + text + } + info.Text = text + + // if ctxInfo.StanzaId != nil || ctxInfo.MentionedJid != nil { + // info.Message.ExtendedTextMessage = &waProto.ExtendedTextMessage{ + // Text: &text, + // ContextInfo: ctxInfo, + // } // } - // return info, sender + //else { + // info.Message.Conversation = &text + // } + // case event.MsgImage: + // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaImage) + // if media == nil { + // return nil, sender + // } + // ctxInfo.MentionedJid = media.MentionedJIDs + // info.Message.ImageMessage = &waProto.ImageMessage{ + // ContextInfo: ctxInfo, + // Caption: &media.Caption, + // JpegThumbnail: media.Thumbnail, + // Url: &media.URL, + // MediaKey: media.MediaKey, + // Mimetype: &content.GetInfo().MimeType, + // FileEncSha256: media.FileEncSHA256, + // FileSha256: media.FileSHA256, + // FileLength: &media.FileLength, + // } + // case event.MsgVideo: + // gifPlayback := content.GetInfo().MimeType == "image/gif" + // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaVideo) + // if media == nil { + // return nil, sender + // } + // duration := uint32(content.GetInfo().Duration) + // ctxInfo.MentionedJid = media.MentionedJIDs + // info.Message.VideoMessage = &waProto.VideoMessage{ + // ContextInfo: ctxInfo, + // Caption: &media.Caption, + // JpegThumbnail: media.Thumbnail, + // Url: &media.URL, + // MediaKey: media.MediaKey, + // Mimetype: &content.GetInfo().MimeType, + // GifPlayback: &gifPlayback, + // Seconds: &duration, + // FileEncSha256: media.FileEncSHA256, + // FileSha256: media.FileSHA256, + // FileLength: &media.FileLength, + // } + // case event.MsgAudio: + // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaAudio) + // if media == nil { + // return nil, sender + // } + // duration := uint32(content.GetInfo().Duration) + // info.Message.AudioMessage = &waProto.AudioMessage{ + // ContextInfo: ctxInfo, + // Url: &media.URL, + // MediaKey: media.MediaKey, + // Mimetype: &content.GetInfo().MimeType, + // Seconds: &duration, + // FileEncSha256: media.FileEncSHA256, + // FileSha256: media.FileSHA256, + // FileLength: &media.FileLength, + // } + // case event.MsgFile: + // media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsapp.MediaDocument) + // if media == nil { + // return nil, sender + // } + // info.Message.DocumentMessage = &waProto.DocumentMessage{ + // ContextInfo: ctxInfo, + // Url: &media.URL, + // Title: &content.Body, + // FileName: &content.Body, + // MediaKey: media.MediaKey, + // Mimetype: &content.GetInfo().MimeType, + // FileEncSha256: media.FileEncSHA256, + // FileSha256: media.FileSHA256, + // FileLength: &media.FileLength, + // } + default: + portal.log.Debugln("Unhandled Matrix event %s: unknown msgtype %s", evt.ID, content.MsgType) + return nil, sender + } + return []*groupme.Message{&info}, sender } func (portal *Portal) wasMessageSent(sender *User, id string) bool { @@ -2017,23 +2023,37 @@ func (portal *Portal) sendDeliveryReceipt(eventID id.EventID) { var timeout = errors.New("message sending timed out") func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) { - println("handle matrix message") - return - // if !portal.HasRelaybot() && ((portal.IsPrivateChat() && sender.JID != portal.Key.Receiver) || - // portal.sendMatrixConnectionError(sender, evt.ID)) { - // return - // } - // portal.log.Debugfln("Received event %s", evt.ID) - // info, sender := portal.convertMatrixMessage(sender, evt) - // if info == nil { - // return - // } - // portal.markHandled(sender, info, evt.ID) - // portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.ID) - // portal.sendRaw(sender, evt, info, false) + if !portal.HasRelaybot() && ((portal.IsPrivateChat() && sender.JID != portal.Key.Receiver) || + portal.sendMatrixConnectionError(sender, evt.ID)) { + return + } + portal.log.Debugfln("Received event %s", evt.ID) + info, sender := portal.convertMatrixMessage(sender, evt) + if info == nil { + return + } + for _, i := range info { + portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info[0].ID) + i = portal.sendRaw(sender, evt, info[0], false) //TODO deal with multiple messages for longer messages + portal.markHandled(sender, i, evt.ID) + } + } -func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *waProto.WebMessageInfo, isRetry bool) { +func (portal *Portal) sendRaw(sender *User, evt *event.Event, info *groupme.Message, isRetry bool) *groupme.Message { + + m, err := sender.Client.CreateMessage(context.TODO(), info.GroupID, info) + id := "" + if m != nil { + id = m.ID.String() + } + if err != nil { + portal.log.Warnln(err, id, info.GroupID.String()) + } + if isRetry && err != nil { + m, err = sender.Client.CreateMessage(context.TODO(), info.GroupID, info) + } + return m // errChan := make(chan error, 1) // go sender.Conn.SendRaw(info, errChan)