tlgr-cli 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. tlgr/__init__.py +3 -0
  2. tlgr/__main__.py +6 -0
  3. tlgr/actions/__init__.py +45 -0
  4. tlgr/actions/forward.py +74 -0
  5. tlgr/actions/reply.py +32 -0
  6. tlgr/cli/__init__.py +259 -0
  7. tlgr/cli/confirm.py +55 -0
  8. tlgr/cli/errors.py +84 -0
  9. tlgr/cli/gen.py +690 -0
  10. tlgr/cli/globals.py +273 -0
  11. tlgr/cli/introspect.py +170 -0
  12. tlgr/cli/params.py +189 -0
  13. tlgr/cli/render.py +418 -0
  14. tlgr/core/__init__.py +0 -0
  15. tlgr/core/accounts.py +384 -0
  16. tlgr/core/config.py +358 -0
  17. tlgr/core/custom_tl.py +170 -0
  18. tlgr/core/errors.py +687 -0
  19. tlgr/core/eventtypes.py +1170 -0
  20. tlgr/core/identity.py +127 -0
  21. tlgr/core/launchd.py +122 -0
  22. tlgr/core/logging.py +194 -0
  23. tlgr/core/media.py +134 -0
  24. tlgr/core/output.py +251 -0
  25. tlgr/core/pagination.py +227 -0
  26. tlgr/core/paths.py +360 -0
  27. tlgr/core/peers.py +427 -0
  28. tlgr/core/process.py +138 -0
  29. tlgr/core/signing.py +38 -0
  30. tlgr/core/systemd.py +96 -0
  31. tlgr/core/telethon_compat.py +295 -0
  32. tlgr/core/text.py +211 -0
  33. tlgr/core/timefmt.py +199 -0
  34. tlgr/core/tl.py +98 -0
  35. tlgr/daemon/__init__.py +0 -0
  36. tlgr/daemon/app.py +869 -0
  37. tlgr/daemon/dispatch.py +446 -0
  38. tlgr/daemon/events.py +723 -0
  39. tlgr/daemon/files.py +431 -0
  40. tlgr/daemon/idle.py +119 -0
  41. tlgr/daemon/jobs.py +68 -0
  42. tlgr/daemon/main.py +161 -0
  43. tlgr/daemon/peercred.py +75 -0
  44. tlgr/daemon/policy.py +113 -0
  45. tlgr/daemon/preauth.py +366 -0
  46. tlgr/daemon/ratelimit.py +391 -0
  47. tlgr/daemon/server.py +24 -0
  48. tlgr/daemon/session.py +648 -0
  49. tlgr/daemon/sessions.py +274 -0
  50. tlgr/daemon/singleton.py +114 -0
  51. tlgr/daemon/stream.py +193 -0
  52. tlgr/daemon/transfers.py +219 -0
  53. tlgr/daemon/webhook.py +390 -0
  54. tlgr/data/catalog_index.json +1 -0
  55. tlgr/data/parity_waivers.toml +90 -0
  56. tlgr/filters/__init__.py +42 -0
  57. tlgr/filters/compose.py +121 -0
  58. tlgr/filters/content.py +85 -0
  59. tlgr/filters/context.py +114 -0
  60. tlgr/filters/message.py +161 -0
  61. tlgr/filters/temporal.py +87 -0
  62. tlgr/filters/user.py +36 -0
  63. tlgr/gateway/__init__.py +1 -0
  64. tlgr/gateway/config.py +161 -0
  65. tlgr/gateway/engine.py +215 -0
  66. tlgr/gateway/event.py +22 -0
  67. tlgr/jobs/__init__.py +0 -0
  68. tlgr/jobs/base.py +81 -0
  69. tlgr/jobs/client.py +37 -0
  70. tlgr/models/__init__.py +1220 -0
  71. tlgr/models/admin.py +744 -0
  72. tlgr/models/auth.py +510 -0
  73. tlgr/models/base.py +81 -0
  74. tlgr/models/bot.py +576 -0
  75. tlgr/models/business.py +265 -0
  76. tlgr/models/call.py +586 -0
  77. tlgr/models/config.py +101 -0
  78. tlgr/models/contact.py +481 -0
  79. tlgr/models/daemon.py +336 -0
  80. tlgr/models/dialog.py +626 -0
  81. tlgr/models/envelope.py +68 -0
  82. tlgr/models/error.py +30 -0
  83. tlgr/models/event.py +79 -0
  84. tlgr/models/export.py +66 -0
  85. tlgr/models/gift.py +275 -0
  86. tlgr/models/inline.py +84 -0
  87. tlgr/models/location.py +115 -0
  88. tlgr/models/media.py +507 -0
  89. tlgr/models/message.py +584 -0
  90. tlgr/models/net.py +232 -0
  91. tlgr/models/notify.py +105 -0
  92. tlgr/models/page.py +32 -0
  93. tlgr/models/payment.py +172 -0
  94. tlgr/models/peer.py +400 -0
  95. tlgr/models/poll.py +119 -0
  96. tlgr/models/premium.py +161 -0
  97. tlgr/models/privacy.py +93 -0
  98. tlgr/models/profile.py +217 -0
  99. tlgr/models/reaction.py +160 -0
  100. tlgr/models/resolve.py +175 -0
  101. tlgr/models/settings.py +103 -0
  102. tlgr/models/stars.py +101 -0
  103. tlgr/models/sticker.py +243 -0
  104. tlgr/models/story.py +467 -0
  105. tlgr/models/sync.py +105 -0
  106. tlgr/models/todo.py +36 -0
  107. tlgr/models/webapp.py +89 -0
  108. tlgr/ops/__init__.py +63 -0
  109. tlgr/ops/_admin.py +313 -0
  110. tlgr/ops/_auth.py +599 -0
  111. tlgr/ops/_bots.py +586 -0
  112. tlgr/ops/_calls.py +535 -0
  113. tlgr/ops/_common.py +160 -0
  114. tlgr/ops/_layer.py +46 -0
  115. tlgr/ops/_media.py +592 -0
  116. tlgr/ops/_params.py +212 -0
  117. tlgr/ops/_rights.py +402 -0
  118. tlgr/ops/_send.py +593 -0
  119. tlgr/ops/_serialize.py +667 -0
  120. tlgr/ops/_settings.py +306 -0
  121. tlgr/ops/_spec.py +167 -0
  122. tlgr/ops/_story.py +743 -0
  123. tlgr/ops/account.py +2604 -0
  124. tlgr/ops/agent.py +937 -0
  125. tlgr/ops/auth.py +1282 -0
  126. tlgr/ops/bot.py +4880 -0
  127. tlgr/ops/business.py +1520 -0
  128. tlgr/ops/call.py +1610 -0
  129. tlgr/ops/chat.py +4025 -0
  130. tlgr/ops/chat_admin.py +929 -0
  131. tlgr/ops/chat_extra.py +1061 -0
  132. tlgr/ops/chat_invite.py +716 -0
  133. tlgr/ops/chat_manage.py +1691 -0
  134. tlgr/ops/chat_member.py +1357 -0
  135. tlgr/ops/chat_stats.py +902 -0
  136. tlgr/ops/chat_topic.py +905 -0
  137. tlgr/ops/conference.py +791 -0
  138. tlgr/ops/config.py +1698 -0
  139. tlgr/ops/contact.py +2330 -0
  140. tlgr/ops/daemon.py +1397 -0
  141. tlgr/ops/draft.py +299 -0
  142. tlgr/ops/emoji.py +343 -0
  143. tlgr/ops/events.py +1327 -0
  144. tlgr/ops/export.py +596 -0
  145. tlgr/ops/folder.py +1322 -0
  146. tlgr/ops/gif.py +522 -0
  147. tlgr/ops/gift.py +1546 -0
  148. tlgr/ops/giveaway.py +541 -0
  149. tlgr/ops/inline.py +773 -0
  150. tlgr/ops/job.py +799 -0
  151. tlgr/ops/location.py +917 -0
  152. tlgr/ops/media.py +4495 -0
  153. tlgr/ops/message.py +3769 -0
  154. tlgr/ops/net.py +536 -0
  155. tlgr/ops/notify.py +840 -0
  156. tlgr/ops/passport.py +464 -0
  157. tlgr/ops/payment.py +907 -0
  158. tlgr/ops/poll.py +1078 -0
  159. tlgr/ops/premium.py +488 -0
  160. tlgr/ops/privacy.py +794 -0
  161. tlgr/ops/profile.py +1481 -0
  162. tlgr/ops/proxy.py +750 -0
  163. tlgr/ops/reaction.py +1475 -0
  164. tlgr/ops/resolve.py +1140 -0
  165. tlgr/ops/search.py +521 -0
  166. tlgr/ops/settings.py +1066 -0
  167. tlgr/ops/stars.py +594 -0
  168. tlgr/ops/sticker.py +1602 -0
  169. tlgr/ops/story.py +3216 -0
  170. tlgr/ops/sync.py +788 -0
  171. tlgr/ops/todo.py +514 -0
  172. tlgr/ops/user.py +1406 -0
  173. tlgr/ops/vc.py +2351 -0
  174. tlgr/ops/webapp.py +717 -0
  175. tlgr/ops/webhook.py +418 -0
  176. tlgr/parity.py +386 -0
  177. tlgr/processors/__init__.py +125 -0
  178. tlgr/processors/regex.py +26 -0
  179. tlgr/processors/text.py +56 -0
  180. tlgr/registry.py +519 -0
  181. tlgr/schema.py +173 -0
  182. tlgr/transport/__init__.py +30 -0
  183. tlgr/transport/autostart.py +293 -0
  184. tlgr/transport/client.py +805 -0
  185. tlgr/transport/ndjson.py +44 -0
  186. tlgr/version.py +31 -0
  187. tlgr_cli-2.0.1.dist-info/METADATA +957 -0
  188. tlgr_cli-2.0.1.dist-info/RECORD +192 -0
  189. tlgr_cli-2.0.1.dist-info/WHEEL +5 -0
  190. tlgr_cli-2.0.1.dist-info/entry_points.txt +2 -0
  191. tlgr_cli-2.0.1.dist-info/licenses/LICENSE +21 -0
  192. tlgr_cli-2.0.1.dist-info/top_level.txt +1 -0
