203 lines
7.3 KiB
Go
203 lines
7.3 KiB
Go
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{}
|
||
}
|