Add basic typing notification bridging

This commit is contained in:
Tulir Asokan 2022-07-08 17:06:02 +03:00
parent 2611cbfa34
commit 1ecf096aec
5 changed files with 82 additions and 12 deletions

View file

@ -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

4
go.mod
View file

@ -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

8
go.sum
View file

@ -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=

View file

@ -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

40
user.go
View file

@ -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