reactions: support custom emojis
Signed-off-by: Sumner Evans <sumner.evans@automattic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ require (
|
||||
github.com/gotd/td v0.102.0
|
||||
github.com/rs/zerolog v1.33.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.uber.org/zap v1.27.0
|
||||
maunium.net/go/mautrix v0.19.0-beta.1.0.20240706124659-b4057a26c3ed
|
||||
|
||||
@@ -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/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=
|
||||
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.20240702170310-bd1da3c069eb/go.mod h1:DsJzUrJAG53lCZnnYvq9/mOyLuPScWwYhvETiTrpdP4=
|
||||
go.mau.fi/util v0.5.1-0.20240708233020-c2f9af6fecf8 h1:7ntkhSR0G/dIwAPjcoOoJz+bPne0gA4PypEn8euMMx0=
|
||||
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/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
||||
go.mau.fi/zerozap v0.1.1 h1:mxE/dW4wtkqBYOXOEEzXldk5qKB+ahsZXjoTGnvEhZQ=
|
||||
|
||||
@@ -119,7 +119,7 @@ func NewTelegramClient(ctx context.Context, tc *TelegramConnector, login *bridge
|
||||
Logger: zaplog,
|
||||
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.reactionMessageLocks = map[int]*sync.Mutex{}
|
||||
go func() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -18,21 +18,13 @@ package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
|
||||
up "go.mau.fi/util/configupgrade"
|
||||
"go.mau.fi/util/dbutil"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/store"
|
||||
)
|
||||
|
||||
type TelegramConfig struct {
|
||||
AppID int `yaml:"app_id"`
|
||||
AppHash string `yaml:"app_hash"`
|
||||
}
|
||||
|
||||
type TelegramConnector struct {
|
||||
Bridge *bridgev2.Bridge
|
||||
Config *TelegramConfig
|
||||
@@ -42,7 +34,6 @@ type TelegramConnector struct {
|
||||
}
|
||||
|
||||
var _ bridgev2.NetworkConnector = (*TelegramConnector)(nil)
|
||||
var _ bridgev2.ConfigValidatingNetwork = (*TelegramConnector)(nil)
|
||||
|
||||
// var _ bridgev2.MaxFileSizeingNetwork = (*TelegramConnector)(nil)
|
||||
|
||||
@@ -53,7 +44,6 @@ func NewConnector() *TelegramConnector {
|
||||
}
|
||||
|
||||
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.Bridge = bridge
|
||||
}
|
||||
@@ -67,28 +57,6 @@ func (tc *TelegramConnector) LoadUserLogin(ctx context.Context, login *bridgev2.
|
||||
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
|
||||
// func (tg *TelegramConnector) SetMaxFileSize(maxSize int64) {
|
||||
// }
|
||||
|
||||
@@ -91,8 +91,8 @@ func (tc *TelegramConnector) Download(ctx context.Context, mediaID networkid.Med
|
||||
return nil, fmt.Errorf("unrecognized document type %T", msgMedia.Document)
|
||||
}
|
||||
|
||||
// Download the thumbnail for this media rather than the media itself.
|
||||
if info.Thumbnail {
|
||||
// Download the thumbnail for this media rather than the media itself.
|
||||
_, _, largestThumbnail := media.GetLargestPhotoSize(document.Thumbs)
|
||||
data, mimeType, err = media.DownloadFileLocation(ctx, client.client.API(), &tg.InputDocumentFileLocation{
|
||||
ID: document.GetID(),
|
||||
|
||||
@@ -31,8 +31,7 @@ func ConvertKnownEmojis(emojiIDs []int64) (result map[networkid.EmojiID]string,
|
||||
result = map[networkid.EmojiID]string{}
|
||||
for _, e := range emojiIDs {
|
||||
if v, ok := reverseUnicodemojiPack[e]; ok {
|
||||
emojiID := ids.MakeEmojiIDFromDocumentID(e)
|
||||
result[emojiID] = v
|
||||
result[ids.MakeEmojiIDFromDocumentID(e)] = v
|
||||
} else {
|
||||
remaining = append(remaining, e)
|
||||
}
|
||||
|
||||
@@ -1,72 +1,19 @@
|
||||
# Get your own API keys at https://my.telegram.org/apps
|
||||
app_id: 12345
|
||||
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?
|
||||
catch_up: true
|
||||
# Should incoming updates be handled sequentially to make sure order is preserved on Matrix?
|
||||
sequential_updates: true
|
||||
exit_on_update_error: false
|
||||
|
||||
# Telethon connection options.
|
||||
connection:
|
||||
# The timeout in seconds to be used when connecting.
|
||||
timeout: 120
|
||||
# How many times the reconnection should retry, either on the initial connection or when
|
||||
# Telegram disconnects us. May be set to a negative or null value for infinite retries, but
|
||||
# this is not recommended, since the program can get stuck in an infinite loop.
|
||||
retries: 5
|
||||
# The delay in seconds to sleep between automatic reconnections.
|
||||
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: ""
|
||||
animated_sticker:
|
||||
# Format to which animated stickers should be converted.
|
||||
# disable - No conversion, send as-is (gzipped lottie)
|
||||
# png - converts to non-animated png (fastest),
|
||||
# gif - converts to animated gif
|
||||
# webm - converts to webm video, requires ffmpeg executable with vp9 codec and webm container support
|
||||
# webp - converts to animated webp, requires ffmpeg executable with webp codec/container support
|
||||
target: gif
|
||||
# Should video stickers be converted to the specified format as well?
|
||||
convert_from_webm: false
|
||||
# Arguments for converter. All converters take width and height.
|
||||
args:
|
||||
width: 256
|
||||
height: 256
|
||||
fps: 25 # only for webm, webp and gif (2, 5, 10, 20 or 25 recommended)
|
||||
|
||||
@@ -193,6 +193,8 @@ func (t *TelegramClient) PreHandleMatrixReaction(ctx context.Context, msg *bridg
|
||||
if strings.HasPrefix(msg.Content.RelatesTo.Key, "mxc://") {
|
||||
if file, err := t.main.Store.TelegramFile.GetByMXC(ctx, msg.Content.RelatesTo.Key); err != nil {
|
||||
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 {
|
||||
return resp, err
|
||||
} else {
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"github.com/gotd/td/tg"
|
||||
)
|
||||
|
||||
func DownloadFileLocation(ctx context.Context, client downloader.Client, file tg.InputFileLocationClass) (data []byte, mimeType string, err error) {
|
||||
// TODO convert to streaming? stream to file?
|
||||
func DownloadFileLocation(ctx context.Context, client downloader.Client, loc tg.InputFileLocationClass) (data []byte, mimeType string, err error) {
|
||||
// TODO convert entire function to streaming? Maybe at least stream to file?
|
||||
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 {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
+102
-20
@@ -9,37 +9,119 @@ import (
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"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.InputFileLocation], or [tg.InputPhotoFileLocation] into a key for use in
|
||||
// the telegram_file table.
|
||||
func LocationToID(location any) (id string) {
|
||||
switch location := location.(type) {
|
||||
// [tg.InputFileLocation], or [tg.InputPhotoFileLocation] into a [LocationID]
|
||||
// for use in the telegram_file table.
|
||||
func getLocationID(loc any) (locID store.TelegramFileLocationID) {
|
||||
var id string
|
||||
switch location := loc.(type) {
|
||||
case *tg.Document:
|
||||
return fmt.Sprintf("%d", location.ID)
|
||||
id = fmt.Sprintf("%d", location.ID)
|
||||
case *tg.InputDocumentFileLocation:
|
||||
return fmt.Sprintf("%d-%s", location.ID, location.ThumbSize)
|
||||
id = fmt.Sprintf("%d-%s", location.ID, location.ThumbSize)
|
||||
case *tg.InputPhotoFileLocation:
|
||||
return fmt.Sprintf("%d-%s", location.ID, location.ThumbSize)
|
||||
id = fmt.Sprintf("%d-%s", location.ID, location.ThumbSize)
|
||||
case *tg.InputFileLocation:
|
||||
return fmt.Sprintf("%d-%d", location.VolumeID, location.LocalID)
|
||||
id = fmt.Sprintf("%d-%d", location.VolumeID, location.LocalID)
|
||||
case *tg.InputPeerPhotoFileLocation:
|
||||
return fmt.Sprintf("%d", location.PhotoID)
|
||||
id = fmt.Sprintf("%d", location.PhotoID)
|
||||
default:
|
||||
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) {
|
||||
data, mimeType, err := DownloadFileLocation(ctx, client, file)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("downloading file failed: %w", err)
|
||||
}
|
||||
var filename string
|
||||
if len(filenameOpt) > 0 {
|
||||
filename = filenameOpt[0]
|
||||
}
|
||||
return intent.UploadMedia(ctx, roomID, data, filename, mimeType)
|
||||
type AnimatedStickerConfig struct {
|
||||
Target string `yaml:"target"`
|
||||
ConvertFromWebm bool `yaml:"convert_from_webm"`
|
||||
Args struct {
|
||||
Width int `yaml:"width"`
|
||||
Height int `yaml:"height"`
|
||||
FPS int `yaml:"fps"`
|
||||
} `yaml:"args"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,15 +3,26 @@ package msgconv
|
||||
import (
|
||||
"github.com/gotd/td/telegram"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/media"
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/store"
|
||||
)
|
||||
|
||||
type MessageConverter struct {
|
||||
client *telegram.Client
|
||||
connector bridgev2.MatrixConnector
|
||||
client *telegram.Client
|
||||
connector bridgev2.MatrixConnector
|
||||
store *store.Container
|
||||
animatedStickerConfig media.AnimatedStickerConfig
|
||||
|
||||
useDirectMedia bool
|
||||
}
|
||||
|
||||
func NewMessageConverter(client *telegram.Client, connector bridgev2.MatrixConnector, useDirectMedia bool) *MessageConverter {
|
||||
return &MessageConverter{client: client, connector: connector, useDirectMedia: useDirectMedia}
|
||||
func NewMessageConverter(client *telegram.Client, connector bridgev2.MatrixConnector, store *store.Container, animatedStickerConfig media.AnimatedStickerConfig, useDirectMedia bool) *MessageConverter {
|
||||
return &MessageConverter{
|
||||
client: client,
|
||||
connector: connector,
|
||||
store: store,
|
||||
animatedStickerConfig: animatedStickerConfig,
|
||||
useDirectMedia: useDirectMedia,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,12 +208,14 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por
|
||||
}
|
||||
|
||||
if info.ThumbnailInfo.ThumbnailURL == "" {
|
||||
info.ThumbnailInfo.ThumbnailURL, info.ThumbnailInfo.ThumbnailFile, err = media.TransferToMatrix(ctx, portal.MXID, mc.client.API(), intent, &tg.InputDocumentFileLocation{
|
||||
ID: document.GetID(),
|
||||
AccessHash: document.GetAccessHash(),
|
||||
FileReference: document.GetFileReference(),
|
||||
ThumbSize: largestThumbnail.GetType(),
|
||||
})
|
||||
info.ThumbnailInfo.ThumbnailURL, info.ThumbnailInfo.ThumbnailFile, info.ThumbnailInfo.Size, info.ThumbnailInfo.MimeType, err = media.NewTransferer(mc.animatedStickerConfig).
|
||||
WithRoomID(portal.MXID).
|
||||
Transfer(ctx, mc.store, mc.client.API(), intent, &tg.InputDocumentFileLocation{
|
||||
ID: document.GetID(),
|
||||
AccessHash: document.GetAccessHash(),
|
||||
FileReference: document.GetFileReference(),
|
||||
ThumbSize: largestThumbnail.GetType(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -256,24 +258,23 @@ func (mc *MessageConverter) convertMediaRequiringUpload(ctx context.Context, por
|
||||
|
||||
if mxcURI == "" {
|
||||
var data []byte
|
||||
var mimeType string
|
||||
var err error
|
||||
switch msgMedia := msgMedia.(type) {
|
||||
case *tg.MessageMediaPhoto:
|
||||
// TODO convert to Transfer
|
||||
data, _, _, info.MimeType, err = media.DownloadPhotoMedia(ctx, mc.client.API(), msgMedia)
|
||||
if _, ok := msgMedia.GetTTLSeconds(); ok {
|
||||
filename = "disappearing_image" + exmime.ExtensionFromMimetype(mimeType)
|
||||
filename = "disappearing_image" + exmime.ExtensionFromMimetype(info.MimeType)
|
||||
} 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:
|
||||
document, ok := msgMedia.Document.(*tg.Document)
|
||||
if !ok {
|
||||
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)
|
||||
default:
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -2,23 +2,14 @@ package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"go.mau.fi/util/dbutil"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
const (
|
||||
insertTelegramFileQuery = `
|
||||
INSERT INTO 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
|
||||
`
|
||||
insertTelegramFileQuery = "INSERT INTO telegram_file (id, mxc, mime_type, size) VALUES ($1, $2, $3, $4)"
|
||||
getTelegramFileSelect = "SELECT id, mxc, mime_type, size FROM telegram_file "
|
||||
getTelegramFileByLocationIDQuery = getTelegramFileSelect + "WHERE id=$1"
|
||||
getTelegramFileByMXCQuery = getTelegramFileSelect + "WHERE mxc=$1"
|
||||
)
|
||||
@@ -32,16 +23,10 @@ type TelegramFileLocationID string
|
||||
type TelegramFile struct {
|
||||
qh *dbutil.QueryHelper[*TelegramFile]
|
||||
|
||||
LocationID TelegramFileLocationID
|
||||
MXC string
|
||||
MimeType string
|
||||
WasConverted bool
|
||||
Timestamp time.Time
|
||||
Size int64
|
||||
Width int
|
||||
Height int
|
||||
ThumbnailID string
|
||||
DecryptionInfo json.RawMessage
|
||||
LocationID TelegramFileLocationID
|
||||
MXC id.ContentURIString
|
||||
MIMEType string
|
||||
Size int
|
||||
}
|
||||
|
||||
var _ dbutil.DataStruct[*TelegramFile] = (*TelegramFile)(nil)
|
||||
@@ -50,7 +35,7 @@ func newTelegramFile(qh *dbutil.QueryHelper[*TelegramFile]) *TelegramFile {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -59,18 +44,7 @@ func (fq *TelegramFileQuery) GetByMXC(ctx context.Context, mxc string) (*Telegra
|
||||
}
|
||||
|
||||
func (f *TelegramFile) sqlVariables() []any {
|
||||
return []any{
|
||||
f.LocationID,
|
||||
f.MXC,
|
||||
f.MimeType,
|
||||
f.WasConverted,
|
||||
f.Timestamp.UnixMilli(),
|
||||
f.Size,
|
||||
f.Width,
|
||||
f.Height,
|
||||
f.ThumbnailID,
|
||||
f.DecryptionInfo,
|
||||
}
|
||||
return []any{f.LocationID, f.MXC, f.MIMEType, f.Size}
|
||||
}
|
||||
|
||||
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) {
|
||||
var thumbnailID sql.NullString
|
||||
var timestamp int64
|
||||
err := row.Scan(
|
||||
&f.LocationID,
|
||||
&f.MXC,
|
||||
&f.MimeType,
|
||||
&f.WasConverted,
|
||||
×tamp,
|
||||
&f.Size,
|
||||
&f.Width,
|
||||
&f.Height,
|
||||
&thumbnailID,
|
||||
&f.DecryptionInfo,
|
||||
)
|
||||
f.Timestamp = time.UnixMilli(timestamp)
|
||||
f.ThumbnailID = thumbnailID.String
|
||||
return f, err
|
||||
return f, row.Scan(&f.LocationID, &f.MXC, &f.MIMEType, &f.Size)
|
||||
}
|
||||
|
||||
@@ -32,16 +32,8 @@ CREATE TABLE telegram_channel_access_hashes (
|
||||
);
|
||||
|
||||
CREATE TABLE telegram_file (
|
||||
id TEXT PRIMARY KEY,
|
||||
mxc TEXT NOT NULL,
|
||||
mime_type TEXT,
|
||||
was_converted BOOLEAN NOT NULL DEFAULT false,
|
||||
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
|
||||
id TEXT PRIMARY KEY,
|
||||
mxc TEXT NOT NULL,
|
||||
mime_type TEXT,
|
||||
size BIGINT
|
||||
);
|
||||
|
||||
+25
-16
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/emojis"
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/ids"
|
||||
"go.mau.fi/mautrix-telegram/pkg/connector/media"
|
||||
"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 {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("handle reactions %+v\n", msg.Reactions)
|
||||
var totalCount int
|
||||
for _, r := range msg.Reactions.Results {
|
||||
totalCount += r.Count
|
||||
@@ -253,6 +253,8 @@ func (t *TelegramClient) handleTelegramReactions(ctx context.Context, msg *tg.Me
|
||||
if len(reactionsList) < totalCount {
|
||||
if user, ok := msg.PeerID.(*tg.PeerUser); ok {
|
||||
reactionsList = splitDMReactionCounts(msg.Reactions.Results, user.UserID, t.loginID)
|
||||
|
||||
// TODO
|
||||
// } else if t.isBot {
|
||||
// // Can't fetch exact reaction senders as a bot
|
||||
// return
|
||||
@@ -325,25 +327,34 @@ func (t *TelegramClient) getReactionLimit(ctx context.Context, sender networkid.
|
||||
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) {
|
||||
result, customEmojiIDs = emojis.ConvertKnownEmojis(customEmojiIDs)
|
||||
for _, customEmojiID := range customEmojiIDs {
|
||||
fmt.Printf("customEmojiID %d\n", customEmojiID)
|
||||
locationID := fmt.Sprintf("%d", customEmojiID)
|
||||
if file, err := t.main.Store.TelegramFile.GetByLocationID(ctx, locationID); err != nil {
|
||||
return nil, fmt.Errorf("failed to search for Telegram file by location ID")
|
||||
} else if file == nil {
|
||||
// TODO download shit
|
||||
} else {
|
||||
result[ids.MakeEmojiIDFromDocumentID(customEmojiID)] = file.MXC
|
||||
|
||||
if len(customEmojiIDs) > 0 {
|
||||
customEmojiDocuments, err := t.client.API().MessagesGetCustomEmojiDocuments(ctx, customEmojiIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, customEmojiDocument := range customEmojiDocuments {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -359,9 +370,8 @@ func (t *TelegramClient) handleTelegramParsedReactionsLocked(ctx context.Context
|
||||
if onlyUserID != nil && existing.SenderID != *onlyUserID {
|
||||
continue
|
||||
}
|
||||
newReactions := reactions[existing.SenderID]
|
||||
var matched bool
|
||||
reactions[existing.SenderID], matched, err = reactionsFilter(newReactions, existing)
|
||||
reactions[existing.SenderID], matched, err = reactionsFilter(reactions[existing.SenderID], existing)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !matched {
|
||||
@@ -369,7 +379,7 @@ func (t *TelegramClient) handleTelegramParsedReactionsLocked(ctx context.Context
|
||||
removed = append(removed, existing)
|
||||
} else if reactionLimit, err := t.getReactionLimit(ctx, existing.SenderID); err != nil {
|
||||
return err
|
||||
} else if len(newReactions) == reactionLimit {
|
||||
} else if len(reactions[existing.SenderID]) >= reactionLimit {
|
||||
removed = append(removed, existing)
|
||||
}
|
||||
}
|
||||
@@ -447,7 +457,6 @@ func (t *TelegramClient) handleTelegramParsedReactionsLocked(ctx context.Context
|
||||
t.main.Bridge.QueueRemoteEvent(t.userLogin, evt)
|
||||
}
|
||||
|
||||
fmt.Printf("%v %v %v\n", isFull, reactions, customEmojiIDs)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user