owpengram-server/internal/rpc/phone_group_call_updates.go

235 lines
8.8 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/iamxvbaba/td/tg"
"telesrv/internal/domain"
)
// 群通话推送策略updateGroupCall / updateGroupCallParticipants / connection 均无
// pts不进 getDifference扇出对象统一为「在线群成员」OnlineChannelMemberUserIDs
// MaxChannelRealtimeFanout 封顶——覆盖未入会的面板观察者Android 点 banner 的
// 参与者列表靠它增量刷新)。离线一致性三板斧:服务消息带频道 pts 可补收banner 靠
// channel.call_active/call_not_empty flag拉 dialogs/getFullChannel 重建);房间内
// 状态靠 checkGroupCall 与 version 跳号触发 getGroupParticipants 全量 reload 自愈。
// groupCallOnlineRecipients 返回在线群成员(含 originUserID 本人,推送时由
// pushUserMessage 的 ctx except 排除发起 session、保留其它设备
// 在线索引候选必须过一次 PG active 复核(与 channelFanoutRecipients 同款纵深):
// 索引 stale如踢人窗口时不得把通话状态/参与者名单继续推给已非成员者。
func (r *Router) groupCallOnlineRecipients(ctx context.Context, channelID int64) []int64 {
provider, ok := r.deps.Sessions.(OnlineUserProvider)
if !ok {
return nil
}
online := provider.OnlineChannelMemberUserIDs(channelID, domain.MaxChannelRealtimeFanout)
if len(online) == 0 || r.deps.Channels == nil {
return online
}
active, err := r.deps.Channels.FilterActiveMemberIDs(ctx, channelID, online)
if err != nil {
// 复核失败宁可漏推不误推:群通话 update 无 pts客户端靠 checkGroupCall/
// version 跳号自愈,漏推不丢一致性。
return nil
}
return active
}
// groupCallUpdateContainer 把单条 update 包进 viewer 视角的 Updates 容器。
func (r *Router) groupCallUpdateContainer(ctx context.Context, viewerUserID int64, channel domain.Channel, update tg.UpdateClass, userIDs []int64) *tg.Updates {
chats := []tg.ChatClass{}
if channel.ID != 0 {
chats = append(chats, tgChannel(viewerUserID, channel, nil))
}
return &tg.Updates{
Updates: []tg.UpdateClass{update},
Users: r.tgUsersForIDs(ctx, viewerUserID, userIDs),
Chats: chats,
Date: int(r.clock.Now().Unix()),
Seq: 0,
}
}
// pushGroupCallUpdate 把 updateGroupCallcall 行变化)推给在线群成员。
// 定时通话需 per-viewer 回填 schedule_start_subscribedTDesktop applyCallFields
// 无条件覆盖本地该 flag漏填会把订阅者的"开播提醒"开关静默关掉。
func (r *Router) pushGroupCallUpdate(ctx context.Context, channel domain.Channel, call domain.GroupCall) {
if call.Conference() {
r.pushConferenceGroupCallUpdate(ctx, call)
return
}
var subscribed map[int64]struct{}
if call.ScheduleDate > 0 {
if ids, err := r.deps.GroupCalls.ScheduleSubscriberIDs(ctx, call.ID); err == nil {
subscribed = make(map[int64]struct{}, len(ids))
for _, id := range ids {
subscribed[id] = struct{}{}
}
}
}
recipients := r.groupCallOnlineRecipients(ctx, channel.ID)
for _, viewerID := range recipients {
viewerCall := call
if subscribed != nil {
_, viewerCall.ScheduleStartSubscribed = subscribed[viewerID]
}
update := &tg.UpdateGroupCall{Call: tgGroupCall(viewerCall, viewerID, false, r.cfg.PublicBaseURL)}
update.SetPeer(&tg.PeerChannel{ChannelID: channel.ID})
r.pushUserMessage(ctx, viewerID, "group call update",
r.groupCallUpdateContainer(ctx, viewerID, channel, update, []int64{call.CreatorUserID}))
}
}
// pushGroupCallParticipantsUpdate 把参与者增量version=N推给在线群成员。
// 每个 viewer 单独构建participant.Self flag 是 per-viewer 的。
func (r *Router) pushGroupCallParticipantsUpdate(ctx context.Context, channel domain.Channel, call domain.GroupCall, rows []domain.GroupCallParticipant) {
if call.Conference() {
r.pushConferenceGroupCallParticipantsUpdate(ctx, call, rows)
return
}
if len(rows) == 0 {
return
}
userIDs := make([]int64, 0, len(rows))
for _, p := range rows {
userIDs = append(userIDs, p.UserID)
}
recipients := r.groupCallOnlineRecipients(ctx, channel.ID)
for _, viewerID := range recipients {
update := &tg.UpdateGroupCallParticipants{
Call: &tg.InputGroupCall{ID: call.ID, AccessHash: call.AccessHash},
Participants: tgGroupCallParticipants(rows, viewerID),
Version: call.Version,
}
r.pushUserMessage(ctx, viewerID, "group call participants",
r.groupCallUpdateContainer(ctx, viewerID, channel, update, userIDs))
}
}
func (r *Router) conferenceCallRecipients(ctx context.Context, callID int64) []int64 {
return r.conferenceCallRecipientsWith(ctx, callID, nil)
}
func (r *Router) conferenceCallRecipientsWith(ctx context.Context, callID int64, extraUserIDs []int64) []int64 {
if r.deps.GroupCalls == nil {
return nil
}
recipients, err := r.deps.GroupCalls.ConferenceRecipients(ctx, callID)
if err != nil {
return nil
}
recipients = append(recipients, extraUserIDs...)
recipients = uniquePositiveUserIDs(recipients)
if len(recipients) == 0 {
return nil
}
if provider, ok := r.deps.Sessions.(OnlineUserProvider); ok {
return provider.OnlineUserIDsForCandidates(recipients, domain.MaxChannelRealtimeFanout)
}
return recipients
}
func (r *Router) pushConferenceGroupCallUpdate(ctx context.Context, call domain.GroupCall) {
r.pushConferenceGroupCallUpdateTo(ctx, call, nil)
}
func (r *Router) pushConferenceGroupCallUpdateTo(ctx context.Context, call domain.GroupCall, extraUserIDs []int64) {
recipients := r.conferenceCallRecipientsWith(ctx, call.ID, extraUserIDs)
for _, viewerID := range recipients {
update := &tg.UpdateGroupCall{Call: tgGroupCall(call, viewerID, viewerID == call.CreatorUserID, r.cfg.PublicBaseURL)}
r.pushUserMessage(ctx, viewerID, "conference call update",
r.groupCallUpdateContainer(ctx, viewerID, domain.Channel{}, update, []int64{call.CreatorUserID}))
}
}
func (r *Router) pushConferenceGroupCallParticipantsUpdate(ctx context.Context, call domain.GroupCall, rows []domain.GroupCallParticipant) {
if len(rows) == 0 {
return
}
userIDs := make([]int64, 0, len(rows))
for _, p := range rows {
userIDs = append(userIDs, p.UserID)
}
recipients := r.conferenceCallRecipients(ctx, call.ID)
for _, viewerID := range recipients {
update := &tg.UpdateGroupCallParticipants{
Call: &tg.InputGroupCall{ID: call.ID, AccessHash: call.AccessHash},
Participants: tgGroupCallParticipants(rows, viewerID),
Version: call.Version,
}
r.pushUserMessage(ctx, viewerID, "conference call participants",
r.groupCallUpdateContainer(ctx, viewerID, domain.Channel{}, update, userIDs))
}
}
// pushGroupCallServiceMessage 把 started/ended/invite 服务消息(带频道 pts推给
// 活跃成员res.Recipients。复用 channelOperationUpdates 的 per-viewer 构建。
func (r *Router) pushGroupCallServiceMessage(ctx context.Context, originUserID int64, res domain.SendChannelMessageResult) {
if res.Event.Pts == 0 {
return
}
op := domain.CreateChannelResult{
Channel: res.Channel,
Message: res.Message,
Event: res.Event,
Recipients: res.Recipients,
}
r.pushChannelUpdates(ctx, originUserID, res.Channel.ID, res.Recipients, func(viewerUserID int64) *tg.Updates {
return r.channelOperationUpdates(ctx, viewerUserID, op)
})
}
// groupCallMutationFanout 是参与者维度变更后的统一扇出participants 增量 +
// call_not_empty 翻转时的 channel 维度刷新Android banner 对 flag 依赖更重)。
func (r *Router) groupCallMutationFanout(ctx context.Context, channel domain.Channel, mut domain.GroupCallMutation) domain.Channel {
if mut.Call.Conference() {
r.pushConferenceGroupCallParticipantsUpdate(ctx, mut.Call, []domain.GroupCallParticipant{mut.Participant})
r.pushConferenceGroupCallUpdate(ctx, mut.Call)
return domain.Channel{}
}
r.pushGroupCallParticipantsUpdate(ctx, channel, mut.Call, []domain.GroupCallParticipant{mut.Participant})
wantNotEmpty := mut.Call.Active() && mut.Call.ParticipantsCount > 0
if channel.ActiveCallNotEmpty != wantNotEmpty && r.deps.Channels != nil {
updated, err := r.deps.Channels.SetActiveCall(ctx, channel.ID, channel.ActiveCallID, channel.ActiveCallAccessHash, wantNotEmpty)
if err == nil {
channel = updated
r.pushGroupCallUpdate(ctx, channel, mut.Call)
r.pushChannelStateToMembers(ctx, 0, channel)
}
} else {
r.pushGroupCallUpdate(ctx, channel, mut.Call)
}
return channel
}
func groupCallParticipantUserIDs(rows []domain.GroupCallParticipant) []int64 {
if len(rows) == 0 {
return nil
}
out := make([]int64, 0, len(rows))
for _, row := range rows {
out = append(out, row.UserID)
}
return out
}
func uniquePositiveUserIDs(ids []int64) []int64 {
if len(ids) == 0 {
return nil
}
seen := make(map[int64]struct{}, len(ids))
out := make([]int64, 0, len(ids))
for _, id := range ids {
if id <= 0 {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
out = append(out, id)
}
return out
}