fix: emit channel avatar service messages

This commit is contained in:
A 2026-07-05 15:14:41 +08:00
parent 4d9f1e271d
commit c38dd73fdd
14 changed files with 346 additions and 27 deletions

View file

@ -516,11 +516,11 @@ func (s *Service) SetSignatures(ctx context.Context, userID, channelID int64, en
}
// SetPhoto 设置/清除频道头像photo==nil 表示清除)。
func (s *Service) SetPhoto(ctx context.Context, userID, channelID int64, photo *domain.Photo) (domain.Channel, error) {
func (s *Service) SetPhoto(ctx context.Context, userID, channelID int64, photo *domain.Photo, date int) (domain.SetChannelPhotoResult, error) {
if s == nil || s.channels == nil || userID == 0 || channelID == 0 {
return domain.Channel{}, domain.ErrChannelInvalid
return domain.SetChannelPhotoResult{}, domain.ErrChannelInvalid
}
return s.channels.SetChannelPhoto(ctx, userID, channelID, photo)
return s.channels.SetChannelPhoto(ctx, userID, channelID, photo, date)
}
// SetPreHistoryHidden toggles hidden history for new supergroup members.

View file

@ -768,6 +768,83 @@ func TestCreateChatCreatesMegagroupWithChannelPts(t *testing.T) {
}
}
func TestSetChannelPhotoCreatesServiceMessagesAndDifference(t *testing.T) {
ctx := context.Background()
service := NewService(memory.NewChannelStore())
created, err := service.CreateMegagroupFromCreateChat(ctx, 1001, domain.CreateChannelRequest{
Title: "Avatar Team",
MemberUserIDs: []int64{1002},
Date: 10,
})
if err != nil {
t.Fatalf("CreateMegagroupFromCreateChat: %v", err)
}
photo := domain.Photo{
ID: 9001,
AccessHash: 9002,
DCID: 2,
Sizes: []domain.PhotoSize{
{Kind: domain.PhotoSizeKindStripped, Type: "i", Bytes: []byte{1, 2, 3}},
{Kind: domain.PhotoSizeKindDefault, Type: "m", W: 160, H: 160, Size: 4096},
},
}
set, err := service.SetPhoto(ctx, 1001, created.Channel.ID, &photo, 11)
if err != nil {
t.Fatalf("SetPhoto: %v", err)
}
if set.Channel.PhotoID != photo.ID || set.Channel.PhotoDCID != photo.DCID || !slices.Equal(set.Channel.PhotoStripped, []byte{1, 2, 3}) {
t.Fatalf("set channel photo fields = %+v, want photo id/dc/stripped", set.Channel)
}
if set.Channel.TopMessageID != set.Message.ID || set.Channel.Pts != set.Event.Pts {
t.Fatalf("set top/pts = channel %+v message %+v event %+v, want service message as top", set.Channel, set.Message, set.Event)
}
if set.Event.Type != domain.ChannelUpdateNewMessage || set.Event.Pts != created.Event.Pts+1 || set.Event.PtsCount != 1 {
t.Fatalf("set event = %+v, want durable new-message pts", set.Event)
}
if set.Message.Action == nil || set.Message.Action.Type != domain.ChannelActionChatEditPhoto || set.Message.Action.Photo == nil || set.Message.Action.Photo.ID != photo.ID {
t.Fatalf("set action = %+v, want chat_edit_photo with photo snapshot", set.Message.Action)
}
if _, err := service.SetPhoto(ctx, 1001, created.Channel.ID, &photo, 12); !errors.Is(err, domain.ErrChannelNotModified) {
t.Fatalf("duplicate SetPhoto err = %v, want ErrChannelNotModified", err)
}
diff, err := service.GetDifference(ctx, 1002, domain.ChannelDifferenceRequest{ChannelID: created.Channel.ID, Pts: created.Event.Pts, Limit: 10})
if err != nil {
t.Fatalf("GetDifference set photo: %v", err)
}
if !diff.Final || diff.Pts != set.Event.Pts || len(diff.NewMessages) != 1 {
t.Fatalf("diff after set = %+v, want one service message through pts %d", diff, set.Event.Pts)
}
if action := diff.NewMessages[0].Action; action == nil || action.Type != domain.ChannelActionChatEditPhoto || action.Photo == nil || action.Photo.ID != photo.ID {
t.Fatalf("diff set action = %+v, want chat_edit_photo", action)
}
cleared, err := service.SetPhoto(ctx, 1001, created.Channel.ID, nil, 13)
if err != nil {
t.Fatalf("ClearPhoto: %v", err)
}
if cleared.Channel.PhotoID != 0 || cleared.Channel.PhotoDCID != 0 || len(cleared.Channel.PhotoStripped) != 0 {
t.Fatalf("cleared channel photo fields = %+v, want empty photo", cleared.Channel)
}
if cleared.Event.Pts != set.Event.Pts+1 || cleared.Message.Action == nil || cleared.Message.Action.Type != domain.ChannelActionChatDeletePhoto {
t.Fatalf("cleared = event %+v action %+v, want chat_delete_photo next pts", cleared.Event, cleared.Message.Action)
}
clearDiff, err := service.GetDifference(ctx, 1002, domain.ChannelDifferenceRequest{ChannelID: created.Channel.ID, Pts: set.Event.Pts, Limit: 10})
if err != nil {
t.Fatalf("GetDifference clear photo: %v", err)
}
if !clearDiff.Final || clearDiff.Pts != cleared.Event.Pts || len(clearDiff.NewMessages) != 1 {
t.Fatalf("diff after clear = %+v, want one delete-photo service message", clearDiff)
}
if action := clearDiff.NewMessages[0].Action; action == nil || action.Type != domain.ChannelActionChatDeletePhoto {
t.Fatalf("diff clear action = %+v, want chat_delete_photo", action)
}
if _, err := service.SetPhoto(ctx, 1001, created.Channel.ID, nil, 14); !errors.Is(err, domain.ErrChannelNotModified) {
t.Fatalf("duplicate ClearPhoto err = %v, want ErrChannelNotModified", err)
}
}
func TestGroupBotPolicies(t *testing.T) {
ctx := context.Background()
store := memory.NewChannelStore()

View file

@ -509,11 +509,13 @@ type ChannelDialog struct {
type ChannelMessageActionType string
const (
ChannelActionNone ChannelMessageActionType = ""
ChannelActionCreate ChannelMessageActionType = "channel_create"
ChannelActionChatAddUser ChannelMessageActionType = "chat_add_user"
ChannelActionChatDelete ChannelMessageActionType = "chat_delete_user"
ChannelActionChatJoined ChannelMessageActionType = "chat_joined"
ChannelActionNone ChannelMessageActionType = ""
ChannelActionCreate ChannelMessageActionType = "channel_create"
ChannelActionChatAddUser ChannelMessageActionType = "chat_add_user"
ChannelActionChatDelete ChannelMessageActionType = "chat_delete_user"
ChannelActionChatEditPhoto ChannelMessageActionType = "chat_edit_photo"
ChannelActionChatDeletePhoto ChannelMessageActionType = "chat_delete_photo"
ChannelActionChatJoined ChannelMessageActionType = "chat_joined"
// ChannelActionChatJoinedByLink 是经邀请链接加入的服务消息,
// 渲染为 "X joined the group via invite link"。
ChannelActionChatJoinedByLink ChannelMessageActionType = "chat_joined_by_link"
@ -572,6 +574,8 @@ type ChannelMessageAction struct {
StarGift *MessageStarGiftAction
// Wallpaper 仅 set_chat_wallpaper 服务消息使用。
Wallpaper *Wallpaper
// Photo 仅 chat_edit_photo 服务消息使用。
Photo *Photo
}
// ChannelMessage is a single stored message in a channel/supergroup.
@ -1341,6 +1345,16 @@ type UpdateChannelUsernameRequest struct {
Username string
}
// SetChannelPhotoResult describes a channel avatar mutation and its durable
// service message.
type SetChannelPhotoResult struct {
Channel Channel
Message ChannelMessage
Event ChannelUpdateEvent
Recipients []int64
Changed bool
}
// DeleteChannelResult describes a deleted channel.
type DeleteChannelResult struct {
Channel Channel

View file

@ -349,6 +349,21 @@ type Photo struct {
Sizes []PhotoSize `json:"sizes,omitempty"`
}
func ClonePhotoPtr(photo *Photo) *Photo {
if photo == nil {
return nil
}
clone := *photo
clone.FileReference = append([]byte(nil), photo.FileReference...)
clone.Sizes = append([]PhotoSize(nil), photo.Sizes...)
for i := range clone.Sizes {
clone.Sizes[i].Bytes = append([]byte(nil), photo.Sizes[i].Bytes...)
clone.Sizes[i].Sizes = append([]int(nil), photo.Sizes[i].Sizes...)
clone.Sizes[i].BackgroundColors = append([]int(nil), photo.Sizes[i].BackgroundColors...)
}
return &clone
}
// MessageMediaKind 枚举消息可挂载的媒体载荷。
type MessageMediaKind string

View file

@ -1206,6 +1206,10 @@ func TestTDesktopPassiveChannelStubs(t *testing.T) {
}); err != nil {
t.Fatalf("messages.addChatUser legacy wrapper: %v", err)
}
seedPhoto := domain.Photo{ID: 7701, AccessHash: 7702, DCID: 2}
if _, err := f.channels.SetChannelPhoto(f.ctx, owner.ID, channel.ID, &seedPhoto, 1700007701); err != nil {
t.Fatalf("seed channel photo before legacy clear: %v", err)
}
if _, err := r.onMessagesEditChatPhoto(ownerCtx, &tg.MessagesEditChatPhotoRequest{
ChatID: channel.ID,
Photo: &tg.InputChatPhotoEmpty{},

View file

@ -372,11 +372,24 @@ func (r *Router) onChannelsEditPhoto(ctx context.Context, req *tg.ChannelsEditPh
if err != nil {
return nil, err
}
channel, err := r.deps.Channels.SetPhoto(ctx, userID, channelID, photo)
res, err := r.deps.Channels.SetPhoto(ctx, userID, channelID, photo, int(r.clock.Now().Unix()))
if err != nil {
return nil, channelAdminErr(err)
}
return r.channelStateMutationUpdates(ctx, userID, channel), nil
r.invalidateRPCProjectionForChannel(res.Channel.ID)
updates := r.channelPhotoUpdates(ctx, userID, res)
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelStateUpdates(viewerUserID, res.Channel)
})
if res.Event.Pts != 0 {
r.enqueueChannelMessageFanout(ctx, userID, domain.SendChannelMessageResult{
Channel: res.Channel,
Message: res.Message,
Event: res.Event,
Recipients: res.Recipients,
}, nil)
}
return updates, nil
}
func (r *Router) channelTitleUpdates(ctx context.Context, viewerUserID int64, res domain.EditChannelTitleResult) *tg.Updates {
@ -395,6 +408,22 @@ func (r *Router) channelTitleUpdates(ctx context.Context, viewerUserID int64, re
}
}
func (r *Router) channelPhotoUpdates(ctx context.Context, viewerUserID int64, res domain.SetChannelPhotoResult) *tg.Updates {
updates := []tg.UpdateClass{&tg.UpdateChannel{ChannelID: res.Channel.ID}}
if res.Event.Pts != 0 {
if update := tgChannelUpdate(viewerUserID, res.Event); update != nil {
updates = append(updates, update)
}
}
return &tg.Updates{
Updates: updates,
Users: r.tgUsersForIDs(ctx, viewerUserID, []int64{res.Message.SenderUserID}),
Chats: []tg.ChatClass{tgChannelChatMin(viewerUserID, res.Channel)},
Date: int(r.clock.Now().Unix()),
Seq: 0,
}
}
func validChannelTitle(title string) bool {
n := utf8.RuneCountInString(title)
return n > 0 && n <= maxChannelTitleLength

View file

@ -255,6 +255,113 @@ func TestMessagesGetChatsUsesBatchServiceRPC(t *testing.T) {
}
}
func TestChannelsEditPhotoReturnsServiceMessageUpdatesRPC(t *testing.T) {
ctx := context.Background()
userStore := memory.NewUserStore()
owner, err := userStore.Create(ctx, domain.User{AccessHash: 91, Phone: "15550009101", FirstName: "Owner"})
if err != nil {
t.Fatalf("create owner: %v", err)
}
member, err := userStore.Create(ctx, domain.User{AccessHash: 92, Phone: "15550009102", FirstName: "Member"})
if err != nil {
t.Fatalf("create member: %v", err)
}
channelStore := memory.NewChannelStore()
channelService := appchannels.NewService(channelStore)
created, err := channelService.CreateChannel(ctx, owner.ID, domain.CreateChannelRequest{
Title: "Photo RPC",
Megagroup: true,
MemberUserIDs: []int64{member.ID},
Date: 1700001900,
})
if err != nil {
t.Fatalf("create channel: %v", err)
}
files := &fakeFiles{}
photo := files.putPhoto(domain.Photo{
ID: 9901,
AccessHash: 9902,
DCID: 2,
Date: 1700001901,
Sizes: []domain.PhotoSize{
{Kind: domain.PhotoSizeKindStripped, Type: "i", Bytes: []byte{4, 5, 6}},
{Kind: domain.PhotoSizeKindDefault, Type: "m", W: 160, H: 160, Size: 4096},
},
})
r := New(Config{}, Deps{
Users: appusers.NewService(userStore),
Channels: channelService,
Files: files,
}, zaptest.NewLogger(t), clock.System)
input := &tg.InputChannel{ChannelID: created.Channel.ID, AccessHash: created.Channel.AccessHash}
setResult, err := r.onChannelsEditPhoto(WithUserID(ctx, owner.ID), &tg.ChannelsEditPhotoRequest{
Channel: input,
Photo: &tg.InputChatPhoto{ID: &tg.InputPhoto{ID: photo.ID, AccessHash: photo.AccessHash}},
})
if err != nil {
t.Fatalf("channels.editPhoto set: %v", err)
}
setAction := requireChannelPhotoServiceAction(t, setResult, created.Channel.ID)
editPhoto, ok := setAction.(*tg.MessageActionChatEditPhoto)
if !ok {
t.Fatalf("set action = %T, want MessageActionChatEditPhoto", setAction)
}
tgPhoto, ok := editPhoto.Photo.(*tg.Photo)
if !ok || tgPhoto.ID != photo.ID {
t.Fatalf("set action photo = %#v, want tg.Photo id %d", editPhoto.Photo, photo.ID)
}
clearResult, err := r.onChannelsEditPhoto(WithUserID(ctx, owner.ID), &tg.ChannelsEditPhotoRequest{
Channel: input,
Photo: &tg.InputChatPhotoEmpty{},
})
if err != nil {
t.Fatalf("channels.editPhoto clear: %v", err)
}
clearAction := requireChannelPhotoServiceAction(t, clearResult, created.Channel.ID)
if _, ok := clearAction.(*tg.MessageActionChatDeletePhoto); !ok {
t.Fatalf("clear action = %T, want MessageActionChatDeletePhoto", clearAction)
}
}
func requireChannelPhotoServiceAction(t *testing.T, updatesClass tg.UpdatesClass, channelID int64) tg.MessageActionClass {
t.Helper()
updates, ok := updatesClass.(*tg.Updates)
if !ok {
t.Fatalf("updates = %T, want *tg.Updates", updatesClass)
}
hasUpdateChannel := false
var action tg.MessageActionClass
for _, update := range updates.Updates {
switch item := update.(type) {
case *tg.UpdateChannel:
if item.ChannelID == channelID {
hasUpdateChannel = true
}
case *tg.UpdateNewChannelMessage:
service, ok := item.Message.(*tg.MessageService)
if !ok {
t.Fatalf("new channel message = %T, want MessageService", item.Message)
}
action = service.Action
}
}
if !hasUpdateChannel {
t.Fatalf("updates = %+v, want UpdateChannel for %d", updates.Updates, channelID)
}
if action == nil {
t.Fatalf("updates = %+v, want UpdateNewChannelMessage service action", updates.Updates)
}
if len(updates.Chats) == 0 {
t.Fatalf("updates chats empty, want channel projection")
}
if len(updates.Users) == 0 {
t.Fatalf("updates users empty, want service sender projection")
}
return action
}
func TestMessagesGetPeerSettingsUsesResolveChannelForAccessCheck(t *testing.T) {
ctx := context.Background()
userStore := memory.NewUserStore()

View file

@ -205,6 +205,17 @@ func tgChannelMessageAction(action domain.ChannelMessageAction) tg.MessageAction
userID = action.UserIDs[0]
}
return &tg.MessageActionChatDeleteUser{UserID: userID}
case domain.ChannelActionChatEditPhoto:
if action.Photo == nil {
return nil
}
photo := tgPhoto(*action.Photo)
if _, empty := photo.(*tg.PhotoEmpty); empty {
return nil
}
return &tg.MessageActionChatEditPhoto{Photo: photo}
case domain.ChannelActionChatDeletePhoto:
return &tg.MessageActionChatDeletePhoto{}
case domain.ChannelActionEditTitle:
return &tg.MessageActionChatEditTitle{Title: action.Title}
case domain.ChannelActionTopicCreate:

View file

@ -485,7 +485,7 @@ type ChannelsService interface {
ResolvePublicUsername(ctx context.Context, userID int64, username string) (domain.Channel, bool, error)
SearchPublicChannels(ctx context.Context, userID int64, query string, limit int) (domain.PublicChannelSearchResult, error)
SetSignatures(ctx context.Context, userID, channelID int64, enabled bool) (domain.Channel, error)
SetPhoto(ctx context.Context, userID, channelID int64, photo *domain.Photo) (domain.Channel, error)
SetPhoto(ctx context.Context, userID, channelID int64, photo *domain.Photo, date int) (domain.SetChannelPhotoResult, error)
SetPreHistoryHidden(ctx context.Context, userID, channelID int64, enabled bool) (domain.Channel, error)
SetParticipantsHidden(ctx context.Context, userID, channelID int64, enabled bool) (domain.Channel, error)
SetForum(ctx context.Context, userID, channelID int64, enabled, tabs bool) (domain.Channel, error)

View file

@ -40,7 +40,7 @@ type ChannelStore interface {
ResolvePublicChannelUsername(ctx context.Context, viewerUserID int64, username string) (domain.Channel, bool, error)
SearchPublicChannels(ctx context.Context, viewerUserID int64, query string, limit int) (domain.PublicChannelSearchResult, error)
SetSignatures(ctx context.Context, userID, channelID int64, enabled bool) (domain.Channel, error)
SetChannelPhoto(ctx context.Context, userID, channelID int64, photo *domain.Photo) (domain.Channel, error)
SetChannelPhoto(ctx context.Context, userID, channelID int64, photo *domain.Photo, date int) (domain.SetChannelPhotoResult, error)
SetPreHistoryHidden(ctx context.Context, userID, channelID int64, enabled bool) (domain.Channel, error)
SetParticipantsHidden(ctx context.Context, userID, channelID int64, enabled bool) (domain.Channel, error)
SetForum(ctx context.Context, userID, channelID int64, enabled, tabs bool) (domain.Channel, error)

View file

@ -70,6 +70,7 @@ func cloneChannelMessageAction(in *domain.ChannelMessageAction) *domain.ChannelM
out.StarGift = &g
}
out.Wallpaper = domain.CloneWallpaperPtr(in.Wallpaper)
out.Photo = domain.ClonePhotoPtr(in.Photo)
return &out
}

View file

@ -219,31 +219,56 @@ func (s *ChannelStore) ResolvePublicChannelUsername(_ context.Context, viewerUse
return domain.Channel{}, false, nil
}
func (s *ChannelStore) SetChannelPhoto(_ context.Context, userID, channelID int64, photo *domain.Photo) (domain.Channel, error) {
func (s *ChannelStore) SetChannelPhoto(_ context.Context, userID, channelID int64, photo *domain.Photo, date int) (domain.SetChannelPhotoResult, error) {
if userID == 0 || channelID == 0 {
return domain.Channel{}, domain.ErrChannelInvalid
return domain.SetChannelPhotoResult{}, domain.ErrChannelInvalid
}
if date == 0 {
date = int(time.Now().Unix())
}
s.mu.Lock()
defer s.mu.Unlock()
channel, err := s.channelForMemberLocked(userID, channelID)
if err != nil {
return domain.Channel{}, err
return domain.SetChannelPhotoResult{}, err
}
member := s.members[channelID][userID]
if !canChangeChannelInfo(member) {
return domain.Channel{}, domain.ErrChannelAdminRequired
return domain.SetChannelPhotoResult{}, domain.ErrChannelAdminRequired
}
var action domain.ChannelMessageAction
if photo != nil && photo.ID != 0 {
if channel.PhotoID == photo.ID {
return domain.SetChannelPhotoResult{}, domain.ErrChannelNotModified
}
action = domain.ChannelMessageAction{
Type: domain.ChannelActionChatEditPhoto,
Photo: domain.ClonePhotoPtr(photo),
}
channel.PhotoID = photo.ID
channel.PhotoDCID = photo.DCID
channel.PhotoStripped = domain.StrippedFromSizes(photo.Sizes)
} else {
if channel.PhotoID == 0 {
return domain.SetChannelPhotoResult{}, domain.ErrChannelNotModified
}
action = domain.ChannelMessageAction{Type: domain.ChannelActionChatDeletePhoto}
channel.PhotoID = 0
channel.PhotoDCID = 0
channel.PhotoStripped = nil
}
msg, event := s.appendChannelServiceMessageLocked(channelID, userID, date, action)
channel.TopMessageID = msg.ID
channel.Pts = event.Pts
s.channels[channelID] = channel
return channel, nil
s.upsertChannelDialogLocked(userID, channel, msg, true)
return domain.SetChannelPhotoResult{
Channel: cloneChannel(channel),
Message: cloneChannelMessage(msg),
Event: cloneChannelEvent(event),
Recipients: s.activeMemberIDsLocked(channelID, 0, 0),
Changed: true,
}, nil
}
func (s *ChannelStore) SetSignatures(_ context.Context, userID, channelID int64, enabled bool) (domain.Channel, error) {

View file

@ -361,18 +361,21 @@ func (s *ChannelStore) SetSignatures(ctx context.Context, userID, channelID int6
return channel, nil
}
// SetChannelPhoto 设置/清除频道头像(反范式列)。photo==nil 表示清除。
func (s *ChannelStore) SetChannelPhoto(ctx context.Context, userID, channelID int64, photo *domain.Photo) (domain.Channel, error) {
// SetChannelPhoto 设置/清除频道头像,并生成对应频道服务消息。photo==nil 表示清除。
func (s *ChannelStore) SetChannelPhoto(ctx context.Context, userID, channelID int64, photo *domain.Photo, date int) (domain.SetChannelPhotoResult, error) {
if userID == 0 || channelID == 0 {
return domain.Channel{}, domain.ErrChannelInvalid
return domain.SetChannelPhotoResult{}, domain.ErrChannelInvalid
}
beginner, ok := s.db.(txBeginner)
if !ok {
return domain.Channel{}, fmt.Errorf("set channel photo: db does not support transactions")
return domain.SetChannelPhotoResult{}, fmt.Errorf("set channel photo: db does not support transactions")
}
if date == 0 {
date = nowUnix()
}
tx, err := beginner.Begin(ctx)
if err != nil {
return domain.Channel{}, fmt.Errorf("begin set channel photo: %w", err)
return domain.SetChannelPhotoResult{}, fmt.Errorf("begin set channel photo: %w", err)
}
committed := false
defer func() {
@ -382,36 +385,68 @@ func (s *ChannelStore) SetChannelPhoto(ctx context.Context, userID, channelID in
}()
channel, member, err := s.getChannelForMember(ctx, tx, userID, channelID)
if err != nil {
return domain.Channel{}, err
return domain.SetChannelPhotoResult{}, err
}
if !canChangeChannelInfo(member) {
return domain.Channel{}, domain.ErrChannelAdminRequired
return domain.SetChannelPhotoResult{}, domain.ErrChannelAdminRequired
}
var (
photoID int64
dcID int
stripped []byte
action domain.ChannelMessageAction
)
if photo != nil && photo.ID != 0 {
if channel.PhotoID == photo.ID {
return domain.SetChannelPhotoResult{}, domain.ErrChannelNotModified
}
photoID = photo.ID
dcID = photo.DCID
stripped = domain.StrippedFromSizes(photo.Sizes)
action = domain.ChannelMessageAction{
Type: domain.ChannelActionChatEditPhoto,
Photo: domain.ClonePhotoPtr(photo),
}
} else {
if channel.PhotoID == 0 {
return domain.SetChannelPhotoResult{}, domain.ErrChannelNotModified
}
action = domain.ChannelMessageAction{Type: domain.ChannelActionChatDeletePhoto}
}
if stripped == nil {
stripped = []byte{}
}
if _, err := tx.Exec(ctx, `UPDATE channels SET photo_id = $2, photo_dc_id = $3, photo_stripped = $4, updated_at = now() WHERE id = $1`,
channelID, photoID, dcID, stripped); err != nil {
return domain.Channel{}, fmt.Errorf("update channel photo: %w", err)
return domain.SetChannelPhotoResult{}, fmt.Errorf("update channel photo: %w", err)
}
if s.rowCache != nil {
s.rowCache.delete(channelID)
}
channel.PhotoID = photoID
channel.PhotoDCID = dcID
channel.PhotoStripped = stripped
msg, event, err := s.insertServiceMessage(ctx, tx, channel, userID, date, action)
if err != nil {
return domain.SetChannelPhotoResult{}, err
}
channel.TopMessageID = msg.ID
channel.Pts = event.Pts
if err := upsertChannelDialogTx(ctx, tx, userID, channel, msg, msg.ID, 0); err != nil {
return domain.SetChannelPhotoResult{}, err
}
if err := tx.Commit(ctx); err != nil {
return domain.Channel{}, fmt.Errorf("commit set channel photo: %w", err)
return domain.SetChannelPhotoResult{}, fmt.Errorf("commit set channel photo: %w", err)
}
committed = true
return channel, nil
recipients, _ := s.ListActiveChannelMemberIDs(ctx, userID, channelID, 0)
return domain.SetChannelPhotoResult{
Channel: channel,
Message: msg,
Event: event,
Recipients: recipients,
Changed: true,
}, nil
}
func (s *ChannelStore) SetAutotranslation(ctx context.Context, userID, channelID int64, enabled bool) (domain.Channel, error) {

View file

@ -46,6 +46,7 @@ func cloneChannelMessageAction(action *domain.ChannelMessageAction) *domain.Chan
clone.StarGift = &g
}
clone.Wallpaper = domain.CloneWallpaperPtr(action.Wallpaper)
clone.Photo = domain.ClonePhotoPtr(action.Photo)
return &clone
}