124 lines
4.2 KiB
Go
124 lines
4.2 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
|
|
"github.com/iamxvbaba/td/tg"
|
|
"go.uber.org/zap"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
// These optional capabilities keep the broad RPC service interfaces stable for compatibility
|
|
// fakes while production app services expose the read-only receipt lookup.
|
|
type privateSendReplayService interface {
|
|
LookupPrivateSendReplay(ctx context.Context, userID int64, req domain.PrivateSendReplayRequest) (domain.SendPrivateTextResult, bool, error)
|
|
}
|
|
|
|
type channelSendReplayService interface {
|
|
LookupChannelSendReplay(ctx context.Context, userID int64, req domain.ChannelSendReplayRequest) (domain.SendChannelMessageResult, bool, error)
|
|
}
|
|
|
|
type outgoingReplayLookup struct {
|
|
private domain.SendPrivateTextResult
|
|
channel domain.SendChannelMessageResult
|
|
found bool
|
|
checked bool
|
|
}
|
|
|
|
// lookupOutgoingReplay is deliberately limited to authenticated sender + destination scope +
|
|
// immutable fingerprint. It does not resolve media/replies/send-as/source messages, consume rate
|
|
// budget, run a send permission gate or emit any realtime/durable side effect.
|
|
func (r *Router) lookupOutgoingReplay(ctx context.Context, userID int64, peer domain.Peer, randomID int64, fingerprint []byte) (outgoingReplayLookup, error) {
|
|
if randomID == 0 || len(fingerprint) != sha256.Size {
|
|
return outgoingReplayLookup{}, nil
|
|
}
|
|
switch peer.Type {
|
|
case domain.PeerTypeUser:
|
|
service, ok := r.deps.Messages.(privateSendReplayService)
|
|
if !ok {
|
|
return outgoingReplayLookup{}, nil
|
|
}
|
|
res, found, err := service.LookupPrivateSendReplay(ctx, userID, domain.PrivateSendReplayRequest{
|
|
SenderUserID: userID,
|
|
RecipientUserID: peer.ID,
|
|
RandomID: randomID,
|
|
IdempotencyFingerprint: fingerprint,
|
|
})
|
|
if err != nil {
|
|
r.log.Warn("private send replay lookup failed",
|
|
append(r.contextLogFields(ctx),
|
|
zap.Error(err),
|
|
zap.Int64("recipient_user_id", peer.ID),
|
|
)...)
|
|
return outgoingReplayLookup{checked: true}, messageSendErr(err)
|
|
}
|
|
return outgoingReplayLookup{private: res, found: found, checked: true}, nil
|
|
case domain.PeerTypeChannel:
|
|
return r.lookupChannelSendReplay(ctx, userID, peer.ID, domain.Peer{}, randomID, fingerprint)
|
|
default:
|
|
return outgoingReplayLookup{}, peerIDInvalidErr()
|
|
}
|
|
}
|
|
|
|
func (r *Router) lookupChannelSendReplay(ctx context.Context, userID, channelID int64, savedPeer domain.Peer, randomID int64, fingerprint []byte) (outgoingReplayLookup, error) {
|
|
if randomID == 0 || len(fingerprint) != sha256.Size {
|
|
return outgoingReplayLookup{}, nil
|
|
}
|
|
service, ok := r.deps.Channels.(channelSendReplayService)
|
|
if !ok {
|
|
return outgoingReplayLookup{}, nil
|
|
}
|
|
res, found, err := service.LookupChannelSendReplay(ctx, userID, domain.ChannelSendReplayRequest{
|
|
ChannelID: channelID,
|
|
SenderUserID: userID,
|
|
SavedPeer: savedPeer,
|
|
RandomID: randomID,
|
|
IdempotencyFingerprint: fingerprint,
|
|
})
|
|
if err != nil {
|
|
return outgoingReplayLookup{checked: true}, channelInvalidErr(err)
|
|
}
|
|
return outgoingReplayLookup{channel: res, found: found, checked: true}, nil
|
|
}
|
|
|
|
func (r *Router) outgoingReplayUpdates(ctx context.Context, userID int64, peer domain.Peer, randomID int64, replay outgoingReplayLookup) tg.UpdatesClass {
|
|
if peer.Type == domain.PeerTypeChannel {
|
|
return r.channelMessageUpdatesWithPeerCache(ctx, userID, replay.channel, randomID, newViewerPeerCache(r))
|
|
}
|
|
return tgPrivateSendResultUpdates(replay.private, randomID, true, nil, nil)
|
|
}
|
|
|
|
func combineSendUpdates(results []tg.UpdatesClass) *tg.Updates {
|
|
combined := make([]tg.UpdateClass, 0, len(results)*2)
|
|
usersByID := map[int64]tg.UserClass{}
|
|
chatsByID := map[int64]tg.ChatClass{}
|
|
date := 0
|
|
for _, result := range results {
|
|
upd, ok := result.(*tg.Updates)
|
|
if !ok || upd == nil {
|
|
continue
|
|
}
|
|
combined = append(combined, upd.Updates...)
|
|
for _, user := range upd.Users {
|
|
if id := userClassID(user); id != 0 {
|
|
usersByID[id] = user
|
|
}
|
|
}
|
|
for _, chat := range upd.Chats {
|
|
if id := chatClassID(chat); id != 0 {
|
|
chatsByID[id] = chat
|
|
}
|
|
}
|
|
if upd.Date > date {
|
|
date = upd.Date
|
|
}
|
|
}
|
|
return &tg.Updates{
|
|
Updates: combined,
|
|
Users: mapValuesUsers(usersByID),
|
|
Chats: mapValuesChats(chatsByID),
|
|
Date: date,
|
|
}
|
|
}
|