userinfo: add support for avoiding contact names/avatars
This commit is contained in:
@@ -449,7 +449,7 @@ func (t *TelegramClient) onConnected(self *tg.User) {
|
||||
ghost, err := t.main.Bridge.GetGhostByID(ctx, t.userID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to get own ghost")
|
||||
} else if wrapped, err := t.wrapUserInfo(ctx, self); err != nil {
|
||||
} else if wrapped, err := t.wrapUserInfo(ctx, self, ghost); err != nil {
|
||||
log.Err(err).Msg("Failed to wrap own user info")
|
||||
} else {
|
||||
ghost.UpdateInfo(ctx, wrapped)
|
||||
|
||||
+17
-18
@@ -56,16 +56,9 @@ type TelegramConfig struct {
|
||||
APIID int `yaml:"api_id"`
|
||||
APIHash string `yaml:"api_hash"`
|
||||
|
||||
DeviceInfo DeviceInfo `yaml:"device_info"`
|
||||
|
||||
AnimatedSticker media.AnimatedStickerConfig `yaml:"animated_sticker"`
|
||||
ImageAsFilePixels int `yaml:"image_as_file_pixels"`
|
||||
|
||||
DisableViewOnce bool `yaml:"disable_view_once"`
|
||||
|
||||
MemberList MemberListConfig `yaml:"member_list"`
|
||||
|
||||
MaxMemberCount int `yaml:"max_member_count"`
|
||||
DeviceInfo DeviceInfo `yaml:"device_info"`
|
||||
AnimatedSticker media.AnimatedStickerConfig `yaml:"animated_sticker"`
|
||||
MemberList MemberListConfig `yaml:"member_list"`
|
||||
|
||||
Ping struct {
|
||||
IntervalSeconds int `yaml:"interval_seconds"`
|
||||
@@ -78,11 +71,14 @@ type TelegramConfig struct {
|
||||
DirectChats bool `yaml:"direct_chats"`
|
||||
} `yaml:"sync"`
|
||||
|
||||
AlwaysCustomEmojiReaction bool `yaml:"always_custom_emoji_reaction"`
|
||||
|
||||
SavedMessagesAvatar id.ContentURIString `yaml:"saved_message_avatar"`
|
||||
|
||||
AlwaysTombstoneOnSupergroupMigration bool `yaml:"always_tombstone_on_supergroup_migration"`
|
||||
ContactAvatars bool `yaml:"contact_avatars"`
|
||||
ContactNames bool `yaml:"contact_names"`
|
||||
MaxMemberCount int `yaml:"max_member_count"`
|
||||
AlwaysCustomEmojiReaction bool `yaml:"always_custom_emoji_reaction"`
|
||||
SavedMessagesAvatar id.ContentURIString `yaml:"saved_message_avatar"`
|
||||
AlwaysTombstoneOnSupergroupMigration bool `yaml:"always_tombstone_on_supergroup_migration"`
|
||||
ImageAsFilePixels int `yaml:"image_as_file_pixels"`
|
||||
DisableViewOnce bool `yaml:"disable_view_once"`
|
||||
}
|
||||
|
||||
func (c TelegramConfig) ShouldBridge(participantCount int) bool {
|
||||
@@ -107,20 +103,22 @@ func upgradeConfig(helper up.Helper) {
|
||||
helper.Copy(up.Int, "animated_sticker", "args", "width")
|
||||
helper.Copy(up.Int, "animated_sticker", "args", "height")
|
||||
helper.Copy(up.Int, "animated_sticker", "args", "fps")
|
||||
helper.Copy(up.Int, "image_as_file_pixels")
|
||||
helper.Copy(up.Bool, "disable_view_once")
|
||||
helper.Copy(up.Int, "member_list", "max_initial_sync")
|
||||
helper.Copy(up.Bool, "member_list", "sync_broadcast_channels")
|
||||
helper.Copy(up.Bool, "member_list", "skip_deleted")
|
||||
helper.Copy(up.Int, "max_member_count")
|
||||
helper.Copy(up.Int, "ping", "interval_seconds")
|
||||
helper.Copy(up.Int, "ping", "timeout_seconds")
|
||||
helper.Copy(up.Int, "sync", "update_limit")
|
||||
helper.Copy(up.Int, "sync", "create_limit")
|
||||
helper.Copy(up.Bool, "sync", "direct_chats")
|
||||
helper.Copy(up.Bool, "contact_avatars")
|
||||
helper.Copy(up.Bool, "contact_names")
|
||||
helper.Copy(up.Int, "max_member_count")
|
||||
helper.Copy(up.Bool, "always_custom_emoji_reaction")
|
||||
helper.Copy(up.Str, "saved_message_avatar")
|
||||
helper.Copy(up.Bool, "always_tombstone_on_supergroup_migration")
|
||||
helper.Copy(up.Int, "image_as_file_pixels")
|
||||
helper.Copy(up.Bool, "disable_view_once")
|
||||
}
|
||||
|
||||
func (tg *TelegramConnector) GetConfig() (example string, data any, upgrader up.Upgrader) {
|
||||
@@ -132,6 +130,7 @@ func (tg *TelegramConnector) GetConfig() (example string, data any, upgrader up.
|
||||
{"member_list"},
|
||||
{"ping"},
|
||||
{"sync"},
|
||||
{"max_member_count"},
|
||||
},
|
||||
Base: ExampleConfig,
|
||||
}
|
||||
|
||||
@@ -30,13 +30,6 @@ animated_sticker:
|
||||
height: 256
|
||||
fps: 25 # only for webm, webp and gif (2, 5, 10, 20 or 25 recommended)
|
||||
|
||||
# Maximum number of pixels in an image before sending to Telegram as a
|
||||
# document. Defaults to 4096x4096 = 16777216.
|
||||
image_as_file_pixels: 16777216
|
||||
|
||||
# Should view-once messages be disabled entirely?
|
||||
disable_view_once: false
|
||||
|
||||
# Settings for syncing the member list for portals.
|
||||
member_list:
|
||||
# Maximum number of members to sync per portal when starting up. Other
|
||||
@@ -53,12 +46,6 @@ member_list:
|
||||
sync_broadcast_channels: false
|
||||
# Whether or not to skip deleted members when syncing members.
|
||||
skip_deleted: true
|
||||
# Maximum number of participants in chats to bridge. Only applies when the
|
||||
# portal is being created. If there are more members when trying to create a
|
||||
# room, the room creation will be cancelled.
|
||||
#
|
||||
# -1 means no limit (which means all chats can be bridged)
|
||||
max_member_count: -1
|
||||
|
||||
# Settings for pings to the Telegram server.
|
||||
ping:
|
||||
@@ -78,13 +65,26 @@ sync:
|
||||
# Whether or not to sync and create portals for direct chats at startup.
|
||||
direct_chats: false
|
||||
|
||||
# Maximum number of participants in chats to bridge. Only applies when the
|
||||
# portal is being created. If there are more members when trying to create a
|
||||
# room, the room creation will be cancelled.
|
||||
#
|
||||
# -1 means no limit (which means all chats can be bridged)
|
||||
max_member_count: -1
|
||||
# Should personal avatars (that are only visible to specific users) be allowed?
|
||||
contact_avatars: false
|
||||
# Should contact names be updated from any source even if a name is already set?
|
||||
contact_names: false
|
||||
# Should the bridge send all unicode reactions as custom emoji reactions to
|
||||
# Telegram? By default, the bridge only uses custom emojis for unicode emojis
|
||||
# that aren't allowed in reactions.
|
||||
always_custom_emoji_reaction: false
|
||||
|
||||
# The avatar to use for the Telegram Saved Messages chat
|
||||
saved_message_avatar: mxc://maunium.net/XhhfHoPejeneOngMyBbtyWDk
|
||||
|
||||
# Create a new room and tombstone the old one when upgrading rooms
|
||||
always_tombstone_on_supergroup_migration: false
|
||||
# Maximum number of pixels in an image before sending to Telegram as a
|
||||
# document. Defaults to 4096x4096 = 16777216.
|
||||
image_as_file_pixels: 16777216
|
||||
# Should view-once messages be disabled entirely?
|
||||
disable_view_once: false
|
||||
|
||||
@@ -716,12 +716,16 @@ func (t *TelegramClient) onUserName(ctx context.Context, e tg.Entities, update *
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
meta := ghost.Metadata.(*GhostMetadata)
|
||||
|
||||
var userInfo bridgev2.UserInfo
|
||||
|
||||
// TODO anti-contact-name logic
|
||||
name := util.FormatFullName(update.FirstName, update.LastName, false, update.UserID)
|
||||
userInfo.Name = &name
|
||||
if meta.ContactSource != 0 && meta.ContactSource != t.telegramUserID && !t.main.Config.ContactNames {
|
||||
// TODO fetch full info to accurately detect if the user is a contact or not
|
||||
userInfo.Name = nil
|
||||
}
|
||||
|
||||
if len(update.Usernames) > 0 {
|
||||
for _, ident := range ghost.Identifiers {
|
||||
@@ -800,7 +804,7 @@ func (t *TelegramClient) updateGhost(ctx context.Context, userID int64, user *tg
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userInfo, err := t.wrapUserInfo(ctx, user)
|
||||
userInfo, err := t.wrapUserInfo(ctx, user, ghost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -44,6 +44,13 @@ type GhostMetadata struct {
|
||||
IsBot bool `json:"is_bot,omitempty"`
|
||||
IsChannel bool `json:"is_channel,omitempty"`
|
||||
Deleted bool `json:"deleted,omitempty"`
|
||||
NotMin bool `json:"not_min,omitempty"`
|
||||
|
||||
ContactSource int64 `json:"contact_source,omitempty"`
|
||||
}
|
||||
|
||||
func (gm *GhostMetadata) IsMin() bool {
|
||||
return !gm.NotMin
|
||||
}
|
||||
|
||||
type PortalMetadata struct {
|
||||
|
||||
+20
-13
@@ -44,19 +44,23 @@ var (
|
||||
|
||||
func (t *TelegramClient) resolveUser(ctx context.Context, user tg.UserClass) (*bridgev2.ResolveIdentifierResponse, error) {
|
||||
networkUserID := ids.MakeUserID(user.GetID())
|
||||
if userInfo, err := t.wrapUserInfo(ctx, user); err != nil {
|
||||
return nil, fmt.Errorf("failed to get user info: %w", err)
|
||||
} else if ghost, err := t.main.Bridge.GetGhostByID(ctx, networkUserID); err != nil {
|
||||
if ghost, err := t.main.Bridge.GetGhostByID(ctx, networkUserID); err != nil {
|
||||
return nil, fmt.Errorf("failed to get ghost: %w", err)
|
||||
} else if userInfo, err := t.wrapUserInfo(ctx, user, ghost); err != nil {
|
||||
return nil, fmt.Errorf("failed to get user info: %w", err)
|
||||
} else {
|
||||
return &bridgev2.ResolveIdentifierResponse{
|
||||
Ghost: ghost,
|
||||
UserID: networkUserID,
|
||||
UserInfo: userInfo,
|
||||
Chat: &bridgev2.CreateChatResponse{
|
||||
PortalKey: t.makePortalKeyFromID(ids.PeerTypeUser, user.GetID(), 0),
|
||||
},
|
||||
}, nil
|
||||
return t.makeResolveIdentifierResponse(ghost, user, userInfo), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TelegramClient) makeResolveIdentifierResponse(ghost *bridgev2.Ghost, user tg.UserClass, info *bridgev2.UserInfo) *bridgev2.ResolveIdentifierResponse {
|
||||
return &bridgev2.ResolveIdentifierResponse{
|
||||
Ghost: ghost,
|
||||
UserID: ids.MakeUserID(user.GetID()),
|
||||
UserInfo: info,
|
||||
Chat: &bridgev2.CreateChatResponse{
|
||||
PortalKey: t.makePortalKeyFromID(ids.PeerTypeUser, user.GetID(), 0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,10 +97,13 @@ func (t *TelegramClient) resolveUserID(ctx context.Context, userID int64) (resp
|
||||
return nil, fmt.Errorf("failed to get user with ID %d: %w", userID, err)
|
||||
} else if user.TypeID() != tg.UserTypeID {
|
||||
return nil, fmt.Errorf("unexpected user type: %T", user)
|
||||
} else if _, err = t.updateGhost(ctx, userID, user.(*tg.User)); err != nil {
|
||||
} else if userInfo, err := t.updateGhost(ctx, userID, user.(*tg.User)); err != nil {
|
||||
return nil, fmt.Errorf("failed to update ghost: %w", err)
|
||||
} else {
|
||||
return t.resolveUser(ctx, user)
|
||||
if resp.Ghost == nil {
|
||||
resp.Ghost, _ = t.main.Bridge.GetExistingGhostByID(ctx, networkUserID)
|
||||
}
|
||||
return t.makeResolveIdentifierResponse(resp.Ghost, user, userInfo), nil
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
@@ -27,7 +27,7 @@ func (t *TelegramClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost)
|
||||
if user, err := t.getSingleUser(ctx, id); err != nil {
|
||||
return nil, fmt.Errorf("failed to get user %d: %w", id, err)
|
||||
} else {
|
||||
return t.wrapUserInfo(ctx, user)
|
||||
return t.wrapUserInfo(ctx, user, ghost)
|
||||
}
|
||||
case ids.PeerTypeChannel:
|
||||
if channel, err := t.getSingleChannel(ctx, id); err != nil {
|
||||
@@ -151,7 +151,8 @@ func (t *TelegramClient) wrapChannelGhostInfo(ctx context.Context, channel *tg.C
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *TelegramClient) wrapUserInfo(ctx context.Context, u tg.UserClass) (*bridgev2.UserInfo, error) {
|
||||
func (t *TelegramClient) wrapUserInfo(ctx context.Context, u tg.UserClass, ghost *bridgev2.Ghost) (*bridgev2.UserInfo, error) {
|
||||
oldMeta := ghost.Metadata.(*GhostMetadata)
|
||||
user, ok := u.(*tg.User)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("user is %T not *tg.User", user)
|
||||
@@ -186,9 +187,10 @@ func (t *TelegramClient) wrapUserInfo(ctx context.Context, u tg.UserClass) (*bri
|
||||
identifiers = slices.Compact(identifiers)
|
||||
|
||||
var avatar *bridgev2.Avatar
|
||||
// TODO don't apply min avatars without checking apply_min_photo or existing min status
|
||||
if p, ok := user.GetPhoto(); ok && p.TypeID() == tg.UserProfilePhotoTypeID {
|
||||
photo := p.(*tg.UserProfilePhoto)
|
||||
photo, ok := user.Photo.(*tg.UserProfilePhoto)
|
||||
if ok &&
|
||||
(!user.Min || user.ApplyMinPhoto || oldMeta.IsMin()) &&
|
||||
(!photo.Personal || t.main.Config.ContactAvatars) {
|
||||
var err error
|
||||
avatar, err = t.convertUserProfilePhoto(ctx, user, photo)
|
||||
if err != nil {
|
||||
@@ -197,18 +199,26 @@ func (t *TelegramClient) wrapUserInfo(ctx context.Context, u tg.UserClass) (*bri
|
||||
}
|
||||
|
||||
name := util.FormatFullName(user.FirstName, user.LastName, user.Deleted, user.ID)
|
||||
namePtr := &name
|
||||
if user.Contact && ghost.Name != "" && oldMeta.ContactSource != t.telegramUserID && oldMeta.ContactSource != 0 && !t.main.Config.ContactNames {
|
||||
namePtr = nil
|
||||
}
|
||||
return &bridgev2.UserInfo{
|
||||
IsBot: &user.Bot,
|
||||
Name: &name,
|
||||
Name: namePtr,
|
||||
Avatar: avatar,
|
||||
Identifiers: identifiers,
|
||||
ExtraUpdates: func(ctx context.Context, ghost *bridgev2.Ghost) (changed bool) {
|
||||
meta := ghost.Metadata.(*GhostMetadata)
|
||||
if !user.Min {
|
||||
changed = changed || meta.IsPremium != user.Premium || meta.IsBot != user.Bot
|
||||
changed = changed || meta.IsPremium != user.Premium || meta.IsBot != user.Bot || meta.IsMin()
|
||||
meta.IsPremium = user.Premium
|
||||
meta.IsBot = user.Bot
|
||||
meta.Deleted = user.Deleted
|
||||
meta.NotMin = true
|
||||
if meta.ContactSource == 0 {
|
||||
meta.ContactSource = t.telegramUserID
|
||||
}
|
||||
}
|
||||
return changed
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user