channels: give kicked/banned/promoted/transferred users a real qts so their client applies it
updateChannelParticipant carries the account's qts per the MTProto spec, but the server always sent Qts: 0, so real clients silently discarded it as a stale duplicate -- the banned/kicked user's channel never vanished locally and no correct "removed by admin" message showed, even though the update was delivered successfully at the transport layer. Add a durable per-device qts queue (channel_participant_event_queue) sharing its qts number space with the existing secret-chat queue (one qts sequence per device, per spec), and use it to stamp a correct, monotonically increasing qts on the update for every device of the affected user -- for channel bans/kicks, admin promotion/demotion, and ownership transfer. A device offline when it happened can now recover the event via updates.getDifference instead of missing it permanently.
This commit is contained in:
parent
97711c9d2e
commit
206bde18e0
16 changed files with 482 additions and 42 deletions
|
|
@ -3,6 +3,7 @@ package rpc
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/iamxvbaba/td/proto"
|
||||
"github.com/iamxvbaba/td/tg"
|
||||
"github.com/iamxvbaba/td/tgerr"
|
||||
"go.uber.org/zap"
|
||||
|
|
@ -451,6 +452,58 @@ func (r *Router) recordChannelStateForUser(ctx context.Context, userID, channelI
|
|||
}
|
||||
}
|
||||
|
||||
// deliverChannelParticipantSelfEvent durably enqueues, and best-effort live
|
||||
// pushes, a correctly qts-numbered updateChannelParticipant to every device of
|
||||
// the affected (non-actor) user. updateChannelParticipant carries the account's
|
||||
// qts per the MTProto spec; a client discards any update whose qts isn't a
|
||||
// proper increment over its own, so the generic pts-based fanout in
|
||||
// pushChannelUpdates -- which always sends qts=0 -- is silently ignored by
|
||||
// real clients for this specific update type. This is the only path that
|
||||
// actually reaches the affected user's own client, both live and (via
|
||||
// AppendChannelParticipantEvent's durable queue, replayed by
|
||||
// updates.getDifference) after being offline when it happened.
|
||||
func (r *Router) deliverChannelParticipantSelfEvent(ctx context.Context, targetUserID, actorUserID int64, channel domain.Channel, previous, participant domain.ChannelMember, date int) {
|
||||
if r.deps.SecretChats == nil || r.deps.Auth == nil || targetUserID == 0 || targetUserID == actorUserID {
|
||||
return
|
||||
}
|
||||
auths, err := r.deps.Auth.ListAuthorizations(ctx, targetUserID)
|
||||
if err != nil || len(auths) == 0 {
|
||||
return
|
||||
}
|
||||
targeted, canTarget := r.deps.Sessions.(AuthKeyTargetedSessionBinder)
|
||||
for _, a := range auths {
|
||||
deviceAuthKeyID := businessAuthKeyInt64(a.AuthKeyID)
|
||||
if deviceAuthKeyID == 0 {
|
||||
continue
|
||||
}
|
||||
stored, err := r.deps.SecretChats.AppendChannelParticipantEvent(ctx, domain.DeviceChannelParticipantEvent{
|
||||
ReceiverAuthKeyID: deviceAuthKeyID,
|
||||
ReceiverUserID: targetUserID,
|
||||
ChannelID: channel.ID,
|
||||
ActorUserID: actorUserID,
|
||||
Date: date,
|
||||
Previous: previous,
|
||||
Participant: participant,
|
||||
})
|
||||
if err != nil || stored.Qts == 0 {
|
||||
continue
|
||||
}
|
||||
if !canTarget {
|
||||
continue
|
||||
}
|
||||
upd := r.channelParticipantUpdates(ctx, targetUserID, actorUserID, channel, previous, participant, date)
|
||||
if upd == nil {
|
||||
continue
|
||||
}
|
||||
for _, u := range upd.Updates {
|
||||
if uc, ok := u.(*tg.UpdateChannelParticipant); ok {
|
||||
uc.Qts = stored.Qts
|
||||
}
|
||||
}
|
||||
_, _ = targeted.PushToUserAuthKey(ctx, targetUserID, deviceAuthKeyBytes(deviceAuthKeyID), proto.MessageFromServer, upd)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) onChannelsEditAdmin(ctx context.Context, req *tg.ChannelsEditAdminRequest) (tg.UpdatesClass, error) {
|
||||
if r.deps.Channels == nil && r.deps.Communities == nil {
|
||||
return nil, notImplementedErr()
|
||||
|
|
@ -511,6 +564,7 @@ func (r *Router) onChannelsEditAdmin(ctx context.Context, req *tg.ChannelsEditAd
|
|||
} else {
|
||||
r.removeOnlineChannelMemberships(res.Channel.ID, res.Participant.UserID)
|
||||
}
|
||||
r.deliverChannelParticipantSelfEvent(ctx, res.Participant.UserID, userID, res.Channel, res.Previous, res.Participant, res.Date)
|
||||
cache := newViewerPeerCache(r)
|
||||
updates := r.channelParticipantUpdatesWithPeerCache(ctx, userID, userID, res.Channel, res.Previous, res.Participant, res.Date, cache)
|
||||
r.pushChannelUpdates(ctx, userID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
|
||||
|
|
@ -557,6 +611,7 @@ func (r *Router) onChannelsEditBanned(ctx context.Context, req *tg.ChannelsEditB
|
|||
if res.Participant.Status == domain.ChannelMemberKicked && res.Previous.Status == domain.ChannelMemberActive {
|
||||
r.recordChannelStateForUser(ctx, res.Participant.UserID, res.Channel.ID, false)
|
||||
}
|
||||
r.deliverChannelParticipantSelfEvent(ctx, res.Participant.UserID, userID, res.Channel, res.Previous, res.Participant, res.Date)
|
||||
cache := newViewerPeerCache(r)
|
||||
build := func(viewerUserID int64) *tg.Updates {
|
||||
updates := r.channelParticipantUpdatesWithPeerCache(ctx, viewerUserID, userID, res.Channel, res.Previous, res.Participant, res.Date, cache)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue