tomatrix: add forward headers

This commit is contained in:
Tulir Asokan
2026-03-17 11:56:17 +02:00
parent 326906644e
commit b695e0b4ea
3 changed files with 185 additions and 39 deletions
-21
View File
@@ -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 &&
+100
View File
@@ -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(
`<a href="%s">%s</a>`,
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(
`<a href="%s">%s</a>`,
portal.MXID.URI().MatrixToURL(),
html.EscapeString(fwdFromText),
)
} else if fwd.FromName != "" {
fwdFromText = fwd.FromName
fwdFromHTML = fmt.Sprintf("<strong>%s</strong>", 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("<strong>%s</strong>", 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("<br><tg-forward><blockquote>%s</blockquote></tg-forward>", 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}
+85 -18
View File
@@ -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 {