Skip to content

Commit

Permalink
render incoming reaction
Browse files Browse the repository at this point in the history
  • Loading branch information
rnons committed Dec 18, 2024
1 parent d79bd2b commit 5d49574
Show file tree
Hide file tree
Showing 6 changed files with 1,934 additions and 1,275 deletions.
42 changes: 42 additions & 0 deletions pkg/connector/handlegchat.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"time"

"github.com/rs/zerolog"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/networkid"
"maunium.net/go/mautrix/bridgev2/simplevent"
Expand Down Expand Up @@ -50,6 +51,8 @@ func (c *GChatClient) onStreamEvent(ctx context.Context, raw any) {
}

c.setPortalRevision(ctx, evt)

c.handleReaction(ctx, evt)
}

func (c *GChatClient) convertToMatrix(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, msg *proto.Message) *bridgev2.ConvertedMessage {
Expand Down Expand Up @@ -96,3 +99,42 @@ func (c *GChatClient) convertToMatrix(ctx context.Context, portal *bridgev2.Port

return cm
}

func (c *GChatClient) handleReaction(ctx context.Context, evt *proto.Event) {
reaction := evt.Body.GetMessageReaction()
if reaction == nil {
return
}

var eventType bridgev2.RemoteEventType
if reaction.GetType() == proto.MessageReactionEvent_ADD {
eventType = bridgev2.RemoteEventReaction
} else {
eventType = bridgev2.RemoteEventReactionRemove

}

sender := reaction.UserId.GetId()
messageId := reaction.MessageId.GetMessageId()
c.userLogin.Bridge.QueueRemoteEvent(c.userLogin, &simplevent.Reaction{
EventMeta: simplevent.EventMeta{
Type: eventType,
LogContext: func(c zerolog.Context) zerolog.Context {
return c.
Str("message_id", messageId).
Str("sender", sender).
Str("emoji", reaction.Emoji.GetUnicode()).
Str("type", reaction.GetType().String())
},
PortalKey: c.makePortalKey(evt),
Timestamp: time.UnixMicro(*reaction.Timestamp),
Sender: bridgev2.EventSender{
IsFromMe: sender == string(c.userLogin.ID),
Sender: networkid.UserID(sender),
},
},
EmojiID: "",
Emoji: reaction.Emoji.GetUnicode(),
TargetMessage: networkid.MessageID(messageId),
})
}
14 changes: 14 additions & 0 deletions pkg/connector/ids.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package connector

import (
"maunium.net/go/mautrix/bridgev2/networkid"

"go.mau.fi/mautrix-googlechat/pkg/gchatmeow/proto"
)

func (c *GChatClient) makePortalKey(evt *proto.Event) networkid.PortalKey {
return networkid.PortalKey{
ID: networkid.PortalID(evt.GroupId.String()),
Receiver: c.userLogin.ID,
}
}
92 changes: 17 additions & 75 deletions pkg/gchatmeow/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package gchatmeow
import (
"context"
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
Expand All @@ -17,7 +16,7 @@ import (
"strconv"
"strings"
"time"
"unicode/utf8"
"unicode/utf16"

"go.mau.fi/util/pblite"

Expand Down Expand Up @@ -62,6 +61,16 @@ type Channel struct {
OnReceiveArray *Event
}

type UTF16String []uint16

func NewUTF16String(s string) UTF16String {
return utf16.Encode([]rune(s))
}

func (u UTF16String) String() string {
return string(utf16.Decode(u))
}

type ChunkParser struct {
buf []byte
}
Expand All @@ -72,91 +81,24 @@ func NewChunkParser() *ChunkParser {
}
}

// bestEffortDecode attempts to decode as much UTF-8 data as possible from the buffer
func bestEffortDecode(data []byte) string {
valid := make([]byte, 0, len(data))
for len(data) > 0 {
r, size := utf8.DecodeRune(data)
if r == utf8.RuneError {
break
}
valid = append(valid, data[:size]...)
data = data[size:]
}
return string(valid)
}

// GetChunks yields chunks generated from received data.
// The buffer may not be decodable as UTF-8 if there's a split multi-byte
// character at the end. To handle this, we do a "best effort" decode of the
// buffer to decode as much of it as possible.
func (p *ChunkParser) GetChunks(newDataBytes []byte) []string {
var chunks []string
p.buf = append(p.buf, newDataBytes...)

for {
// Decode buffer with best effort
bufDecoded := bestEffortDecode(p.buf)

// Convert to UTF-16 (removing BOM)
var bufUtf16 []byte
for _, r := range bufDecoded {
// Convert each rune to UTF-16
buf := make([]byte, 2)
binary.BigEndian.PutUint16(buf, uint16(r))
bufUtf16 = append(bufUtf16, buf...)
}

// Find length string match
matches := lenRegex.FindStringSubmatch(bufDecoded)
if matches == nil {
break
}

lengthStr := matches[1]
// Both lengths are in number of bytes in UTF-16 encoding
bufStr := string(p.buf)
lengthStr, after, _ := strings.Cut(bufStr, "\n")
length, err := strconv.Atoi(lengthStr)
if err != nil {
break
}
length *= 2 // Convert to UTF-16 byte count

// Calculate length of the submission length and newline in UTF-16
lenStrAndNewline := lengthStr + "\n"
var lenLength int
for _, r := range lenStrAndNewline {
lenLength += 2 // Each UTF-16 character is 2 bytes
_ = r
}

if len(bufUtf16)-lenLength < length {
utf16Str := NewUTF16String(after)
if len(utf16Str) < length {
break
}

// Extract submission
submission := bufUtf16[lenLength : lenLength+length]

// Convert UTF-16 bytes back to string
var result string
for i := 0; i < len(submission); i += 2 {
if i+1 >= len(submission) {
break
}
char := binary.BigEndian.Uint16(submission[i : i+2])
result += string(rune(char))
}

chunks = append(chunks, result)

// Calculate how many bytes to drop from the buffer
dropLength := len(matches[0]) // length of the length string and newline
dropLength += len(result) // length of the actual content in UTF-8

if dropLength <= len(p.buf) {
p.buf = p.buf[dropLength:]
} else {
p.buf = p.buf[:0]
}
chunks = append(chunks, utf16Str[0:length].String())
p.buf = []byte(utf16Str[length:].String())
}

return chunks
Expand Down
Loading

0 comments on commit 5d49574

Please sign in to comment.