package rpc import ( "context" "errors" "github.com/gotd/td/tg" "telesrv/internal/app/users" "telesrv/internal/compat/tdesktop" "telesrv/internal/domain" ) const maxSavedMusicLimit = 100 // registerUsers 注册 users.* RPC handler。 func (r *Router) registerUsers(d *tg.ServerDispatcher) { d.OnUsersGetUsers(r.onUsersGetUsers) d.OnUsersGetFullUser(r.onUsersGetFullUser) d.OnUsersGetSavedMusic(r.onUsersGetSavedMusic) d.OnUsersGetSavedMusicByID(r.onUsersGetSavedMusicByID) } // onUsersGetUsers 处理 users.getUsers:支持 self 和已知 user peer(含 777000 官方账号)。 func (r *Router) onUsersGetUsers(ctx context.Context, ids []tg.InputUserClass) ([]tg.UserClass, error) { currentUserID, authorized, err := r.currentUserID(ctx) if err != nil { return nil, internalErr() } out := make([]tg.UserClass, 0, len(ids)) for _, in := range ids { if r.deps.Users == nil { continue } switch v := in.(type) { case *tg.InputUserSelf: if !authorized { continue } u, err := r.deps.Users.Self(ctx, currentUserID) if err != nil { if errors.Is(err, users.ErrNotAuthorized) { continue // 未登录:getUsers 尽力而为,跳过 self } return nil, internalErr() } out = append(out, r.tgSelfUser(u)) case *tg.InputUser: if !authorized { continue } u, found, err := r.deps.Users.ByID(ctx, currentUserID, v.UserID) if err != nil { if errors.Is(err, users.ErrNotAuthorized) { continue } return nil, internalErr() } if !found || (v.AccessHash != 0 && v.AccessHash != u.AccessHash) { continue } out = append(out, r.tgUser(u)) } } return out, nil } func (r *Router) onUsersGetFullUser(ctx context.Context, id tg.InputUserClass) (*tg.UsersUserFull, error) { if r.deps.Users == nil { return emptyUserFull(), nil } currentUserID, _, err := r.currentUserID(ctx) if err != nil { return nil, internalErr() } u, found, err := r.userFromInput(ctx, currentUserID, id) if err != nil { if errors.Is(err, users.ErrNotAuthorized) { return emptyUserFull(), nil } return nil, internalErr() } if !found { return emptyUserFull(), nil } user := r.tgUser(u) if _, ok := id.(*tg.InputUserSelf); ok { user = r.tgSelfUser(u) } about := u.About if r.deps.Privacy != nil && u.ID != currentUserID { allowed, err := r.deps.Privacy.CanSee(ctx, u.ID, currentUserID, domain.PrivacyKeyAbout) if err != nil { return nil, internalErr() } if !allowed { about = "" } } full := tg.UserFull{ ID: u.ID, About: about, Settings: tg.PeerSettings{}, NotifySettings: *tdesktop.NotifySettings(), } if err := r.fillUserFullPhotos(ctx, currentUserID, u.ID, &full); err != nil { return nil, err } if r.deps.Channels != nil && u.ID != currentUserID { common, err := r.deps.Channels.CommonChannels(ctx, currentUserID, domain.CommonChannelsRequest{ UserID: currentUserID, TargetUserID: u.ID, Limit: 1, CountOnly: true, }) if err != nil { return nil, internalErr() } full.CommonChatsCount = common.Count } return &tg.UsersUserFull{ FullUser: full, Users: []tg.UserClass{user}, }, nil } func (r *Router) onUsersGetSavedMusic(ctx context.Context, req *tg.UsersGetSavedMusicRequest) (tg.UsersSavedMusicClass, error) { if req == nil || req.Offset < 0 || req.Limit < 0 || req.Limit > maxSavedMusicLimit { return nil, limitInvalidErr() } if err := r.validateInputUser(ctx, req.ID); err != nil { return nil, err } return &tg.UsersSavedMusic{ Count: 0, Documents: []tg.DocumentClass{}, }, nil } func (r *Router) onUsersGetSavedMusicByID(ctx context.Context, req *tg.UsersGetSavedMusicByIDRequest) (tg.UsersSavedMusicClass, error) { if req == nil || len(req.Documents) > maxSavedMusicLimit { return nil, limitInvalidErr() } if err := r.validateInputUser(ctx, req.ID); err != nil { return nil, err } return &tg.UsersSavedMusic{ Count: 0, Documents: []tg.DocumentClass{}, }, nil } func (r *Router) fillUserFullPhotos(ctx context.Context, viewerUserID, ownerUserID int64, full *tg.UserFull) error { if r.deps.Files == nil || full == nil || ownerUserID == 0 { return nil } if viewerUserID == ownerUserID { if photo, found, err := r.deps.Files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, ownerUserID, domain.ProfilePhotoKindProfile); err != nil { return internalErr() } else if found { full.SetProfilePhoto(tgPhoto(photo)) } if photo, found, err := r.deps.Files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, ownerUserID, domain.ProfilePhotoKindFallback); err != nil { return internalErr() } else if found { full.SetFallbackPhoto(tgPhoto(photo)) } return nil } if r.deps.Contacts != nil { refs, err := r.deps.Contacts.PersonalPhotos(ctx, viewerUserID, []int64{ownerUserID}) if err != nil { return internalErr() } if ref, ok := refs[ownerUserID]; ok && ref.PhotoID != 0 { photo, found, err := r.deps.Files.GetPhoto(ctx, ref.PhotoID) if err != nil { return internalErr() } if found { full.SetPersonalPhoto(tgPhoto(photo)) } } } profileAllowed := true if r.deps.Privacy != nil { var err error profileAllowed, err = r.deps.Privacy.CanSee(ctx, ownerUserID, viewerUserID, domain.PrivacyKeyProfilePhoto) if err != nil { return internalErr() } } if profileAllowed { if photo, found, err := r.deps.Files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, ownerUserID, domain.ProfilePhotoKindProfile); err != nil { return internalErr() } else if found { full.SetProfilePhoto(tgPhoto(photo)) } return nil } if photo, found, err := r.deps.Files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, ownerUserID, domain.ProfilePhotoKindFallback); err != nil { return internalErr() } else if found { full.SetFallbackPhoto(tgPhoto(photo)) } return nil } func emptyUserFull() *tg.UsersUserFull { return &tg.UsersUserFull{ FullUser: tg.UserFull{ Settings: tg.PeerSettings{}, NotifySettings: *tdesktop.NotifySettings(), }, } } func (r *Router) userFromInput(ctx context.Context, currentUserID int64, id tg.InputUserClass) (domain.User, bool, error) { switch v := id.(type) { case *tg.InputUserSelf: u, err := r.deps.Users.Self(ctx, currentUserID) return u, err == nil, err case *tg.InputUser: u, found, err := r.deps.Users.ByID(ctx, currentUserID, v.UserID) if err != nil || !found { return domain.User{}, found, err } if v.AccessHash != 0 && v.AccessHash != u.AccessHash { return domain.User{}, false, nil } return u, true, nil default: return domain.User{}, false, nil } } func (r *Router) validateInputUser(ctx context.Context, id tg.InputUserClass) error { if r.deps.Users == nil { return nil } currentUserID, _, err := r.currentUserID(ctx) if err != nil { return internalErr() } _, found, err := r.userFromInput(ctx, currentUserID, id) if err != nil { if errors.Is(err, users.ErrNotAuthorized) { return userIDInvalidErr() } return internalErr() } if !found { return userIDInvalidErr() } return nil }