reactions: support custom emojis

Signed-off-by: Sumner Evans <sumner.evans@automattic.com>
This commit is contained in:
Sumner Evans
2024-06-28 10:41:28 -06:00
parent 33dc5bad03
commit a63f264804
16 changed files with 244 additions and 228 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ require (
github.com/gotd/td v0.102.0 github.com/gotd/td v0.102.0
github.com/rs/zerolog v1.33.0 github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
go.mau.fi/util v0.5.1-0.20240702170310-bd1da3c069eb go.mau.fi/util v0.5.1-0.20240708233020-c2f9af6fecf8
go.mau.fi/zerozap v0.1.1 go.mau.fi/zerozap v0.1.1
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
maunium.net/go/mautrix v0.19.0-beta.1.0.20240706124659-b4057a26c3ed maunium.net/go/mautrix v0.19.0-beta.1.0.20240706124659-b4057a26c3ed
+2 -2
View File
@@ -67,8 +67,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
go.mau.fi/util v0.5.1-0.20240702170310-bd1da3c069eb h1:VZPo2pvfjNj6fkFv5e9FyTYx96BLwwYNA19WYaY+KN8= go.mau.fi/util v0.5.1-0.20240708233020-c2f9af6fecf8 h1:7ntkhSR0G/dIwAPjcoOoJz+bPne0gA4PypEn8euMMx0=
go.mau.fi/util v0.5.1-0.20240702170310-bd1da3c069eb/go.mod h1:DsJzUrJAG53lCZnnYvq9/mOyLuPScWwYhvETiTrpdP4= go.mau.fi/util v0.5.1-0.20240708233020-c2f9af6fecf8/go.mod h1:DsJzUrJAG53lCZnnYvq9/mOyLuPScWwYhvETiTrpdP4=
go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto= go.mau.fi/zeroconfig v0.1.2 h1:DKOydWnhPMn65GbXZOafgkPm11BvFashZWLct0dGFto=
go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= go.mau.fi/zeroconfig v0.1.2/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
go.mau.fi/zerozap v0.1.1 h1:mxE/dW4wtkqBYOXOEEzXldk5qKB+ahsZXjoTGnvEhZQ= go.mau.fi/zerozap v0.1.1 h1:mxE/dW4wtkqBYOXOEEzXldk5qKB+ahsZXjoTGnvEhZQ=
+1 -1
View File
@@ -119,7 +119,7 @@ func NewTelegramClient(ctx context.Context, tc *TelegramConnector, login *bridge
Logger: zaplog, Logger: zaplog,
UpdateHandler: updatesManager, UpdateHandler: updatesManager,
}) })
client.msgConv = msgconv.NewMessageConverter(client.client, tc.Bridge.Matrix, tc.useDirectMedia) client.msgConv = msgconv.NewMessageConverter(client.client, tc.Bridge.Matrix, tc.Store, tc.Config.AnimatedSticker, tc.useDirectMedia)
client.clientCancel, err = connectTelegramClient(ctx, client.client) client.clientCancel, err = connectTelegramClient(ctx, client.client)
client.reactionMessageLocks = map[int]*sync.Mutex{} client.reactionMessageLocks = map[int]*sync.Mutex{}
go func() { go func() {
+47
View File
@@ -0,0 +1,47 @@
package connector
import (
_ "embed"
"fmt"
up "go.mau.fi/util/configupgrade"
"maunium.net/go/mautrix/bridgev2"
"go.mau.fi/mautrix-telegram/pkg/connector/media"
)
var _ bridgev2.ConfigValidatingNetwork = (*TelegramConnector)(nil)
type TelegramConfig struct {
AppID int `yaml:"app_id"`
AppHash string `yaml:"app_hash"`
AnimatedSticker media.AnimatedStickerConfig `yaml:"animated_sticker"`
}
//go:embed example-config.yaml
var ExampleConfig string
func upgradeConfig(helper up.Helper) {
helper.Copy(up.Int, "app_id")
helper.Copy(up.Str, "app_hash")
helper.Copy(up.Str, "animated_sticker.target")
helper.Copy(up.Bool, "animated_sticker.convert_from_webm")
helper.Copy(up.Int, "animated_sticker.args.width")
helper.Copy(up.Int, "animated_sticker.args.height")
helper.Copy(up.Int, "animated_sticker.args.fps")
}
func (tg *TelegramConnector) GetConfig() (example string, data any, upgrader up.Upgrader) {
return ExampleConfig, tg.Config, up.SimpleUpgrader(upgradeConfig)
}
func (tg *TelegramConnector) ValidateConfig() error {
if tg.Config.AppID == 0 {
return fmt.Errorf("app_id is required")
}
if tg.Config.AppHash == "" {
return fmt.Errorf("app_hash is required")
}
return nil
}
-32
View File
@@ -18,21 +18,13 @@ package connector
import ( import (
"context" "context"
_ "embed"
"fmt"
up "go.mau.fi/util/configupgrade"
"go.mau.fi/util/dbutil" "go.mau.fi/util/dbutil"
"maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2"
"go.mau.fi/mautrix-telegram/pkg/connector/store" "go.mau.fi/mautrix-telegram/pkg/connector/store"
) )
type TelegramConfig struct {
AppID int `yaml:"app_id"`
AppHash string `yaml:"app_hash"`
}
type TelegramConnector struct { type TelegramConnector struct {
Bridge *bridgev2.Bridge Bridge *bridgev2.Bridge
Config *TelegramConfig Config *TelegramConfig
@@ -42,7 +34,6 @@ type TelegramConnector struct {
} }
var _ bridgev2.NetworkConnector = (*TelegramConnector)(nil) var _ bridgev2.NetworkConnector = (*TelegramConnector)(nil)
var _ bridgev2.ConfigValidatingNetwork = (*TelegramConnector)(nil)
// var _ bridgev2.MaxFileSizeingNetwork = (*TelegramConnector)(nil) // var _ bridgev2.MaxFileSizeingNetwork = (*TelegramConnector)(nil)
@@ -53,7 +44,6 @@ func NewConnector() *TelegramConnector {
} }
func (tg *TelegramConnector) Init(bridge *bridgev2.Bridge) { func (tg *TelegramConnector) Init(bridge *bridgev2.Bridge) {
// TODO
tg.Store = store.NewStore(bridge.DB.Database, dbutil.ZeroLogger(bridge.Log.With().Str("db_section", "telegram").Logger())) tg.Store = store.NewStore(bridge.DB.Database, dbutil.ZeroLogger(bridge.Log.With().Str("db_section", "telegram").Logger()))
tg.Bridge = bridge tg.Bridge = bridge
} }
@@ -67,28 +57,6 @@ func (tc *TelegramConnector) LoadUserLogin(ctx context.Context, login *bridgev2.
return return
} }
//go:embed example-config.yaml
var ExampleConfig string
func upgradeConfig(helper up.Helper) {
helper.Copy(up.Int, "app_id")
helper.Copy(up.Str, "app_hash")
}
func (tg *TelegramConnector) GetConfig() (example string, data any, upgrader up.Upgrader) {
return ExampleConfig, tg.Config, up.SimpleUpgrader(upgradeConfig)
}
func (tg *TelegramConnector) ValidateConfig() error {
if tg.Config.AppID == 0 {
return fmt.Errorf("app_id is required")
}
if tg.Config.AppHash == "" {
return fmt.Errorf("app_hash is required")
}
return nil
}
// TODO // TODO
// func (tg *TelegramConnector) SetMaxFileSize(maxSize int64) { // func (tg *TelegramConnector) SetMaxFileSize(maxSize int64) {
// } // }
+1 -1
View File
@@ -91,8 +91,8 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med
return nil, fmt.Errorf("unrecognized document type %T", msgMedia.Document) return nil, fmt.Errorf("unrecognized document type %T", msgMedia.Document)
} }
// Download the thumbnail for this media rather than the media itself.
if info.Thumbnail { if info.Thumbnail {
// Download the thumbnail for this media rather than the media itself.
_, _, largestThumbnail := media.GetLargestPhotoSize(document.Thumbs) _, _, largestThumbnail := media.GetLargestPhotoSize(document.Thumbs)
data, mimeType, err = media.DownloadFileLocation(ctx, client.client.API(), &tg.InputDocumentFileLocation{ data, mimeType, err = media.DownloadFileLocation(ctx, client.client.API(), &tg.InputDocumentFileLocation{
ID: document.GetID(), ID: document.GetID(),
+1 -2
View File
@@ -31,8 +31,7 @@ func ConvertKnownEmojis(emojiIDs []int64) (result map[networkid.EmojiID]string,
result = map[networkid.EmojiID]string{} result = map[networkid.EmojiID]string{}
for _, e := range emojiIDs { for _, e := range emojiIDs {
if v, ok := reverseUnicodemojiPack[e]; ok { if v, ok := reverseUnicodemojiPack[e]; ok {
emojiID := ids.MakeEmojiIDFromDocumentID(e) result[ids.MakeEmojiIDFromDocumentID(e)] = v
result[emojiID] = v
} else { } else {
remaining = append(remaining, e) remaining = append(remaining, e)
} }
+15 -68
View File
@@ -1,72 +1,19 @@
# Get your own API keys at https://my.telegram.org/apps # Get your own API keys at https://my.telegram.org/apps
app_id: 12345 app_id: 12345
app_hash: tjyd5yge35lbodk1xwzw2jstp90k55qz app_hash: tjyd5yge35lbodk1xwzw2jstp90k55qz
# (Optional) Create your own bot at https://t.me/BotFather
bot_token: disabled
# Should the bridge request missed updates from Telegram when restarting? animated_sticker:
catch_up: true # Format to which animated stickers should be converted.
# Should incoming updates be handled sequentially to make sure order is preserved on Matrix? # disable - No conversion, send as-is (gzipped lottie)
sequential_updates: true # png - converts to non-animated png (fastest),
exit_on_update_error: false # gif - converts to animated gif
# webm - converts to webm video, requires ffmpeg executable with vp9 codec and webm container support
# Telethon connection options. # webp - converts to animated webp, requires ffmpeg executable with webp codec/container support
connection: target: gif
# The timeout in seconds to be used when connecting. # Should video stickers be converted to the specified format as well?
timeout: 120 convert_from_webm: false
# How many times the reconnection should retry, either on the initial connection or when # Arguments for converter. All converters take width and height.
# Telegram disconnects us. May be set to a negative or null value for infinite retries, but args:
# this is not recommended, since the program can get stuck in an infinite loop. width: 256
retries: 5 height: 256
# The delay in seconds to sleep between automatic reconnections. fps: 25 # only for webm, webp and gif (2, 5, 10, 20 or 25 recommended)
retry_delay: 1
# The threshold below which the library should automatically sleep on flood wait errors
# (inclusive). For instance, if a FloodWaitError for 17s occurs and flood_sleep_threshold
# is 20s, the library will sleep automatically. If the error was for 21s, it would raise
# the error instead. Values larger than a day (86400) will be changed to a day.
flood_sleep_threshold: 60
# How many times a request should be retried. Request are retried when Telegram is having
# internal issues, when there is a FloodWaitError less than flood_sleep_threshold, or when
# there's a migrate error. May take a negative or null value for infinite retries, but this
# is not recommended, since some requests can always trigger a call fail (such as searching
# for messages).
request_retries: 5
# Use IPv6 for Telethon connection
use_ipv6: false
# Device info sent to Telegram.
device_info:
# "auto" = OS name+version.
device_model: mautrix-telegram
# "auto" = Telethon version.
system_version: auto
# "auto" = mautrix-telegram version.
app_version: auto
lang_code: en
system_lang_code: en
# Custom server to connect to.
server:
# Set to true to use these server settings. If false, will automatically
# use production server assigned by Telegram. Set to false in production.
enabled: false
# The DC ID to connect to.
dc: 2
# The IP to connect to.
ip: 149.154.167.40
# The port to connect to. 443 may not work, 80 is better and both are equally secure.
port: 80
# Telethon proxy configuration.
# You must install PySocks from pip for proxies to work.
proxy:
# Allowed types: disabled, socks4, socks5, http, mtproxy
type: disabled
# Proxy IP address and port.
address: 127.0.0.1
port: 1080
# Whether or not to perform DNS resolving remotely. Only for socks/http proxies.
rdns: true
# Proxy authentication (optional). Put MTProxy secret in password field.
username: ""
password: ""
+2
View File
@@ -193,6 +193,8 @@ func (t *TelegramClient) PreHandleMatrixReaction(ctx context.Context, msg *bridg
if strings.HasPrefix(msg.Content.RelatesTo.Key, "mxc://") { if strings.HasPrefix(msg.Content.RelatesTo.Key, "mxc://") {
if file, err := t.main.Store.TelegramFile.GetByMXC(ctx, msg.Content.RelatesTo.Key); err != nil { if file, err := t.main.Store.TelegramFile.GetByMXC(ctx, msg.Content.RelatesTo.Key); err != nil {
return resp, err return resp, err
} else if file == nil {
return resp, fmt.Errorf("reaction MXC URI %s does not correspond with any known Telegram files", msg.Content.RelatesTo.Key)
} else if documentID, err := strconv.ParseInt(string(file.LocationID), 10, 64); err != nil { } else if documentID, err := strconv.ParseInt(string(file.LocationID), 10, 64); err != nil {
return resp, err return resp, err
} else { } else {
+3 -3
View File
@@ -9,10 +9,10 @@ import (
"github.com/gotd/td/tg" "github.com/gotd/td/tg"
) )
func DownloadFileLocation(ctx context.Context, client downloader.Client, file tg.InputFileLocationClass) (data []byte, mimeType string, err error) { func DownloadFileLocation(ctx context.Context, client downloader.Client, loc tg.InputFileLocationClass) (data []byte, mimeType string, err error) {
// TODO convert to streaming? stream to file? // TODO convert entire function to streaming? Maybe at least stream to file?
var buf bytes.Buffer var buf bytes.Buffer
storageFileTypeClass, err := downloader.NewDownloader().Download(client, file).Stream(ctx, &buf) storageFileTypeClass, err := downloader.NewDownloader().Download(client, loc).Stream(ctx, &buf)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
+102 -20
View File
@@ -9,37 +9,119 @@ import (
"maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"go.mau.fi/util/lottie"
"go.mau.fi/mautrix-telegram/pkg/connector/store"
) )
// LocationToID converts a Telegram [tg.Document], // getLocationID converts a Telegram [tg.Document],
// [tg.InputDocumentFileLocation], [tg.InputPeerPhotoFileLocation], // [tg.InputDocumentFileLocation], [tg.InputPeerPhotoFileLocation],
// [tg.InputFileLocation], or [tg.InputPhotoFileLocation] into a key for use in // [tg.InputFileLocation], or [tg.InputPhotoFileLocation] into a [LocationID]
// the telegram_file table. // for use in the telegram_file table.
func LocationToID(location any) (id string) { func getLocationID(loc any) (locID store.TelegramFileLocationID) {
switch location := location.(type) { var id string
switch location := loc.(type) {
case *tg.Document: case *tg.Document:
return fmt.Sprintf("%d", location.ID) id = fmt.Sprintf("%d", location.ID)
case *tg.InputDocumentFileLocation: case *tg.InputDocumentFileLocation:
return fmt.Sprintf("%d-%s", location.ID, location.ThumbSize) id = fmt.Sprintf("%d-%s", location.ID, location.ThumbSize)
case *tg.InputPhotoFileLocation: case *tg.InputPhotoFileLocation:
return fmt.Sprintf("%d-%s", location.ID, location.ThumbSize) id = fmt.Sprintf("%d-%s", location.ID, location.ThumbSize)
case *tg.InputFileLocation: case *tg.InputFileLocation:
return fmt.Sprintf("%d-%d", location.VolumeID, location.LocalID) id = fmt.Sprintf("%d-%d", location.VolumeID, location.LocalID)
case *tg.InputPeerPhotoFileLocation: case *tg.InputPeerPhotoFileLocation:
return fmt.Sprintf("%d", location.PhotoID) id = fmt.Sprintf("%d", location.PhotoID)
default: default:
panic(fmt.Errorf("unknown location type %T", location)) panic(fmt.Errorf("unknown location type %T", location))
} }
return store.TelegramFileLocationID(id)
} }
func TransferToMatrix(ctx context.Context, roomID id.RoomID, client downloader.Client, intent bridgev2.MatrixAPI, file tg.InputFileLocationClass, filenameOpt ...string) (id.ContentURIString, *event.EncryptedFileInfo, error) { type AnimatedStickerConfig struct {
data, mimeType, err := DownloadFileLocation(ctx, client, file) Target string `yaml:"target"`
if err != nil { ConvertFromWebm bool `yaml:"convert_from_webm"`
return "", nil, fmt.Errorf("downloading file failed: %w", err) Args struct {
} Width int `yaml:"width"`
var filename string Height int `yaml:"height"`
if len(filenameOpt) > 0 { FPS int `yaml:"fps"`
filename = filenameOpt[0] } `yaml:"args"`
} }
return intent.UploadMedia(ctx, roomID, data, filename, mimeType)
func (c AnimatedStickerConfig) TGSConvert() bool {
return c.Target == "gif" || c.Target == "png"
}
func (c AnimatedStickerConfig) WebmConvert() bool {
return c.ConvertFromWebm && c.Target != "webm"
}
type Transferer struct {
RoomID id.RoomID
Filename string
IsSticker bool
Config AnimatedStickerConfig
}
func NewTransferer(cfg AnimatedStickerConfig) *Transferer {
return &Transferer{Config: cfg}
}
func (t *Transferer) WithRoomID(roomID id.RoomID) *Transferer {
t.RoomID = roomID
return t
}
func (t *Transferer) WithFilename(filename string) *Transferer {
t.Filename = filename
return t
}
func (t *Transferer) WithIsSticker(isSticker bool) *Transferer {
t.IsSticker = isSticker
return t
}
func (t *Transferer) Transfer(ctx context.Context, store *store.Container, client downloader.Client, intent bridgev2.MatrixAPI, loc tg.InputFileLocationClass) (mxc id.ContentURIString, encryptedFileInfo *event.EncryptedFileInfo, size int, mimeType string, err error) {
locationID := getLocationID(loc)
if file, err := store.TelegramFile.GetByLocationID(ctx, locationID); err != nil {
return "", nil, 0, "", fmt.Errorf("failed to search for Telegram file by location ID: %w", err)
} else if file != nil {
return file.MXC, nil, file.Size, file.MIMEType, nil
}
var data []byte
data, mimeType, err = DownloadFileLocation(ctx, client, loc)
if err != nil {
return "", nil, 0, "", fmt.Errorf("downloading file failed: %w", err)
}
if t.IsSticker {
if lottie.Supported() && t.Config.TGSConvert() && mimeType == "application/x-gzip" {
data, err = lottie.ConvertBytes(ctx, data, t.Config.Target, t.Config.Args.Width, t.Config.Args.Height, fmt.Sprintf("%d", t.Config.Args.FPS))
if err != nil {
return "", nil, 0, "", err
}
mimeType = fmt.Sprintf("image/%s", t.Config.Target)
// TODO support ffmpeg conversion
// } else if ffmpeg.Supported() && t.Config.WebmConvert() && mimeType == "video/webm" {
}
}
mxcURI, encryptedFileInfo, err := intent.UploadMedia(ctx, t.RoomID, data, t.Filename, mimeType)
if err != nil {
return "", nil, 0, "", err
}
if len(mxcURI) > 0 {
file := store.TelegramFile.New()
file.LocationID = locationID
file.MXC = mxcURI
file.Size = len(data)
file.MIMEType = mimeType
// TODO width, height, thumbnail?
if err = file.Insert(ctx); err != nil {
return "", nil, 0, "", fmt.Errorf("failed to insert Telegram file into database: %w", err)
}
}
return mxcURI, encryptedFileInfo, len(data), mimeType, nil
} }
+15 -4
View File
@@ -3,15 +3,26 @@ package msgconv
import ( import (
"github.com/gotd/td/telegram" "github.com/gotd/td/telegram"
"maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2"
"go.mau.fi/mautrix-telegram/pkg/connector/media"
"go.mau.fi/mautrix-telegram/pkg/connector/store"
) )
type MessageConverter struct { type MessageConverter struct {
client *telegram.Client client *telegram.Client
connector bridgev2.MatrixConnector connector bridgev2.MatrixConnector
store *store.Container
animatedStickerConfig media.AnimatedStickerConfig
useDirectMedia bool useDirectMedia bool
} }
func NewMessageConverter(client *telegram.Client, connector bridgev2.MatrixConnector, useDirectMedia bool) *MessageConverter { func NewMessageConverter(client *telegram.Client, connector bridgev2.MatrixConnector, store *store.Container, animatedStickerConfig media.AnimatedStickerConfig, useDirectMedia bool) *MessageConverter {
return &MessageConverter{client: client, connector: connector, useDirectMedia: useDirectMedia} return &MessageConverter{
client: client,
connector: connector,
store: store,
animatedStickerConfig: animatedStickerConfig,
useDirectMedia: useDirectMedia,
}
} }
+15 -14
View File
@@ -208,12 +208,14 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por
} }
if info.ThumbnailInfo.ThumbnailURL == "" { if info.ThumbnailInfo.ThumbnailURL == "" {
info.ThumbnailInfo.ThumbnailURL, info.ThumbnailInfo.ThumbnailFile, err = media.TransferToMatrix(ctx, portal.MXID, mc.client.API(), intent, &tg.InputDocumentFileLocation{ info.ThumbnailInfo.ThumbnailURL, info.ThumbnailInfo.ThumbnailFile, info.ThumbnailInfo.Size, info.ThumbnailInfo.MimeType, err = media.NewTransferer(mc.animatedStickerConfig).
ID: document.GetID(), WithRoomID(portal.MXID).
AccessHash: document.GetAccessHash(), Transfer(ctx, mc.store, mc.client.API(), intent, &tg.InputDocumentFileLocation{
FileReference: document.GetFileReference(), ID: document.GetID(),
ThumbSize: largestThumbnail.GetType(), AccessHash: document.GetAccessHash(),
}) FileReference: document.GetFileReference(),
ThumbSize: largestThumbnail.GetType(),
})
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -256,24 +258,23 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por
if mxcURI == "" { if mxcURI == "" {
var data []byte var data []byte
var mimeType string
var err error
switch msgMedia := msgMedia.(type) { switch msgMedia := msgMedia.(type) {
case *tg.MessageMediaPhoto: case *tg.MessageMediaPhoto:
// TODO convert to Transfer
data, _, _, info.MimeType, err = media.DownloadPhotoMedia(ctx, mc.client.API(), msgMedia)
if _, ok := msgMedia.GetTTLSeconds(); ok { if _, ok := msgMedia.GetTTLSeconds(); ok {
filename = "disappearing_image" + exmime.ExtensionFromMimetype(mimeType) filename = "disappearing_image" + exmime.ExtensionFromMimetype(info.MimeType)
} else { } else {
filename = "image" + exmime.ExtensionFromMimetype(mimeType) filename = "image" + exmime.ExtensionFromMimetype(info.MimeType)
} }
data, _, _, mimeType, err = media.DownloadPhotoMedia(ctx, mc.client.API(), msgMedia)
case *tg.MessageMediaDocument: case *tg.MessageMediaDocument:
document, ok := msgMedia.Document.(*tg.Document) document, ok := msgMedia.Document.(*tg.Document)
if !ok { if !ok {
return nil, nil, fmt.Errorf("unrecognized document type %T", msgMedia.Document) return nil, nil, fmt.Errorf("unrecognized document type %T", msgMedia.Document)
} }
mimeType = document.GetMimeType() info.MimeType = document.GetMimeType()
// TODO convert to Transfer
data, err = media.DownloadDocument(ctx, mc.client.API(), document) data, err = media.DownloadDocument(ctx, mc.client.API(), document)
default: default:
return nil, nil, fmt.Errorf("unhandled media type %T", msgMedia) return nil, nil, fmt.Errorf("unhandled media type %T", msgMedia)
@@ -282,7 +283,7 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por
return nil, nil, err return nil, nil, err
} }
mxcURI, encryptedFileInfo, err = intent.UploadMedia(ctx, portal.MXID, data, filename, mimeType) mxcURI, encryptedFileInfo, err = intent.UploadMedia(ctx, portal.MXID, data, filename, info.MimeType)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
+10 -52
View File
@@ -2,23 +2,14 @@ package store
import ( import (
"context" "context"
"database/sql"
"encoding/json"
"time"
"go.mau.fi/util/dbutil" "go.mau.fi/util/dbutil"
"maunium.net/go/mautrix/id"
) )
const ( const (
insertTelegramFileQuery = ` insertTelegramFileQuery = "INSERT INTO telegram_file (id, mxc, mime_type, size) VALUES ($1, $2, $3, $4)"
INSERT INTO telegram_file ( getTelegramFileSelect = "SELECT id, mxc, mime_type, size FROM telegram_file "
id, mxc, mime_type, was_converted, timestamp, size, width, height, thumbnail, decryption_info)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
`
getTelegramFileSelect = `
SELECT id, mxc, mime_type, was_converted, timestamp, size, width, height, thumbnail, decryption_info
FROM telegram_file
`
getTelegramFileByLocationIDQuery = getTelegramFileSelect + "WHERE id=$1" getTelegramFileByLocationIDQuery = getTelegramFileSelect + "WHERE id=$1"
getTelegramFileByMXCQuery = getTelegramFileSelect + "WHERE mxc=$1" getTelegramFileByMXCQuery = getTelegramFileSelect + "WHERE mxc=$1"
) )
@@ -32,16 +23,10 @@ type TelegramFileLocationID string
type TelegramFile struct { type TelegramFile struct {
qh *dbutil.QueryHelper[*TelegramFile] qh *dbutil.QueryHelper[*TelegramFile]
LocationID TelegramFileLocationID LocationID TelegramFileLocationID
MXC string MXC id.ContentURIString
MimeType string MIMEType string
WasConverted bool Size int
Timestamp time.Time
Size int64
Width int
Height int
ThumbnailID string
DecryptionInfo json.RawMessage
} }
var _ dbutil.DataStruct[*TelegramFile] = (*TelegramFile)(nil) var _ dbutil.DataStruct[*TelegramFile] = (*TelegramFile)(nil)
@@ -50,7 +35,7 @@ func newTelegramFile(qh *dbutil.QueryHelper[*TelegramFile]) *TelegramFile {
return &TelegramFile{qh: qh} return &TelegramFile{qh: qh}
} }
func (fq *TelegramFileQuery) GetByLocationID(ctx context.Context, locationID string) (*TelegramFile, error) { func (fq *TelegramFileQuery) GetByLocationID(ctx context.Context, locationID TelegramFileLocationID) (*TelegramFile, error) {
return fq.QueryOne(ctx, getTelegramFileByLocationIDQuery, locationID) return fq.QueryOne(ctx, getTelegramFileByLocationIDQuery, locationID)
} }
@@ -59,18 +44,7 @@ func (fq *TelegramFileQuery) GetByMXC(ctx context.Context, mxc string) (*Telegra
} }
func (f *TelegramFile) sqlVariables() []any { func (f *TelegramFile) sqlVariables() []any {
return []any{ return []any{f.LocationID, f.MXC, f.MIMEType, f.Size}
f.LocationID,
f.MXC,
f.MimeType,
f.WasConverted,
f.Timestamp.UnixMilli(),
f.Size,
f.Width,
f.Height,
f.ThumbnailID,
f.DecryptionInfo,
}
} }
func (f *TelegramFile) Insert(ctx context.Context) error { func (f *TelegramFile) Insert(ctx context.Context) error {
@@ -78,21 +52,5 @@ func (f *TelegramFile) Insert(ctx context.Context) error {
} }
func (f *TelegramFile) Scan(row dbutil.Scannable) (*TelegramFile, error) { func (f *TelegramFile) Scan(row dbutil.Scannable) (*TelegramFile, error) {
var thumbnailID sql.NullString return f, row.Scan(&f.LocationID, &f.MXC, &f.MIMEType, &f.Size)
var timestamp int64
err := row.Scan(
&f.LocationID,
&f.MXC,
&f.MimeType,
&f.WasConverted,
&timestamp,
&f.Size,
&f.Width,
&f.Height,
&thumbnailID,
&f.DecryptionInfo,
)
f.Timestamp = time.UnixMilli(timestamp)
f.ThumbnailID = thumbnailID.String
return f, err
} }
+4 -12
View File
@@ -32,16 +32,8 @@ CREATE TABLE telegram_channel_access_hashes (
); );
CREATE TABLE telegram_file ( CREATE TABLE telegram_file (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
mxc TEXT NOT NULL, mxc TEXT NOT NULL,
mime_type TEXT, mime_type TEXT,
was_converted BOOLEAN NOT NULL DEFAULT false, size BIGINT
timestamp BIGINT NOT NULL DEFAULT 0,
size BIGINT,
width INTEGER,
height INTEGER,
thumbnail TEXT,
decryption_info jsonb,
FOREIGN KEY (thumbnail) REFERENCES telegram_file(id)
ON UPDATE CASCADE ON DELETE SET NULL
); );
+25 -16
View File
@@ -15,6 +15,7 @@ import (
"go.mau.fi/mautrix-telegram/pkg/connector/emojis" "go.mau.fi/mautrix-telegram/pkg/connector/emojis"
"go.mau.fi/mautrix-telegram/pkg/connector/ids" "go.mau.fi/mautrix-telegram/pkg/connector/ids"
"go.mau.fi/mautrix-telegram/pkg/connector/media"
"go.mau.fi/mautrix-telegram/pkg/connector/util" "go.mau.fi/mautrix-telegram/pkg/connector/util"
) )
@@ -225,7 +226,6 @@ func (t *TelegramClient) handleTelegramReactions(ctx context.Context, msg *tg.Me
if _, set := msg.GetReactions(); !set { if _, set := msg.GetReactions(); !set {
return nil return nil
} }
fmt.Printf("handle reactions %+v\n", msg.Reactions)
var totalCount int var totalCount int
for _, r := range msg.Reactions.Results { for _, r := range msg.Reactions.Results {
totalCount += r.Count totalCount += r.Count
@@ -253,6 +253,8 @@ func (t *TelegramClient) handleTelegramReactions(ctx context.Context, msg *tg.Me
if len(reactionsList) < totalCount { if len(reactionsList) < totalCount {
if user, ok := msg.PeerID.(*tg.PeerUser); ok { if user, ok := msg.PeerID.(*tg.PeerUser); ok {
reactionsList = splitDMReactionCounts(msg.Reactions.Results, user.UserID, t.loginID) reactionsList = splitDMReactionCounts(msg.Reactions.Results, user.UserID, t.loginID)
// TODO
// } else if t.isBot { // } else if t.isBot {
// // Can't fetch exact reaction senders as a bot // // Can't fetch exact reaction senders as a bot
// return // return
@@ -325,25 +327,34 @@ func (t *TelegramClient) getReactionLimit(ctx context.Context, sender networkid.
return 1, nil return 1, nil
} }
// TODO move this to emojis package
func (t *TelegramClient) transferEmojisToMatrix(ctx context.Context, customEmojiIDs []int64) (result map[networkid.EmojiID]string, err error) { func (t *TelegramClient) transferEmojisToMatrix(ctx context.Context, customEmojiIDs []int64) (result map[networkid.EmojiID]string, err error) {
result, customEmojiIDs = emojis.ConvertKnownEmojis(customEmojiIDs) result, customEmojiIDs = emojis.ConvertKnownEmojis(customEmojiIDs)
for _, customEmojiID := range customEmojiIDs {
fmt.Printf("customEmojiID %d\n", customEmojiID) if len(customEmojiIDs) > 0 {
locationID := fmt.Sprintf("%d", customEmojiID) customEmojiDocuments, err := t.client.API().MessagesGetCustomEmojiDocuments(ctx, customEmojiIDs)
if file, err := t.main.Store.TelegramFile.GetByLocationID(ctx, locationID); err != nil { if err != nil {
return nil, fmt.Errorf("failed to search for Telegram file by location ID") return nil, err
} else if file == nil { }
// TODO download shit
} else { for _, customEmojiDocument := range customEmojiDocuments {
result[ids.MakeEmojiIDFromDocumentID(customEmojiID)] = file.MXC document := customEmojiDocument.(*tg.Document)
mxcURI, _, _, _, err := media.NewTransferer(t.main.Config.AnimatedSticker).
WithIsSticker(true).
Transfer(ctx, t.main.Store, t.client.API(), t.main.Bridge.Bot, &tg.InputDocumentFileLocation{
ID: document.GetID(),
AccessHash: document.GetAccessHash(),
FileReference: document.GetFileReference(),
})
if err != nil {
return nil, err
}
result[ids.MakeEmojiIDFromDocumentID(document.ID)] = string(mxcURI)
} }
} }
return return
} }
func (t *TelegramClient) handleTelegramParsedReactionsLocked(ctx context.Context, msg *database.Message, reactions map[networkid.UserID][]tg.MessagePeerReaction, customEmojiIDs []int64, isFull bool, onlyUserID *networkid.UserID, timestamp *time.Time) error { func (t *TelegramClient) handleTelegramParsedReactionsLocked(ctx context.Context, msg *database.Message, reactions map[networkid.UserID][]tg.MessagePeerReaction, customEmojiIDs []int64, isFull bool, onlyUserID *networkid.UserID, timestamp *time.Time) error {
// TODO deal with the custom emoji IDs
customEmojis, err := t.transferEmojisToMatrix(ctx, customEmojiIDs) customEmojis, err := t.transferEmojisToMatrix(ctx, customEmojiIDs)
if err != nil { if err != nil {
return err return err
@@ -359,9 +370,8 @@ func (t *TelegramClient) handleTelegramParsedReactionsLocked(ctx context.Context
if onlyUserID != nil && existing.SenderID != *onlyUserID { if onlyUserID != nil && existing.SenderID != *onlyUserID {
continue continue
} }
newReactions := reactions[existing.SenderID]
var matched bool var matched bool
reactions[existing.SenderID], matched, err = reactionsFilter(newReactions, existing) reactions[existing.SenderID], matched, err = reactionsFilter(reactions[existing.SenderID], existing)
if err != nil { if err != nil {
return err return err
} else if !matched { } else if !matched {
@@ -369,7 +379,7 @@ func (t *TelegramClient) handleTelegramParsedReactionsLocked(ctx context.Context
removed = append(removed, existing) removed = append(removed, existing)
} else if reactionLimit, err := t.getReactionLimit(ctx, existing.SenderID); err != nil { } else if reactionLimit, err := t.getReactionLimit(ctx, existing.SenderID); err != nil {
return err return err
} else if len(newReactions) == reactionLimit { } else if len(reactions[existing.SenderID]) >= reactionLimit {
removed = append(removed, existing) removed = append(removed, existing)
} }
} }
@@ -447,7 +457,6 @@ func (t *TelegramClient) handleTelegramParsedReactionsLocked(ctx context.Context
t.main.Bridge.QueueRemoteEvent(t.userLogin, evt) t.main.Bridge.QueueRemoteEvent(t.userLogin, evt)
} }
fmt.Printf("%v %v %v\n", isFull, reactions, customEmojiIDs)
return nil return nil
} }