同行数据库
API 中的许多构造函数需要在接收时存储在本地数据库中,并且只有在通过更新或其他方式(如文档中所述)接收到时才应被动地进行更新,以避免因不断请求相同未更改的信息而使服务器过载。
用户、聊天、频道构造函数及其完整对应物userFull、chatFull、channelFull尤为重要,因为它们包含有关用户、机器人、聊天和频道(从现在起称为对等节点)的重要信息,最重要的是,它们包含与 API 中的对等节点交互所需的access_hash值。
本页详细介绍了如何以及何时刷新上面列出的构造函数的本地数据库,并包含有关基本对等概念的更详细说明。
为了简单起见,文档在提及对等数据库时通常使用术语“缓存”而不是“数据库”,但请注意,建议将接收到的信息(或至少是 id+访问哈希)持久化到数据库中,以便在启动后调用需要访问哈希的方法并查看其他对等信息,而无需重新获取聊天记录和对等信息。
同行信息数据库
userEmpty#d3bc4b7a id:long = User; user#20b1422 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true contact_require_premium:flags2.10?true bot_business:flags2.11?true bot_has_main_app:flags2.13?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector<Username> stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor bot_active_users:flags2.12?int bot_verification_icon:flags2.14?long send_paid_messages_stars:flags2.15?long = User;chatEmpty#29562865 id:long = Chat; chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat;channel#fe685355 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true autotranslation:flags2.15?true broadcast_messages_allowed:flags2.16?true monoforum:flags2.17?true forum_tabs:flags2.19?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector<Username> stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long send_paid_messages_stars:flags2.14?long linked_monoforum_id:flags2.18?long = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;inputUserEmpty#b98886cf = InputUser; inputUserSelf#f7c1b13f = InputUser; inputUser#f21158c6 user_id:long access_hash:long = InputUser; inputUserFromMessage#1da448e2 peer:InputPeer msg_id:int user_id:long = InputUser;// No inputChat, just a long is used (because basic chats don't have access hashes)inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; inputChannelFromMessage#5b934f9d peer:InputPeer msg_id:int channel_id:long = InputChannel;inputPeerEmpty#7f3b18ea = InputPeer; inputPeerSelf#7da07ec9 = InputPeer; inputPeerChat#35a95cb9 chat_id:long = InputPeer; inputPeerUser#dde8a54c user_id:long access_hash:long = InputPeer; inputPeerChannel#27bcbbfc channel_id:long access_hash:long = InputPeer; inputPeerUserFromMessage#a87b0a1c peer:InputPeer msg_id:int user_id:long = InputPeer; inputPeerChannelFromMessage#bd2a0840 peer:InputPeer msg_id:int channel_id:long = InputPeer;---functions---users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>; messages.getChats#49e9528f id:Vector<long> = messages.Chats; channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
对等体信息数据库包含以下信息:
- 对等 ID »
- 访问哈希值 »
- 其他信息 »
它必须按如下方式填写:
- 通过保存接收到的用户、聊天记录、频道构造函数 »
- 通过处理某些更新 »
- 手动刷新 »
示例实现:tdlib。
保存构造函数
每当收到类型为user、chat和channel的新构造函数(以及它们的forbidden对应项,用于用户无法访问但可以查看其基本信息的对等节点)时,都需要更新对等信息数据库。
这些构造函数是在与 API 交互时收到的(例如在普通聊天中、通过搜索功能、用户名解析、临时个人资料链接等等……)。
除非另有规定(有关特殊情况,请参阅构造函数页面),否则在更新本地对等数据库时,新接收的构造函数中的所有字段优先于本地缓存的旧构造函数(包括删除新构造函数中未设置的字段)。
处理某些更新
以下更新应触发对等数据库中包含的一小部分信息的更新(包括非完整版本和完整版本):
- updateUserStatus- 更新用户。status
- updateUserName-更新用户。///first_namelast_nameusernameusernames
- updatePeerBlocked-更新userFull,channelFullblocked。blocked
- updatePeerSettings- 更新userFull。settings
- updatePeerWallpaper- 更新userFull.wallpaper,channelFull.wallpaper
- updateUserEmojiStatus- 更新用户。emoji_status
- updateChatParticipants- 更新聊天。participants_count,聊天满。participants。
- updateChatParticipant- 更新聊天。participants_count,聊天满。participants。
- updateChannelAvailableMessages- 更新通道。hidden_prehistory
手动刷新
出于性能原因,服务器不会总是发送包含所有对等节点更新信息的更新:因此,在某些情况下,可以使用批量用户.getUsers、messages.getChats、channels.getChannels方法手动刷新已缓存对等节点的信息,所有这些方法都需要先前缓存的信息。access_hash
以下列表并不详尽,客户也可以在其他一些情况下选择刷新同伴信息(例如,打开个人资料页面时等)。
-
当获取以下内部用户 ID 的信息时,如果该信息尚未缓存,或者缓存的信息为min:777000(服务通知用户)、1271266957(回复机器人)、1087968824(匿名机器人)、136817688(频道机器人)、5434988373(反垃圾邮件机器人),
则即使是用户也可以使用零访问哈希值获取这些 ID 的信息。 -
在更改了机器人的信息后(但不是更改 `<string>`或 `<string>` ,因为更改这些字段已经触发了整个信息数据库以及对等数据库的刷新),调用`bots.setBotInfo`会更新我们更改过的机器人的信息。nameaboutdescription
-
CHAT_FORWARDS_RESTRICTED在转发聊天消息时收到错误后,需要刷新有关源聊天的信息(即,我们收到此错误意味着客户端没有在本地阻止用户从受保护的聊天转发消息,因为本地缓存的通道.noforwards/chat.noforwards标志已过期)。
-
在向讨论组CHAT_GUEST_SEND_FORBIDDEN发送消息时收到错误后,需要刷新有关讨论组的信息(即,我们收到此错误意味着客户端没有在本地阻止非成员用户向只有成员才能发送消息的讨论组发送消息,因为本地缓存的通道标志已过期)。join_to_send
-
USER_NOT_PARTICIPANT在调用channels.leaveChannel时收到错误后,需要刷新有关频道/超级组的信息(即,我们收到此错误意味着客户端尝试离开它不是成员的频道/超级组,并且客户端没有尝试在本地阻止此操作,因为其频道构造函数已过时)。
-
调用以下方法后(无论调用成功与否USERNAME_NOT_MODIFIED,收到错误也视为成功):
- account.toggleUsername、account.reorderUsernames- 仅供我们自己使用
- bots.toggleUsername、bots.reorderUsernames- 用于我们已更新用户名的机器人
- channels.toggleUsername、channels.reorderUsernames- 用于我们已更新用户名的频道
只有当新的用户名顺序/活动用户名无法在本地应用时(例如,方法调用成功将某个未与本地缓存中的对等节点关联的用户名设置为活动用户名,等等),才应通过调用 users.getUsers、channels.getChannels 手动刷新信息;否则,应使用用户传递给 toggle/reorder 方法的信息在本地更新对等节点信息数据库的字段username。usernames
-
调用以下方法后,如果照片中未返回我们为其设置个人资料照片的用户,则:users
- photos.updateProfilePhoto
- photos.uploadProfilePhoto
- photos.uploadContactProfilePhoto- 仅当suggest未设置时
-
调用photos.deletePhotos后,如果删除传递给该方法的照片后,本地缓存中没有更多照片供当前用户使用。
-
下载同伴照片失败后。
-
客户端应每隔几秒调用`contacts.getStatuses`70000-100000方法来更新联系人的status`user` 字段。
具体的联系人状态轮询间隔应在70000和之间随机选择100000,并且每次调用`contacts.getStatuses`时都应重新选择。
如果`contacts.getStatuses`5查询失败,则在秒后重复调用该方法10。
对等ID
对等体id是一个唯一的 64 位 ID,用于识别特定的用户、聊天或频道。
该字段应用作频道、聊天和用户数据库的主键。
请注意,用户、聊天和频道的 ID 序列存在重叠,因此您必须执行以下操作之一:
- 使用单独的表/哈希表来存储用户、聊天记录和频道信息。
-
按照此处的说明将对等节点 ID 转换为机器人 API ID»,这样您就可以为所有三种对等节点类型使用单个 ID 序列(和数据库),从而保持唯一性。在这种情况下,所有对等节点类型都可以
使用 单个表,但由于构造函数的结构不同,为了避免不必要的类型检查,最好像第一种方法那样使用三个表。
点击以下链接查看各种聊天类型的可用 MTProto ID 范围,以及如何将它们转换为单个机器人 API 范围的信息:
- 用户 »
- 聊天 »
- 超级群组/频道 »
- Monoforum »
即使您决定使用单独的数据库,将对等 ID 转换为机器人 API ID也是一个好主意,因为这将使 ID 对您和您的用户来说更具视觉识别性,并保证与机器人 API 的兼容性。
访问哈希
这是存储在对等数据库access_hash中的第二重要字段,用于生成InputPeer、inputUser和inputChannel构造函数,以便通过 API 与对等节点交互。请注意,聊天(基本群组)没有也不需要访问哈希。用户和频道(超级群组和频道)都有访问哈希,并且它可以有多种形式:
- 完全访问哈希:可在 API 的任何位置使用。
- 最小访问哈希:从最小构造函数接收 »,只能用于使用inputPeerPhotoFileLocation »获取个人资料图片。
- 来自消息的访问哈希:不是真正的访问哈希,而是按照此处规定构造的 »,当本地只有最小访问哈希可用,但需要完整访问哈希时,必须使用此哈希。
- 零访问哈希:等于0,当本地只有最小访问哈希(或没有访问哈希)可用,但需要完整访问哈希时,机器人必须使用零访问哈希。
上面列出的访问哈希版本按优先级降序排列,如果当前缓存了优先级较高的版本,则不能用优先级较低的版本覆盖它。
当您与 API 交互时(例如在公共聊天、搜索功能、用户名解析、临时个人资料链接等),系统会获取访问哈希值:如果您只有用户/频道/超级群组 ID 而没有访问哈希值,则无法与该用户/频道/超级群组进行交互。
访问哈希值不能在不同的帐户或同一帐户的不同会话之间重复使用。
这是 Telegram 的一项核心反垃圾邮件功能。
客户端和客户端 API 应该避免向用户暴露访问哈希值,因为这些哈希值无法在当前会话之外重用,而且当客户端可以完美地自行完成这项工作时,用户不应该承担存储这些哈希值的负担。
注意:其他一些与对等节点无关的构造函数(即非用户、聊天或频道)也可能包含访问哈希值,这些哈希值应该存储在不同的数据库中。
其他信息
客户端常用的其他各种字段,如构造函数页面(用户、聊天和频道)中所述。如构造函数文档中所述,如果收到最小构造函数
,
则某些字段不得被覆盖,而其他一些字段的更改必须触发整个信息数据库的失效。
完整信息数据库
users.userFull#3b6d152e full_user:UserFull chats:Vector<Chat> users:Vector<User> = users.UserFull; messages.chatFull#e5d7d19c full_chat:ChatFull chats:Vector<Chat> users:Vector<User> = messages.ChatFull;userFull#c577b5ad flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true display_gifts_button:flags2.16?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme:flags.15?ChatTheme private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long disallowed_gifts:flags2.15?DisallowedGiftsSettings stars_rating:flags2.17?StarsRating stars_my_pending_rating:flags2.18?StarsRating stars_my_pending_rating_date:flags2.18?int main_tab:flags2.20?ProfileTab saved_music:flags2.21?Document = UserFull; chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull; channelFull#e4e0b29d flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int send_paid_messages_stars:flags2.21?long main_tab:flags2.22?ProfileTab = ChatFull;---functions---users.getFullUser#b60f5918 id:InputUser = users.UserFull; messages.getFullChat#aeb00b34 chat_id:long = messages.ChatFull; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull;
示例实现:tdlib。
完整的数据库包含来自userFull、chatFull和channelFull构造函数的信息。
要填充对等节点的完整信息数据库,请调用users.getFullUser、messages.getFullChat、channels.getFullChannel,所有这些都需要先前缓存的access_hash »。
仅使userFull和channelFull条目在存储 60 秒后失效。
当客户端需要从完整构造函数中获取某些数据,但数据库中尚无相应条目,或者所需条目已因 TTL 过期而失效,或者出现以下情况时,刷新完整信息数据库:
-
某些事件(此处指定 » )会更改(非完整!)对等信息数据库中某个条目的特定字段子集的值»。有关更多信息,请参阅用户和通道
构造器页面 中的文档(搜索关键字“invalidate”)。 - 当收到updateUser、updateChat、updateChannel以及其他一些更新时(如这里所述)»
- 在更改或(但不)信息后调用bots.setBotInfo(即使在出错时),对于我们更改了信息的机器人。aboutdescriptionname
- 在调用messages.setChatWallPaper删除壁纸并收到错误后,对于我们尝试更改其壁纸的对等方,获取正确和更新的壁纸设置。
- 调用messages.setChatAvailableReactionsCHAT_NOT_MODIFIED后,对于我们尝试更改其反应设置的对等方,出现了一个与 不同的错误。
- 在收到SEND_AS_PEER_INVALID与组/超级组交互的任何方法的错误后:刷新有关目标的信息,以查看哪些通道对等体可用于通过 send_as 向组/超级组发送消息。
- 如果chat不photo等于chatFull。chat_photo
- 通道/超组 A 的channelFull已更新为指向通道/超组 B,但通道/超组B的channelFull未指向通道/超组 A,请刷新通道/超组 B 的channelFull。linked_chat_idlinked_chat_id
- 参与频道/超级群组的机器人的信息是通过其他方式获取的,但该机器人不包含在channelFull中。bot_info
- 如果channelFullparticipants_count小于channelFull,admins_count则在通过其他方式对管理员列表进行本地更新后, channelFull 的值会发生变化。
- 如果当前登录用户的inputPrivacyKeyStatusTimestamp 隐私设置 »发生更改,则刷新所有用户的整个userFull缓存。
- USER_NOT_PARTICIPANT收到与调用channels.leaveChannel时不同的错误后
- 如果个人资料图片更新或删除
-
成功调用以下任一方法后,对于相关机器人:
- 机器人删除预览媒体
-
调用以下任一方法后(无论成功与否):
- bots.setBotGroupDefaultAdminRights- 为机器人
- bots.setBotBroadcastDefaultAdminRights- 为机器人设置权限
- channels.inviteToChannel- 用于频道/超级组
- channels.editAdmin- 用于频道/超级组
- channels.editCreator- 用于频道/超级组
- 收到CHANNEL_PRIVATERPCCHANNEL_PUBLIC_GROUP_NA错误时。
- 当频道变为私有/公开时(之前是公开/私有。公开频道至少有一个用户名,私有频道没有用户名)。
- 如果我们通过指定chatBannedRights来禁止聊天/超级群组参与者,until_date则管理员应刷新该聊天/超级群组的完整缓存until_date。
以上列表并不详尽,客户也可以在其他一些情况下选择刷新同伴信息(例如,打开个人资料页面时等)。
生活质量检查
请注意以下事项:对等信息也用于改善生活质量,即直接阻止用户执行对等信息不允许的操作,而不是允许该操作然后显示错误。
所有客户端都应该实施这些改进,但是,服务器也会始终通过发出适当的RPC 错误来阻止用户执行非法操作,因为即使实施了本地检查,有时仍然有可能执行非法操作(见下文)。
客户端不应(或同时)阻止用户在客户端执行非法操作,而应始终提供服务器返回的错误的本地化版本(如从RPC 错误页面下载的 JSON 文件中所列),以告知用户尝试操作失败的原因。
换句话说,如果客户端没有在本地阻止失败的方法调用,因为:
- 为了简单起见,它没有实现本地检查。
-
它确实实现了本地检查,但是:
- 同伴信息已过时或
- 服务器发送了对等信息的更新,但由于服务器发送更新和客户端发送查询之间存在(不可避免的!)竞争条件,因此未能及时应用。
- 其他未指明的原因(例如,新功能在未来的图层中引入的新检查等)
……它仍然应该根据 RPC 错误的描述(如果JSON 错误数据库中有该描述,否则就显示 RPC 错误本身)让用户知道查询失败的原因。