direct media: implement direct download for photos
Signed-off-by: Sumner Evans <sumner.evans@automattic.com>
This commit is contained in:
+16
-15
@@ -17,6 +17,7 @@ import (
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/msgconv"
|
||||
)
|
||||
|
||||
@@ -71,7 +72,7 @@ func NewTelegramClient(ctx context.Context, tc *TelegramConnector, login *bridge
|
||||
Logger: zaplog,
|
||||
UpdateHandler: updatesManager,
|
||||
})
|
||||
client.msgConv = msgconv.NewMessageConverter(client.client)
|
||||
client.msgConv = msgconv.NewMessageConverter(client.client, tc.useDirectMedia)
|
||||
client.clientCancel, err = connectTelegramClient(ctx, client.client)
|
||||
go func() {
|
||||
err = updatesManager.Run(ctx, client.client.API(), loginID, updates.AuthOptions{})
|
||||
@@ -127,21 +128,21 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, e tg.Entities,
|
||||
var sender bridgev2.EventSender
|
||||
if msg.Out {
|
||||
sender.IsFromMe = true
|
||||
sender.SenderLogin = makeUserLoginID(t.loginID)
|
||||
sender.Sender = makeUserID(t.loginID)
|
||||
sender.SenderLogin = ids.MakeUserLoginID(t.loginID)
|
||||
sender.Sender = ids.MakeUserID(t.loginID)
|
||||
} else if msg.FromID != nil {
|
||||
switch from := msg.FromID.(type) {
|
||||
case *tg.PeerUser:
|
||||
sender.SenderLogin = makeUserLoginID(from.UserID)
|
||||
sender.Sender = makeUserID(from.UserID)
|
||||
sender.SenderLogin = ids.MakeUserLoginID(from.UserID)
|
||||
sender.Sender = ids.MakeUserID(from.UserID)
|
||||
default:
|
||||
fmt.Printf("%+v\n", msg.FromID)
|
||||
fmt.Printf("%T\n", msg.FromID)
|
||||
panic("unimplemented FromID")
|
||||
}
|
||||
} else if peer, ok := msg.PeerID.(*tg.PeerUser); ok {
|
||||
sender.SenderLogin = makeUserLoginID(peer.UserID)
|
||||
sender.Sender = makeUserID(peer.UserID)
|
||||
sender.SenderLogin = ids.MakeUserLoginID(peer.UserID)
|
||||
sender.Sender = ids.MakeUserID(peer.UserID)
|
||||
} else {
|
||||
panic("not from anyone")
|
||||
}
|
||||
@@ -155,9 +156,9 @@ func (t *TelegramClient) onUpdateNewMessage(ctx context.Context, e tg.Entities,
|
||||
Str("sender_login", string(sender.SenderLogin)).
|
||||
Bool("is_from_me", sender.IsFromMe)
|
||||
},
|
||||
ID: makeMessageID(msg.ID),
|
||||
ID: ids.MakeMessageID(msg.ID),
|
||||
Sender: sender,
|
||||
PortalKey: makePortalID(msg.PeerID),
|
||||
PortalKey: ids.MakePortalID(msg.PeerID),
|
||||
Data: msg,
|
||||
CreatePortal: true,
|
||||
ConvertMessageFunc: t.msgConv.ToMatrix,
|
||||
@@ -186,7 +187,7 @@ func getFullName(user *tg.User) string {
|
||||
|
||||
func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.PortalInfo, error) {
|
||||
fmt.Printf("%+v\n", portal)
|
||||
peerType, id, err := parsePortalID(portal.ID)
|
||||
peerType, id, err := ids.ParsePortalID(portal.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -195,7 +196,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta
|
||||
var isSpace, isDM bool
|
||||
|
||||
switch peerType {
|
||||
case peerTypeUser:
|
||||
case ids.PeerTypeUser:
|
||||
users, err := t.client.API().UsersGetUsers(ctx, []tg.InputUserClass{&tg.InputUser{UserID: id}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -207,10 +208,10 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta
|
||||
return nil, fmt.Errorf("returned user is not *tg.User")
|
||||
} else {
|
||||
name = getFullName(user) // TODO gate this behind a config?
|
||||
members = []networkid.UserID{makeUserID(id), makeUserID(t.loginID)}
|
||||
members = []networkid.UserID{ids.MakeUserID(id), ids.MakeUserID(t.loginID)}
|
||||
isDM = true
|
||||
}
|
||||
case peerTypeChat:
|
||||
case ids.PeerTypeChat:
|
||||
// TODO get name of chat
|
||||
chat, err := t.client.API().MessagesGetFullChat(ctx, id)
|
||||
if err != nil {
|
||||
@@ -220,7 +221,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta
|
||||
return nil, fmt.Errorf("no users found in chat %d", id)
|
||||
}
|
||||
for _, user := range chat.Users {
|
||||
members = append(members, makeUserID(user.GetID()))
|
||||
members = append(members, ids.MakeUserID(user.GetID()))
|
||||
}
|
||||
default:
|
||||
fmt.Printf("%s %d\n", peerType, id)
|
||||
@@ -240,7 +241,7 @@ func (t *TelegramClient) GetChatInfo(ctx context.Context, portal *bridgev2.Porta
|
||||
}
|
||||
|
||||
func (t *TelegramClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.UserInfo, error) {
|
||||
id, err := parseUserID(ghost.ID)
|
||||
id, err := ids.ParseUserID(ghost.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ type TelegramConnector struct {
|
||||
Bridge *bridgev2.Bridge
|
||||
Config *TelegramConfig
|
||||
|
||||
store *store.Container
|
||||
store *store.Container
|
||||
useDirectMedia bool
|
||||
}
|
||||
|
||||
var _ bridgev2.NetworkConnector = (*TelegramConnector)(nil)
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package connector
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/gotd/td/tg"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
"maunium.net/go/mautrix/mediaproxy"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
|
||||
conmedia "go.mau.fi/mautrix-telegram/pkg/connector/media"
|
||||
)
|
||||
|
||||
var _ bridgev2.DirectMediableNetwork = (*TelegramConnector)(nil)
|
||||
|
||||
type getMessages interface {
|
||||
GetMessages() []tg.MessageClass
|
||||
}
|
||||
|
||||
func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.MediaID) (mediaproxy.GetMediaResponse, error) {
|
||||
info, err := ids.ParseDirectMediaInfo(mediaID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logins, err := tc.Bridge.GetUserLoginsInPortal(ctx, info.PeerType.AsPortalKey(info.ChatID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(logins) == 0 {
|
||||
return nil, fmt.Errorf("no user logins in the portal (%s %d)", info.PeerType, info.ChatID)
|
||||
}
|
||||
|
||||
client := logins[0].Client.(*TelegramClient)
|
||||
var messages tg.MessagesMessagesClass
|
||||
switch info.PeerType {
|
||||
case ids.PeerTypeUser, ids.PeerTypeChat:
|
||||
messages, err = client.client.API().MessagesGetMessages(ctx, []tg.InputMessageClass{
|
||||
&tg.InputMessageID{ID: int(info.MessageID)},
|
||||
})
|
||||
case ids.PeerTypeChannel:
|
||||
messages, err = client.client.API().ChannelsGetMessages(ctx, &tg.ChannelsGetMessagesRequest{
|
||||
Channel: &tg.InputChannel{ChannelID: info.ChatID},
|
||||
ID: []tg.InputMessageClass{
|
||||
&tg.InputMessageID{ID: int(info.MessageID)},
|
||||
},
|
||||
})
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown peer type %s", info.PeerType)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var media tg.MessageMediaClass
|
||||
if m, ok := messages.(getMessages); !ok {
|
||||
return nil, fmt.Errorf("unknown message type")
|
||||
} else {
|
||||
for _, message := range m.GetMessages() {
|
||||
if msg, ok := message.(*tg.Message); ok && msg.ID == int(info.MessageID) {
|
||||
media = msg.Media
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch media := media.(type) {
|
||||
case *tg.MessageMediaPhoto:
|
||||
data, mimeType, err := conmedia.DownloadPhoto(ctx, client.client.API(), media)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mediaproxy.GetMediaResponseData{
|
||||
Reader: io.NopCloser(bytes.NewBuffer(data)),
|
||||
ContentType: mimeType,
|
||||
ContentLength: int64(len(data)),
|
||||
}, nil
|
||||
|
||||
// TODO all of these
|
||||
// case *tg.MessageMediaGeo: // messageMediaGeo#56e0d474
|
||||
// case *tg.MessageMediaContact: // messageMediaContact#70322949
|
||||
// case *tg.MessageMediaUnsupported: // messageMediaUnsupported#9f84f49e
|
||||
// case *tg.MessageMediaDocument: // messageMediaDocument#4cf4d72d
|
||||
// case *tg.MessageMediaWebPage: // messageMediaWebPage#ddf10c3b
|
||||
// case *tg.MessageMediaVenue: // messageMediaVenue#2ec0533f
|
||||
// case *tg.MessageMediaGame: // messageMediaGame#fdb19008
|
||||
// case *tg.MessageMediaInvoice: // messageMediaInvoice#f6a548d3
|
||||
// case *tg.MessageMediaGeoLive: // messageMediaGeoLive#b940c666
|
||||
// case *tg.MessageMediaPoll: // messageMediaPoll#4bd6e798
|
||||
// case *tg.MessageMediaDice: // messageMediaDice#3f7ee58b
|
||||
// case *tg.MessageMediaStory: // messageMediaStory#68cb6283
|
||||
// case *tg.MessageMediaGiveaway: // messageMediaGiveaway#daad85b0
|
||||
// case *tg.MessageMediaGiveawayResults: // messageMediaGiveawayResults#c6991068
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled media type %T", media)
|
||||
}
|
||||
}
|
||||
|
||||
func (tg *TelegramConnector) SetUseDirectMedia() {
|
||||
tg.useDirectMedia = true
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package connector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gotd/td/tg"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
)
|
||||
|
||||
func makeUserID(userID int64) networkid.UserID {
|
||||
return networkid.UserID(strconv.FormatInt(userID, 10))
|
||||
}
|
||||
|
||||
func parseUserID(userID networkid.UserID) (int64, error) {
|
||||
return strconv.ParseInt(string(userID), 10, 64)
|
||||
}
|
||||
|
||||
func makeUserLoginID(userID int64) networkid.UserLoginID {
|
||||
return networkid.UserLoginID(strconv.FormatInt(userID, 10))
|
||||
}
|
||||
|
||||
func makeMessageID(messageID int) networkid.MessageID {
|
||||
return networkid.MessageID(strconv.Itoa(messageID))
|
||||
}
|
||||
|
||||
type peerType string
|
||||
|
||||
const (
|
||||
peerTypeUser peerType = "user"
|
||||
peerTypeChat peerType = "chat"
|
||||
peerTypeChannel peerType = "channel"
|
||||
)
|
||||
|
||||
func makePortalID(peer tg.PeerClass) networkid.PortalKey {
|
||||
switch v := peer.(type) {
|
||||
case *tg.PeerUser:
|
||||
return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", peerTypeUser, v.UserID))}
|
||||
case *tg.PeerChat:
|
||||
return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", peerTypeChat, v.ChatID))}
|
||||
case *tg.PeerChannel:
|
||||
return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", peerTypeChannel, v.ChannelID))}
|
||||
default:
|
||||
panic(fmt.Errorf("unknown peer class type %T", v))
|
||||
}
|
||||
}
|
||||
|
||||
func parsePortalID(portalID networkid.PortalID) (pt peerType, id int64, err error) {
|
||||
parts := strings.Split(string(portalID), ":")
|
||||
pt = peerType(parts[0])
|
||||
id, err = strconv.ParseInt(parts[1], 10, 64)
|
||||
return
|
||||
}
|
||||
|
||||
func inputPeerForPortalID(portalID networkid.PortalID) (tg.InputPeerClass, error) {
|
||||
peerType, id, err := parsePortalID(portalID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch peerType {
|
||||
case peerTypeUser:
|
||||
return &tg.InputPeerUser{UserID: id}, nil
|
||||
case peerTypeChat:
|
||||
return &tg.InputPeerChat{ChatID: id}, nil
|
||||
case peerTypeChannel:
|
||||
return &tg.InputPeerChannel{ChannelID: id}, nil
|
||||
default:
|
||||
panic("invalid peer type")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package ids
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gotd/td/tg"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
)
|
||||
|
||||
func MakeUserID(userID int64) networkid.UserID {
|
||||
return networkid.UserID(strconv.FormatInt(userID, 10))
|
||||
}
|
||||
|
||||
func ParseUserID(userID networkid.UserID) (int64, error) {
|
||||
return strconv.ParseInt(string(userID), 10, 64)
|
||||
}
|
||||
|
||||
func MakeUserLoginID(userID int64) networkid.UserLoginID {
|
||||
return networkid.UserLoginID(strconv.FormatInt(userID, 10))
|
||||
}
|
||||
|
||||
func MakeMessageID(messageID int) networkid.MessageID {
|
||||
return networkid.MessageID(strconv.Itoa(messageID))
|
||||
}
|
||||
|
||||
type PeerType string
|
||||
|
||||
const (
|
||||
PeerTypeUser PeerType = "user"
|
||||
PeerTypeChat PeerType = "chat"
|
||||
PeerTypeChannel PeerType = "channel"
|
||||
)
|
||||
|
||||
func PeerTypeFromByte(pt byte) (PeerType, error) {
|
||||
switch pt {
|
||||
case 0x01:
|
||||
return PeerTypeUser, nil
|
||||
case 0x02:
|
||||
return PeerTypeChat, nil
|
||||
case 0x03:
|
||||
return PeerTypeChannel, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown peer type %d", pt)
|
||||
}
|
||||
}
|
||||
|
||||
func (pt PeerType) AsByte() byte {
|
||||
switch pt {
|
||||
case PeerTypeUser:
|
||||
return 0x01
|
||||
case PeerTypeChat:
|
||||
return 0x02
|
||||
case PeerTypeChannel:
|
||||
return 0x03
|
||||
default:
|
||||
panic(fmt.Errorf("unknown peer type %s", pt))
|
||||
}
|
||||
}
|
||||
|
||||
func (pt PeerType) AsPortalKey(chatID int64) networkid.PortalKey {
|
||||
return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", pt, chatID))}
|
||||
}
|
||||
|
||||
func MakePortalID(peer tg.PeerClass) networkid.PortalKey {
|
||||
switch v := peer.(type) {
|
||||
case *tg.PeerUser:
|
||||
return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", PeerTypeUser, v.UserID))}
|
||||
case *tg.PeerChat:
|
||||
return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", PeerTypeChat, v.ChatID))}
|
||||
case *tg.PeerChannel:
|
||||
return networkid.PortalKey{ID: networkid.PortalID(fmt.Sprintf("%s:%d", PeerTypeChannel, v.ChannelID))}
|
||||
default:
|
||||
panic(fmt.Errorf("unknown peer class type %T", v))
|
||||
}
|
||||
}
|
||||
|
||||
func ParsePortalID(portalID networkid.PortalID) (pt PeerType, id int64, err error) {
|
||||
parts := strings.Split(string(portalID), ":")
|
||||
pt = PeerType(parts[0])
|
||||
id, err = strconv.ParseInt(parts[1], 10, 64)
|
||||
return
|
||||
}
|
||||
|
||||
func InputPeerForPortalID(portalID networkid.PortalID) (tg.InputPeerClass, error) {
|
||||
peerType, id, err := ParsePortalID(portalID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch peerType {
|
||||
case PeerTypeUser:
|
||||
return &tg.InputPeerUser{UserID: id}, nil
|
||||
case PeerTypeChat:
|
||||
return &tg.InputPeerChat{ChatID: id}, nil
|
||||
case PeerTypeChannel:
|
||||
return &tg.InputPeerChannel{ChannelID: id}, nil
|
||||
default:
|
||||
panic("invalid peer type")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package ids
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
)
|
||||
|
||||
// DirectMediaInfo is the information that is encoded in the media ID when
|
||||
// using direct media.
|
||||
//
|
||||
// The format of the media ID is as follows (each character represents a single
|
||||
// byte, |'s added for clarity):
|
||||
//
|
||||
// v|p|CCCCCCCC|TTTTTTTT
|
||||
//
|
||||
// v (int8) = binary encoding format version. Should be 0.
|
||||
// p (byte) = the peer type of the Telegram chat ID
|
||||
// CCCCCCCC (int64) = the Telegram chat ID (big endian)
|
||||
// TTTTTTTT (int64) = the Telegram message ID (big endian)
|
||||
type DirectMediaInfo struct {
|
||||
PeerType PeerType
|
||||
ChatID int64
|
||||
MessageID int64
|
||||
}
|
||||
|
||||
func (m DirectMediaInfo) AsMediaID() (networkid.MediaID, error) {
|
||||
mediaID := []byte{
|
||||
0x00, // Version
|
||||
m.PeerType.AsByte(), // Peer Type
|
||||
}
|
||||
mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.ChatID)) // Telegram Chat ID
|
||||
mediaID = binary.BigEndian.AppendUint64(mediaID, uint64(m.MessageID)) // Telegram Message ID
|
||||
return mediaID, nil
|
||||
}
|
||||
|
||||
func ParseDirectMediaInfo(mediaID networkid.MediaID) (info DirectMediaInfo, err error) {
|
||||
if len(mediaID) == 0 {
|
||||
err = fmt.Errorf("empty media ID")
|
||||
return
|
||||
}
|
||||
if mediaID[0] != 0x00 {
|
||||
err = fmt.Errorf("invalid version %d", mediaID[0])
|
||||
return
|
||||
}
|
||||
if len(mediaID) != 18 {
|
||||
err = fmt.Errorf("invalid media ID")
|
||||
return
|
||||
}
|
||||
fmt.Printf("%v\n", mediaID)
|
||||
info.PeerType, err = PeerTypeFromByte(mediaID[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info.ChatID = int64(binary.BigEndian.Uint64(mediaID[2:]))
|
||||
info.MessageID = int64(binary.BigEndian.Uint64(mediaID[10:]))
|
||||
return
|
||||
}
|
||||
@@ -30,6 +30,8 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/database"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
|
||||
)
|
||||
|
||||
const LoginFlowIDPhone = "phone"
|
||||
@@ -187,7 +189,7 @@ func (p *PhoneLogin) handleAuthSuccess(ctx context.Context, authorization *tg.Au
|
||||
}
|
||||
p.clientCancel()
|
||||
|
||||
userLoginID := makeUserLoginID(authorization.User.GetID())
|
||||
userLoginID := ids.MakeUserLoginID(authorization.User.GetID())
|
||||
ul, err := p.user.NewLogin(ctx, &database.UserLogin{
|
||||
ID: userLoginID,
|
||||
Metadata: database.UserLoginMetadata{
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gotd/td/telegram/downloader"
|
||||
"github.com/gotd/td/tg"
|
||||
)
|
||||
|
||||
func getLargestPhotoSize(sizes []tg.PhotoSizeClass) (largest tg.PhotoSizeClass) {
|
||||
var maxSize int
|
||||
for _, s := range sizes {
|
||||
var currentSize int
|
||||
switch size := s.(type) {
|
||||
case *tg.PhotoSize:
|
||||
currentSize = size.GetSize()
|
||||
case *tg.PhotoCachedSize:
|
||||
currentSize = max(size.GetW(), size.GetH())
|
||||
case *tg.PhotoSizeProgressive:
|
||||
currentSize = max(size.GetW(), size.GetH())
|
||||
case *tg.PhotoPathSize:
|
||||
currentSize = len(size.GetBytes())
|
||||
case *tg.PhotoStrippedSize:
|
||||
currentSize = len(size.GetBytes())
|
||||
}
|
||||
|
||||
if currentSize > maxSize {
|
||||
maxSize = currentSize
|
||||
largest = s
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func DownloadPhoto(ctx context.Context, client downloader.Client, media *tg.MessageMediaPhoto) (data []byte, mimeType string, err error) {
|
||||
p, ok := media.GetPhoto()
|
||||
if !ok {
|
||||
return nil, "", fmt.Errorf("photo message sent without a photo")
|
||||
}
|
||||
photo, ok := p.(*tg.Photo)
|
||||
if !ok {
|
||||
return nil, "", fmt.Errorf("unrecognized photo type %T", p)
|
||||
}
|
||||
|
||||
largest := getLargestPhotoSize(photo.GetSizes())
|
||||
file := tg.InputPhotoFileLocation{
|
||||
ID: photo.GetID(),
|
||||
AccessHash: photo.GetAccessHash(),
|
||||
FileReference: photo.GetFileReference(),
|
||||
ThumbSize: largest.GetType(),
|
||||
}
|
||||
|
||||
// TODO convert to streaming
|
||||
var buf bytes.Buffer
|
||||
storageFileTypeClass, err := downloader.NewDownloader().Download(client, &file).Stream(ctx, &buf)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
switch storageFileTypeClass.(type) {
|
||||
case *tg.StorageFileJpeg:
|
||||
mimeType = "image/jpeg"
|
||||
case *tg.StorageFileGif:
|
||||
mimeType = "image/gif"
|
||||
case *tg.StorageFilePng:
|
||||
mimeType = "image/png"
|
||||
case *tg.StorageFilePdf:
|
||||
mimeType = "application/pdf"
|
||||
case *tg.StorageFileMp3:
|
||||
mimeType = "audio/mp3"
|
||||
case *tg.StorageFileMov:
|
||||
mimeType = "video/quicktime"
|
||||
case *tg.StorageFileMp4:
|
||||
mimeType = "video/mp4"
|
||||
case *tg.StorageFileWebp:
|
||||
mimeType = "image/webp"
|
||||
default:
|
||||
mimeType = http.DetectContentType(buf.Bytes())
|
||||
}
|
||||
return buf.Bytes(), mimeType, nil
|
||||
}
|
||||
@@ -4,8 +4,10 @@ import "github.com/gotd/td/telegram"
|
||||
|
||||
type MessageConverter struct {
|
||||
client *telegram.Client
|
||||
|
||||
useDirectMedia bool
|
||||
}
|
||||
|
||||
func NewMessageConverter(client *telegram.Client) *MessageConverter {
|
||||
return &MessageConverter{client: client}
|
||||
func NewMessageConverter(client *telegram.Client, useDirectMedia bool) *MessageConverter {
|
||||
return &MessageConverter{client: client, useDirectMedia: useDirectMedia}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package msgconv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gotd/td/telegram/downloader"
|
||||
"github.com/gotd/td/tg"
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/exmime"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
|
||||
conmedia "go.mau.fi/mautrix-telegram/pkg/connector/media"
|
||||
)
|
||||
|
||||
func (mc *MessageConverter) ToMatrix(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, msg *tg.Message) (*bridgev2.ConvertedMessage, error) {
|
||||
@@ -27,64 +28,50 @@ func (mc *MessageConverter) ToMatrix(ctx context.Context, portal *bridgev2.Porta
|
||||
Content: &event.MessageEventContent{MsgType: event.MsgText, Body: msg.Message},
|
||||
})
|
||||
}
|
||||
|
||||
if m, ok := msg.GetMedia(); ok {
|
||||
switch media := m.(type) {
|
||||
case *tg.MessageMediaPhoto:
|
||||
p, ok := media.GetPhoto()
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("photo message sent without a photo")
|
||||
}
|
||||
photo, ok := p.(*tg.Photo)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unrecognized photo type %T", p)
|
||||
}
|
||||
|
||||
largest := getLargestPhotoSize(photo.GetSizes())
|
||||
file := tg.InputPhotoFileLocation{
|
||||
ID: photo.GetID(),
|
||||
AccessHash: photo.GetAccessHash(),
|
||||
FileReference: photo.GetFileReference(),
|
||||
ThumbSize: largest.GetType(),
|
||||
}
|
||||
|
||||
// TODO convert to streaming directly into UploadMedia
|
||||
var buf bytes.Buffer
|
||||
storageFileTypeClass, err := downloader.NewDownloader().Download(mc.client.API(), &file).Stream(ctx, &buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var mimeType string
|
||||
switch storageFileTypeClass.(type) {
|
||||
case *tg.StorageFileJpeg:
|
||||
mimeType = "image/jpeg"
|
||||
case *tg.StorageFileGif:
|
||||
mimeType = "image/gif"
|
||||
case *tg.StorageFilePng:
|
||||
mimeType = "image/png"
|
||||
case *tg.StorageFilePdf:
|
||||
mimeType = "application/pdf"
|
||||
case *tg.StorageFileMp3:
|
||||
mimeType = "audio/mp3"
|
||||
case *tg.StorageFileMov:
|
||||
mimeType = "video/quicktime"
|
||||
case *tg.StorageFileMp4:
|
||||
mimeType = "video/mp4"
|
||||
case *tg.StorageFileWebp:
|
||||
mimeType = "image/webp"
|
||||
default:
|
||||
mimeType = http.DetectContentType(buf.Bytes())
|
||||
}
|
||||
var filename string
|
||||
if _, ok := media.GetTTLSeconds(); ok {
|
||||
// TODO set the ttl on the converted message
|
||||
filename = "disappearing_image" + exmime.ExtensionFromMimetype(mimeType)
|
||||
} else {
|
||||
filename = "image" + exmime.ExtensionFromMimetype(mimeType)
|
||||
var mxcURI id.ContentURIString
|
||||
var encryptedFileInfo *event.EncryptedFileInfo
|
||||
if mc.useDirectMedia {
|
||||
var err error
|
||||
filename = "image"
|
||||
peerType, chatID, err := ids.ParsePortalID(portal.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mediaID, err := ids.DirectMediaInfo{
|
||||
PeerType: peerType,
|
||||
ChatID: chatID,
|
||||
MessageID: int64(msg.ID),
|
||||
}.AsMediaID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mxcURI, err = portal.Bridge.Matrix.GenerateContentURI(ctx, mediaID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mxcURI, encryptedFileInfo, err := intent.UploadMedia(ctx, "", buf.Bytes(), filename, mimeType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if mxcURI == "" {
|
||||
data, mimeType, err := conmedia.DownloadPhoto(ctx, mc.client.API(), media)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := media.GetTTLSeconds(); ok {
|
||||
// TODO set the ttl on the converted message
|
||||
filename = "disappearing_image" + exmime.ExtensionFromMimetype(mimeType)
|
||||
} else {
|
||||
filename = "image" + exmime.ExtensionFromMimetype(mimeType)
|
||||
}
|
||||
|
||||
mxcURI, encryptedFileInfo, err = intent.UploadMedia(ctx, "", data, filename, mimeType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
extra := map[string]any{}
|
||||
@@ -128,28 +115,3 @@ func (mc *MessageConverter) ToMatrix(ctx context.Context, portal *bridgev2.Porta
|
||||
}
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
func getLargestPhotoSize(sizes []tg.PhotoSizeClass) (largest tg.PhotoSizeClass) {
|
||||
var maxSize int
|
||||
for _, s := range sizes {
|
||||
var currentSize int
|
||||
switch size := s.(type) {
|
||||
case *tg.PhotoSize:
|
||||
currentSize = size.GetSize()
|
||||
case *tg.PhotoCachedSize:
|
||||
currentSize = max(size.GetW(), size.GetH())
|
||||
case *tg.PhotoSizeProgressive:
|
||||
currentSize = max(size.GetW(), size.GetH())
|
||||
case *tg.PhotoPathSize:
|
||||
currentSize = len(size.GetBytes())
|
||||
case *tg.PhotoStrippedSize:
|
||||
currentSize = len(size.GetBytes())
|
||||
}
|
||||
|
||||
if currentSize > maxSize {
|
||||
maxSize = currentSize
|
||||
largest = s
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user