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:
parent
b0fd3976f1
commit
fff8de783a
169 changed files with 55769 additions and 282 deletions
|
|
@ -64,6 +64,12 @@ func (r *Router) registerAccount(d *tlprofile.Dispatcher) {
|
|||
return r.onAccountUpdateUsername(ctx, layerRequest.
|
||||
Username)
|
||||
})
|
||||
registerRPC[*tg.AccountReorderUsernamesRequest](d, tlprofile.SemanticMethodAccountReorderUsernames, func(ctx context.Context, layerRequest *tg.AccountReorderUsernamesRequest) (any, error) {
|
||||
return r.onAccountReorderUsernames(ctx, layerRequest)
|
||||
})
|
||||
registerRPC[*tg.AccountToggleUsernameRequest](d, tlprofile.SemanticMethodAccountToggleUsername, func(ctx context.Context, layerRequest *tg.AccountToggleUsernameRequest) (any, error) {
|
||||
return r.onAccountToggleUsername(ctx, layerRequest)
|
||||
})
|
||||
registerRPC[*tg.AccountUpdateBirthdayRequest](d, tlprofile.SemanticMethodAccountUpdateBirthday, func(ctx context.Context, layerRequest *tg.AccountUpdateBirthdayRequest) (any, error) {
|
||||
return r.onAccountUpdateBirthday(ctx, layerRequest)
|
||||
})
|
||||
|
|
@ -1555,6 +1561,54 @@ func (r *Router) onAccountUpdateUsername(ctx context.Context, username string) (
|
|||
return r.tgSelfUser(u), nil
|
||||
}
|
||||
|
||||
// onAccountReorderUsernames rewrites the caller's active username order
|
||||
// (account.reorderUsernames). The editable slot is included when active, just as
|
||||
// it is in the vector sent by official clients.
|
||||
//
|
||||
// Without a username registry the account owns a single editable username, which
|
||||
// has no order to change: USERNAME_NOT_MODIFIED is the accurate answer and keeps
|
||||
// clients from believing a reorder took effect.
|
||||
func (r *Router) onAccountReorderUsernames(ctx context.Context, req *tg.AccountReorderUsernamesRequest) (bool, error) {
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return false, internalErr()
|
||||
}
|
||||
if req == nil {
|
||||
return false, usernameInvalidErr()
|
||||
}
|
||||
if len(req.Order) > domain.MaxPeerCollectibleUsernames+1 {
|
||||
return false, limitInvalidErr()
|
||||
}
|
||||
if r.deps.Usernames == nil {
|
||||
return false, usernameNotModifiedErr()
|
||||
}
|
||||
if err := r.reorderRegistryUsernames(ctx, domain.Peer{Type: domain.PeerTypeUser, ID: userID}, req.Order); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// onAccountToggleUsername activates/deactivates one of the caller's own
|
||||
// collectible usernames (account.toggleUsername). Deactivating the editable slot
|
||||
// through this method is rejected by the domain rules with USERNAME_INVALID:
|
||||
// clearing the editable username is account.updateUsername's job.
|
||||
func (r *Router) onAccountToggleUsername(ctx context.Context, req *tg.AccountToggleUsernameRequest) (bool, error) {
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return false, internalErr()
|
||||
}
|
||||
if req == nil {
|
||||
return false, usernameInvalidErr()
|
||||
}
|
||||
if r.deps.Usernames == nil {
|
||||
return false, usernameNotModifiedErr()
|
||||
}
|
||||
if err := r.toggleRegistryUsername(ctx, domain.Peer{Type: domain.PeerTypeUser, ID: userID}, req.Username, req.Active); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// onAccountUpdateBirthday 持久化资料页生日(account.updateBirthday)。birthday 缺省即清除;
|
||||
// 月/日/年非法返回 BIRTHDAY_INVALID。生日落在 userFull(按隐私 PrivacyKeyBirthday 对外裁剪)。
|
||||
// 写入后推 updateUser 信号给本人其它在线 session,促使已加载 full profile 的客户端重拉。
|
||||
|
|
@ -1869,14 +1923,24 @@ func (r *Router) pushUsernameUpdate(ctx context.Context, u domain.User) {
|
|||
if u.ID == 0 {
|
||||
return
|
||||
}
|
||||
// updateUserName carries the vector clients persist, so it has to be the full
|
||||
// registry list when one exists. One peer, one registry read; the overlay
|
||||
// degrades to tgUsernames(u.Username) whenever the registry is unavailable.
|
||||
self := r.tgSelfUser(u)
|
||||
users := []tg.UserClass{self}
|
||||
r.applyUsernamesToPeerObjects(ctx, users, nil)
|
||||
usernames := tgUsernames(u.Username)
|
||||
if vector, ok := self.GetUsernames(); ok && len(vector) > 0 {
|
||||
usernames = vector
|
||||
}
|
||||
r.pushUserUpdates(ctx, u.ID, &tg.Updates{
|
||||
Updates: []tg.UpdateClass{&tg.UpdateUserName{
|
||||
UserID: u.ID,
|
||||
FirstName: u.FirstName,
|
||||
LastName: u.LastName,
|
||||
Usernames: tgUsernames(u.Username),
|
||||
Usernames: usernames,
|
||||
}},
|
||||
Users: []tg.UserClass{r.tgSelfUser(u)},
|
||||
Users: users,
|
||||
Date: int(r.clock.Now().Unix()),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue