diff --git a/go.mod b/go.mod index 9b1bdf5..3af91f4 100644 --- a/go.mod +++ b/go.mod @@ -27,4 +27,4 @@ require ( maunium.net/go/mauflag v1.0.0 // indirect ) -replace github.com/bwmarrin/discordgo => gitlab.com/beeper/discordgo v0.23.3-0.20220528185832-6fcb85e150f7 +replace github.com/bwmarrin/discordgo => gitlab.com/beeper/discordgo v0.23.3-0.20220528212118-5e6370d356e6 diff --git a/go.sum b/go.sum index 09d1e53..0c45970 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0= github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -gitlab.com/beeper/discordgo v0.23.3-0.20220528185832-6fcb85e150f7 h1:S8hbrkgKGU4aU5kXW4d8CA/9ayi8ymI3QU6yg/aWfUw= -gitlab.com/beeper/discordgo v0.23.3-0.20220528185832-6fcb85e150f7/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +gitlab.com/beeper/discordgo v0.23.3-0.20220528212118-5e6370d356e6 h1:JegmFzU6WlZ0vW28fBFkKaZbMgVE/laetJlQJO3wQsk= +gitlab.com/beeper/discordgo v0.23.3-0.20220528212118-5e6370d356e6/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c= golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= diff --git a/portal.go b/portal.go index 5b68bf8..124060e 100644 --- a/portal.go +++ b/portal.go @@ -465,9 +465,9 @@ func (portal *Portal) sendMediaFailedMessage(intent *appservice.IntentAPI, bridg MsgType: event.MsgNotice, } - _, err := portal.sendMatrixMessage(intent, event.EventMessage, content, nil, time.Now().UTC().UnixMilli()) + _, err := portal.sendMatrixMessage(intent, event.EventMessage, content, nil, 0) if err != nil { - portal.log.Warnfln("failed to send error message to matrix: %v", err) + portal.log.Warnfln("Failed to send media error message to matrix: %v", err) } } @@ -713,10 +713,14 @@ func (portal *Portal) handleDiscordMessageUpdate(user *User, msg *discordgo.Mess Body: msg.Content, MsgType: event.MsgText, } - content.SetEdit(existing.MXID) - _, err := portal.sendMatrixMessage(intent, event.EventMessage, content, nil, time.Now().UTC().UnixMilli()) + var editTS int64 + if msg.EditedTimestamp != nil { + editTS = msg.EditedTimestamp.UnixMilli() + } + // TODO figure out some way to deduplicate outgoing edits + _, err := portal.sendMatrixMessage(intent, event.EventMessage, content, nil, editTS) if err != nil { portal.log.Warnfln("failed to send message %q to matrix: %v", msg.ID, err) @@ -846,6 +850,74 @@ func generateNonce() string { return strconv.FormatInt(snowflake, 10) } +func (portal *Portal) getEvent(mxid id.EventID) (*event.Event, error) { + evt, err := portal.MainIntent().GetEvent(portal.MXID, mxid) + if err != nil { + return nil, err + } + _ = evt.Content.ParseRaw(evt.Type) + if evt.Type == event.EventEncrypted { + decryptedEvt, err := portal.bridge.Crypto.Decrypt(evt) + if err != nil { + return nil, err + } else { + evt = decryptedEvt + } + } + return evt, nil +} + +func genThreadName(evt *event.Event) string { + body := evt.Content.AsMessage().Body + if len(body) == 0 { + return "thread" + } + fields := strings.Fields(body) + var title string + for _, field := range fields { + if len(title)+len(field) < 40 { + title += field + title += " " + continue + } + if len(title) == 0 { + title = field[:40] + } + break + } + return title +} + +func (portal *Portal) startThreadFromMatrix(sender *User, threadRoot id.EventID) (string, error) { + rootEvt, err := portal.getEvent(threadRoot) + if err != nil { + return "", fmt.Errorf("failed to get root event: %w", err) + } + threadName := genThreadName(rootEvt) + + existingMsg := portal.bridge.DB.Message.GetByMXID(portal.Key, threadRoot) + if existingMsg == nil { + return "", fmt.Errorf("unknown root event") + } else if existingMsg.ThreadID != "" { + return "", fmt.Errorf("root event is already in a thread") + } else { + var ch *discordgo.Channel + ch, err = sender.Session.MessageThreadStartComplex(portal.Key.ChannelID, existingMsg.DiscordID, &discordgo.ThreadStart{ + Name: threadName, + AutoArchiveDuration: 24 * 60, + Type: discordgo.ChannelTypeGuildPublicThread, + Location: "Message", + }) + if err != nil { + return "", fmt.Errorf("error starting thread: %v", err) + } + portal.log.Debugfln("Created Discord thread from %s/%s", threadRoot, ch.ID) + fmt.Printf("Created thread %+v\n", ch) + portal.bridge.GetThreadByID(existingMsg.DiscordID, existingMsg) + return ch.ID, nil + } +} + func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) { if portal.IsPrivateChat() && sender.DiscordID != portal.Key.Receiver { return @@ -874,12 +946,18 @@ func (portal *Portal) handleMatrixMessage(sender *User, evt *event.Event) { } else if threadRoot := content.GetRelatesTo().GetThreadParent(); threadRoot != "" { existingThread := portal.bridge.DB.Thread.GetByMatrixRootMsg(threadRoot) if existingThread != nil { - channelID = existingThread.ID threadID = existingThread.ID } else { - // TODO create new thread + var err error + threadID, err = portal.startThreadFromMatrix(sender, threadRoot) + if err != nil { + portal.log.Warnfln("Failed to start thread from %s: %v", threadRoot, err) + } } } + if threadID != "" { + channelID = threadID + } var sendReq discordgo.MessageSend @@ -1190,8 +1268,13 @@ func (portal *Portal) handleDiscordReaction(user *User, reaction *discordgo.Mess Key: matrixReaction, }, }} + if intent.IsCustomPuppet { + content.Raw = map[string]interface{}{ + bridge.DoublePuppetKey: doublePuppetValue, + } + } - resp, err := intent.Client.SendMessageEvent(portal.MXID, event.EventReaction, &content) + resp, err := intent.SendMessageEvent(portal.MXID, event.EventReaction, &content) if err != nil { portal.log.Errorfln("failed to send reaction from %s: %v", reaction.MessageID, err) diff --git a/user.go b/user.go index f956fd3..375fca8 100644 --- a/user.go +++ b/user.go @@ -543,6 +543,20 @@ func (user *User) handlePrivateChannel(portal *Portal, meta *discordgo.Channel, }) } +func (user *User) addGuildToSpace(guild *Guild) bool { + if len(guild.MXID) > 0 { + _, err := user.bridge.Bot.SendStateEvent(user.GetSpaceRoom(), event.StateSpaceChild, guild.MXID.String(), &event.SpaceChildEventContent{ + Via: []string{user.bridge.AS.HomeserverDomain}, + }) + if err != nil { + user.log.Errorfln("Failed to add guild space %s to user space: %v", guild.MXID, err) + } else { + return true + } + } + return false +} + func (user *User) handleGuild(meta *discordgo.Guild, timestamp time.Time, isInSpace bool) { guild := user.bridge.GetGuildByID(meta.ID, true) guild.UpdateInfo(user, meta) @@ -559,15 +573,8 @@ func (user *User) handleGuild(meta *discordgo.Guild, timestamp time.Time, isInSp } } } - if len(guild.MXID) > 0 && !isInSpace { - _, err := user.bridge.Bot.SendStateEvent(user.GetSpaceRoom(), event.StateSpaceChild, guild.MXID.String(), &event.SpaceChildEventContent{ - Via: []string{user.bridge.AS.HomeserverDomain}, - }) - if err != nil { - user.log.Errorfln("Failed to add guild space %s to user space: %v", guild.MXID, err) - } else { - isInSpace = true - } + if !isInSpace { + isInSpace = user.addGuildToSpace(guild) } user.MarkInPortal(database.UserPortal{ DiscordID: meta.ID, @@ -640,7 +647,9 @@ func (user *User) channelUpdateHandler(_ *discordgo.Session, c *discordgo.Channe } func (user *User) pushPortalMessage(msg interface{}, typeName, channelID, guildID string) { - fmt.Printf("%+v\n", msg) + if user.Session.LogLevel == discordgo.LogDebug { + fmt.Printf("%+v\n", msg) + } if !user.bridgeMessage(guildID) { return } @@ -812,6 +821,13 @@ func (user *User) bridgeGuild(guildID string, everything bool) error { if err != nil { return err } + user.addGuildToSpace(guild) + user.MarkInPortal(database.UserPortal{ + DiscordID: meta.ID, + Type: database.UserPortalTypeGuild, + Timestamp: time.Now(), + InSpace: true, + }) for _, ch := range meta.Channels { portal := user.GetPortalByMeta(ch) if (everything && channelIsBridgeable(ch)) || ch.Type == discordgo.ChannelTypeGuildCategory {