protocol: expose privacy and profile photo RPCs

(cherry picked from commit 77b033c8bf8c0a76ff0d7065e2192cbe55d3a3b6)
This commit is contained in:
A 2026-06-08 01:07:38 +08:00
parent 8ff3343ae0
commit 75a8861ec9
9 changed files with 476 additions and 26 deletions

View file

@ -106,7 +106,8 @@ status 取值done(真实实现) / stub(兼容响应) / todo(已发现未实
| account.getAccountTTL | stub | default-365d | TDesktop Settings/self-destruct 预取;当前不做账号自动销毁持久配置,返回正数默认 TTL 避免后台 NOT_IMPLEMENTED |
| account.getNotifySettings | stub | default | TDesktop 主界面读取通知设置;显式返回 show_previews=true、silent=false、mute_until=0 与 default sound避免空 settings 被客户端按静默展示 |
| account.updateNotifySettings | stub | ok | 第一阶段不持久化通知偏好,但接受 TDesktop 设置写入,避免本地通知状态 RPC 报错 |
| account.getPrivacy | stub | default | Settings/Folders 预取隐私项;参考实现 默认规则:手机号默认 disallowAll、生日 allowContacts、其它 allowAll |
| account.getPrivacy | done | real-privacy | 按 owner+key 读取 `account_privacy_rules`;无记录时默认 PhoneNumber=DisallowAll、Birthday=AllowContacts、其它=AllowAll支持 Layer225 全部 privacy key/rule kindusers/chats 按规则回填 |
| account.setPrivacy | done | real-privacy/online-update | 校验 `InputPrivacyKey/InputPrivacyRule` 与 input user access_hash 后持久化;返回 `account.privacyRules` 并向当前账号在线 session 推 `updatePrivacy`Stars/Premium/Bot 等高级产品只保存规则,不扩展商业模型。离线 `updates.getDifference` 恢复 `updatePrivacy` 仍需扩展 `user_update_events` privacy payload |
| account.getAuthorizations | stub | empty | Settings 设备列表预取;第一阶段不展示授权设备管理,返回空 authorizations |
| account.getDefaultEmojiStatuses | stub | notModified | 第一阶段不提供默认 emoji status 列表 |
| account.getCollectibleEmojiStatuses | stub | empty | TDesktop 启动/emoji status 面板会刷新 collectible gift emoji statuses当前无 gift/status 模型,返回空 `account.emojiStatuses` |
@ -119,7 +120,7 @@ status 取值done(真实实现) / stub(兼容响应) / todo(已发现未实
| account.getGlobalPrivacySettings | stub | default | Settings 全局隐私项预取,返回空默认设置 |
| account.getPasskeys | stub | empty | Settings 安全项预取,第一阶段不提供 passkeys |
| account.getSavedMusicIds | stub | empty | TDesktop saved/profile music 预取;当前没有 profile music media store返回空 id vector |
| account.updateStatus | done | persisted-presence | 记录 `auth_key/session/user` 维度运行时在线状态并持久化 `users.last_seen_at``offline=false` 与重连后 session 身份恢复都会写入 last_seen 并推送 `updateUserStatus(userStatusOnline expires=now+5m)``offline=true` 写入精确 `userStatusOffline.was_online`;最后一个 MTProto session 断开/destroy_session 也会写 last_seen 并推送 offline。状态推送给当前用户其它在线 session、在线联系人、以及已有私聊 dialog 的在线对端session 恢复时还会向当前 session 补发在线联系人/私聊对端状态,避免 TDesktop 等用户动作后才刷新。当前未接 account privacy已知 last_seen 默认精确可见 |
| account.updateStatus | done | persisted-presence | 记录 `auth_key/session/user` 维度运行时在线状态并持久化 `users.last_seen_at``offline=false` 与重连后 session 身份恢复都会写入 last_seen 并推送 `updateUserStatus(userStatusOnline expires=now+5m)``offline=true` 写入精确 `userStatusOffline.was_online`;最后一个 MTProto session 断开/destroy_session 也会写 last_seen 并推送 offline。状态推送给当前用户其它在线 session、在线联系人、以及已有私聊 dialog 的在线对端session 恢复时还会向当前 session 补发在线联系人/私聊对端状态。users/dialog/history projection 已按 StatusTimestamp privacy 裁剪返回值presence fanout 的精细 privacy 过滤仍为后续项 |
## Updates
@ -150,7 +151,7 @@ outbox 多 worker 并发 + 发送事务乱序提交 → **主动推送可能乱
| messages.updateDialogFiltersOrder | done | real | 持久化自定义 filter 顺序,去重并忽略默认/归档保留 ID写 durable updateDialogFilterOrder + dispatch_outbox |
| messages.toggleDialogFilterTags | done | real | 持久化 folder tags 开关,写 durable updateDialogFilters reload + dispatch_outbox |
| folders.editPeerFolders | done | real | 支持 folder_id=0/1 的归档/还原user peer 更新 dialogs.folder_idchannel peer 更新 channel_dialogs.folder_id返回/记录 updateFolderPeers(pts,pts_count=1),可靠投递其它 session |
| messages.getDialogs | done | real | 从按 user_id HASH 分区的 dialogs/message_boxes/users 查询当前账号私聊会话,并合并 channel_dialogs/channels/channel_messages支持 exclude_pinned、folder_id(0 主列表/1 归档/2+自定义 filter)、offset_date/offset_id/offset_peer seek pagination、limit、hash notModifiedchannel offset_peer 校验非零 access_hashchannel dialog 返回持久 `read_outbox_max_id`,可恢复离线/丢失的 `updateReadChannelOutbox`;不会把返回的 channel 宽泛标记为 active viewer避免 typing/reaction 等瞬时事件误推给全体 dialog 列表;登录后可见 777000 官方系统会话 |
| messages.getDialogs | done | real | 从按 user_id HASH 分区的 dialogs/message_boxes/users 查询当前账号私聊会话,并合并 channel_dialogs/channels/channel_messages返回 users 统一走 viewer user projection联系人名/手机号可见性 + 当前 `profile_photos` 头像),避免 TDesktop dialog 列表退回字母头像或手机号标题;支持 exclude_pinned、folder_id(0 主列表/1 归档/2+自定义 filter)、offset_date/offset_id/offset_peer seek pagination、limit、hash notModifiedchannel offset_peer 校验非零 access_hashchannel dialog 返回持久 `read_outbox_max_id`,可恢复离线/丢失的 `updateReadChannelOutbox`;不会把返回的 channel 宽泛标记为 active viewer避免 typing/reaction 等瞬时事件误推给全体 dialog 列表;登录后可见 777000 官方系统会话 |
| messages.getPinnedDialogs | done | real | 从 dialogs + channel_dialogs 合并查询 pinned 会话,返回 top messages/users/channels并附持久化 update state |
| messages.getPeerDialogs | done | real | 按 InputDialogPeer 精确查询当前账号会话,返回 dialog/top message/users/update statechannel peer 同样返回持久 `read_outbox_max_id`;公开 username channel/supergroup 对非成员返回只读 preview dialog/top messageprivate/ban/kick/view_messages 不暴露;未建会话的 user peer 返回空 dialog 占位peer vector cap=100 |
| messages.getPeerSettings | done | real | 当前 owner 视角 peerSettings非联系人显示 add/block当前 owner 已保存对方但双方未 mutual 时显示 shareContactacceptContact/add/import 形成 mutual 后清除 shareContact若该 owner 已 hide peer settings bar 则返回空 action bar附带当前 owner 视角用户资料 |
@ -349,9 +350,9 @@ outbox 多 worker 并发 + 发送事务乱序提交 → **主动推送可能乱
| method | status | behavior | note |
|---|---|---|---|
| contacts.getContacts | done | real | 从 contacts 表查询当前账号通讯录;支持 hash notModified返回当前 owner 保存的姓名/电话/备注视角,互相关系按双方是否互存维护 |
| contacts.getContacts | done | real | 从 contacts 表查询当前账号通讯录;支持 hash notModified返回当前 owner 保存的姓名/电话/备注视角,并经统一 user projection 补当前头像/状态;互相关系按双方是否互存维护 |
| contacts.getContactIDs | done | real | 返回当前 owner 通讯录 user_id 列表,支持 hash notModified |
| contacts.getStatuses | done | persisted-presence | 返回当前通讯录联系人 `contactStatus`;状态来自运行时 presence、活跃 session 兜底与持久化 `users.last_seen_at`,离线联系人返回精确 `userStatusOffline.was_online`,未知用户退回 `userStatusRecently`当前未接 account privacy粗粒度 `recently/lastWeek/lastMonth/empty` 隐私降级仍为后续项 |
| contacts.getStatuses | done | persisted-presence | 返回当前通讯录联系人 `contactStatus`;状态来自运行时 presence、活跃 session 兜底与持久化 `users.last_seen_at`,离线联系人返回精确 `userStatusOffline.was_online`,未知用户退回 `userStatusRecently`users/dialog/history projection 已裁剪 StatusTimestamp`contacts.getStatuses` 的逐项 privacy 降级仍为后续项 |
| contacts.importContacts | done | real | 按手机号匹配已注册用户,写入当前 owner 视角的联系人姓名/电话/备注,返回 imported/users并写 durable updatePeerSettings + updateContactsReset + dispatch_outbox |
| contacts.addContact | done | real | 写入当前 owner 视角联系人资料;禁止 self/空姓名/不存在 user维护 reverse mutual并写 durable updatePeerSettings + updateContactsReset + dispatch_outbox |
| contacts.acceptContact | done | real-share-phone | TDesktop “Share my phone number” 入口;要求当前 owner 已有该联系人,否则 CONTACT_REQ_MISSING把当前用户手机号/姓名写入对方 owner 视角联系人,维护 mutual返回 updatePeerSettings(shareContact=false)+updateContactsReset并为双方写 durable peer settings / contacts reset |
@ -359,10 +360,10 @@ outbox 多 worker 并发 + 发送事务乱序提交 → **主动推送可能乱
| contacts.updateContactNote | done | real | 更新当前 owner 对某个联系人的备注与备注实体,不影响其它 owner 对同一 user 的备注,并写 durable updateContactsReset + dispatch_outbox |
| contacts.search | done | real | TDesktop 搜索框 peer 分支strip `@`、空/过短查询报 SEARCH_QUERY_EMPTY/QUERY_TOO_SHORTlimit cap=50联系人 user 进 MyResults非联系人 user 进 Results公开 username channel/supergroup 同步返回 PeerChannel + Chats当前已加入的放 MyResults其它公开命中放 Results 并以 left chat 标记只读预览,避免 TDesktop 误显示已加入;用户搜索走手机号前缀/username/姓名/owner 保存姓名索引,公开频道搜索走 username/title trgm 索引 |
| contacts.resolveUsername | done | real | 按大小写不敏感 username 解析 user 或公开 channel/supergroup peerchannel 返回 PeerChannel + Chats不存在返回 USERNAME_NOT_OCCUPIED非法格式返回 USERNAME_INVALID |
| contacts.resolvePhone | done | real-partial | 按手机号解析 user peer当前阶段未接完整 privacy默认已知手机号可解析未命中返回 PHONE_NOT_OCCUPIED |
| contacts.resolvePhone | done | real-partial | 按手机号解析 user peerPhoneNumber privacy 已用于资料投影与 ShareContact 例外,手机号反查入口仍按已知手机号解析,未命中返回 PHONE_NOT_OCCUPIED按 AddedByPhone/PhoneNumber privacy 限制反查为后续项 |
| contacts.block | done | real-blocklist | 写入当前 owner blocklist幂等同步刷新 peer settingsstory-only block flag 当前按主 blocklist 处理,完整 stories privacy 留后续 |
| contacts.unblock | done | real-blocklist | 从当前 owner blocklist 删除 peer幂等同步刷新 peer settings |
| contacts.getBlocked | done | real-blocklist | Settings 隐私/安全预取;`contacts.block/unblock/getBlocked` 维护 `owner_user_id + blocked_user_id` 唯一 blocklistlimit cap=100按 date/user_id 返回 `peerBlocked` + users`contacts.getPeerSettings` 按当前 owner block 状态返回 block/unblock action。当前不扩展完整 Telegram privacy key 体系blocklist 是本阶段 send/edit/delete 的唯一 privacy gate |
| contacts.getBlocked | done | real-blocklist | Settings 隐私/安全预取;`contacts.block/unblock/getBlocked` 维护 `owner_user_id + blocked_user_id` 唯一 blocklistlimit cap=100按 date/user_id 返回 `peerBlocked` + users`contacts.getPeerSettings` 按当前 owner block 状态返回 block/unblock action,并按 PhoneNumber privacy 决定 `ShareContact`。blocklist 仍是私聊 send/edit/delete 的拒绝 gate完整 privacy key 用于资料投影/手机号分享 |
| contacts.getTopPeers | stub | disabled | 第一阶段不维护 top peers 统计 |
| contacts.getSponsoredPeers | stub | empty | 第一阶段不做 sponsored peersTDesktop 搜索框分支返回 sponsoredPeersEmpty |
| users.getUsers | done | real | InputUserSelf 与已知 InputUser 返回用户(含 777000 官方账号);未登录则跳过(空列表) |
@ -400,12 +401,13 @@ outbox 多 worker 并发 + 发送事务乱序提交 → **主动推送可能乱
| upload.saveBigFilePart | done | real-localfs | 累积 big file 分片,校验 total_parts 上限;组装见 uploadMedia/uploadProfilePhoto |
| upload.getFile | done | real-localfs | 把 `inputDocumentFileLocation/inputPhotoFileLocation/inputPeerPhotoFileLocation` 推导为 `file_blobs.location_key`document 查 `doc:<id>[:type]`photo 查 `photo:<id>:<type>`),从本地 blob backend 按 offset/limit 切片返回 `upload.file`storage type 优先按 bytes 魔数、再按 mime 判定,避免历史 seed 把 WebP thumb 误标 `image/jpeg` 后客户端解码失败;`location_key→FileBlob` 元数据 LRU 消除每 chunk PG 查≤256KB sticker/reaction/thumb 小 blob 走 `object_key→bytes` LRU 并由启动 `WarmCaches` 预热CDN/legacy `inputFileLocation`/`inputStickerSetThumb` 返回 `LOCATION_INVALID`todolocation 仅按 `id` 解析,**不校验 `access_hash`/`file_reference`**(安全取舍见下方 note |
| upload.getFileHashes | stub | empty | 本阶段不做 CDN/分片完整性校验,返回空 hash 列表(客户端信任数据) |
| photos.uploadProfilePhoto | done | real-photo | 组装上传分片→建头像 Photo合成 a/c 尺寸,落 `photos`/`file_blobs`/`profile_photos`),设为当前头像,返回 `photos.photo{photo, 带头像 self}` 并向其它在线 session 推 `updateUser`+self头像即时同步见下方 note仅支持 file 变体fallback/video/emoji-markup 返回 `PHOTO_INVALID` |
| photos.updateProfilePhoto | done | real-photo | `inputPhoto` 把历史头像设为当前;`inputPhotoEmpty` 停用当前头像;变更后向其它在线 session 推 `updateUser`+self 同步头像 |
| photos.getUserPhotos | done | real-photo | 按 `profile_photos` 返回某用户头像历史最新在前offset/limit/max_id 有界,返回 `photos.photos[Slice]` + target user |
| photos.deletePhotos | done | real-photo | 按 inputPhoto id 停用头像,返回被删 id 列表 |
| photos.uploadProfilePhoto | done | real-photo | 组装上传分片→建头像 Photo合成 a/c 尺寸,落 `photos`/`file_blobs`/`profile_photos(kind=profile|fallback)``fallback=true` 设为 fallback 当前头像,否则设为真实 profile 当前头像;返回 `photos.photo{photo, 带头像 self}` 并向其它在线 session 推 `updateUser`+selfvideo/emoji-markup 返回 `PHOTO_INVALID` |
| photos.updateProfilePhoto | done | real-photo | `inputPhoto` 把历史 photo 设为当前 profile/fallback`inputPhotoEmpty` 停用对应 kind 当前头像;变更后向其它在线 session 推 `updateUser`+self 同步头像 |
| photos.uploadContactProfilePhoto | todo | personal-real/suggest-pending | `save=true + file` 设置当前 viewer 对目标 contact 的 personal photo后续 `userProfilePhoto.personal=true``save=true + no file` 清除 personal photo`suggest=true` 当前只创建/返回 photo私聊 `messageActionSuggestProfilePhoto` 仍待 private service-message action 模型 |
| photos.getUserPhotos | done | real-photo | 只按真实 `profile_photos(kind=profile)` 返回某用户头像历史(最新在前),不混入 fallback/personaloffset/limit/max_id 有界,返回 `photos.photos[Slice]` + target user |
| photos.deletePhotos | done | real-photo | 只停用真实 profile kind 的 inputPhoto id返回被删 id 列表fallback/personal 不受影响 |
> 头像渲染:`users` 表反范式 `photo_id/photo_dc_id/photo_stripped`users 服务对 getUsers/getFullUser/self/resolve 批量富化,`tgUser`/`tgSelfUser` 输出 `userProfilePhoto`channel 头像反范式 `channels.photo_*``tgChannel.Photo`/`tgChannelFull.ChatPhoto` 渲染。资源 seed 来自 `TELESRV_STICKER_SEED_DIR` 真实导出启动时幂等导入reactions/default 系统集/常规集)。
> 头像渲染:用户头像统一来自 `internal/app/userprojection`,顺序为 viewer personal photo > ProfilePhoto privacy 允许的真实 profile > fallback > 空头像;同时叠加联系人备注/本地手机号、PhoneNumber/StatusTimestamp privacy 裁剪,`tgUser` 输出 `userProfilePhoto.personal``users.getFullUser` 额外回填 `personal_photo/profile_photo/fallback_photo` 与 About privacy。channel 头像反范式 `channels.photo_*``tgChannel.Photo`/`tgChannelFull.ChatPhoto` 渲染。资源 seed 来自 `TELESRV_STICKER_SEED_DIR` 真实导出启动时幂等导入reactions/default 系统集/常规集)。
> 头像多设备同步:`uploadProfilePhoto`/`updateProfilePhoto`(含 `inputPhotoEmpty` 清除)变更后复用 `pushUserUpdates`,向该账号其它在线 session 推 `updateUser` + `Updates.users` 携带含新 `userProfilePhoto` 的 self当前设备经 RPC 返回更新TDesktop 经 `processUser→setPhoto→peerUpdated(Photo)` 即时刷新(与 参考实现 `MakeUpdatesByUpdatesUsers([self],updateUser)` 对齐;`updateUserName` 不含 photo 无法刷新头像)。联系人/对话方不主动广播头像Telegram 同行为,对端下次拉取 user 时刷新);频道头像变更经 `channels.editPhoto``pushChannelStateToMembers` 推在线成员。