protocol: expose privacy and profile photo RPCs
(cherry picked from commit 77b033c8bf8c0a76ff0d7065e2192cbe55d3a3b6)
This commit is contained in:
parent
8ff3343ae0
commit
75a8861ec9
9 changed files with 476 additions and 26 deletions
|
|
@ -35,9 +35,8 @@ func (r *Router) registerAccount(d *tg.ServerDispatcher) {
|
|||
d.OnAccountUpdateNotifySettings(func(ctx context.Context, req *tg.AccountUpdateNotifySettingsRequest) (bool, error) {
|
||||
return true, nil
|
||||
})
|
||||
d.OnAccountGetPrivacy(func(ctx context.Context, key tg.InputPrivacyKeyClass) (*tg.AccountPrivacyRules, error) {
|
||||
return tdesktop.PrivacyRules(key), nil
|
||||
})
|
||||
d.OnAccountGetPrivacy(r.onAccountGetPrivacy)
|
||||
d.OnAccountSetPrivacy(r.onAccountSetPrivacy)
|
||||
d.OnAccountGetAuthorizations(func(ctx context.Context) (*tg.AccountAuthorizations, error) {
|
||||
return tdesktop.Authorizations(), nil
|
||||
})
|
||||
|
|
@ -80,6 +79,60 @@ func (r *Router) registerAccount(d *tg.ServerDispatcher) {
|
|||
d.OnAccountUpdateStatus(r.onAccountUpdateStatus)
|
||||
}
|
||||
|
||||
func (r *Router) onAccountGetPrivacy(ctx context.Context, key tg.InputPrivacyKeyClass) (*tg.AccountPrivacyRules, error) {
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
domainKey, ok := domainPrivacyKeyFromInput(key)
|
||||
if !ok {
|
||||
return nil, privacyKeyInvalidErr()
|
||||
}
|
||||
if r.deps.Privacy == nil {
|
||||
return tdesktop.PrivacyRules(key), nil
|
||||
}
|
||||
rules, err := r.deps.Privacy.GetRules(ctx, userID, domainKey)
|
||||
if err != nil {
|
||||
return nil, privacyErr(err)
|
||||
}
|
||||
return r.tgAccountPrivacyRules(ctx, userID, rules)
|
||||
}
|
||||
|
||||
func (r *Router) onAccountSetPrivacy(ctx context.Context, req *tg.AccountSetPrivacyRequest) (*tg.AccountPrivacyRules, error) {
|
||||
userID, _, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
domainKey, ok := domainPrivacyKeyFromInput(req.Key)
|
||||
if !ok {
|
||||
return nil, privacyKeyInvalidErr()
|
||||
}
|
||||
rules, err := r.domainPrivacyRulesFromInput(ctx, userID, req.Rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.deps.Privacy == nil {
|
||||
return &tg.AccountPrivacyRules{Rules: tgPrivacyRules(rules), Users: []tg.UserClass{}, Chats: []tg.ChatClass{}}, nil
|
||||
}
|
||||
saved, err := r.deps.Privacy.SetRules(ctx, userID, domainKey, rules)
|
||||
if err != nil {
|
||||
return nil, privacyErr(err)
|
||||
}
|
||||
out, err := r.tgAccountPrivacyRules(ctx, userID, saved)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.pushUserUpdates(ctx, userID, &tg.Updates{
|
||||
Updates: []tg.UpdateClass{&tg.UpdatePrivacy{
|
||||
Key: tgPrivacyKey(saved.Key),
|
||||
Rules: tgPrivacyRules(saved.Rules),
|
||||
}},
|
||||
Users: []tg.UserClass{},
|
||||
Chats: []tg.ChatClass{},
|
||||
})
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *Router) onAccountGetAccountTTL(ctx context.Context) (*tg.AccountDaysTTL, error) {
|
||||
if _, _, err := r.currentUserID(ctx); err != nil {
|
||||
return nil, internalErr()
|
||||
|
|
@ -100,6 +153,218 @@ func (r *Router) onAccountUpdateStatus(ctx context.Context, offline bool) (bool,
|
|||
return true, nil
|
||||
}
|
||||
|
||||
func (r *Router) tgAccountPrivacyRules(ctx context.Context, viewerUserID int64, rules domain.PrivacyRules) (*tg.AccountPrivacyRules, error) {
|
||||
userIDs := privacyRuleUserIDs(rules.Rules)
|
||||
users := []domain.User{}
|
||||
if r.deps.Users != nil && len(userIDs) > 0 {
|
||||
var err error
|
||||
users, err = r.deps.Users.ByIDs(ctx, viewerUserID, userIDs)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
}
|
||||
return &tg.AccountPrivacyRules{
|
||||
Rules: tgPrivacyRules(rules.Rules),
|
||||
Users: tgUsers(users),
|
||||
Chats: []tg.ChatClass{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Router) domainPrivacyRulesFromInput(ctx context.Context, userID int64, in []tg.InputPrivacyRuleClass) ([]domain.PrivacyRule, error) {
|
||||
out := make([]domain.PrivacyRule, 0, len(in))
|
||||
for _, rule := range in {
|
||||
switch v := rule.(type) {
|
||||
case *tg.InputPrivacyValueAllowContacts:
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleAllowContacts})
|
||||
case *tg.InputPrivacyValueAllowAll:
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleAllowAll})
|
||||
case *tg.InputPrivacyValueAllowUsers:
|
||||
ids, err := r.privacyUserIDsFromInput(ctx, userID, v.Users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleAllowUsers, UserIDs: ids})
|
||||
case *tg.InputPrivacyValueDisallowContacts:
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleDisallowContacts})
|
||||
case *tg.InputPrivacyValueDisallowAll:
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleDisallowAll})
|
||||
case *tg.InputPrivacyValueDisallowUsers:
|
||||
ids, err := r.privacyUserIDsFromInput(ctx, userID, v.Users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleDisallowUsers, UserIDs: ids})
|
||||
case *tg.InputPrivacyValueAllowChatParticipants:
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleAllowChatParticipants, ChatIDs: append([]int64(nil), v.Chats...)})
|
||||
case *tg.InputPrivacyValueDisallowChatParticipants:
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleDisallowChatParticipants, ChatIDs: append([]int64(nil), v.Chats...)})
|
||||
case *tg.InputPrivacyValueAllowCloseFriends:
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleAllowCloseFriends})
|
||||
case *tg.InputPrivacyValueAllowPremium:
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleAllowPremium})
|
||||
case *tg.InputPrivacyValueAllowBots:
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleAllowBots})
|
||||
case *tg.InputPrivacyValueDisallowBots:
|
||||
out = append(out, domain.PrivacyRule{Kind: domain.PrivacyRuleDisallowBots})
|
||||
default:
|
||||
return nil, privacyValueInvalidErr()
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *Router) privacyUserIDsFromInput(ctx context.Context, currentUserID int64, inputs []tg.InputUserClass) ([]int64, error) {
|
||||
out := make([]int64, 0, len(inputs))
|
||||
seen := make(map[int64]struct{}, len(inputs))
|
||||
for _, input := range inputs {
|
||||
u, found, err := r.userFromInput(ctx, currentUserID, input)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if !found || u.ID == 0 {
|
||||
return nil, userIDInvalidErr()
|
||||
}
|
||||
if _, ok := seen[u.ID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[u.ID] = struct{}{}
|
||||
out = append(out, u.ID)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func domainPrivacyKeyFromInput(key tg.InputPrivacyKeyClass) (domain.PrivacyKey, bool) {
|
||||
switch key.(type) {
|
||||
case *tg.InputPrivacyKeyStatusTimestamp:
|
||||
return domain.PrivacyKeyStatusTimestamp, true
|
||||
case *tg.InputPrivacyKeyChatInvite:
|
||||
return domain.PrivacyKeyChatInvite, true
|
||||
case *tg.InputPrivacyKeyPhoneCall:
|
||||
return domain.PrivacyKeyPhoneCall, true
|
||||
case *tg.InputPrivacyKeyPhoneP2P:
|
||||
return domain.PrivacyKeyPhoneP2P, true
|
||||
case *tg.InputPrivacyKeyForwards:
|
||||
return domain.PrivacyKeyForwards, true
|
||||
case *tg.InputPrivacyKeyProfilePhoto:
|
||||
return domain.PrivacyKeyProfilePhoto, true
|
||||
case *tg.InputPrivacyKeyPhoneNumber:
|
||||
return domain.PrivacyKeyPhoneNumber, true
|
||||
case *tg.InputPrivacyKeyAddedByPhone:
|
||||
return domain.PrivacyKeyAddedByPhone, true
|
||||
case *tg.InputPrivacyKeyVoiceMessages:
|
||||
return domain.PrivacyKeyVoiceMessages, true
|
||||
case *tg.InputPrivacyKeyAbout:
|
||||
return domain.PrivacyKeyAbout, true
|
||||
case *tg.InputPrivacyKeyBirthday:
|
||||
return domain.PrivacyKeyBirthday, true
|
||||
case *tg.InputPrivacyKeyStarGiftsAutoSave:
|
||||
return domain.PrivacyKeyStarGiftsAutoSave, true
|
||||
case *tg.InputPrivacyKeyNoPaidMessages:
|
||||
return domain.PrivacyKeyNoPaidMessages, true
|
||||
case *tg.InputPrivacyKeySavedMusic:
|
||||
return domain.PrivacyKeySavedMusic, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func tgPrivacyKey(key domain.PrivacyKey) tg.PrivacyKeyClass {
|
||||
switch key {
|
||||
case domain.PrivacyKeyStatusTimestamp:
|
||||
return &tg.PrivacyKeyStatusTimestamp{}
|
||||
case domain.PrivacyKeyChatInvite:
|
||||
return &tg.PrivacyKeyChatInvite{}
|
||||
case domain.PrivacyKeyPhoneCall:
|
||||
return &tg.PrivacyKeyPhoneCall{}
|
||||
case domain.PrivacyKeyPhoneP2P:
|
||||
return &tg.PrivacyKeyPhoneP2P{}
|
||||
case domain.PrivacyKeyForwards:
|
||||
return &tg.PrivacyKeyForwards{}
|
||||
case domain.PrivacyKeyProfilePhoto:
|
||||
return &tg.PrivacyKeyProfilePhoto{}
|
||||
case domain.PrivacyKeyPhoneNumber:
|
||||
return &tg.PrivacyKeyPhoneNumber{}
|
||||
case domain.PrivacyKeyAddedByPhone:
|
||||
return &tg.PrivacyKeyAddedByPhone{}
|
||||
case domain.PrivacyKeyVoiceMessages:
|
||||
return &tg.PrivacyKeyVoiceMessages{}
|
||||
case domain.PrivacyKeyAbout:
|
||||
return &tg.PrivacyKeyAbout{}
|
||||
case domain.PrivacyKeyBirthday:
|
||||
return &tg.PrivacyKeyBirthday{}
|
||||
case domain.PrivacyKeyStarGiftsAutoSave:
|
||||
return &tg.PrivacyKeyStarGiftsAutoSave{}
|
||||
case domain.PrivacyKeyNoPaidMessages:
|
||||
return &tg.PrivacyKeyNoPaidMessages{}
|
||||
case domain.PrivacyKeySavedMusic:
|
||||
return &tg.PrivacyKeySavedMusic{}
|
||||
default:
|
||||
return &tg.PrivacyKeyStatusTimestamp{}
|
||||
}
|
||||
}
|
||||
|
||||
func tgPrivacyRules(rules []domain.PrivacyRule) []tg.PrivacyRuleClass {
|
||||
out := make([]tg.PrivacyRuleClass, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
switch rule.Kind {
|
||||
case domain.PrivacyRuleAllowContacts:
|
||||
out = append(out, &tg.PrivacyValueAllowContacts{})
|
||||
case domain.PrivacyRuleAllowAll:
|
||||
out = append(out, &tg.PrivacyValueAllowAll{})
|
||||
case domain.PrivacyRuleAllowUsers:
|
||||
out = append(out, &tg.PrivacyValueAllowUsers{Users: append([]int64(nil), rule.UserIDs...)})
|
||||
case domain.PrivacyRuleDisallowContacts:
|
||||
out = append(out, &tg.PrivacyValueDisallowContacts{})
|
||||
case domain.PrivacyRuleDisallowAll:
|
||||
out = append(out, &tg.PrivacyValueDisallowAll{})
|
||||
case domain.PrivacyRuleDisallowUsers:
|
||||
out = append(out, &tg.PrivacyValueDisallowUsers{Users: append([]int64(nil), rule.UserIDs...)})
|
||||
case domain.PrivacyRuleAllowChatParticipants:
|
||||
out = append(out, &tg.PrivacyValueAllowChatParticipants{Chats: append([]int64(nil), rule.ChatIDs...)})
|
||||
case domain.PrivacyRuleDisallowChatParticipants:
|
||||
out = append(out, &tg.PrivacyValueDisallowChatParticipants{Chats: append([]int64(nil), rule.ChatIDs...)})
|
||||
case domain.PrivacyRuleAllowCloseFriends:
|
||||
out = append(out, &tg.PrivacyValueAllowCloseFriends{})
|
||||
case domain.PrivacyRuleAllowPremium:
|
||||
out = append(out, &tg.PrivacyValueAllowPremium{})
|
||||
case domain.PrivacyRuleAllowBots:
|
||||
out = append(out, &tg.PrivacyValueAllowBots{})
|
||||
case domain.PrivacyRuleDisallowBots:
|
||||
out = append(out, &tg.PrivacyValueDisallowBots{})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func privacyRuleUserIDs(rules []domain.PrivacyRule) []int64 {
|
||||
seen := map[int64]struct{}{}
|
||||
out := make([]int64, 0)
|
||||
for _, rule := range rules {
|
||||
for _, id := range rule.UserIDs {
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
out = append(out, id)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func privacyErr(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, domain.ErrPrivacyKeyInvalid):
|
||||
return privacyKeyInvalidErr()
|
||||
case errors.Is(err, domain.ErrPrivacyRuleInvalid):
|
||||
return privacyValueInvalidErr()
|
||||
default:
|
||||
return internalErr()
|
||||
}
|
||||
}
|
||||
|
||||
type accountReactionSettingsService interface {
|
||||
GetReactionSettings(ctx context.Context, userID int64) (domain.AccountReactionSettings, error)
|
||||
SetReactionsNotifySettings(ctx context.Context, userID int64, settings domain.ReactionsNotifySettings) (domain.AccountReactionSettings, error)
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ func tgUserProfilePhoto(u domain.User) tg.UserProfilePhotoClass {
|
|||
if u.PhotoID == 0 {
|
||||
return nil
|
||||
}
|
||||
photo := &tg.UserProfilePhoto{PhotoID: u.PhotoID, DCID: u.PhotoDCID}
|
||||
photo := &tg.UserProfilePhoto{PhotoID: u.PhotoID, DCID: u.PhotoDCID, Personal: u.PhotoPersonal}
|
||||
if len(u.PhotoStripped) > 0 {
|
||||
photo.SetStrippedThumb(u.PhotoStripped)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,3 +29,35 @@ func TestTGMessagesMessagesMarksViewerSelfAndKeepsProjectedPhone(t *testing.T) {
|
|||
t.Fatalf("peer user = %+v ok=%v, want projected non-self without phone", full.Users[1], ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTGMessagesDialogsIncludesUserProfilePhoto(t *testing.T) {
|
||||
const viewerID int64 = 1001
|
||||
const peerID int64 = 1002
|
||||
res := tgMessagesDialogs(viewerID, domain.DialogList{
|
||||
Dialogs: []domain.Dialog{{
|
||||
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: peerID},
|
||||
TopMessage: 1,
|
||||
TopMessageDate: 10,
|
||||
}},
|
||||
Users: []domain.User{{
|
||||
ID: peerID,
|
||||
AccessHash: 22,
|
||||
FirstName: "Alice A",
|
||||
PhotoID: 9301,
|
||||
PhotoDCID: 2,
|
||||
PhotoStripped: []byte{9, 10},
|
||||
}},
|
||||
})
|
||||
full, ok := res.(*tg.MessagesDialogs)
|
||||
if !ok {
|
||||
t.Fatalf("result = %T, want *tg.MessagesDialogs", res)
|
||||
}
|
||||
peer, ok := full.Users[0].(*tg.User)
|
||||
if !ok {
|
||||
t.Fatalf("user = %T, want *tg.User", full.Users[0])
|
||||
}
|
||||
photo, ok := peer.Photo.(*tg.UserProfilePhoto)
|
||||
if !ok || photo.PhotoID != 9301 || photo.DCID != 2 || string(photo.StrippedThumb) != string([]byte{9, 10}) {
|
||||
t.Fatalf("photo = %+v ok=%v, want userProfilePhoto 9301/2/[9 10]", peer.Photo, ok)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,10 @@ func pollAnswerInvalidErr() error { return tgerr.New(400, "POLL_ANSWER_INVALID")
|
|||
|
||||
func reactionInvalidErr() error { return tgerr.New(400, "REACTION_INVALID") }
|
||||
|
||||
func privacyKeyInvalidErr() error { return tgerr.New(400, "PRIVACY_KEY_INVALID") }
|
||||
|
||||
func privacyValueInvalidErr() error { return tgerr.New(400, "PRIVACY_VALUE_INVALID") }
|
||||
|
||||
func todoItemsEmptyErr() error { return tgerr.New(400, "TODO_ITEMS_EMPTY") }
|
||||
|
||||
func todoNotModifiedErr() error { return tgerr.New(400, "TODO_NOT_MODIFIED") }
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
func (r *Router) registerPhotos(d *tg.ServerDispatcher) {
|
||||
d.OnPhotosUploadProfilePhoto(r.onPhotosUploadProfilePhoto)
|
||||
d.OnPhotosUpdateProfilePhoto(r.onPhotosUpdateProfilePhoto)
|
||||
d.OnPhotosUploadContactProfilePhoto(r.onPhotosUploadContactProfilePhoto)
|
||||
d.OnPhotosGetUserPhotos(r.onPhotosGetUserPhotos)
|
||||
d.OnPhotosDeletePhotos(r.onPhotosDeletePhotos)
|
||||
}
|
||||
|
|
@ -28,16 +29,23 @@ func (r *Router) onPhotosUploadProfilePhoto(ctx context.Context, req *tg.PhotosU
|
|||
if !ok || userID == 0 {
|
||||
return nil, photoInvalidErr()
|
||||
}
|
||||
if bot, hasBot := req.GetBot(); hasBot && bot != nil {
|
||||
return nil, inputConstructorInvalidErr()
|
||||
}
|
||||
file, hasFile := req.GetFile()
|
||||
if !hasFile {
|
||||
// 仅 fallback / video / emoji markup 等本阶段不支持的头像变体。
|
||||
// video / emoji markup 等头像变体本阶段不支持。
|
||||
return nil, photoInvalidErr()
|
||||
}
|
||||
ref, ok := uploadedFileRef(userID, file)
|
||||
if !ok {
|
||||
return nil, fileReferenceInvalidErr()
|
||||
}
|
||||
photo, err := r.deps.Files.UploadProfilePhoto(ctx, domain.PeerTypeUser, userID, ref, int(r.clock.Now().Unix()))
|
||||
kind := domain.ProfilePhotoKindProfile
|
||||
if req.GetFallback() {
|
||||
kind = domain.ProfilePhotoKindFallback
|
||||
}
|
||||
photo, err := r.deps.Files.UploadProfilePhotoKind(ctx, domain.PeerTypeUser, userID, kind, ref, int(r.clock.Now().Unix()))
|
||||
if err != nil {
|
||||
return nil, photoUploadErr(err)
|
||||
}
|
||||
|
|
@ -55,9 +63,16 @@ func (r *Router) onPhotosUpdateProfilePhoto(ctx context.Context, req *tg.PhotosU
|
|||
if !ok || userID == 0 {
|
||||
return nil, photoInvalidErr()
|
||||
}
|
||||
if bot, hasBot := req.GetBot(); hasBot && bot != nil {
|
||||
return nil, inputConstructorInvalidErr()
|
||||
}
|
||||
kind := domain.ProfilePhotoKindProfile
|
||||
if req.GetFallback() {
|
||||
kind = domain.ProfilePhotoKindFallback
|
||||
}
|
||||
switch in := req.ID.(type) {
|
||||
case *tg.InputPhoto:
|
||||
photo, found, err := r.deps.Files.SetCurrentProfilePhoto(ctx, domain.PeerTypeUser, userID, in.ID, int(r.clock.Now().Unix()))
|
||||
photo, found, err := r.deps.Files.SetCurrentProfilePhotoKind(ctx, domain.PeerTypeUser, userID, kind, in.ID, int(r.clock.Now().Unix()))
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
|
|
@ -67,13 +82,60 @@ func (r *Router) onPhotosUpdateProfilePhoto(ctx context.Context, req *tg.PhotosU
|
|||
return r.photosPhotoForSelf(ctx, userID, photo), nil
|
||||
default:
|
||||
// InputPhotoEmpty:移除当前头像(停用现有当前照片)。
|
||||
if cur, found, err := r.deps.Files.CurrentProfilePhoto(ctx, domain.PeerTypeUser, userID); err == nil && found {
|
||||
_, _ = r.deps.Files.DeleteProfilePhotos(ctx, domain.PeerTypeUser, userID, []int64{cur.ID})
|
||||
if cur, found, err := r.deps.Files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, userID, kind); err == nil && found {
|
||||
_, _ = r.deps.Files.DeleteProfilePhotosKind(ctx, domain.PeerTypeUser, userID, kind, []int64{cur.ID})
|
||||
}
|
||||
return r.photosPhotoForSelf(ctx, userID, domain.Photo{}), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) onPhotosUploadContactProfilePhoto(ctx context.Context, req *tg.PhotosUploadContactProfilePhotoRequest) (*tg.PhotosPhoto, error) {
|
||||
if r.deps.Files == nil || r.deps.Contacts == nil {
|
||||
return nil, notImplementedErr()
|
||||
}
|
||||
userID, ok, err := r.currentUserID(ctx)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if !ok || userID == 0 {
|
||||
return nil, photoInvalidErr()
|
||||
}
|
||||
target, found, err := r.userFromInput(ctx, userID, req.UserID)
|
||||
if err != nil {
|
||||
return nil, internalErr()
|
||||
}
|
||||
if !found || target.ID == 0 || target.ID == userID {
|
||||
return nil, userIDInvalidErr()
|
||||
}
|
||||
file, hasFile := req.GetFile()
|
||||
if !hasFile {
|
||||
if req.GetSave() {
|
||||
if _, err := r.deps.Contacts.ClearPersonalPhoto(ctx, userID, target.ID, int(r.clock.Now().Unix())); err != nil {
|
||||
return nil, contactErr(err)
|
||||
}
|
||||
return r.photosPhotoForUser(ctx, userID, target.ID, domain.Photo{}), nil
|
||||
}
|
||||
return nil, photoInvalidErr()
|
||||
}
|
||||
ref, ok := uploadedFileRef(userID, file)
|
||||
if !ok {
|
||||
return nil, fileReferenceInvalidErr()
|
||||
}
|
||||
photo, err := r.deps.Files.CreateAvatarFromUpload(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, photoUploadErr(err)
|
||||
}
|
||||
if req.GetSuggest() {
|
||||
// TODO: add private MessageActionSuggestProfilePhoto once private service-message
|
||||
// actions exist in the domain message model.
|
||||
return r.photosPhotoForUser(ctx, userID, target.ID, photo), nil
|
||||
}
|
||||
if _, err := r.deps.Contacts.SetPersonalPhoto(ctx, userID, target.ID, photo, int(r.clock.Now().Unix())); err != nil {
|
||||
return nil, contactErr(err)
|
||||
}
|
||||
return r.photosPhotoForUser(ctx, userID, target.ID, photo), nil
|
||||
}
|
||||
|
||||
func (r *Router) onPhotosGetUserPhotos(ctx context.Context, req *tg.PhotosGetUserPhotosRequest) (tg.PhotosPhotosClass, error) {
|
||||
if r.deps.Files == nil {
|
||||
return &tg.PhotosPhotos{}, nil
|
||||
|
|
@ -158,6 +220,19 @@ func (r *Router) photosPhotoForSelf(ctx context.Context, userID int64, photo dom
|
|||
return out
|
||||
}
|
||||
|
||||
func (r *Router) photosPhotoForUser(ctx context.Context, viewerUserID, targetUserID int64, photo domain.Photo) *tg.PhotosPhoto {
|
||||
out := &tg.PhotosPhoto{Photo: tgPhoto(photo), Users: []tg.UserClass{}}
|
||||
if r.deps.Users == nil {
|
||||
return out
|
||||
}
|
||||
user, found, err := r.deps.Users.ByID(ctx, viewerUserID, targetUserID)
|
||||
if err != nil || !found {
|
||||
return out
|
||||
}
|
||||
out.Users = append(out.Users, r.tgUser(user))
|
||||
return out
|
||||
}
|
||||
|
||||
// pushSelfPhotoUpdate 向该账号其它在线设备推送头像变更。updateUserName 不含 photo 无法刷新
|
||||
// 头像;updateUser 只是「该 user 变了」的信号(TDesktop 仅当 peer 已 full-loaded 时才
|
||||
// forceFull 重拉);最可靠是在 Updates.Users 带上含新 userProfilePhoto 的完整 self user,
|
||||
|
|
|
|||
|
|
@ -87,12 +87,25 @@ func (r *Router) onUsersGetFullUser(ctx context.Context, id tg.InputUserClass) (
|
|||
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: u.About,
|
||||
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,
|
||||
|
|
@ -137,6 +150,62 @@ func (r *Router) onUsersGetSavedMusicByID(ctx context.Context, req *tg.UsersGetS
|
|||
}, 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{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue