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>
This commit is contained in:
Egor Egorov 2026-07-27 20:18:00 +03:00 • committed by GitHub
parent b0fd3976f1
commit fff8de783a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
169 changed files with 55769 additions and 282 deletions

View file

@ -59,9 +59,11 @@ func (r *Router) onMessagesCheckChatInvite(ctx context.Context, hash string) (tg
return nil, channelInviteErr(err)
}
if res.Already {
// chatInviteAlready#5a686d7c wraps a full Chat, so the badge flags already
// travel through tgChannelChat; nothing to re-apply here.
return &tg.ChatInviteAlready{Chat: tgChannelChat(userID, res.Channel, &res.Self)}, nil
}
return &tg.ChatInvite{
invite := &tg.ChatInvite{
Channel: true,
Broadcast: res.Channel.Broadcast,
Megagroup: res.Channel.Megagroup,
@ -71,7 +73,37 @@ func (r *Router) onMessagesCheckChatInvite(ctx context.Context, hash string) (tg
About: res.Channel.About,
Photo: &tg.PhotoEmpty{},
ParticipantsCount: res.Channel.ParticipantsCount,
}, nil
}
// chatInvite#5c9d3702 carries verified:flags.7 / scam:flags.8 / fake:flags.9.
// A non-member sees only this preview, so the peer's moderation and official
// verification state must already be visible here: without it the badge (or the
// scam/fake warning) appears only after joining, which is exactly backwards.
// Set*, not raw field assignment, so Flags stays consistent before Encode.
applyChatInviteModerationFlags(invite, res.Channel)
// bot_verification:flags.13 extends the same reasoning one field further: the
// third-party mark is part of what identifies the peer, so the preview a
// non-member sees has to carry it as well.
r.applyBotVerificationToChatInvite(ctx, invite, res.Channel.ID)
return invite, nil
}
// applyChatInviteModerationFlags mirrors the persistent channel record's official
// verification and moderation flags onto an invite preview. Absent flags are left
// unset rather than explicitly cleared, so the encoded chatInvite matches what an
// official server sends for an unflagged peer.
func applyChatInviteModerationFlags(invite *tg.ChatInvite, ch domain.Channel) {
if invite == nil {
return
}
if ch.Verified {
invite.SetVerified(true)
}
if ch.Scam {
invite.SetScam(true)
}
if ch.Fake {
invite.SetFake(true)
}
}
func (r *Router) onMessagesImportChatInvite(ctx context.Context, hash string) (tg.MessagesChatInviteJoinResultClass, error) {