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>
107 lines
4.7 KiB
Go
107 lines
4.7 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
// NotifyPeerBotVerification is the protocol-edge hook invoked after a third-party
|
|
// verification mark has already committed -- from bots.setCustomVerification, or
|
|
// from an operator action on the same rows. It makes the new
|
|
// user#b1b8cc83 bot_verification_icon:flags2.14 /
|
|
// channel#d49f34c6 bot_verification_icon:flags2.13 (and the matching
|
|
// userFull/channelFull payload) observable without waiting for a cache TTL:
|
|
//
|
|
// - the cached peer projections for the target are dropped, so the next
|
|
// users.getFullUser / channels.getFullChannel rebuilds from the committed row;
|
|
// - online clients that already know the peer are pushed the ordinary, non-PTS
|
|
// refresh update (updateUser / updateChannel) together with the re-projected
|
|
// peer object, which is what makes the badge appear live in an official client.
|
|
//
|
|
// It is deliberately the same mechanism as NotifyPeerVerified, which is the same
|
|
// mechanism the scam/fake moderation flags use: the third-party mark is one more
|
|
// fact on the peer's base record, and inventing a verification-specific update
|
|
// would only add a second path that can drift. Nothing new is added to TL.
|
|
//
|
|
// The official verified flag is untouched here. That badge is granted by the
|
|
// operator alone (see verification_notify.go) and the two mechanisms never read or
|
|
// write each other's state.
|
|
//
|
|
// Offline sessions are not pushed to and do not need to be: the peer's base read
|
|
// model version is bumped by the users/channels triggers, so
|
|
// updates.getDifference and any later getUsers / getFullUser answer already carries
|
|
// the new mark.
|
|
//
|
|
// A push failure never invalidates the committed change, so callers log and swallow
|
|
// the returned error; this method therefore reports problems instead of panicking,
|
|
// and is safe on a nil receiver.
|
|
func (r *Router) NotifyPeerBotVerification(ctx context.Context, peer domain.Peer) error {
|
|
if r == nil {
|
|
return nil
|
|
}
|
|
if peer.ID <= 0 {
|
|
return fmt.Errorf("notify peer bot verification: invalid peer id %d", peer.ID)
|
|
}
|
|
switch peer.Type {
|
|
case domain.PeerTypeUser:
|
|
return r.notifyUserBotVerification(ctx, peer.ID)
|
|
case domain.PeerTypeChannel:
|
|
return r.notifyChannelBotVerification(ctx, peer.ID)
|
|
default:
|
|
return fmt.Errorf("notify peer bot verification: unsupported peer type %q for peer %d", peer.Type, peer.ID)
|
|
}
|
|
}
|
|
|
|
// notifyUserBotVerification covers ordinary accounts and bots alike: a marked bot is
|
|
// a user#b1b8cc83 with bot_verification_icon:flags2.14, so it takes the same
|
|
// audience-wide updateUser fan-out the moderation flags use (the peer itself plus
|
|
// every online account that already sees it).
|
|
func (r *Router) notifyUserBotVerification(ctx context.Context, userID int64) error {
|
|
// Invalidate first and unconditionally: a committed mark whose projection still
|
|
// says "unmarked" would keep serving the stale badge state even if the push below
|
|
// cannot run.
|
|
r.invalidateRPCProjectionForUser(userID)
|
|
if r.deps.Users == nil {
|
|
return nil
|
|
}
|
|
user, found, err := r.verificationUser(ctx, userID)
|
|
if err != nil {
|
|
return fmt.Errorf("notify peer bot verification: load user %d: %w", userID, err)
|
|
}
|
|
if !found || user.ID == 0 {
|
|
return fmt.Errorf("notify peer bot verification: user %d not found", userID)
|
|
}
|
|
// NotifyUserModerationFlagsChanged re-projects the peer per recipient, so the
|
|
// snapshot handed in only carries identity; the pushed tg.User is always built
|
|
// from a fresh read and therefore picks the icon up from the batch overlay.
|
|
return r.NotifyUserModerationFlagsChanged(ctx, user)
|
|
}
|
|
|
|
// notifyChannelBotVerification reuses the channel state-mutation path, which
|
|
// invalidates the channel projections (plus a linked monoforum's) and pushes
|
|
// updateChannel with the refreshed chat object to the channel's members.
|
|
func (r *Router) notifyChannelBotVerification(ctx context.Context, channelID int64) error {
|
|
r.invalidateRPCProjectionForChannel(channelID)
|
|
// The bot list cached for channelFull carries botInfo.verifier_settings, so a
|
|
// verifier's own status change has to drop it too; the channel projection cache
|
|
// is cleared by the same call.
|
|
r.invalidateChannelFullBotInfoCacheForChannel(channelID)
|
|
if r.deps.Channels == nil {
|
|
return nil
|
|
}
|
|
directory, ok := r.deps.Channels.(verificationChannelDirectory)
|
|
if !ok {
|
|
return fmt.Errorf("notify peer bot verification: channel service does not expose GetChannelByID")
|
|
}
|
|
channel, err := directory.GetChannelByID(ctx, channelID)
|
|
if err != nil {
|
|
return fmt.Errorf("notify peer bot verification: load channel %d: %w", channelID, err)
|
|
}
|
|
if channel.ID == 0 {
|
|
return fmt.Errorf("notify peer bot verification: channel %d not found", channelID)
|
|
}
|
|
// Same hook the admin panel uses for any other channel base fact.
|
|
return r.NotifyChannelChanged(ctx, channel)
|
|
}
|