mirror of
https://github.com/mautrix/telegram.git
synced 2026-05-16 23:15:45 +03:00
imagepack: switch to new shared metadata field
This commit is contained in:
4
go.mod
4
go.mod
@@ -27,7 +27,7 @@ require (
|
||||
github.com/rs/zerolog v1.35.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
go.mau.fi/util v0.9.8
|
||||
go.mau.fi/util v0.9.9-0.20260424160448-fd0d9737ad38
|
||||
go.mau.fi/webp v0.2.0
|
||||
go.mau.fi/zerozap v0.1.2
|
||||
go.opentelemetry.io/otel v1.42.0
|
||||
@@ -42,7 +42,7 @@ require (
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/tools v0.44.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
maunium.net/go/mautrix v0.27.1-0.20260422171355-c6fe96e2dea3
|
||||
maunium.net/go/mautrix v0.27.1-0.20260427165325-7724eaafca8b
|
||||
rsc.io/qr v0.2.0
|
||||
)
|
||||
|
||||
|
||||
8
go.sum
8
go.sum
@@ -112,8 +112,8 @@ github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
go.mau.fi/util v0.9.8 h1:+/jf8eM2dAT2wx9UidmaneH28r/CSCKCniCyby1qWz8=
|
||||
go.mau.fi/util v0.9.8/go.mod h1:up/5mbzH2M1pSBNXqRxODn8dg/hEKbLJu92W4/SNAX0=
|
||||
go.mau.fi/util v0.9.9-0.20260424160448-fd0d9737ad38 h1:D4OKITjyvlud39Q10oMnfhdeNkzEIVkXrEeCW6nvgLk=
|
||||
go.mau.fi/util v0.9.9-0.20260424160448-fd0d9737ad38/go.mod h1:up/5mbzH2M1pSBNXqRxODn8dg/hEKbLJu92W4/SNAX0=
|
||||
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
||||
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
||||
go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU=
|
||||
@@ -236,7 +236,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
||||
maunium.net/go/mautrix v0.27.1-0.20260422171355-c6fe96e2dea3 h1:V5L7Yo0fH1fs6lybfR+BUWG1D25xIdUZNWBIPXCV8cY=
|
||||
maunium.net/go/mautrix v0.27.1-0.20260422171355-c6fe96e2dea3/go.mod h1:7QpEQiTy6p4LHkXXaZI+N46tGYy8HMhD0JjzZAFoFWs=
|
||||
maunium.net/go/mautrix v0.27.1-0.20260427165325-7724eaafca8b h1:Wfkd74wYRUTb36gqvXSvY5IWW8R2sIxNGjWPM0x60+U=
|
||||
maunium.net/go/mautrix v0.27.1-0.20260427165325-7724eaafca8b/go.mod h1:4fZ0M0xB5ZtueQI65RilX28J/3794BeK+LaCg4U61Jk=
|
||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
||||
|
||||
@@ -119,7 +119,8 @@ type TelegramClient struct {
|
||||
prevReactionPoll map[networkid.PortalKey]time.Time
|
||||
prevReactionPollLock sync.Mutex
|
||||
|
||||
stickerPackCache map[string]map[int64]*tg.Document
|
||||
stickerPacksByName map[string]*stickerPackCache
|
||||
stickerPacksByID map[int64]*stickerPackCache
|
||||
stickerPackCacheLock sync.Mutex
|
||||
}
|
||||
|
||||
@@ -175,8 +176,9 @@ func NewTelegramClient(ctx context.Context, tc *TelegramConnector, login *bridge
|
||||
|
||||
takeoutAccepted: exsync.NewEvent(),
|
||||
|
||||
prevReactionPoll: map[networkid.PortalKey]time.Time{},
|
||||
stickerPackCache: map[string]map[int64]*tg.Document{},
|
||||
prevReactionPoll: map[networkid.PortalKey]time.Time{},
|
||||
stickerPacksByName: map[string]*stickerPackCache{},
|
||||
stickerPacksByID: map[int64]*stickerPackCache{},
|
||||
|
||||
recentMessageRooms: exsync.NewRingBuffer[networkid.MessageID, networkid.PortalKey](32),
|
||||
|
||||
|
||||
@@ -243,85 +243,12 @@ func (tc *TelegramClient) pollSponsoredMessage(ctx context.Context, portal *brid
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *TelegramClient) parseInputPack(meta map[string]any) (shortName string, id, accessHash int64) {
|
||||
shortName, _ = meta["short_name"].(string)
|
||||
idStr, _ := meta["id"].(string)
|
||||
if idStr != "" {
|
||||
id, _ = strconv.ParseInt(idStr, 10, 64)
|
||||
}
|
||||
accessHashStr, _ := meta["access_hash"].(string)
|
||||
accessHashSourceStr, _ := meta["access_hash_source"].(string)
|
||||
if id != 0 && accessHashStr != "" && accessHashSourceStr != "" {
|
||||
accessHashSource, _ := strconv.ParseInt(accessHashSourceStr, 10, 64)
|
||||
if accessHashSource == tc.telegramUserID {
|
||||
accessHash, _ = strconv.ParseInt(accessHashStr, 10, 64)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tc *TelegramClient) findOriginalStickerDocument(ctx context.Context, info map[string]any, forceClearCache bool) (tg.InputMediaClass, error) {
|
||||
stickerIDStr, ok := info["id"].(string)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
stickerID, err := strconv.ParseInt(stickerIDStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
pack, ok := info["pack"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
var inputPack tg.InputStickerSetClass
|
||||
var cacheKey string
|
||||
packName, packID, packAccessHash := tc.parseInputPack(pack)
|
||||
if packAccessHash != 0 {
|
||||
inputPack = &tg.InputStickerSetID{ID: packID, AccessHash: packAccessHash}
|
||||
cacheKey = fmt.Sprintf("pack_id:%d", packID)
|
||||
} else if packName != "" {
|
||||
inputPack = &tg.InputStickerSetShortName{ShortName: packName}
|
||||
cacheKey = fmt.Sprintf("pack_name:%s", packName)
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
tc.stickerPackCacheLock.Lock()
|
||||
defer tc.stickerPackCacheLock.Unlock()
|
||||
docMap, ok := tc.stickerPackCache[cacheKey]
|
||||
if !ok || forceClearCache {
|
||||
resp, err := tc.client.API().MessagesGetStickerSet(ctx, &tg.MessagesGetStickerSetRequest{Stickerset: inputPack})
|
||||
if err != nil {
|
||||
if tgerr.Is(err, tg.ErrStickersetInvalid) {
|
||||
tc.stickerPackCache[cacheKey] = nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get sticker set: %w", err)
|
||||
}
|
||||
set, ok := resp.AsModified()
|
||||
if !ok {
|
||||
tc.stickerPackCache[cacheKey] = nil
|
||||
return nil, fmt.Errorf("unexpected response type for MessagesGetStickerSet: %T", resp)
|
||||
}
|
||||
docMap = set.MapDocuments().DocumentToMap()
|
||||
tc.stickerPackCache[cacheKey] = docMap
|
||||
tc.stickerPackCache[fmt.Sprintf("pack_id:%d", set.Set.ID)] = docMap
|
||||
tc.stickerPackCache[fmt.Sprintf("pack_name:%s", set.Set.ShortName)] = docMap
|
||||
}
|
||||
stickerDoc, ok := docMap[stickerID]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return &tg.InputMediaDocument{ID: stickerDoc.AsInput()}, nil
|
||||
}
|
||||
|
||||
func (tc *TelegramClient) transferMediaToTelegram(ctx context.Context, content *event.MessageEventContent, sticker, forceRetry, forceDocument bool) (tg.InputMediaClass, error) {
|
||||
var upload tg.InputFileClass
|
||||
filename := getMediaFilename(content)
|
||||
info := content.GetInfo()
|
||||
if sticker {
|
||||
extra, ok := info.Extra["fi.mau.telegram.sticker"].(map[string]any)
|
||||
if !ok {
|
||||
// Not from telegram, continue to reupload
|
||||
} else if origFile, err := tc.findOriginalStickerDocument(ctx, extra, forceRetry); err != nil {
|
||||
if origFile, err := tc.findOriginalStickerDocument(ctx, info.BridgedSticker, forceRetry); err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to find original sticker document, falling back to reupload")
|
||||
} else if origFile != nil {
|
||||
return origFile, nil
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/exmaps"
|
||||
"go.mau.fi/util/ffmpeg"
|
||||
"go.mau.fi/util/variationselector"
|
||||
@@ -473,13 +474,18 @@ func (tc *TelegramClient) fnDownloadEmojiPack(ce *commands.Event) {
|
||||
linkType = "addemoji"
|
||||
usage = event.ImagePackUsageEmoji
|
||||
}
|
||||
packURL := fmt.Sprintf("https://t.me/%s/%s", linkType, set.Set.ShortName)
|
||||
pack := &event.ImagePackEventContent{
|
||||
Images: make(map[string]*event.ImagePackImage, len(set.Documents)),
|
||||
Metadata: event.ImagePackMetadata{
|
||||
DisplayName: set.Set.Title,
|
||||
AvatarURL: "",
|
||||
Usage: []event.ImagePackUsage{usage},
|
||||
Attribution: fmt.Sprintf("Imported from https://t.me/%s/%s", linkType, set.Set.ShortName),
|
||||
Attribution: fmt.Sprintf("Imported from %s", packURL),
|
||||
BridgedPack: &event.BridgedStickerPack{
|
||||
Network: StickerSourceID,
|
||||
URL: packURL,
|
||||
},
|
||||
},
|
||||
}
|
||||
topLevelExtra := map[string]any{
|
||||
@@ -534,14 +540,10 @@ func (tc *TelegramClient) fnDownloadEmojiPack(ce *commands.Event) {
|
||||
if !set.Set.Emojis {
|
||||
// Stickers need extra info in each sticker so they can be accurately bridged back to Telegram
|
||||
// Custom emojis don't have space for such info and can be used with just the document ID
|
||||
info.Extra = map[string]any{
|
||||
"fi.mau.telegram.sticker": map[string]any{
|
||||
"id": strconv.FormatInt(rawDoc.GetID(), 10),
|
||||
"pack": map[string]any{
|
||||
"short_name": set.Set.ShortName,
|
||||
"id": strconv.FormatInt(set.Set.ID, 10),
|
||||
},
|
||||
},
|
||||
info.BridgedSticker = &event.BridgedSticker{
|
||||
Network: StickerSourceID,
|
||||
ID: strconv.FormatInt(rawDoc.GetID(), 10),
|
||||
PackURL: StickerPackURLPrefix + set.Set.ShortName,
|
||||
}
|
||||
}
|
||||
pack.Images[key] = &event.ImagePackImage{
|
||||
@@ -563,3 +565,104 @@ func (tc *TelegramClient) fnDownloadEmojiPack(ce *commands.Event) {
|
||||
spaceRoom.URI(tc.main.Bridge.Matrix.ServerName()).MatrixToURL()))
|
||||
}
|
||||
}
|
||||
|
||||
const StickerSourceID = "telegram"
|
||||
const StickerPackURLPrefix = "https://t.me/addstickers/"
|
||||
|
||||
func (tc *TelegramClient) stickerSourceFromAttribute(ctx context.Context, documentID int64, attr *tg.DocumentAttributeSticker) *event.BridgedSticker {
|
||||
var shortName string
|
||||
switch set := attr.Stickerset.(type) {
|
||||
case *tg.InputStickerSetID:
|
||||
pack, err := tc.GetCachedStickerPack(ctx, "", set, false)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Debug().Err(err).
|
||||
Int64("pack_id", set.ID).
|
||||
Msg("Failed to get sticker pack by ID to fill info")
|
||||
return nil
|
||||
}
|
||||
shortName = pack.meta.ShortName
|
||||
case *tg.InputStickerSetShortName:
|
||||
shortName = set.ShortName
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return &event.BridgedSticker{
|
||||
Network: StickerSourceID,
|
||||
ID: strconv.FormatInt(documentID, 10),
|
||||
Emoji: attr.Alt,
|
||||
PackURL: StickerPackURLPrefix + shortName,
|
||||
}
|
||||
}
|
||||
|
||||
type stickerPackCache struct {
|
||||
docs map[int64]*tg.Document
|
||||
meta tg.StickerSet
|
||||
}
|
||||
|
||||
func (tc *TelegramClient) GetCachedStickerPack(ctx context.Context, shortName string, id *tg.InputStickerSetID, forceClearCache bool) (*stickerPackCache, error) {
|
||||
tc.stickerPackCacheLock.Lock()
|
||||
defer tc.stickerPackCacheLock.Unlock()
|
||||
cacheName := strings.ToLower(shortName)
|
||||
cache, ok := tc.stickerPacksByName[cacheName]
|
||||
if !ok {
|
||||
cache, ok = tc.stickerPacksByID[id.GetID()]
|
||||
}
|
||||
if !ok || forceClearCache {
|
||||
var inputSet tg.InputStickerSetClass = id
|
||||
if id == nil {
|
||||
inputSet = &tg.InputStickerSetShortName{ShortName: shortName}
|
||||
}
|
||||
resp, err := tc.client.API().MessagesGetStickerSet(ctx, &tg.MessagesGetStickerSetRequest{Stickerset: inputSet})
|
||||
if err != nil {
|
||||
if tgerr.Is(err, tg.ErrStickersetInvalid) {
|
||||
if cacheName != "" {
|
||||
tc.stickerPacksByName[cacheName] = nil
|
||||
}
|
||||
if id != nil {
|
||||
tc.stickerPacksByID[id.GetID()] = nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get sticker set: %w", err)
|
||||
}
|
||||
set, ok := resp.AsModified()
|
||||
if !ok {
|
||||
if cacheName != "" {
|
||||
tc.stickerPacksByName[cacheName] = nil
|
||||
}
|
||||
if id != nil {
|
||||
tc.stickerPacksByID[id.GetID()] = nil
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected response type for MessagesGetStickerSet: %T", resp)
|
||||
}
|
||||
cache = &stickerPackCache{
|
||||
docs: set.MapDocuments().DocumentToMap(),
|
||||
meta: set.Set,
|
||||
}
|
||||
tc.stickerPacksByName[strings.ToLower(set.Set.ShortName)] = cache
|
||||
tc.stickerPacksByID[set.Set.ID] = cache
|
||||
}
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
func (tc *TelegramClient) findOriginalStickerDocument(ctx context.Context, meta *event.BridgedSticker, forceClearCache bool) (tg.InputMediaClass, error) {
|
||||
if meta == nil || !strings.HasPrefix(meta.PackURL, StickerPackURLPrefix) {
|
||||
return nil, nil
|
||||
}
|
||||
shortName := strings.TrimPrefix(meta.PackURL, StickerPackURLPrefix)
|
||||
if shortName == "" {
|
||||
return nil, nil
|
||||
}
|
||||
idNum, err := strconv.ParseInt(meta.ID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
cache, err := tc.GetCachedStickerPack(ctx, shortName, nil, forceClearCache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stickerDoc, ok := cache.docs[idNum]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return &tg.InputMediaDocument{ID: stickerDoc.AsInput()}, nil
|
||||
}
|
||||
|
||||
@@ -144,6 +144,11 @@ func (t *Transferer) WithStickerConfig(cfg AnimatedStickerConfig) *Transferer {
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transferer) WithStickerMetadata(meta *event.BridgedSticker) *Transferer {
|
||||
t.fileInfo.BridgedSticker = meta
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Transferer) WithForceWebmStickerConvert(force bool) *Transferer {
|
||||
if force {
|
||||
t.animatedStickerConfig.ConvertFromWebm = true
|
||||
|
||||
@@ -618,21 +618,9 @@ func (tc *TelegramClient) convertMediaRequiringUpload(
|
||||
content.FileName = content.Body
|
||||
content.Body = a.Alt
|
||||
}
|
||||
stickerInfo := map[string]any{"alt": a.Alt, "id": strconv.FormatInt(document.ID, 10)}
|
||||
|
||||
if setID, ok := a.Stickerset.(*tg.InputStickerSetID); ok {
|
||||
stickerInfo["pack"] = map[string]any{
|
||||
"id": strconv.FormatInt(setID.ID, 10),
|
||||
"access_hash": strconv.FormatInt(setID.AccessHash, 10),
|
||||
"access_hash_source": strconv.FormatInt(tc.telegramUserID, 10),
|
||||
}
|
||||
} else if shortName, ok := a.Stickerset.(*tg.InputStickerSetShortName); ok {
|
||||
stickerInfo["pack"] = map[string]any{
|
||||
"short_name": shortName.ShortName,
|
||||
}
|
||||
}
|
||||
extraInfo["fi.mau.telegram.sticker"] = stickerInfo
|
||||
transferer = transferer.WithStickerConfig(tc.main.Config.AnimatedSticker)
|
||||
transferer = transferer.
|
||||
WithStickerConfig(tc.main.Config.AnimatedSticker).
|
||||
WithStickerMetadata(tc.stickerSourceFromAttribute(ctx, document.ID, a))
|
||||
case *tg.DocumentAttributeAnimated:
|
||||
isVideoGif = true
|
||||
extraInfo["fi.mau.telegram.gif"] = true
|
||||
|
||||
Reference in New Issue
Block a user