owpengram-server/internal/rpc/channels_state_mutation.go
Egor Egorov fff8de783a
feat: add NFT usernames and bot verification (#22)
Implements collectible usernames, official verification workflows, and third-party bot verification after maintainer protocol and migration review.

The composite activity/moderation rating remains an admin-only read model; Telegram Stars Rating wire fields stay unset pending a dedicated official-semantics implementation.

Reviewed-Head: 2796345775ea0f908fb7734601e5e1dee4b653b9
Original-Head: fa082b892fd5180c9c9bc53c81c21cf5d250a75b

Co-authored-by: Egor Egorov <business.egor.sg@gmail.com>
2026-07-28 01:18:00 +08:00

89 lines
3.1 KiB
Go

package rpc
import (
"context"
"github.com/iamxvbaba/td/tg"
"telesrv/internal/domain"
)
type channelStateMutation func(ctx context.Context, userID, channelID int64) (domain.Channel, error)
func (r *Router) applyChannelAdminStateMutation(ctx context.Context, input tg.InputChannelClass, mutate channelStateMutation) (tg.UpdatesClass, error) {
if r.deps.Channels == nil {
return nil, notImplementedErr()
}
userID, _, err := r.currentUserID(ctx)
if err != nil {
return nil, internalErr()
}
channelID, err := r.channelIDFromInput(ctx, userID, input)
if err != nil {
return nil, err
}
channel, err := mutate(ctx, userID, channelID)
if err != nil {
return nil, channelAdminErr(err)
}
return r.channelStateMutationUpdates(ctx, userID, channel), nil
}
type channelChangeInfoMutation func(ctx context.Context, userID int64, view domain.ChannelView) (domain.Channel, error)
func (r *Router) applyChannelChangeInfoMutation(ctx context.Context, input tg.InputChannelClass, mutate channelChangeInfoMutation) (tg.UpdatesClass, error) {
userID, view, err := r.channelChangeInfoView(ctx, input)
if err != nil {
return nil, err
}
channel, err := mutate(ctx, userID, view)
if err != nil {
return nil, channelAdminErr(err)
}
return r.channelStateMutationUpdates(ctx, userID, channel), nil
}
func (r *Router) channelStateMutationUpdates(ctx context.Context, userID int64, channel domain.Channel) tg.UpdatesClass {
r.invalidateRPCProjectionForChannel(channel.ID)
if channel.LinkedMonoforumID != 0 {
r.invalidateRPCProjectionForChannel(channel.LinkedMonoforumID)
}
mono, includeMono := r.linkedMonoforumForChannelState(ctx, userID, channel)
// One read for the whole fan-out plus the returned updates: the third-party
// verification mark is a peer-wide fact, and the per-recipient builder must stay
// read-free (see channelStateUpdatesWithLinkedMonoforum).
icon := r.peerBotVerificationIcon(ctx, domain.Peer{Type: domain.PeerTypeChannel, ID: channel.ID})
usernames := r.channelStateUsernameRegistry(ctx, channel, mono, includeMono)
r.pushChannelStateToMembersWithLinkedMonoforum(ctx, userID, channel, mono, includeMono, icon, usernames)
return r.channelStateUpdatesWithLinkedMonoforum(userID, channel, mono, includeMono, icon, usernames)
}
func (r *Router) channelPaidMessagesPriceUpdates(ctx context.Context, userID int64, res domain.ChannelPaidMessagesPriceResult) tg.UpdatesClass {
state := r.channelStateMutationUpdates(ctx, userID, res.Channel)
services := res.ServiceMessages
if len(services) == 0 && res.ServiceMessage != nil {
services = []domain.SendChannelMessageResult{*res.ServiceMessage}
}
if len(services) == 0 {
return state
}
service := r.channelMessagesUpdatesWithPeerCache(ctx, userID, services, nil, false, nil, newViewerPeerCache(r))
if updates, ok := state.(*tg.Updates); ok {
return mergeUpdates(updates, service)
}
return state
}
func mergeUpdates(a, b *tg.Updates) *tg.Updates {
if a == nil {
return b
}
if b == nil {
return a
}
a.Updates = append(a.Updates, b.Updates...)
a.Users = appendUniqueTGUsers(a.Users, b.Users...)
a.Chats = appendUniqueTGChats(a.Chats, b.Chats...)
if b.Date > a.Date {
a.Date = b.Date
}
return a
}