diff --git a/pkg/connector/client.go b/pkg/connector/client.go
index 67e699b9..1ab5499f 100644
--- a/pkg/connector/client.go
+++ b/pkg/connector/client.go
@@ -549,27 +549,6 @@ func (t *TelegramClient) Disconnect() {
t.userLogin.Log.Info().Msg("Disconnect complete")
}
-func (t *TelegramClient) getSingleChannel(ctx context.Context, id int64) (*tg.Channel, error) {
- accessHash, err := t.ScopedStore.GetAccessHash(ctx, ids.PeerTypeChannel, id)
- if err != nil {
- return nil, err
- }
- chats, err := APICallWithOnlyChatUpdates(ctx, t, func() (tg.MessagesChatsClass, error) {
- return t.client.API().ChannelsGetChannels(ctx, []tg.InputChannelClass{
- &tg.InputChannel{ChannelID: id, AccessHash: accessHash},
- })
- })
- if err != nil {
- return nil, err
- } else if len(chats.GetChats()) == 0 {
- return nil, fmt.Errorf("failed to get channel info for channel %d", id)
- } else if channel, ok := chats.GetChats()[0].(*tg.Channel); !ok {
- return nil, fmt.Errorf("unexpected channel type %T", chats.GetChats()[id])
- } else {
- return channel, nil
- }
-}
-
func (t *TelegramClient) IsLoggedIn() bool {
// TODO use less hacky check than context cancellation
return t != nil && t.client != nil &&
diff --git a/pkg/connector/tomatrix.go b/pkg/connector/tomatrix.go
index 7272a19a..501350cd 100644
--- a/pkg/connector/tomatrix.go
+++ b/pkg/connector/tomatrix.go
@@ -17,6 +17,7 @@
package connector
import (
+ "cmp"
"context"
"crypto/sha256"
"encoding/binary"
@@ -209,6 +210,12 @@ func (c *TelegramClient) convertToMatrix(
ContentHash: hasher.Sum(nil),
ContentURI: contentURI,
}
+ if fwd, isForwarded := msg.GetFwdFrom(); isForwarded {
+ err = c.addForwardHeader(ctx, cm.Parts[0], fwd)
+ if err != nil {
+ return nil, fmt.Errorf("failed to add forward header: %w", err)
+ }
+ }
if replyTo, ok := msg.GetReplyTo(); ok {
switch replyTo := replyTo.(type) {
@@ -236,6 +243,99 @@ func (c *TelegramClient) convertToMatrix(
return
}
+func (t *TelegramClient) addForwardHeader(ctx context.Context, part *bridgev2.ConvertedMessagePart, fwd tg.MessageFwdHeader) error {
+ var fwdFromText, fwdFromHTML string
+ switch from := fwd.FromID.(type) {
+ case *tg.PeerUser:
+ user := t.main.Bridge.GetCachedUserLoginByID(ids.MakeUserLoginID(from.UserID))
+ var mxid id.UserID
+ if user != nil {
+ mxid = user.UserMXID
+ fwdFromText = cmp.Or(user.RemoteName, user.UserMXID.String())
+ } else if ghost, err := t.main.Bridge.GetGhostByID(ctx, ids.MakeUserID(from.UserID)); err != nil {
+ return err
+ } else {
+ if ghost.Name == "" {
+ info, err := t.GetUserInfo(ctx, ghost)
+ if err != nil {
+ zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get user info to add forward header")
+ } else if info != nil {
+ ghost.UpdateInfo(ctx, info)
+ }
+ }
+ mxid = ghost.Intent.GetMXID()
+ fwdFromText = cmp.Or(ghost.Name, fwd.FromName, "unknown user")
+ }
+ fwdFromHTML = fmt.Sprintf(
+ `%s`,
+ mxid.URI().MatrixToURL(),
+ html.EscapeString(fwdFromText),
+ )
+ case *tg.PeerChannel, *tg.PeerChat:
+ unknownType := "unknown chat"
+ if _, ok := from.(*tg.PeerChannel); ok {
+ unknownType = "unknown channel"
+ }
+ portal, err := t.main.Bridge.GetExistingPortalByKey(ctx, t.makePortalKeyFromPeer(from, 0))
+ if err != nil {
+ return err
+ } else if portal != nil && portal.MXID != "" {
+ fwdFromText = cmp.Or(portal.Name, fwd.FromName, unknownType)
+ fwdFromHTML = fmt.Sprintf(
+ `%s`,
+ portal.MXID.URI().MatrixToURL(),
+ html.EscapeString(fwdFromText),
+ )
+ } else if fwd.FromName != "" {
+ fwdFromText = fwd.FromName
+ fwdFromHTML = fmt.Sprintf("%s", html.EscapeString(fwd.FromName))
+ } else {
+ fwdFromText = unknownType
+ fwdFromHTML = unknownType
+ }
+ // TODO fetch channel if not found
+ }
+ if fwdFromText == "" && fwd.FromName != "" {
+ fwdFromText = fwd.FromName
+ fwdFromHTML = fmt.Sprintf("%s", html.EscapeString(fwd.FromName))
+ }
+ if fwdFromText == "" {
+ fwdFromText = "unknown source"
+ fwdFromHTML = fwdFromText
+ }
+
+ if part.Content.MsgType.IsMedia() {
+ if part.Content.FileName == "" {
+ part.Content.FileName = part.Content.Body
+ }
+ if part.Content.Body == part.Content.FileName {
+ part.Content.Body = ""
+ }
+ }
+
+ part.Content.EnsureHasHTML()
+ existingBodyLines := strings.Split(part.Content.Body, "\n")
+ for i, line := range existingBodyLines {
+ existingBodyLines[i] = fmt.Sprintf("> %s", line)
+ }
+ if len(existingBodyLines) > 0 {
+ existingBodyLines = append([]string{"\n"}, existingBodyLines...)
+ }
+ part.Content.Body = fmt.Sprintf(
+ "Forwarded message from %s%s",
+ fwdFromText, strings.Join(existingBodyLines, "\n"),
+ )
+ existingFormattedBody := part.Content.FormattedBody
+ if existingFormattedBody != "" {
+ existingFormattedBody = fmt.Sprintf("
%s
", existingFormattedBody)
+ }
+ part.Content.FormattedBody = fmt.Sprintf(
+ "Forwarded message from %s%s",
+ fwdFromHTML, existingFormattedBody,
+ )
+ return nil
+}
+
func (t *TelegramClient) parseBodyAndHTML(ctx context.Context, message string, entities []tg.MessageEntityClass) *event.MessageEventContent {
if len(entities) == 0 {
return &event.MessageEventContent{MsgType: event.MsgText, Body: message}
diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go
index e99f40ed..6f5f9b53 100644
--- a/pkg/connector/userinfo.go
+++ b/pkg/connector/userinfo.go
@@ -40,33 +40,64 @@ func (t *TelegramClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost)
}
}
+func (t *TelegramClient) getChatPeerForInputFromMessage(ctx context.Context, id int64, peerID tg.PeerClass) (tg.InputPeerClass, error) {
+ switch typedChat := peerID.(type) {
+ case *tg.PeerUser:
+ if id == typedChat.UserID {
+ // We don't have the user's access hash
+ return nil, nil
+ }
+ accessHash, err := t.ScopedStore.GetAccessHash(ctx, ids.PeerTypeUser, typedChat.UserID)
+ if err != nil {
+ return nil, err
+ }
+ return &tg.InputPeerUser{UserID: typedChat.UserID, AccessHash: accessHash}, nil
+ case *tg.PeerChat:
+ return &tg.InputPeerChat{ChatID: typedChat.ChatID}, nil
+ case *tg.PeerChannel:
+ if id == typedChat.ChannelID {
+ // We don't have the channel's access hash
+ return nil, nil
+ }
+ accessHash, err := t.ScopedStore.GetAccessHash(ctx, ids.PeerTypeChannel, typedChat.ChannelID)
+ if err != nil {
+ return nil, err
+ }
+ return &tg.InputPeerChannel{ChannelID: typedChat.ChannelID, AccessHash: accessHash}, nil
+ default:
+ return nil, nil
+ }
+}
+
func (t *TelegramClient) getInputUserFromContext(ctx context.Context, id int64) (*tg.InputUserFromMessage, error) {
msg, ok := bridgev2.GetRemoteEventFromContext(ctx).(*simplevent.Message[*tg.Message])
if !ok {
return nil, nil
}
- fromUser, ok := msg.Data.FromID.(*tg.PeerUser)
- if !ok || fromUser.UserID != id {
- return nil, nil
- }
- var inputPeer tg.InputPeerClass
- switch typedChat := msg.Data.PeerID.(type) {
- case *tg.PeerUser:
- // We don't have the user's access hash
- return nil, nil
- case *tg.PeerChat:
- inputPeer = &tg.InputPeerChat{ChatID: typedChat.ChatID}
- case *tg.PeerChannel:
- accessHash, err := t.ScopedStore.GetAccessHash(ctx, ids.PeerTypeChannel, typedChat.ChannelID)
- if err != nil {
- return nil, err
- }
- inputPeer = &tg.InputPeerChannel{ChannelID: typedChat.ChannelID, AccessHash: accessHash}
+ inputPeer, err := t.getChatPeerForInputFromMessage(ctx, id, msg.Data.PeerID)
+ if err != nil || inputPeer == nil {
+ return nil, err
}
return &tg.InputUserFromMessage{
Peer: inputPeer,
MsgID: msg.Data.ID,
- UserID: fromUser.UserID,
+ UserID: id,
+ }, nil
+}
+
+func (t *TelegramClient) getInputChannelFromContext(ctx context.Context, id int64) (*tg.InputChannelFromMessage, error) {
+ msg, ok := bridgev2.GetRemoteEventFromContext(ctx).(*simplevent.Message[*tg.Message])
+ if !ok {
+ return nil, nil
+ }
+ inputPeer, err := t.getChatPeerForInputFromMessage(ctx, id, msg.Data.PeerID)
+ if err != nil || inputPeer == nil {
+ return nil, err
+ }
+ return &tg.InputChannelFromMessage{
+ Peer: inputPeer,
+ MsgID: msg.Data.ID,
+ ChannelID: id,
}, nil
}
@@ -112,6 +143,42 @@ func (t *TelegramClient) getSingleUser(ctx context.Context, id int64) (tg.UserCl
}
}
+func (t *TelegramClient) getInputChannel(ctx context.Context, id int64) (tg.InputChannelClass, error) {
+ accessHash, err := t.ScopedStore.GetAccessHash(ctx, ids.PeerTypeChannel, id)
+ if err != nil {
+ fromMsg, fromMsgErr := t.getInputChannelFromContext(ctx, id)
+ if fromMsgErr != nil {
+ return nil, fmt.Errorf("%w, also failed to get from message: %w", err, fromMsgErr)
+ } else if fromMsg == nil {
+ return nil, err
+ }
+ zerolog.Ctx(ctx).Trace().
+ Any("input_peer", fromMsg).
+ Msg("Using InputChannelFromMessage as access hash wasn't found")
+ return fromMsg, nil
+ }
+ return &tg.InputChannel{ChannelID: id, AccessHash: accessHash}, nil
+}
+
+func (t *TelegramClient) getSingleChannel(ctx context.Context, id int64) (*tg.Channel, error) {
+ inputChannel, err := t.getInputChannel(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+ chats, err := APICallWithOnlyChatUpdates(ctx, t, func() (tg.MessagesChatsClass, error) {
+ return t.client.API().ChannelsGetChannels(ctx, []tg.InputChannelClass{inputChannel})
+ })
+ if err != nil {
+ return nil, err
+ } else if len(chats.GetChats()) == 0 {
+ return nil, fmt.Errorf("failed to get channel info for channel %d", id)
+ } else if channel, ok := chats.GetChats()[0].(*tg.Channel); !ok {
+ return nil, fmt.Errorf("unexpected channel type %T", chats.GetChats()[id])
+ } else {
+ return channel, nil
+ }
+}
+
func (t *TelegramClient) wrapChannelGhostInfo(ctx context.Context, channel *tg.Channel) (*bridgev2.UserInfo, error) {
var err error
if accessHash, ok := channel.GetAccessHash(); ok && !channel.Min {