owpengram-server/internal/rpc/messages_reactions_privacy.go

203 lines
7.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rpc
import (
"context"
"github.com/gotd/td/tg"
"go.uber.org/zap"
"telesrv/internal/domain"
)
func (r *Router) onMessagesSetDefaultReaction(ctx context.Context, reaction tg.ReactionClass) (bool, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
parsed, err := domainMessageReactionFromTL(reaction)
if err != nil {
return false, err
}
if svc, ok := r.deps.Account.(accountDefaultReactionService); ok {
if _, err := svc.SetDefaultReaction(ctx, userID, parsed); err != nil {
return false, internalErr()
}
}
return true, nil
}
func (r *Router) onMessagesGetPaidReactionPrivacy(ctx context.Context) (tg.UpdatesClass, error) {
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
settings := domain.DefaultAccountReactionSettings()
if svc, ok := r.deps.Account.(accountPaidReactionPrivacyService); ok {
next, err := svc.GetReactionSettings(ctx, userID)
if err != nil {
return nil, internalErr()
}
settings = next
}
return &tg.Updates{
Updates: []tg.UpdateClass{&tg.UpdatePaidReactionPrivacy{
Private: r.tgPaidReactionPrivacy(ctx, userID, settings.PaidPrivacy),
}},
Users: []tg.UserClass{},
Chats: []tg.ChatClass{},
Date: int(r.clock.Now().Unix()),
Seq: 0,
}, nil
}
func (r *Router) onMessagesTogglePaidReactionPrivacy(ctx context.Context, req *tg.MessagesTogglePaidReactionPrivacyRequest) (bool, error) {
if req.MsgID <= 0 || req.MsgID > domain.MaxMessageBoxID {
return false, messageIDInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return false, internalErr()
}
if _, err := r.checkedDomainPeerFromInputPeer(ctx, userID, req.Peer); err != nil {
return false, err
}
privacy, err := r.domainPaidReactionPrivacy(ctx, userID, req.Private)
if err != nil {
return false, err
}
if svc, ok := r.deps.Account.(accountPaidReactionPrivacyService); ok {
next, err := svc.SetPaidReactionPrivacy(ctx, userID, privacy)
if err != nil {
return false, internalErr()
}
privacy = next.PaidPrivacy
}
r.pushUserUpdates(ctx, userID, &tg.Updates{
Updates: []tg.UpdateClass{&tg.UpdatePaidReactionPrivacy{Private: r.tgPaidReactionPrivacy(ctx, userID, privacy)}},
Users: []tg.UserClass{},
Chats: []tg.ChatClass{},
Date: int(r.clock.Now().Unix()),
Seq: 0,
})
return true, nil
}
// onMessagesSendPaidReaction 为一条广播频道消息发送付费 reaction从 Stars 账本 Debit
// req.Count 星,累计到消息上,返回带 updateMessageReactions含 ReactionPaid 总星数 +
// top reactors与 updateStarsBalance 的 Updates。崩溃约束必须返回合法 Updates——
// DrKLO StarsController 对响应无 instanceof 强转 (TLRPC.Updates)。
func (r *Router) onMessagesSendPaidReaction(ctx context.Context, req *tg.MessagesSendPaidReactionRequest) (tg.UpdatesClass, error) {
if req.MsgID <= 0 || req.MsgID > domain.MaxMessageBoxID {
return nil, messageIDInvalidErr()
}
if req.Count <= 0 || req.Count > domain.MaxPaidReactionStarsPerRequest {
return nil, starsAmountInvalidErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
peer, err := r.checkedDomainPeerFromInputPeer(ctx, userID, req.Peer)
if err != nil {
return nil, err
}
// 付费 reaction 仅用于(广播)频道帖子。
if peer.Type != domain.PeerTypeChannel {
return nil, peerIDInvalidErr()
}
anonymous, err := r.resolvePaidReactionAnonymous(ctx, userID, req)
if err != nil {
return nil, err
}
if r.deps.Stars == nil {
return nil, balanceTooLowErr()
}
paidSvc, ok := r.deps.Channels.(channelPaidReactionService)
if !ok {
return nil, notImplementedErr()
}
channelPeer := domain.Peer{Type: domain.PeerTypeChannel, ID: peer.ID}
// 1. 先从账本 Debit余额不足→真实 BALANCE_TOO_LOW
balance, err := r.deps.Stars.Debit(ctx, userID, int64(req.Count), domain.StarsReasonReaction, channelPeer, "Paid reaction", "")
if err != nil {
return nil, starsErr(err)
}
// 2. 累计付费 reaction失败则补偿退款。
res, err := paidSvc.SendPaidReaction(ctx, userID, domain.SendChannelPaidReactionRequest{
UserID: userID,
ChannelID: peer.ID,
MessageID: req.MsgID,
Stars: int64(req.Count),
Anonymous: anonymous,
Date: int(r.clock.Now().Unix()),
})
if err != nil {
if _, refundErr := r.deps.Stars.Credit(ctx, userID, int64(req.Count), domain.StarsReasonReaction, channelPeer, "Paid reaction refund", ""); refundErr != nil {
r.log.Error("paid reaction refund failed after record error",
zap.Int64("user_id", userID), zap.Int64("channel_id", peer.ID), zap.Int("msg_id", req.MsgID), zap.Error(refundErr))
}
return nil, channelReactionErr(err)
}
// 3. 构建并扇出 updateMessageReactions请求者额外带 updateStarsBalance。
ids := []int{res.Message.ID}
build := func(viewerUserID int64) *tg.Updates {
updates := r.channelPaidReactionUpdates(ctx, userID, viewerUserID, res, ids)
if updates != nil && viewerUserID == userID {
updates.Updates = append(updates.Updates, &tg.UpdateStarsBalance{Balance: &tg.StarsAmount{Amount: balance.Balance}})
}
return updates
}
recipients := append([]int64{res.Message.SenderUserID}, res.Recipients...)
r.pushChannelViewerUpdates(ctx, userID, res.Channel.ID, recipients, build)
return build(userID), nil
}
// resolvePaidReactionAnonymous 计算本次付费 reaction 是否匿名:显式 private 优先,
// 缺省回退用户保存的默认付费 reaction 隐私。
func (r *Router) resolvePaidReactionAnonymous(ctx context.Context, userID int64, req *tg.MessagesSendPaidReactionRequest) (bool, error) {
if private, ok := req.GetPrivate(); ok {
privacy, err := r.domainPaidReactionPrivacy(ctx, userID, private)
if err != nil {
return false, err
}
return privacy.Kind == domain.PaidReactionPrivacyAnonymous, nil
}
if svc, ok := r.deps.Account.(accountPaidReactionPrivacyService); ok {
if settings, err := svc.GetReactionSettings(ctx, userID); err == nil {
return settings.PaidPrivacy.Kind == domain.PaidReactionPrivacyAnonymous, nil
}
}
return false, nil
}
func (r *Router) domainPaidReactionPrivacy(ctx context.Context, userID int64, in tg.PaidReactionPrivacyClass) (domain.PaidReactionPrivacy, error) {
switch typed := in.(type) {
case nil, *tg.PaidReactionPrivacyDefault:
return domain.PaidReactionPrivacy{Kind: domain.PaidReactionPrivacyDefault}, nil
case *tg.PaidReactionPrivacyAnonymous:
return domain.PaidReactionPrivacy{Kind: domain.PaidReactionPrivacyAnonymous}, nil
case *tg.PaidReactionPrivacyPeer:
peer, err := r.checkedDomainPeerFromInputPeer(ctx, userID, typed.Peer)
if err != nil {
return domain.PaidReactionPrivacy{}, err
}
return domain.PaidReactionPrivacy{Kind: domain.PaidReactionPrivacyPeer, Peer: &peer}, nil
default:
return domain.PaidReactionPrivacy{}, inputConstructorInvalidErr()
}
}
func (r *Router) tgPaidReactionPrivacy(ctx context.Context, userID int64, in domain.PaidReactionPrivacy) tg.PaidReactionPrivacyClass {
switch in.Kind {
case domain.PaidReactionPrivacyAnonymous:
return &tg.PaidReactionPrivacyAnonymous{}
case domain.PaidReactionPrivacyPeer:
if in.Peer == nil {
return &tg.PaidReactionPrivacyDefault{}
}
if peer := r.inputPeerForDomainPeer(ctx, userID, *in.Peer); peer != nil {
return &tg.PaidReactionPrivacyPeer{Peer: peer}
}
}
return &tg.PaidReactionPrivacyDefault{}
}