421 lines
20 KiB
Go
421 lines
20 KiB
Go
package memory
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
const suggestedPostSettlementAge = 24 * 60 * 60
|
|
|
|
type memorySuggestedPostKey struct {
|
|
monoforumID int64
|
|
messageID int
|
|
}
|
|
|
|
type memorySuggestedPostApproval struct {
|
|
actorUserID int64
|
|
parentID int64
|
|
savedPeer domain.Peer
|
|
state domain.SuggestedPostLifecycleState
|
|
price *domain.SuggestedPostPrice
|
|
scheduleDate int
|
|
publishedMessageID int
|
|
settlementDue int
|
|
lastResult domain.ToggleSuggestedPostApprovalResult
|
|
}
|
|
|
|
func (s *ChannelStore) ToggleSuggestedPostApproval(_ context.Context, req domain.ToggleSuggestedPostApprovalRequest) (domain.ToggleSuggestedPostApprovalResult, error) {
|
|
if req.UserID == 0 || req.MonoforumID == 0 || req.MessageID <= 0 || (!req.Reject && strings.TrimSpace(req.RejectComment) != "") {
|
|
return domain.ToggleSuggestedPostApprovalResult{}, domain.ErrSuggestedPostInvalid
|
|
}
|
|
if req.Date == 0 {
|
|
req.Date = int(time.Now().Unix())
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.toggleSuggestedPostApprovalLocked(req)
|
|
}
|
|
|
|
func (s *ChannelStore) toggleSuggestedPostApprovalLocked(req domain.ToggleSuggestedPostApprovalRequest) (domain.ToggleSuggestedPostApprovalResult, error) {
|
|
mono, ok := s.channels[req.MonoforumID]
|
|
if !ok || mono.Deleted || !mono.Monoforum || mono.LinkedMonoforumID == 0 {
|
|
return domain.ToggleSuggestedPostApprovalResult{}, domain.ErrSuggestedPostInvalid
|
|
}
|
|
parent, ok := s.channels[mono.LinkedMonoforumID]
|
|
if !ok || parent.Deleted || !parent.Broadcast || parent.LinkedMonoforumID != mono.ID {
|
|
return domain.ToggleSuggestedPostApprovalResult{}, domain.ErrSuggestedPostInvalid
|
|
}
|
|
idx := -1
|
|
var original domain.ChannelMessage
|
|
for i := range s.messages[mono.ID] {
|
|
candidate := s.messages[mono.ID][i]
|
|
if candidate.ID == req.MessageID && !candidate.Deleted {
|
|
idx, original = i, cloneChannelMessage(candidate)
|
|
break
|
|
}
|
|
}
|
|
if idx < 0 || original.SavedPeer.Type != domain.PeerTypeUser || original.SavedPeer.ID == 0 || original.SuggestedPost == nil {
|
|
return domain.ToggleSuggestedPostApprovalResult{}, domain.ErrSuggestedPostInvalid
|
|
}
|
|
manager := s.members[parent.ID][req.UserID]
|
|
fromSubscriber := original.From.Type == domain.PeerTypeUser
|
|
if fromSubscriber {
|
|
if !manager.CanManageDirectMessages() || (!req.Reject && !manager.CanPostChannelMessages()) {
|
|
return domain.ToggleSuggestedPostApprovalResult{}, domain.ErrSuggestedPostApprovalForbidden
|
|
}
|
|
} else if req.UserID != original.SavedPeer.ID {
|
|
return domain.ToggleSuggestedPostApprovalResult{}, domain.ErrSuggestedPostApprovalForbidden
|
|
}
|
|
key := memorySuggestedPostKey{monoforumID: mono.ID, messageID: original.ID}
|
|
approval, exists := s.suggestedPostApprovals[key]
|
|
if exists && approval.state != domain.SuggestedPostStateBalanceLow {
|
|
out := cloneSuggestedPostResult(approval.lastResult)
|
|
out.Duplicate = true
|
|
return out, nil
|
|
}
|
|
if original.SuggestedPost.Accepted || original.SuggestedPost.Rejected {
|
|
return domain.ToggleSuggestedPostApprovalResult{}, domain.ErrSuggestedPostAlreadyHandled
|
|
}
|
|
price := cloneSuggestedPostPrice(original.SuggestedPost.Price)
|
|
scheduleDate := original.SuggestedPost.ScheduleDate
|
|
if req.ScheduleDate > 0 {
|
|
scheduleDate = req.ScheduleDate
|
|
}
|
|
if !req.Reject && scheduleDate > 0 && (scheduleDate < req.Date+5*60 || scheduleDate > req.Date+31*24*60*60) {
|
|
return domain.ToggleSuggestedPostApprovalResult{}, domain.ErrSuggestedPostInvalid
|
|
}
|
|
recipients := s.monoforumRecipientsLocked(parent.ID, original.SavedPeer.ID)
|
|
base := domain.ToggleSuggestedPostApprovalResult{
|
|
Monoforum: cloneChannel(mono), Parent: cloneChannel(parent), SavedPeer: original.SavedPeer,
|
|
State: domain.SuggestedPostStateBalanceLow, Recipients: recipients,
|
|
}
|
|
if req.Reject {
|
|
original.SuggestedPost.Rejected = true
|
|
original.SuggestedPost.Accepted = false
|
|
original.Pts = s.nextChannelPtsLocked(mono.ID)
|
|
s.messages[mono.ID][idx] = cloneChannelMessage(original)
|
|
edit := domain.ChannelUpdateEvent{ChannelID: mono.ID, Type: domain.ChannelUpdateEditMessage, Pts: original.Pts, PtsCount: 1, Date: req.Date, Message: cloneChannelMessage(original), SenderUserID: req.UserID}
|
|
s.appendChannelEventLocked(edit)
|
|
service, serviceEvent := s.appendSuggestedPostServiceLocked(mono, parent, req.UserID, original.SavedPeer, original.ID, req.Date, domain.ChannelMessageAction{
|
|
Type: domain.ChannelActionSuggestedPostApproval, SuggestedPostRejected: true,
|
|
SuggestedPostRejectComment: strings.TrimSpace(req.RejectComment), SuggestedPostPrice: price,
|
|
})
|
|
mono = s.channels[mono.ID]
|
|
base.Monoforum, base.State = cloneChannel(mono), domain.SuggestedPostStateRejected
|
|
base.OriginalMessage, base.OriginalEvent = cloneChannelMessage(original), cloneChannelEvent(edit)
|
|
base.ServiceMessage, base.ServiceEvent = cloneChannelMessage(service), cloneChannelEvent(serviceEvent)
|
|
approval = memorySuggestedPostApproval{actorUserID: req.UserID, parentID: parent.ID, savedPeer: original.SavedPeer, state: base.State, price: price, lastResult: cloneSuggestedPostResult(base)}
|
|
s.suggestedPostApprovals[key] = approval
|
|
return base, nil
|
|
}
|
|
|
|
starsBalance, tonBalance, enough := s.reserveSuggestedPostPaymentLocked(original.SavedPeer.ID, parent.ID, price)
|
|
if !enough {
|
|
if exists {
|
|
out := cloneSuggestedPostResult(approval.lastResult)
|
|
out.PayerStarsBalance, out.PayerTONBalance = starsBalance, tonBalance
|
|
out.Duplicate = true
|
|
return out, nil
|
|
}
|
|
service, serviceEvent := s.appendSuggestedPostServiceLocked(mono, parent, req.UserID, original.SavedPeer, original.ID, req.Date, domain.ChannelMessageAction{
|
|
Type: domain.ChannelActionSuggestedPostApproval, SuggestedPostBalanceTooLow: true,
|
|
SuggestedPostScheduleDate: scheduleDate, SuggestedPostPrice: price,
|
|
})
|
|
base.Monoforum = cloneChannel(s.channels[mono.ID])
|
|
base.ServiceMessage, base.ServiceEvent = cloneChannelMessage(service), cloneChannelEvent(serviceEvent)
|
|
base.PayerStarsBalance, base.PayerTONBalance = starsBalance, tonBalance
|
|
approval = memorySuggestedPostApproval{actorUserID: req.UserID, parentID: parent.ID, savedPeer: original.SavedPeer, state: base.State, price: price, scheduleDate: scheduleDate, lastResult: cloneSuggestedPostResult(base)}
|
|
s.suggestedPostApprovals[key] = approval
|
|
return base, nil
|
|
}
|
|
|
|
original.SuggestedPost.Accepted = true
|
|
original.SuggestedPost.Rejected = false
|
|
effectivePublishDate := scheduleDate
|
|
if effectivePublishDate == 0 {
|
|
// TDesktop deliberately omits schedule_date for "Publish Now", but
|
|
// renders the approval service action as an absolute date. Persist one
|
|
// effective publication timestamp across the edited suggestion, action
|
|
// and approval record instead of leaking an accepted zero date.
|
|
effectivePublishDate = req.Date
|
|
}
|
|
original.SuggestedPost.ScheduleDate = effectivePublishDate
|
|
original.Pts = s.nextChannelPtsLocked(mono.ID)
|
|
s.messages[mono.ID][idx] = cloneChannelMessage(original)
|
|
edit := domain.ChannelUpdateEvent{ChannelID: mono.ID, Type: domain.ChannelUpdateEditMessage, Pts: original.Pts, PtsCount: 1, Date: req.Date, Message: cloneChannelMessage(original), SenderUserID: req.UserID}
|
|
s.appendChannelEventLocked(edit)
|
|
service, serviceEvent := s.appendSuggestedPostServiceLocked(mono, parent, req.UserID, original.SavedPeer, original.ID, req.Date, domain.ChannelMessageAction{
|
|
Type: domain.ChannelActionSuggestedPostApproval, SuggestedPostScheduleDate: effectivePublishDate, SuggestedPostPrice: price,
|
|
})
|
|
base.Monoforum, base.OriginalMessage, base.OriginalEvent = cloneChannel(s.channels[mono.ID]), cloneChannelMessage(original), cloneChannelEvent(edit)
|
|
base.ServiceMessage, base.ServiceEvent = cloneChannelMessage(service), cloneChannelEvent(serviceEvent)
|
|
base.PayerStarsBalance, base.PayerTONBalance = starsBalance, tonBalance
|
|
base.State = domain.SuggestedPostStateScheduled
|
|
approval = memorySuggestedPostApproval{actorUserID: req.UserID, parentID: parent.ID, savedPeer: original.SavedPeer, state: base.State, price: price, scheduleDate: effectivePublishDate}
|
|
if effectivePublishDate <= req.Date {
|
|
published := s.publishSuggestedPostLocked(parent, original, req.UserID, req.Date)
|
|
base.Published = &published
|
|
approval.publishedMessageID = published.Message.ID
|
|
if price == nil {
|
|
base.State = domain.SuggestedPostStateCompleted
|
|
} else {
|
|
base.State = domain.SuggestedPostStatePublished
|
|
approval.settlementDue = req.Date + suggestedPostSettlementAge
|
|
}
|
|
approval.state = base.State
|
|
}
|
|
approval.lastResult = cloneSuggestedPostResult(base)
|
|
s.suggestedPostApprovals[key] = approval
|
|
return base, nil
|
|
}
|
|
|
|
func (s *ChannelStore) ProcessSuggestedPostLifecycle(_ context.Context, req domain.SuggestedPostLifecycleRequest) ([]domain.ToggleSuggestedPostApprovalResult, error) {
|
|
if req.Now == 0 {
|
|
req.Now = int(time.Now().Unix())
|
|
}
|
|
if req.Limit <= 0 || req.Limit > 100 {
|
|
req.Limit = 100
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
out := make([]domain.ToggleSuggestedPostApprovalResult, 0)
|
|
for key, approval := range s.suggestedPostApprovals {
|
|
if len(out) >= req.Limit {
|
|
break
|
|
}
|
|
if approval.state != domain.SuggestedPostStateScheduled && approval.state != domain.SuggestedPostStatePublished {
|
|
continue
|
|
}
|
|
mono, monoOK := s.channels[key.monoforumID]
|
|
parent, parentOK := s.channels[approval.parentID]
|
|
if !monoOK || !parentOK {
|
|
return out, fmt.Errorf("suggested post lifecycle invariant: missing monoforum %d or parent %d", key.monoforumID, approval.parentID)
|
|
}
|
|
if mono.Deleted || !mono.Monoforum || mono.LinkedMonoforumID != parent.ID || parent.Deleted || !parent.Broadcast || parent.LinkedMonoforumID != mono.ID {
|
|
return out, fmt.Errorf("suggested post lifecycle invariant: broken monoforum link %d <-> %d", mono.ID, parent.ID)
|
|
}
|
|
if approval.scheduleDate <= 0 {
|
|
return out, fmt.Errorf("suggested post lifecycle invariant: state %s has zero publish date", approval.state)
|
|
}
|
|
var original domain.ChannelMessage
|
|
originalFound := false
|
|
for _, message := range s.messages[mono.ID] {
|
|
if message.ID == key.messageID {
|
|
original = cloneChannelMessage(message)
|
|
originalFound = true
|
|
break
|
|
}
|
|
}
|
|
if !originalFound || original.SuggestedPost == nil || !original.SuggestedPost.Accepted || original.SuggestedPost.Rejected {
|
|
return out, fmt.Errorf("suggested post lifecycle invariant: missing or invalid accepted suggestion %d/%d", mono.ID, key.messageID)
|
|
}
|
|
result := domain.ToggleSuggestedPostApprovalResult{Monoforum: cloneChannel(mono), Parent: cloneChannel(parent), SavedPeer: approval.savedPeer, State: approval.state, Recipients: s.monoforumRecipientsLocked(parent.ID, approval.savedPeer.ID)}
|
|
changed := false
|
|
if approval.state == domain.SuggestedPostStateScheduled && original.Deleted {
|
|
if approval.price != nil {
|
|
s.refundSuggestedPostPaymentLocked(approval.savedPeer.ID, approval.price)
|
|
service, event := s.appendSuggestedPostServiceLocked(mono, parent, approval.actorUserID, approval.savedPeer, key.messageID, req.Now, domain.ChannelMessageAction{Type: domain.ChannelActionSuggestedPostRefund})
|
|
result.ServiceMessage, result.ServiceEvent = service, event
|
|
}
|
|
approval.state, result.State, changed = domain.SuggestedPostStateRefunded, domain.SuggestedPostStateRefunded, true
|
|
}
|
|
if approval.state == domain.SuggestedPostStateScheduled && approval.scheduleDate <= req.Now {
|
|
published := s.publishSuggestedPostLocked(parent, original, approval.actorUserID, req.Now)
|
|
result.Published = &published
|
|
approval.publishedMessageID = published.Message.ID
|
|
if approval.price == nil {
|
|
approval.state = domain.SuggestedPostStateCompleted
|
|
} else {
|
|
approval.state = domain.SuggestedPostStatePublished
|
|
approval.settlementDue = req.Now + suggestedPostSettlementAge
|
|
}
|
|
result.State, changed = approval.state, true
|
|
}
|
|
if approval.state == domain.SuggestedPostStatePublished {
|
|
if approval.price == nil || approval.publishedMessageID <= 0 || approval.settlementDue <= 0 {
|
|
return out, fmt.Errorf("suggested post lifecycle invariant: incomplete published state %d/%d", mono.ID, key.messageID)
|
|
}
|
|
deleted := false
|
|
publishedFound := false
|
|
for _, message := range s.messages[parent.ID] {
|
|
if message.ID == approval.publishedMessageID {
|
|
deleted = message.Deleted
|
|
publishedFound = true
|
|
break
|
|
}
|
|
}
|
|
if !publishedFound {
|
|
return out, fmt.Errorf("suggested post lifecycle invariant: missing published message %d/%d", parent.ID, approval.publishedMessageID)
|
|
}
|
|
deleteDate := s.channelMessageDeleteDateLocked(parent.ID, approval.publishedMessageID)
|
|
if deleted && (deleteDate == 0 || deleteDate < approval.settlementDue) {
|
|
s.refundSuggestedPostPaymentLocked(approval.savedPeer.ID, approval.price)
|
|
service, event := s.appendSuggestedPostServiceLocked(mono, parent, approval.actorUserID, approval.savedPeer, key.messageID, req.Now, domain.ChannelMessageAction{Type: domain.ChannelActionSuggestedPostRefund})
|
|
result.ServiceMessage, result.ServiceEvent = service, event
|
|
approval.state, result.State, changed = domain.SuggestedPostStateRefunded, domain.SuggestedPostStateRefunded, true
|
|
} else if approval.settlementDue <= req.Now {
|
|
s.settleSuggestedPostPaymentLocked(parent.ID, approval.price)
|
|
service, event := s.appendSuggestedPostServiceLocked(mono, parent, approval.actorUserID, approval.savedPeer, key.messageID, req.Now, domain.ChannelMessageAction{Type: domain.ChannelActionSuggestedPostSuccess, SuggestedPostPrice: cloneSuggestedPostPrice(approval.price)})
|
|
result.ServiceMessage, result.ServiceEvent = service, event
|
|
approval.state, result.State, changed = domain.SuggestedPostStateCompleted, domain.SuggestedPostStateCompleted, true
|
|
}
|
|
}
|
|
if changed {
|
|
result.Monoforum, result.Parent = cloneChannel(s.channels[mono.ID]), cloneChannel(s.channels[parent.ID])
|
|
approval.lastResult = cloneSuggestedPostResult(result)
|
|
s.suggestedPostApprovals[key] = approval
|
|
out = append(out, result)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *ChannelStore) channelMessageDeleteDateLocked(channelID int64, messageID int) int {
|
|
for i := len(s.events[channelID]) - 1; i >= 0; i-- {
|
|
event := s.events[channelID][i]
|
|
if event.Type != domain.ChannelUpdateDeleteMessages {
|
|
continue
|
|
}
|
|
for _, id := range event.MessageIDs {
|
|
if id == messageID {
|
|
return event.Date
|
|
}
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (s *ChannelStore) reserveSuggestedPostPaymentLocked(payerID, parentID int64, price *domain.SuggestedPostPrice) (*domain.StarsBalance, *int64, bool) {
|
|
if price == nil {
|
|
return nil, nil, true
|
|
}
|
|
switch price.Kind {
|
|
case domain.SuggestedPostPriceStars:
|
|
current, ok := s.starsBalances[payerID]
|
|
if !ok {
|
|
current = domain.DefaultStarsStartingGrant
|
|
}
|
|
balance := &domain.StarsBalance{UserID: payerID, Balance: current, Granted: true}
|
|
if price.Nanos != 0 || current < price.Amount {
|
|
return balance, nil, false
|
|
}
|
|
current -= price.Amount
|
|
s.starsBalances[payerID] = current
|
|
balance.Balance = current
|
|
return balance, nil, true
|
|
case domain.SuggestedPostPriceTON:
|
|
current := s.tonBalances[payerID]
|
|
balance := current
|
|
if current < price.Amount {
|
|
return nil, &balance, false
|
|
}
|
|
current -= price.Amount
|
|
s.tonBalances[payerID] = current
|
|
balance = current
|
|
return nil, &balance, true
|
|
default:
|
|
return nil, nil, false
|
|
}
|
|
}
|
|
|
|
func (s *ChannelStore) refundSuggestedPostPaymentLocked(payerID int64, price *domain.SuggestedPostPrice) {
|
|
if price == nil {
|
|
return
|
|
}
|
|
if price.Kind == domain.SuggestedPostPriceStars {
|
|
s.starsBalances[payerID] += price.Amount
|
|
} else if price.Kind == domain.SuggestedPostPriceTON {
|
|
s.tonBalances[payerID] += price.Amount
|
|
}
|
|
}
|
|
|
|
func (s *ChannelStore) settleSuggestedPostPaymentLocked(parentID int64, price *domain.SuggestedPostPrice) {
|
|
if price == nil {
|
|
return
|
|
}
|
|
credit := price.Amount * paidMessageChannelCommissionPermille / 1000
|
|
if price.Kind == domain.SuggestedPostPriceStars {
|
|
s.channelStarsBalances[parentID] += credit
|
|
} else if price.Kind == domain.SuggestedPostPriceTON {
|
|
s.channelTONBalances[parentID] += credit
|
|
}
|
|
}
|
|
|
|
func (s *ChannelStore) appendSuggestedPostServiceLocked(mono, parent domain.Channel, actor int64, saved domain.Peer, replyID, date int, action domain.ChannelMessageAction) (domain.ChannelMessage, domain.ChannelUpdateEvent) {
|
|
pts := s.nextChannelPtsLocked(mono.ID)
|
|
from := domain.Peer{Type: domain.PeerTypeUser, ID: actor}
|
|
if member, ok := s.members[parent.ID][actor]; ok && member.CanManageDirectMessages() {
|
|
from = domain.Peer{Type: domain.PeerTypeChannel, ID: parent.ID}
|
|
}
|
|
msg := domain.ChannelMessage{ChannelID: mono.ID, ID: s.nextChannelMessageIDLocked(mono.ID), SenderUserID: actor, From: from, SavedPeer: saved, Date: date, Action: cloneChannelMessageAction(&action), ReplyTo: &domain.MessageReply{Peer: domain.Peer{Type: domain.PeerTypeChannel, ID: mono.ID}, MessageID: replyID}, Pts: pts}
|
|
event := domain.ChannelUpdateEvent{ChannelID: mono.ID, Type: domain.ChannelUpdateNewMessage, Pts: pts, PtsCount: 1, Date: date, Message: cloneChannelMessage(msg), SenderUserID: actor}
|
|
s.messages[mono.ID] = append(s.messages[mono.ID], cloneChannelMessage(msg))
|
|
s.appendChannelEventLocked(event)
|
|
mono.TopMessageID, mono.Pts = msg.ID, pts
|
|
s.channels[mono.ID] = mono
|
|
return cloneChannelMessage(msg), cloneChannelEvent(event)
|
|
}
|
|
|
|
func (s *ChannelStore) publishSuggestedPostLocked(parent domain.Channel, original domain.ChannelMessage, actor int64, date int) domain.SendChannelMessageResult {
|
|
msg := cloneChannelMessage(original)
|
|
msg.ChannelID, msg.ID, msg.RandomID, msg.SenderUserID = parent.ID, s.nextChannelMessageIDLocked(parent.ID), 0, actor
|
|
msg.From, msg.SavedPeer, msg.Date, msg.EditDate, msg.Post = domain.Peer{Type: domain.PeerTypeChannel, ID: parent.ID}, domain.Peer{}, date, 0, true
|
|
msg.ReplyTo, msg.PaidMessageStars, msg.Pts, msg.Deleted = nil, 0, s.nextChannelPtsLocked(parent.ID), false
|
|
event := domain.ChannelUpdateEvent{ChannelID: parent.ID, Type: domain.ChannelUpdateNewMessage, Pts: msg.Pts, PtsCount: 1, Date: date, Message: cloneChannelMessage(msg), SenderUserID: actor}
|
|
s.messages[parent.ID] = append(s.messages[parent.ID], cloneChannelMessage(msg))
|
|
s.appendChannelEventLocked(event)
|
|
parent.TopMessageID, parent.Pts = msg.ID, msg.Pts
|
|
s.channels[parent.ID] = parent
|
|
recipients := make([]int64, 0, len(s.members[parent.ID]))
|
|
for id, member := range s.members[parent.ID] {
|
|
if member.Status == domain.ChannelMemberActive {
|
|
recipients = append(recipients, id)
|
|
}
|
|
}
|
|
return domain.SendChannelMessageResult{Channel: cloneChannel(parent), Message: cloneChannelMessage(msg), Event: cloneChannelEvent(event), Recipients: uniqueNonZero(recipients, 0)}
|
|
}
|
|
|
|
func (s *ChannelStore) monoforumRecipientsLocked(parentID, subscriberID int64) []int64 {
|
|
ids := []int64{subscriberID}
|
|
for id, member := range s.members[parentID] {
|
|
if member.CanManageDirectMessages() {
|
|
ids = append(ids, id)
|
|
}
|
|
}
|
|
return uniqueNonZero(ids, 0)
|
|
}
|
|
|
|
func cloneSuggestedPostPrice(in *domain.SuggestedPostPrice) *domain.SuggestedPostPrice {
|
|
if in == nil {
|
|
return nil
|
|
}
|
|
out := *in
|
|
return &out
|
|
}
|
|
|
|
func cloneSuggestedPostResult(in domain.ToggleSuggestedPostApprovalResult) domain.ToggleSuggestedPostApprovalResult {
|
|
in.Monoforum, in.Parent = cloneChannel(in.Monoforum), cloneChannel(in.Parent)
|
|
in.OriginalMessage, in.ServiceMessage = cloneChannelMessage(in.OriginalMessage), cloneChannelMessage(in.ServiceMessage)
|
|
in.OriginalEvent, in.ServiceEvent = cloneChannelEvent(in.OriginalEvent), cloneChannelEvent(in.ServiceEvent)
|
|
in.Recipients = append([]int64(nil), in.Recipients...)
|
|
if in.Published != nil {
|
|
p := *in.Published
|
|
p.Message = cloneChannelMessage(p.Message)
|
|
p.Event = cloneChannelEvent(p.Event)
|
|
p.Recipients = append([]int64(nil), p.Recipients...)
|
|
in.Published = &p
|
|
}
|
|
if in.PayerStarsBalance != nil {
|
|
b := *in.PayerStarsBalance
|
|
in.PayerStarsBalance = &b
|
|
}
|
|
if in.PayerTONBalance != nil {
|
|
b := *in.PayerTONBalance
|
|
in.PayerTONBalance = &b
|
|
}
|
|
return in
|
|
}
|