Преглед на файлове

Add basic typing notification bridging

Tulir Asokan преди 3 години
родител
ревизия
1ecf096aec
променени са 5 файла, в които са добавени 82 реда и са изтрити 12 реда
  1. 2 2
      ROADMAP.md
  2. 2 2
      go.mod
  3. 4 4
      go.sum
  4. 36 2
      portal.go
  5. 38 2
      user.go

+ 2 - 2
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

+ 2 - 2
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

+ 4 - 4
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=

+ 36 - 2
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

+ 38 - 2
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