capabilities: update to new format
This commit is contained in:
@@ -2,7 +2,7 @@ module go.mau.fi/mautrix-telegram
|
||||
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.23.3
|
||||
toolchain go1.23.4
|
||||
|
||||
require (
|
||||
github.com/gorilla/mux v1.8.0
|
||||
@@ -10,12 +10,12 @@ require (
|
||||
github.com/gotd/td v0.111.0
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.mau.fi/util v0.8.4-0.20250106152331-30b8c95e7d7a
|
||||
go.mau.fi/util v0.8.4-0.20250110124612-64d4dbbec957
|
||||
go.mau.fi/zerozap v0.1.1
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329
|
||||
golang.org/x/net v0.33.0
|
||||
maunium.net/go/mautrix v0.22.2-0.20250109192415-9748015309bd
|
||||
golang.org/x/net v0.34.0
|
||||
maunium.net/go/mautrix v0.22.2-0.20250110154103-bbcb1904e268
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -76,8 +76,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.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
go.mau.fi/util v0.8.4-0.20250106152331-30b8c95e7d7a h1:D9RCHBFjxah9F/YB7amvRJjT2IEOFWcz8jpcEY8dBV0=
|
||||
go.mau.fi/util v0.8.4-0.20250106152331-30b8c95e7d7a/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||
go.mau.fi/util v0.8.4-0.20250110124612-64d4dbbec957 h1:tsLt3t6ARc55niz+JMgJy6U4sL210Z0K/nyxF09xT0E=
|
||||
go.mau.fi/util v0.8.4-0.20250110124612-64d4dbbec957/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
|
||||
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
||||
go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
||||
go.mau.fi/zerozap v0.1.1 h1:mxE/dW4wtkqBYOXOEEzXldk5qKB+ahsZXjoTGnvEhZQ=
|
||||
@@ -98,8 +98,8 @@ golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588=
|
||||
golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
@@ -119,8 +119,8 @@ 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.22.2-0.20250109192415-9748015309bd h1:v83tY7Tp8vF1/dOH8nP762eDwm5cAuijnj3sjrjYzvc=
|
||||
maunium.net/go/mautrix v0.22.2-0.20250109192415-9748015309bd/go.mod h1:FmwzK7RSzrd1OfGDgJzFWXl7nYmYm8/P0Y77sy/A1Uw=
|
||||
maunium.net/go/mautrix v0.22.2-0.20250110154103-bbcb1904e268 h1:p+3TofdhqiVYIkLjgzidayg2XriGUEbj+nbWs3/UQbk=
|
||||
maunium.net/go/mautrix v0.22.2-0.20250110154103-bbcb1904e268/go.mod h1:07i96D7BALyuAqxFhRzvaId8FC9NABgRQBPY5HWndf4=
|
||||
nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
|
||||
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
// mautrix-telegram - A Matrix-Telegram puppeting bridge.
|
||||
// Copyright (C) 2025 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"go.mau.fi/util/ptr"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/event"
|
||||
)
|
||||
|
||||
func (tg *TelegramConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities {
|
||||
return &bridgev2.NetworkGeneralCapabilities{
|
||||
DisappearingMessages: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (tg *TelegramConnector) GetBridgeInfoVersion() (info, capabilities int) {
|
||||
return 1, 1
|
||||
}
|
||||
|
||||
// TODO get these from getConfig instead of hardcoding?
|
||||
|
||||
const MaxTextLength = 4096
|
||||
const MaxCaptionLength = 1024
|
||||
const MaxFileSize = 2 * 1024 * 1024 * 1024
|
||||
|
||||
var formattingCaps = event.FormattingFeatureMap{
|
||||
event.FmtBold: event.CapLevelFullySupported,
|
||||
event.FmtItalic: event.CapLevelFullySupported,
|
||||
event.FmtUnderline: event.CapLevelFullySupported,
|
||||
event.FmtStrikethrough: event.CapLevelFullySupported,
|
||||
event.FmtInlineCode: event.CapLevelFullySupported,
|
||||
event.FmtCodeBlock: event.CapLevelFullySupported,
|
||||
event.FmtSyntaxHighlighting: event.CapLevelFullySupported,
|
||||
event.FmtBlockquote: event.CapLevelFullySupported,
|
||||
event.FmtInlineLink: event.CapLevelFullySupported,
|
||||
event.FmtUserLink: event.CapLevelFullySupported,
|
||||
// TODO support room links and event links (convert to appropriate t.me links)
|
||||
event.FmtUnorderedList: event.CapLevelPartialSupport,
|
||||
event.FmtOrderedList: event.CapLevelPartialSupport,
|
||||
event.FmtListStart: event.CapLevelPartialSupport,
|
||||
event.FmtListJumpValue: event.CapLevelDropped,
|
||||
// TODO support custom emojis in messages
|
||||
event.FmtCustomEmoji: event.CapLevelDropped,
|
||||
event.FmtSpoiler: event.CapLevelFullySupported,
|
||||
event.FmtSpoilerReason: event.CapLevelDropped,
|
||||
event.FmtHeaders: event.CapLevelPartialSupport,
|
||||
}
|
||||
|
||||
var fileCaps = event.FileFeatureMap{
|
||||
event.MsgImage: {
|
||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||
"image/jpeg": event.CapLevelFullySupported,
|
||||
"image/png": event.CapLevelPartialSupport,
|
||||
},
|
||||
Caption: event.CapLevelFullySupported,
|
||||
MaxCaptionLength: MaxCaptionLength,
|
||||
MaxSize: 10 * 1024 * 1024,
|
||||
},
|
||||
event.MsgVideo: {
|
||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||
"video/mp4": event.CapLevelFullySupported,
|
||||
},
|
||||
Caption: event.CapLevelFullySupported,
|
||||
MaxCaptionLength: MaxCaptionLength,
|
||||
MaxSize: MaxFileSize,
|
||||
},
|
||||
event.MsgAudio: {
|
||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||
"audio/mpeg": event.CapLevelFullySupported,
|
||||
"audio/mp4": event.CapLevelFullySupported,
|
||||
// TODO some other formats are probably supported too
|
||||
},
|
||||
Caption: event.CapLevelFullySupported,
|
||||
MaxCaptionLength: MaxCaptionLength,
|
||||
MaxSize: MaxFileSize,
|
||||
},
|
||||
event.MsgFile: {
|
||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||
"*/*": event.CapLevelFullySupported,
|
||||
},
|
||||
Caption: event.CapLevelFullySupported,
|
||||
MaxCaptionLength: MaxCaptionLength,
|
||||
MaxSize: MaxFileSize,
|
||||
},
|
||||
event.CapMsgGIF: {
|
||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||
"image/gif": event.CapLevelPartialSupport,
|
||||
"video/mp4": event.CapLevelFullySupported,
|
||||
},
|
||||
Caption: event.CapLevelFullySupported,
|
||||
MaxCaptionLength: MaxCaptionLength,
|
||||
MaxSize: MaxFileSize,
|
||||
},
|
||||
event.CapMsgSticker: {
|
||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||
"image/webp": event.CapLevelFullySupported,
|
||||
// TODO
|
||||
//"image/lottie+json": event.CapLevelFullySupported,
|
||||
//"video/webm": event.CapLevelFullySupported,
|
||||
},
|
||||
},
|
||||
event.CapMsgVoice: {
|
||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||
"audio/ogg": event.CapLevelFullySupported,
|
||||
"audio/mpeg": event.CapLevelFullySupported,
|
||||
"audio/mp4": event.CapLevelFullySupported,
|
||||
},
|
||||
Caption: event.CapLevelFullySupported,
|
||||
MaxCaptionLength: MaxCaptionLength,
|
||||
MaxSize: MaxFileSize,
|
||||
},
|
||||
}
|
||||
var premiumFileCaps event.FileFeatureMap
|
||||
|
||||
func init() {
|
||||
premiumFileCaps = make(event.FileFeatureMap, len(fileCaps))
|
||||
for k, v := range fileCaps {
|
||||
cloned := ptr.Clone(v)
|
||||
if k == event.MsgFile || k == event.MsgVideo || k == event.MsgAudio {
|
||||
cloned.MaxSize *= 2
|
||||
}
|
||||
cloned.MaxCaptionLength *= 2
|
||||
premiumFileCaps[k] = cloned
|
||||
}
|
||||
}
|
||||
|
||||
func hashEmojiList(emojis []string) string {
|
||||
hasher := sha256.New()
|
||||
for _, emoji := range emojis {
|
||||
hasher.Write([]byte(emoji))
|
||||
}
|
||||
return hex.EncodeToString(hasher.Sum(nil))[:8]
|
||||
}
|
||||
|
||||
func (t *TelegramClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *event.RoomFeatures {
|
||||
baseID := "fi.mau.telegram.capabilities.2025_01_10"
|
||||
feat := &event.RoomFeatures{
|
||||
Formatting: formattingCaps,
|
||||
File: fileCaps,
|
||||
MaxTextLength: MaxTextLength,
|
||||
LocationMessage: event.CapLevelFullySupported,
|
||||
Reply: event.CapLevelFullySupported,
|
||||
Edit: event.CapLevelFullySupported,
|
||||
Delete: event.CapLevelFullySupported,
|
||||
Reaction: event.CapLevelFullySupported,
|
||||
ReadReceipts: true,
|
||||
TypingNotifications: true,
|
||||
}
|
||||
// TODO non-admins can only edit messages within 48 hours
|
||||
|
||||
reactions := portal.Metadata.(*PortalMetadata).AllowedReactions
|
||||
if reactions == nil {
|
||||
baseID += "+reactions_any"
|
||||
feat.AllowedReactions, feat.CustomEmojiReactions = t.getAvailableReactionsForCapability(ctx)
|
||||
} else if len(reactions) == 0 {
|
||||
baseID += "+reactions_none"
|
||||
feat.Reaction = event.CapLevelRejected
|
||||
} else {
|
||||
baseID += "+reactions_" + hashEmojiList(reactions)
|
||||
feat.AllowedReactions = reactions
|
||||
}
|
||||
if t.isPremiumCache.Load() {
|
||||
baseID += "+premium"
|
||||
feat.File = premiumFileCaps
|
||||
}
|
||||
feat.ID = baseID
|
||||
return feat
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/gotd/td/tg"
|
||||
@@ -124,7 +125,34 @@ func (t *TelegramClient) getGroupChatInfo(fullChat *tg.MessagesChatFull, chatID
|
||||
},
|
||||
CanBackfill: true,
|
||||
ExtraUpdates: func(ctx context.Context, p *bridgev2.Portal) bool {
|
||||
return p.Metadata.(*PortalMetadata).SetIsSuperGroup(isMegagroup)
|
||||
meta := p.Metadata.(*PortalMetadata)
|
||||
changed := meta.SetIsSuperGroup(isMegagroup)
|
||||
|
||||
if reactions, ok := fullChat.FullChat.GetAvailableReactions(); ok {
|
||||
switch typedReactions := reactions.(type) {
|
||||
case *tg.ChatReactionsAll:
|
||||
changed = meta.AllowedReactions != nil
|
||||
meta.AllowedReactions = nil
|
||||
case *tg.ChatReactionsNone:
|
||||
changed = meta.AllowedReactions == nil || len(meta.AllowedReactions) > 0
|
||||
meta.AllowedReactions = []string{}
|
||||
case *tg.ChatReactionsSome:
|
||||
allowedReactions := make([]string, 0, len(typedReactions.Reactions))
|
||||
for _, react := range typedReactions.Reactions {
|
||||
emoji, ok := react.(*tg.ReactionEmoji)
|
||||
if ok {
|
||||
allowedReactions = append(allowedReactions, emoji.Emoticon)
|
||||
}
|
||||
}
|
||||
slices.Sort(allowedReactions)
|
||||
if !slices.Equal(meta.AllowedReactions, allowedReactions) {
|
||||
changed = true
|
||||
meta.AllowedReactions = allowedReactions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+3
-16
@@ -9,6 +9,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -58,6 +59,8 @@ type TelegramClient struct {
|
||||
availableReactions map[string]struct{}
|
||||
availableReactionsHash int
|
||||
availableReactionsFetched time.Time
|
||||
availableReactionsList []string
|
||||
isPremiumCache atomic.Bool
|
||||
|
||||
telegramFmtParams *telegramfmt.FormatParams
|
||||
matrixParser *matrixfmt.HTMLParser
|
||||
@@ -717,22 +720,6 @@ func (t *TelegramClient) IsThisUser(ctx context.Context, userID networkid.UserID
|
||||
return userID == networkid.UserID(t.userLogin.ID)
|
||||
}
|
||||
|
||||
func (t *TelegramClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *bridgev2.NetworkRoomCapabilities {
|
||||
return &bridgev2.NetworkRoomCapabilities{
|
||||
FormattedText: true,
|
||||
UserMentions: true,
|
||||
RoomMentions: true, // TODO?
|
||||
LocationMessages: true,
|
||||
Captions: true,
|
||||
Threads: false, // TODO
|
||||
Replies: true,
|
||||
Edits: true,
|
||||
Deletes: true,
|
||||
ReadReceipts: true,
|
||||
Reactions: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TelegramClient) mySender() bridgev2.EventSender {
|
||||
return bridgev2.EventSender{
|
||||
IsFromMe: true,
|
||||
|
||||
@@ -68,9 +68,3 @@ func (tg *TelegramConnector) GetName() bridgev2.BridgeName {
|
||||
DefaultCommandPrefix: "!tg",
|
||||
}
|
||||
}
|
||||
|
||||
func (tg *TelegramConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities {
|
||||
return &bridgev2.NetworkGeneralCapabilities{
|
||||
DisappearingMessages: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,10 @@ func (t *TelegramClient) transferMediaToTelegram(ctx context.Context, content *e
|
||||
attributes = append(attributes, &tg.DocumentAttributeImageSize{W: content.Info.Width, H: content.Info.Height})
|
||||
}
|
||||
|
||||
if content.Info != nil && content.Info.MauGIF {
|
||||
attributes = append(attributes, &tg.DocumentAttributeAnimated{})
|
||||
}
|
||||
|
||||
if sticker {
|
||||
attributes = append(attributes, &tg.DocumentAttributeSticker{
|
||||
Alt: content.Body,
|
||||
|
||||
@@ -29,9 +29,10 @@ type GhostMetadata struct {
|
||||
}
|
||||
|
||||
type PortalMetadata struct {
|
||||
IsSuperGroup bool `json:"is_supergroup,omitempty"`
|
||||
ReadUpTo int `json:"read_up_to,omitempty"`
|
||||
MessagesTTL int `json:"messages_ttl,omitempty"`
|
||||
IsSuperGroup bool `json:"is_supergroup,omitempty"`
|
||||
ReadUpTo int `json:"read_up_to,omitempty"`
|
||||
MessagesTTL int `json:"messages_ttl,omitempty"`
|
||||
AllowedReactions []string `json:"allowed_reactions"`
|
||||
}
|
||||
|
||||
func (pm *PortalMetadata) SetIsSuperGroup(isSupergroup bool) (changed bool) {
|
||||
|
||||
@@ -898,6 +898,14 @@ func (t *TelegramClient) getAppConfigCached(ctx context.Context) (map[string]any
|
||||
return t.appConfig, nil
|
||||
}
|
||||
|
||||
func (t *TelegramClient) getAvailableReactionsForCapability(ctx context.Context) ([]string, bool) {
|
||||
_, err := t.getAvailableReactions(ctx)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get available reactions for capability listing")
|
||||
}
|
||||
return t.availableReactionsList, t.isPremiumCache.Load()
|
||||
}
|
||||
|
||||
func (t *TelegramClient) getAvailableReactions(ctx context.Context) (map[string]struct{}, error) {
|
||||
log := zerolog.Ctx(ctx).With().Str("handler", "get_available_reactions").Logger()
|
||||
t.availableReactionsLock.Lock()
|
||||
@@ -929,6 +937,15 @@ func (t *TelegramClient) getAvailableReactions(ctx context.Context) (map[string]
|
||||
}
|
||||
|
||||
t.availableReactionsHash = availableReactions.Hash
|
||||
if myGhost.Metadata.(*GhostMetadata).IsPremium {
|
||||
// All reactions are allowed via the unicodemojipack feature
|
||||
t.availableReactionsList = nil
|
||||
t.isPremiumCache.Store(true)
|
||||
} else {
|
||||
t.availableReactionsList = maps.Keys(t.availableReactions)
|
||||
t.isPremiumCache.Store(false)
|
||||
slices.Sort(t.availableReactionsList)
|
||||
}
|
||||
case *tg.MessagesAvailableReactionsNotModified:
|
||||
log.Debug().Msg("Available reactions not modified")
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user