fix(stargifts): sync correct channel gift notifications
This commit is contained in:
parent
9ac63f8006
commit
5433801380
19 changed files with 893 additions and 26 deletions
|
|
@ -17,7 +17,8 @@ import (
|
|||
)
|
||||
|
||||
// Star gift(payments.* 礼物 RPC):目录 / 购买表单 / 发送 / 收礼列表 / 展示切换 / 转换回 Stars。
|
||||
// 扣费经 r.deps.Stars 账本;用户礼物走私聊服务消息,频道礼物只落 saved gifts + admin log。
|
||||
// 扣费经原子礼物聚合账本;用户礼物走私聊服务消息,频道礼物落 saved gift/admin log,
|
||||
// 并由持久 notification job 向启用通知的礼物管理员投递私聊 service message。
|
||||
|
||||
func starGiftInvalidErr() error { return tgerr.New(400, "STARGIFT_INVALID") }
|
||||
|
||||
|
|
@ -560,6 +561,15 @@ func (r *Router) onPaymentsGetSavedStarGifts(ctx context.Context, req *tg.Paymen
|
|||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if settings, ok := r.deps.Gifts.(interface {
|
||||
NotificationsEnabled(context.Context, int64, int64) (bool, error)
|
||||
}); ok && owner.Type == domain.PeerTypeChannel && r.ensureCanManageStarGiftOwner(ctx, userID, owner) == nil {
|
||||
enabled, settingsErr := settings.NotificationsEnabled(ctx, userID, owner.ID)
|
||||
if settingsErr != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
response.SetChatNotificationsEnabled(enabled)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
|
|
@ -753,6 +763,17 @@ func (r *Router) starGiftRefFromInput(ctx context.Context, userID int64, ref tg.
|
|||
if v == nil || v.MsgID <= 0 {
|
||||
return domain.SavedStarGiftRef{}, false, nil
|
||||
}
|
||||
if resolver, ok := r.deps.Gifts.(interface {
|
||||
ResolveUserMessageRef(context.Context, int64, int) (domain.SavedStarGiftRef, bool, error)
|
||||
}); ok {
|
||||
resolved, found, err := resolver.ResolveUserMessageRef(ctx, userID, v.MsgID)
|
||||
if err != nil {
|
||||
return domain.SavedStarGiftRef{}, false, internalErr()
|
||||
}
|
||||
if found {
|
||||
return resolved, resolved.Valid(), nil
|
||||
}
|
||||
}
|
||||
return domain.SavedStarGiftRef{
|
||||
Owner: domain.Peer{Type: domain.PeerTypeUser, ID: userID},
|
||||
MsgID: v.MsgID,
|
||||
|
|
|
|||
|
|
@ -93,6 +93,48 @@ func (s *uniqueGiftRPCService) UniqueBySlug(_ context.Context, slug string) (dom
|
|||
return s.unique, slug == s.unique.Slug, nil
|
||||
}
|
||||
|
||||
type starGiftMessageAliasRPCService struct {
|
||||
GiftsService
|
||||
viewerUserID int64
|
||||
msgID int
|
||||
ref domain.SavedStarGiftRef
|
||||
}
|
||||
|
||||
func (s *starGiftMessageAliasRPCService) ResolveUserMessageRef(_ context.Context, viewerUserID int64, msgID int) (domain.SavedStarGiftRef, bool, error) {
|
||||
if viewerUserID == s.viewerUserID && msgID == s.msgID {
|
||||
return s.ref, true, nil
|
||||
}
|
||||
return domain.SavedStarGiftRef{}, false, nil
|
||||
}
|
||||
|
||||
func TestStarGiftUserInputResolvesOnlyExplicitChannelNotificationAlias(t *testing.T) {
|
||||
channelRef := domain.SavedStarGiftRef{
|
||||
Owner: domain.Peer{Type: domain.PeerTypeChannel, ID: 8801},
|
||||
SavedID: 91,
|
||||
}
|
||||
service := &starGiftMessageAliasRPCService{viewerUserID: 7102, msgID: 44, ref: channelRef}
|
||||
r := New(Config{DC: 2}, Deps{Gifts: service}, zaptest.NewLogger(t), clock.System)
|
||||
|
||||
resolved, ok, err := r.starGiftRefFromInput(context.Background(), 7102, &tg.InputSavedStarGiftUser{MsgID: 44})
|
||||
if err != nil || !ok || resolved != channelRef {
|
||||
t.Fatalf("channel notification alias = %+v ok=%v err=%v", resolved, ok, err)
|
||||
}
|
||||
fallback, ok, err := r.starGiftRefFromInput(context.Background(), 7102, &tg.InputSavedStarGiftUser{MsgID: 45})
|
||||
wantFallback := domain.SavedStarGiftRef{Owner: domain.Peer{Type: domain.PeerTypeUser, ID: 7102}, MsgID: 45}
|
||||
if err != nil || !ok || fallback != wantFallback {
|
||||
t.Fatalf("unknown notification alias = %+v ok=%v err=%v, want user fallback %+v", fallback, ok, err, wantFallback)
|
||||
}
|
||||
}
|
||||
|
||||
type starGiftNotificationSettingsRPCService struct {
|
||||
GiftsService
|
||||
enabled bool
|
||||
}
|
||||
|
||||
func (s *starGiftNotificationSettingsRPCService) NotificationsEnabled(context.Context, int64, int64) (bool, error) {
|
||||
return s.enabled, nil
|
||||
}
|
||||
|
||||
type craftStarGiftRPCService struct {
|
||||
GiftsService
|
||||
uniques map[string]domain.UniqueStarGift
|
||||
|
|
@ -1154,10 +1196,41 @@ func TestStarGiftChannelSaga(t *testing.T) {
|
|||
if !savedRes.Gifts[0].CanUpgrade {
|
||||
t.Fatal("channel saved gift must advertise upgrade when a collectible pool is available")
|
||||
}
|
||||
ownerSavedRes, err := r.onPaymentsGetSavedStarGifts(ownerCtx, &tg.PaymentsGetSavedStarGiftsRequest{Peer: channelPeer})
|
||||
if err != nil {
|
||||
t.Fatalf("getSavedStarGifts(channel owner): %v", err)
|
||||
}
|
||||
if enabled, ok := ownerSavedRes.GetChatNotificationsEnabled(); !ok || !enabled {
|
||||
t.Fatalf("channel owner notification setting enabled=%v ok=%v, want default true", enabled, ok)
|
||||
}
|
||||
baseGifts := r.deps.Gifts
|
||||
r.deps.Gifts = &starGiftNotificationSettingsRPCService{GiftsService: baseGifts, enabled: false}
|
||||
disabledSavedRes, err := r.onPaymentsGetSavedStarGifts(ownerCtx, &tg.PaymentsGetSavedStarGiftsRequest{Peer: channelPeer})
|
||||
r.deps.Gifts = baseGifts
|
||||
if err != nil {
|
||||
t.Fatalf("getSavedStarGifts(channel notifications disabled): %v", err)
|
||||
}
|
||||
if enabled, ok := disabledSavedRes.GetChatNotificationsEnabled(); !ok || enabled {
|
||||
t.Fatalf("disabled channel notification setting enabled=%v ok=%v, want false/present", enabled, ok)
|
||||
}
|
||||
savedID, ok := savedRes.Gifts[0].GetSavedID()
|
||||
if !ok || savedID <= 0 {
|
||||
t.Fatalf("saved gift saved_id = %d ok %v, want positive", savedID, ok)
|
||||
}
|
||||
baseGifts = r.deps.Gifts
|
||||
r.deps.Gifts = &starGiftMessageAliasRPCService{
|
||||
GiftsService: baseGifts,
|
||||
viewerUserID: sender.ID,
|
||||
msgID: 777,
|
||||
ref: domain.SavedStarGiftRef{Owner: domain.Peer{Type: domain.PeerTypeChannel, ID: channel.ID}, SavedID: savedID},
|
||||
}
|
||||
_, aliasPermissionErr := r.onPaymentsSaveStarGift(senderCtx, &tg.PaymentsSaveStarGiftRequest{
|
||||
Stargift: &tg.InputSavedStarGiftUser{MsgID: 777},
|
||||
})
|
||||
r.deps.Gifts = baseGifts
|
||||
if !tgerr.Is(aliasPermissionErr, "CHAT_ADMIN_REQUIRED") {
|
||||
t.Fatalf("non-admin channel notification alias err=%v, want CHAT_ADMIN_REQUIRED", aliasPermissionErr)
|
||||
}
|
||||
if _, ok := savedRes.Gifts[0].GetMsgID(); ok {
|
||||
t.Fatalf("channel saved gift should not expose inputSavedStarGiftUser.msg_id")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -234,11 +234,7 @@ func collectMessagePeerRefs(msg domain.Message, currentChannelID int64, userIDs,
|
|||
if msg.Media != nil && msg.Media.Contact != nil && msg.Media.Contact.UserID != 0 {
|
||||
userIDs[msg.Media.Contact.UserID] = struct{}{}
|
||||
}
|
||||
if msg.Media != nil && msg.Media.ServiceAction != nil && msg.Media.ServiceAction.RequestedPeer != nil {
|
||||
for _, peer := range msg.Media.ServiceAction.RequestedPeer.Peers {
|
||||
addDomainPeerRef(peer, currentChannelID, userIDs, channelIDs)
|
||||
}
|
||||
}
|
||||
collectServiceActionPeerRefs(msg.Media, currentChannelID, userIDs, channelIDs)
|
||||
collectPollMediaUserRefs(msg.Media, userIDs)
|
||||
collectTodoMediaUserRefs(msg.Media, userIDs)
|
||||
if msg.Reactions != nil {
|
||||
|
|
@ -332,6 +328,7 @@ func collectChannelMessagePeerRefs(msg domain.ChannelMessage, currentChannelID i
|
|||
channelIDs[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
collectStarGiftUniquePeerRefs(msg.Action.StarGiftUnique, currentChannelID, userIDs, channelIDs)
|
||||
}
|
||||
if msg.Reactions != nil {
|
||||
for _, reaction := range msg.Reactions.Recent {
|
||||
|
|
@ -342,6 +339,49 @@ func collectChannelMessagePeerRefs(msg domain.ChannelMessage, currentChannelID i
|
|||
}
|
||||
}
|
||||
|
||||
func collectServiceActionPeerRefs(media *domain.MessageMedia, currentChannelID int64, userIDs, channelIDs map[int64]struct{}) {
|
||||
if media == nil || media.ServiceAction == nil {
|
||||
return
|
||||
}
|
||||
action := media.ServiceAction
|
||||
if action.RequestedPeer != nil {
|
||||
for _, peer := range action.RequestedPeer.Peers {
|
||||
addDomainPeerRef(peer, currentChannelID, userIDs, channelIDs)
|
||||
}
|
||||
}
|
||||
if gift := action.StarGift; gift != nil {
|
||||
if gift.FromUserID != 0 && !gift.NameHidden {
|
||||
userIDs[gift.FromUserID] = struct{}{}
|
||||
}
|
||||
if gift.PeerUserID != 0 {
|
||||
userIDs[gift.PeerUserID] = struct{}{}
|
||||
}
|
||||
if gift.PeerChannelID != 0 && gift.PeerChannelID != currentChannelID {
|
||||
channelIDs[gift.PeerChannelID] = struct{}{}
|
||||
}
|
||||
addDomainPeerRef(gift.To, currentChannelID, userIDs, channelIDs)
|
||||
}
|
||||
collectStarGiftUniquePeerRefs(action.StarGiftUnique, currentChannelID, userIDs, channelIDs)
|
||||
}
|
||||
|
||||
func collectStarGiftUniquePeerRefs(action *domain.MessageStarGiftUniqueAction, currentChannelID int64, userIDs, channelIDs map[int64]struct{}) {
|
||||
if action == nil {
|
||||
return
|
||||
}
|
||||
if action.FromUserID != 0 {
|
||||
userIDs[action.FromUserID] = struct{}{}
|
||||
}
|
||||
addDomainPeerRef(action.Peer, currentChannelID, userIDs, channelIDs)
|
||||
addDomainPeerRef(action.Gift.Owner, currentChannelID, userIDs, channelIDs)
|
||||
addDomainPeerRef(action.Gift.OriginalOwner, currentChannelID, userIDs, channelIDs)
|
||||
addDomainPeerRef(action.Gift.ReleasedBy, currentChannelID, userIDs, channelIDs)
|
||||
addDomainPeerRef(action.Gift.ThemePeer, currentChannelID, userIDs, channelIDs)
|
||||
addDomainPeerRef(action.Gift.Host, currentChannelID, userIDs, channelIDs)
|
||||
if action.Gift.OriginalFromUserID != 0 && !action.Gift.OriginalNameHidden {
|
||||
userIDs[action.Gift.OriginalFromUserID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func addDomainPeerRef(peer domain.Peer, currentChannelID int64, userIDs, channelIDs map[int64]struct{}) {
|
||||
switch peer.Type {
|
||||
case domain.PeerTypeUser:
|
||||
|
|
|
|||
|
|
@ -28,3 +28,96 @@ func TestRemoveKnownChannelRefs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectMessagePeerRefsIncludesStarGiftServiceActions(t *testing.T) {
|
||||
users := map[int64]struct{}{}
|
||||
channels := map[int64]struct{}{}
|
||||
collectMessagePeerRefs(domain.Message{Media: &domain.MessageMedia{
|
||||
Kind: domain.MessageMediaKindService,
|
||||
ServiceAction: &domain.MessageServiceAction{
|
||||
Kind: domain.MessageServiceActionStarGift,
|
||||
StarGift: &domain.MessageStarGiftAction{
|
||||
FromUserID: 1001, PeerChannelID: 55,
|
||||
},
|
||||
},
|
||||
}}, 0, users, channels)
|
||||
if _, ok := users[1001]; !ok {
|
||||
t.Fatalf("ordinary star-gift user refs=%v, missing sender", users)
|
||||
}
|
||||
if _, ok := channels[55]; !ok {
|
||||
t.Fatalf("ordinary star-gift channel refs=%v", channels)
|
||||
}
|
||||
collectMessagePeerRefs(domain.Message{Media: &domain.MessageMedia{
|
||||
Kind: domain.MessageMediaKindService,
|
||||
ServiceAction: &domain.MessageServiceAction{
|
||||
Kind: domain.MessageServiceActionStarGift,
|
||||
StarGift: &domain.MessageStarGiftAction{PeerUserID: 1002},
|
||||
},
|
||||
}}, 0, users, channels)
|
||||
if _, ok := users[1002]; !ok {
|
||||
t.Fatalf("ordinary star-gift recipient refs=%v", users)
|
||||
}
|
||||
|
||||
collectMessagePeerRefs(domain.Message{Media: &domain.MessageMedia{
|
||||
Kind: domain.MessageMediaKindService,
|
||||
ServiceAction: &domain.MessageServiceAction{
|
||||
Kind: domain.MessageServiceActionStarGiftUnique,
|
||||
StarGiftUnique: &domain.MessageStarGiftUniqueAction{
|
||||
FromUserID: 2001,
|
||||
Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: 56},
|
||||
Gift: domain.UniqueStarGift{
|
||||
Owner: domain.Peer{Type: domain.PeerTypeChannel, ID: 56},
|
||||
OriginalFromUserID: 2002,
|
||||
OriginalOwner: domain.Peer{Type: domain.PeerTypeUser, ID: 2003},
|
||||
ReleasedBy: domain.Peer{Type: domain.PeerTypeUser, ID: 2004},
|
||||
ThemePeer: domain.Peer{Type: domain.PeerTypeChannel, ID: 57},
|
||||
Host: domain.Peer{Type: domain.PeerTypeUser, ID: 2005},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}, 0, users, channels)
|
||||
for _, id := range []int64{2001, 2002, 2003, 2004, 2005} {
|
||||
if _, ok := users[id]; !ok {
|
||||
t.Fatalf("unique star-gift user refs=%v, missing %d", users, id)
|
||||
}
|
||||
}
|
||||
for _, id := range []int64{56, 57} {
|
||||
if _, ok := channels[id]; !ok {
|
||||
t.Fatalf("unique star-gift channel refs=%v, missing %d", channels, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectMessagePeerRefsHidesStarGiftSenderDetails(t *testing.T) {
|
||||
users := map[int64]struct{}{}
|
||||
channels := map[int64]struct{}{}
|
||||
collectMessagePeerRefs(domain.Message{Media: &domain.MessageMedia{
|
||||
Kind: domain.MessageMediaKindService,
|
||||
ServiceAction: &domain.MessageServiceAction{
|
||||
Kind: domain.MessageServiceActionStarGift,
|
||||
StarGift: &domain.MessageStarGiftAction{
|
||||
FromUserID: 1001, NameHidden: true, PeerChannelID: 55,
|
||||
},
|
||||
},
|
||||
}}, 0, users, channels)
|
||||
collectMessagePeerRefs(domain.Message{Media: &domain.MessageMedia{
|
||||
Kind: domain.MessageMediaKindService,
|
||||
ServiceAction: &domain.MessageServiceAction{
|
||||
Kind: domain.MessageServiceActionStarGiftUnique,
|
||||
StarGiftUnique: &domain.MessageStarGiftUniqueAction{
|
||||
Gift: domain.UniqueStarGift{
|
||||
OriginalFromUserID: 2001,
|
||||
OriginalNameHidden: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}, 0, users, channels)
|
||||
for _, id := range []int64{1001, 2001} {
|
||||
if _, ok := users[id]; ok {
|
||||
t.Fatalf("hidden star-gift sender %d leaked into refs=%v", id, users)
|
||||
}
|
||||
}
|
||||
if _, ok := channels[55]; !ok {
|
||||
t.Fatalf("hidden ordinary gift lost recipient channel ref=%v", channels)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue