diff --git a/ROADMAP.md b/ROADMAP.md index 48c52af..bcdd773 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,7 +11,7 @@ * [x] Unicode emojis * [ ] Custom emojis (re-reacting with custom emojis sent from Discord already works) * [ ] Presence - * [ ] Typing notifications + * [x] Typing notifications * [x] Own read status * [ ] Power level * [ ] Membership actions @@ -39,7 +39,7 @@ * [x] Custom emojis (not yet supported on Matrix) * [x] Avatars * [ ] Presence - * [ ] Typing notifications + * [ ] Typing notifications (currently partial support: DMs work after you type in them) * [x] Own read status * [ ] Membership actions * [ ] Invite diff --git a/go.mod b/go.mod index 9a3928b..39a9f29 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/yuin/goldmark v1.4.12 maunium.net/go/maulogger/v2 v2.3.2 - maunium.net/go/mautrix v0.11.1-0.20220708121944-cda2329dd1df + maunium.net/go/mautrix v0.11.1-0.20220708134822-7534f9e547b0 ) require ( @@ -26,4 +26,4 @@ require ( maunium.net/go/mauflag v1.0.0 // indirect ) -replace github.com/bwmarrin/discordgo => gitlab.com/beeper/discordgo v0.23.3-0.20220708122002-c27922e0ba67 +replace github.com/bwmarrin/discordgo => gitlab.com/beeper/discordgo v0.23.3-0.20220708140423-bd66a3680fbc diff --git a/go.sum b/go.sum index a1b24b4..943f977 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,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.20220708122002-c27922e0ba67 h1:FSxw+90bXpsAJZfH5oz49LL33TAk4L/0U7eJW+He4ys= -gitlab.com/beeper/discordgo v0.23.3-0.20220708122002-c27922e0ba67/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +gitlab.com/beeper/discordgo v0.23.3-0.20220708140423-bd66a3680fbc h1:alYFPNfqFSY4I7vHrdqvHZT1Aa1JRleNnhPoNtOgeu0= +gitlab.com/beeper/discordgo v0.23.3-0.20220708140423-bd66a3680fbc/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-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -59,5 +59,5 @@ maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= maunium.net/go/maulogger/v2 v2.3.2 h1:1XmIYmMd3PoQfp9J+PaHhpt80zpfmMqaShzUTC7FwY0= maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A= -maunium.net/go/mautrix v0.11.1-0.20220708121944-cda2329dd1df h1:MvSfTply7Vn+02RukSqW02REGy2qYzDWm7tH+0i7Akc= -maunium.net/go/mautrix v0.11.1-0.20220708121944-cda2329dd1df/go.mod h1:Lj4pBam5P0zIvieIFHnGsuaj+xfFtI3y/sC8yGlyna8= +maunium.net/go/mautrix v0.11.1-0.20220708134822-7534f9e547b0 h1:RSa+T5R0nNqz4aB3mPFWv8zwt40yfMkC7iTYbIX2G6Y= +maunium.net/go/mautrix v0.11.1-0.20220708134822-7534f9e547b0/go.mod h1:Lj4pBam5P0zIvieIFHnGsuaj+xfFtI3y/sC8yGlyna8= diff --git a/portal.go b/portal.go index edc055d..c1fbaa9 100644 --- a/portal.go +++ b/portal.go @@ -52,6 +52,9 @@ type Portal struct { discordMessages chan portalDiscordMessage matrixMessages chan portalMatrixMessage + + currentlyTyping []id.UserID + currentlyTypingLock sync.Mutex } func (portal *Portal) IsEncrypted() bool { @@ -547,8 +550,7 @@ func (portal *Portal) handleDiscordSticker(intent *appservice.IntentAPI, sticker case discordgo.StickerFormatTypeAPNG: mime = "image/apng" case discordgo.StickerFormatTypeLottie: - //mime = "application/json" - return nil + mime = "application/json" } content := &event.MessageEventContent{ Body: sticker.Name, // TODO find description from somewhere? @@ -1475,6 +1477,38 @@ func (portal *Portal) HandleMatrixReadReceipt(brUser bridge.User, eventID id.Eve } } +func typingDiff(prev, new []id.UserID) (started []id.UserID) { +OuterNew: + for _, userID := range new { + for _, previousUserID := range prev { + if userID == previousUserID { + continue OuterNew + } + } + started = append(started, userID) + } + return +} + +func (portal *Portal) HandleMatrixTyping(newTyping []id.UserID) { + portal.currentlyTypingLock.Lock() + defer portal.currentlyTypingLock.Unlock() + startedTyping := typingDiff(portal.currentlyTyping, newTyping) + portal.currentlyTyping = newTyping + for _, userID := range startedTyping { + user := portal.bridge.GetUserByMXID(userID) + if user != nil && user.Session != nil { + user.ViewingChannel(portal) + err := user.Session.ChannelTyping(portal.Key.ChannelID) + if err != nil { + portal.log.Warnfln("Failed to mark %s as typing: %v", user.MXID, err) + } else { + portal.log.Debugfln("Marked %s as typing", user.MXID) + } + } + } +} + func (portal *Portal) UpdateName(name string) bool { if portal.Name == name && portal.NameSet { return false diff --git a/user.go b/user.go index 643c277..ed5895e 100644 --- a/user.go +++ b/user.go @@ -47,6 +47,9 @@ type User struct { Session *discordgo.Session BridgeState *bridge.BridgeStateQueue + + markedOpened map[string]time.Time + markedOpenedLock sync.Mutex } func (user *User) GetRemoteID() string { @@ -181,9 +184,10 @@ func (br *DiscordBridge) NewUser(dbUser *database.User) *User { User: dbUser, bridge: br, log: br.Log.Sub("User").Sub(string(dbUser.MXID)), - } - user.PermissionLevel = br.Config.Bridge.Permissions.Get(user.MXID) + markedOpened: make(map[string]time.Time), + PermissionLevel: br.Config.Bridge.Permissions.Get(dbUser.MXID), + } user.BridgeState = br.NewBridgeStateQueue(user, user.log) return user } @@ -370,6 +374,25 @@ func (user *User) tryAutomaticDoublePuppeting() { user.log.Infoln("Successfully automatically enabled custom puppet") } +func (user *User) ViewingChannel(portal *Portal) bool { + if portal.GuildID != "" { + return false + } + user.markedOpenedLock.Lock() + defer user.markedOpenedLock.Unlock() + ts := user.markedOpened[portal.Key.ChannelID] + // TODO is there an expiry time? + if ts.IsZero() { + user.markedOpened[portal.Key.ChannelID] = time.Now() + err := user.Session.MarkViewing(portal.Key.ChannelID) + if err != nil { + user.log.Errorfln("Failed to mark user as viewing %s: %v", portal.Key.ChannelID, err) + } + return true + } + return false +} + func (user *User) syncChatDoublePuppetDetails(portal *Portal, justCreated bool) { doublePuppet := portal.bridge.GetPuppetByCustomMXID(user.MXID) if doublePuppet == nil { @@ -474,6 +497,7 @@ func (user *User) Connect() error { user.Session.AddHandler(user.reactionAddHandler) user.Session.AddHandler(user.reactionRemoveHandler) user.Session.AddHandler(user.messageAckHandler) + user.Session.AddHandler(user.typingStartHandler) user.Session.Identify.Presence.Status = "online" @@ -847,6 +871,18 @@ func (user *User) messageAckHandler(_ *discordgo.Session, m *discordgo.MessageAc } } +func (user *User) typingStartHandler(_ *discordgo.Session, t *discordgo.TypingStart) { + portal := user.GetExistingPortalByID(t.ChannelID) + if portal == nil || portal.MXID == "" { + return + } + puppet := user.bridge.GetPuppetByID(t.UserID) + _, err := puppet.IntentFor(portal).UserTyping(portal.MXID, true, 12*time.Second) + if err != nil { + user.log.Warnfln("Failed to mark %s as typing in %s: %v", puppet.MXID, portal.MXID, err) + } +} + func (user *User) ensureInvited(intent *appservice.IntentAPI, roomID id.RoomID, isDirect bool) bool { if intent == nil { intent = user.bridge.Bot