tlgr/models/contact.py ADDED
@@ -0,0 +1,481 @@
1
+ """Contacts, users and blocking — the address-book side of the model.
2
+
3
+ Three shapes here exist because Telegram's own answers are ambiguous and a
4
+ CLI must not pass that ambiguity on as a fact.
5
+
6
+ * **`UserStatus.by_me`.** `userStatusRecently` is a *coarse bucket*, and the
7
+ reason it is coarse is usually MY last-seen privacy, not theirs. The flag
8
+ is carried through so nothing reports "they hid from you" when the truth is
9
+ "you hid from everyone".
10
+ * **`ContactAdded.retry` and `imported`.** An empty `imported` means the
11
+ number has no account **or** its owner refuses phone lookups. Both lists
12
+ are reported rather than collapsed into a boolean.
13
+ * **`DialogStatus` is three-valued.** `resolved=false` carries
14
+ `has_dialog=null`, never `false`. AGENT.md freezes this and
15
+ `tests/test_ops_contacts.py` holds the line.
16
+
17
+ Access hashes never appear. `access_hash_cached` says whether one is held;
18
+ the value itself is per-login-session state that has no business in output a
19
+ human pastes into a bug report.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import Any, Literal
25
+
26
+ from tlgr.models.base import Model
27
+ from tlgr.models.message import Message
28
+ from tlgr.models.peer import Peer, Photo
29
+
30
+ __all__ = [
31
+ "BlockResult",
32
+ "BlockedPeer",
33
+ "BlockedSet",
34
+ "CloseFriends",
35
+ "Contact",
36
+ "ContactAdded",
37
+ "ContactImport",
38
+ "ContactModel",
39
+ "ContactNote",
40
+ "ContactRemoved",
41
+ "ContactRenamed",
42
+ "ContactRequirement",
43
+ "ContactShared",
44
+ "ContactSync",
45
+ "DialogStatus",
46
+ "FoundPeer",
47
+ "ImportedPhone",
48
+ "MusicTrack",
49
+ "PersonalChannel",
50
+ "PhoneShared",
51
+ "PhotoResult",
52
+ "ProfilePhoto",
53
+ "SavedPhoneContact",
54
+ "SignUp",
55
+ "SuggestedBirthday",
56
+ "TopPeer",
57
+ "TopPeerState",
58
+ "UserLink",
59
+ "UserProfile",
60
+ "UserStatus",
61
+ ]
62
+
63
+ StatusKind = Literal["online", "offline", "recently", "last_week", "last_month", "empty"]
64
+
65
+
66
+ class ContactModel(Model, omit_defaults=False):
67
+ """A contact/user shape that emits every field, including the false ones.
68
+
69
+ `Model` drops defaults so that "absent" can mean "not applicable"; here
70
+ the opposite is true. `already: false`, `resolved: false`, `has_dialog:
71
+ null`, `added: false` and `hidden: false` are the *answer*, and AGENT.md
72
+ publishes them — a caller that reads a missing key as "not set" would
73
+ re-introduce exactly the three-valued confusion this group exists to
74
+ remove.
75
+ """
76
+
77
+
78
+ class UserStatus(ContactModel):
79
+ """Online / last-seen, with the honesty flag Telegram attaches to it.
80
+
81
+ `by_me` is set on the coarse buckets (`recently`, `last_week`,
82
+ `last_month`) when the reason the answer is coarse is *our own*
83
+ last-seen privacy. Reporting that as "they are hiding from you" is a
84
+ conclusion the data does not support.
85
+ """
86
+
87
+ user_id: int = 0
88
+ kind: StatusKind = "empty"
89
+ expires: str | None = None
90
+ expires_unix: int | None = None
91
+ was_online: str | None = None
92
+ was_online_unix: int | None = None
93
+ by_me: bool = False
94
+
95
+
96
+ class Contact(ContactModel):
97
+ """A row of the contact list.
98
+
99
+ `phone` is present only where privacy allows it, which is why it is
100
+ nullable rather than empty-string: "hidden" and "has none" are different.
101
+ """
102
+
103
+ id: int
104
+ raw_id: int = 0
105
+ first_name: str | None = None
106
+ last_name: str | None = None
107
+ name: str = ""
108
+ username: str | None = None
109
+ usernames: list[str] = []
110
+ phone: str | None = None
111
+ mutual: bool = False
112
+ close_friend: bool = False
113
+ premium: bool = False
114
+ bot: bool = False
115
+ deleted: bool = False
116
+ verified: bool = False
117
+ scam: bool = False
118
+ fake: bool = False
119
+ stories_hidden: bool = False
120
+ has_unseen_stories: bool | None = None
121
+ status: UserStatus | None = None
122
+ birthday: str | None = None
123
+ age: int | None = None
124
+ note: str | None = None
125
+ #: The total size of the server-side phonebook, echoed on every row's
126
+ #: page rather than per row; `contact list --ids-only` reports it alone.
127
+ saved_count: int | None = None
128
+
129
+
130
+ class ContactAdded(ContactModel):
131
+ """The reply to `contact add`, keeping v1's `added`/`user_id` keys.
132
+
133
+ `imported` empty with `retry` empty is the ambiguous case: the number has
134
+ no Telegram account, or its owner hides it behind
135
+ `inputPrivacyKeyAddedByPhone`. `reason` says which one we can and cannot
136
+ tell apart.
137
+ """
138
+
139
+ added: bool = False
140
+ user_id: int | None = None
141
+ first_name: str | None = None
142
+ last_name: str | None = None
143
+ imported: list[int] = []
144
+ retry: list[int] = []
145
+ popular_importers: int | None = None
146
+ shared_phone: bool = False
147
+ note: str | None = None
148
+ reason: str | None = None
149
+
150
+
151
+ class ContactRenamed(ContactModel):
152
+ """v1's shape, unchanged: this is only *our* view of their name."""
153
+
154
+ saved: bool = True
155
+ user_id: int = 0
156
+ first_name: str = ""
157
+ last_name: str = ""
158
+
159
+
160
+ class ContactRemoved(ContactModel):
161
+ removed: bool = False
162
+ user_ids: list[int] = []
163
+ phones: list[str] = []
164
+
165
+
166
+ class ContactNote(ContactModel):
167
+ user_id: int = 0
168
+ note: str | None = None
169
+ cleared: bool = False
170
+
171
+
172
+ class ImportedPhone(ContactModel):
173
+ """One line of a phonebook import, and what the server made of it."""
174
+
175
+ phone: str = ""
176
+ first_name: str = ""
177
+ last_name: str = ""
178
+ user_id: int | None = None
179
+ importers: int | None = None
180
+ retry: bool = False
181
+
182
+
183
+ class ContactImport(ContactModel):
184
+ """`contact import`, reporting the retry list rather than swallowing it.
185
+
186
+ `retry` is not an error list: the server asks for those numbers to be
187
+ sent again later, and a caller that drops them silently loses contacts.
188
+ """
189
+
190
+ parsed: int = 0
191
+ imported: list[ImportedPhone] = []
192
+ retry: list[ImportedPhone] = []
193
+ popular_invites: list[ImportedPhone] = []
194
+ batches: int = 0
195
+ flood_waits: int = 0
196
+ dry_run: bool = False
197
+
198
+
199
+ class ContactSync(ContactModel):
200
+ """The diff between a local phonebook file and the server's list."""
201
+
202
+ to_import: list[ImportedPhone] = []
203
+ to_delete: list[str] = []
204
+ applied: bool = False
205
+ imported: int = 0
206
+ deleted: int = 0
207
+
208
+
209
+ class SavedPhoneContact(ContactModel):
210
+ """A number this account once uploaded, whether or not it has an account."""
211
+
212
+ phone: str = ""
213
+ first_name: str = ""
214
+ last_name: str = ""
215
+ date: str | None = None
216
+ date_unix: int | None = None
217
+ has_account: bool | None = None
218
+ invite_text: str | None = None
219
+
220
+
221
+ class BlockedPeer(ContactModel):
222
+ peer: Peer
223
+ date: str | None = None
224
+ date_unix: int | None = None
225
+ kind: Literal["main", "stories"] = "main"
226
+
227
+
228
+ class BlockResult(ContactModel):
229
+ """`user block` / `user unblock`. `already` means no RPC was needed."""
230
+
231
+ peer_id: int = 0
232
+ blocked: bool = False
233
+ stories_only: bool = False
234
+ already: bool = False
235
+ deleted: bool = False
236
+ reported: bool = False
237
+
238
+
239
+ class BlockedSet(ContactModel):
240
+ """`contact blocked set` — a replacement, so the diff is the answer."""
241
+
242
+ count: int = 0
243
+ blocked: list[int] = []
244
+ unblocked: list[int] = []
245
+ kind: Literal["main", "stories"] = "main"
246
+ applied: bool = False
247
+
248
+
249
+ class CloseFriends(ContactModel):
250
+ user_ids: list[int] = []
251
+ count: int = 0
252
+ contacts: list[Contact] = []
253
+
254
+
255
+ class SignUp(ContactModel):
256
+ """A contact who joined Telegram, found as a service message."""
257
+
258
+ user_id: int = 0
259
+ name: str = ""
260
+ username: str | None = None
261
+ chat_id: int = 0
262
+ msg_id: int = 0
263
+ date: str | None = None
264
+ date_unix: int | None = None
265
+ #: The account-wide "X joined Telegram" notification switch, echoed on
266
+ #: the page so `--notify on|off` has somewhere to report its result.
267
+ notify: bool | None = None
268
+
269
+
270
+ class TopPeer(ContactModel):
271
+ peer: Peer
272
+ category: str = "correspondents"
273
+ rating: float = 0.0
274
+
275
+
276
+ class TopPeerState(ContactModel):
277
+ enabled: bool | None = None
278
+ reset_peer: int | None = None
279
+ category: str | None = None
280
+ disabled_by_user: bool = False
281
+
282
+
283
+ class FoundPeer(ContactModel):
284
+ """A `contacts.search` hit, labelled with where it came from.
285
+
286
+ `source` is the whole point: `mine` is a contact or an already-known
287
+ peer, `global` is a public username match, `recent` is local search
288
+ history and `sponsored` is an ad. Merging them into one list without the
289
+ label is how a CLI ends up presenting an advert as a contact.
290
+ """
291
+
292
+ peer: Peer
293
+ source: Literal["mine", "global", "recent", "sponsored", "tme"] = "mine"
294
+ sponsored: bool = False
295
+ random_id: str | None = None
296
+ url: str | None = None
297
+
298
+
299
+ class ContactRequirement(ContactModel):
300
+ """Can I message this user, and at what price?"""
301
+
302
+ user_id: int = 0
303
+ result: Literal["free", "premium", "paid", "unknown"] = "unknown"
304
+ stars_amount: int | None = None
305
+ contact_require_premium: bool | None = None
306
+
307
+
308
+ class DialogStatus(ContactModel):
309
+ """SEMANTICS FROZEN (AGENT.md). Three answers, never conflated.
310
+
311
+ `resolved=true, has_dialog=true` — a dialog exists; `message_count` is
312
+ the server's exact total.
313
+ `resolved=true, has_dialog=false` — definitively none, because the
314
+ account's *complete* dialog list was enumerated.
315
+ `resolved=false, has_dialog=null` — could not be established; exit 13.
316
+
317
+ `has_dialog` is deliberately `bool | None`: there is no third boolean,
318
+ and a caller that reads `null` as `false` re-introduces the cold-contact
319
+ bug this command exists to remove.
320
+
321
+ The server's dialog object carries more than "does it exist", and the
322
+ three read-state fields are echoed verbatim rather than reduced to a
323
+ convenience boolean: `read_outbox_max_id` (the highest message of OURS
324
+ the peer has read), `unread_count` and `top_message`. They are always
325
+ present — a *missing* key is what makes an unanswerable question look
326
+ answered, since a caller reading it back gets `null` and cannot tell
327
+ "not read" from "never reported". `read_outbox_max_id >= top_message`
328
+ only means "they saw our last message" when the last message is in fact
329
+ ours, so that comparison is left to the caller, which is the only party
330
+ that knows.
331
+ """
332
+
333
+ ref: str = ""
334
+ id: int | None = None
335
+ username: str | None = None
336
+ resolved: bool = False
337
+ has_dialog: bool | None = None
338
+ message_count: int | None = None
339
+ read_outbox_max_id: int | None = None
340
+ unread_count: int | None = None
341
+ top_message: int | None = None
342
+ source: str = "unknown"
343
+ reason: str | None = None
344
+ scanned_dialogs: int | None = None
345
+
346
+
347
+ class UserProfile(ContactModel):
348
+ """`user get` — v1's keys, plus everything `users.getFullUser` carries.
349
+
350
+ v1's `id`, `first_name`, `last_name`, `username`, `phone`, `bio`,
351
+ `is_bot`, `status`, `has_photo`, `deleted` and `stories_hidden` are all
352
+ still here and still mean the same thing; `status` stays the short
353
+ lowercase string v1 printed and `status_detail` carries the structured
354
+ form.
355
+ """
356
+
357
+ id: int
358
+ raw_id: int = 0
359
+ kind: Literal["user", "bot"] = "user"
360
+ first_name: str = ""
361
+ last_name: str = ""
362
+ name: str = ""
363
+ username: str | None = None
364
+ usernames: list[str] = []
365
+ phone: str | None = None
366
+ bio: str = ""
367
+ bio_translated: str | None = None
368
+ note: str | None = None
369
+ birthday: str | None = None
370
+ status: str = ""
371
+ status_detail: UserStatus | None = None
372
+ is_self: bool = False
373
+ is_bot: bool = False
374
+ is_contact: bool = False
375
+ is_mutual_contact: bool = False
376
+ is_close_friend: bool = False
377
+ is_premium: bool = False
378
+ is_support: bool = False
379
+ is_verified: bool = False
380
+ is_scam: bool = False
381
+ is_fake: bool = False
382
+ deleted: bool = False
383
+ restricted: bool = False
384
+ restriction_reason: list[str] = []
385
+ has_photo: bool = False
386
+ stories_hidden: bool = False
387
+ lang_code: str | None = None
388
+ photo: Photo | None = None
389
+ personal_photo: Photo | None = None
390
+ fallback_photo: Photo | None = None
391
+ emoji_status_id: int | None = None
392
+ colors: dict[str, Any] | None = None
393
+ #: True only when `users.getFullUser` ran. Everything below it is null
394
+ #: until it does, and this flag is how "not asked" is told apart from
395
+ #: "asked, and the answer was nothing".
396
+ full: bool = False
397
+ blocked: bool | None = None
398
+ blocked_my_stories_from: bool | None = None
399
+ common_chats_count: int | None = None
400
+ personal_channel_id: int | None = None
401
+ personal_channel_message_id: int | None = None
402
+ contact_require_premium: bool | None = None
403
+ send_paid_messages_stars: int | None = None
404
+ stargifts_count: int | None = None
405
+ stars_rating: int | None = None
406
+ main_tab: str | None = None
407
+ unofficial_security_risk: bool | None = None
408
+ business_hours: dict[str, Any] | None = None
409
+ business_location: str | None = None
410
+ business_intro: dict[str, Any] | None = None
411
+ wallpaper: str | None = None
412
+ action_bar: dict[str, Any] | None = None
413
+ #: Whether this account holds a usable access hash for them. The hash
414
+ #: itself is never printed: it is per-login-session and worthless (and
415
+ #: dangerous) anywhere else.
416
+ access_hash_cached: bool = False
417
+ min: bool = False
418
+
419
+
420
+ class SuggestedBirthday(ContactModel):
421
+ user_id: int = 0
422
+ birthday: str = ""
423
+ sent: bool = False
424
+
425
+
426
+ class UserLink(ContactModel):
427
+ url: str = ""
428
+ kind: str = "profile"
429
+ expires: str | None = None
430
+ expires_unix: int | None = None
431
+
432
+
433
+ class MusicTrack(ContactModel):
434
+ id: int = 0
435
+ title: str | None = None
436
+ performer: str | None = None
437
+ duration: int | None = None
438
+ mime_type: str | None = None
439
+ size: int | None = None
440
+ file: str | None = None
441
+
442
+
443
+ class ProfilePhoto(ContactModel):
444
+ id: int = 0
445
+ date: str | None = None
446
+ date_unix: int | None = None
447
+ sizes: list[str] = []
448
+ video: bool = False
449
+ dc_id: int | None = None
450
+ file: str | None = None
451
+ #: Only filled for my own history (`profile photo list`): which of these
452
+ #: is the avatar in force. Absent on another user's photos, because the
453
+ #: server does not say.
454
+ current: bool = False
455
+
456
+
457
+ class PhotoResult(ContactModel):
458
+ user_id: int = 0
459
+ photo_id: int | None = None
460
+ suggested: bool = False
461
+ reset: bool = False
462
+
463
+
464
+ class PersonalChannel(ContactModel):
465
+ """The channel a user pinned to their profile, with a post preview."""
466
+
467
+ user_id: int = 0
468
+ channel: Peer | None = None
469
+ msg_id: int | None = None
470
+ posts: list[Message] = []
471
+
472
+
473
+ class ContactShared(ContactModel):
474
+ chat_id: int = 0
475
+ msg_id: int = 0
476
+ contact: Contact | None = None
477
+
478
+
479
+ class PhoneShared(ContactModel):
480
+ user_id: int = 0
481
+ shared: bool = False