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.
- tlgr/__init__.py +3 -0
- tlgr/__main__.py +6 -0
- tlgr/actions/__init__.py +45 -0
- tlgr/actions/forward.py +74 -0
- tlgr/actions/reply.py +32 -0
- tlgr/cli/__init__.py +259 -0
- tlgr/cli/confirm.py +55 -0
- tlgr/cli/errors.py +84 -0
- tlgr/cli/gen.py +690 -0
- tlgr/cli/globals.py +273 -0
- tlgr/cli/introspect.py +170 -0
- tlgr/cli/params.py +189 -0
- tlgr/cli/render.py +418 -0
- tlgr/core/__init__.py +0 -0
- tlgr/core/accounts.py +384 -0
- tlgr/core/config.py +358 -0
- tlgr/core/custom_tl.py +170 -0
- tlgr/core/errors.py +687 -0
- tlgr/core/eventtypes.py +1170 -0
- tlgr/core/identity.py +127 -0
- tlgr/core/launchd.py +122 -0
- tlgr/core/logging.py +194 -0
- tlgr/core/media.py +134 -0
- tlgr/core/output.py +251 -0
- tlgr/core/pagination.py +227 -0
- tlgr/core/paths.py +360 -0
- tlgr/core/peers.py +427 -0
- tlgr/core/process.py +138 -0
- tlgr/core/signing.py +38 -0
- tlgr/core/systemd.py +96 -0
- tlgr/core/telethon_compat.py +295 -0
- tlgr/core/text.py +211 -0
- tlgr/core/timefmt.py +199 -0
- tlgr/core/tl.py +98 -0
- tlgr/daemon/__init__.py +0 -0
- tlgr/daemon/app.py +869 -0
- tlgr/daemon/dispatch.py +446 -0
- tlgr/daemon/events.py +723 -0
- tlgr/daemon/files.py +431 -0
- tlgr/daemon/idle.py +119 -0
- tlgr/daemon/jobs.py +68 -0
- tlgr/daemon/main.py +161 -0
- tlgr/daemon/peercred.py +75 -0
- tlgr/daemon/policy.py +113 -0
- tlgr/daemon/preauth.py +366 -0
- tlgr/daemon/ratelimit.py +391 -0
- tlgr/daemon/server.py +24 -0
- tlgr/daemon/session.py +648 -0
- tlgr/daemon/sessions.py +274 -0
- tlgr/daemon/singleton.py +114 -0
- tlgr/daemon/stream.py +193 -0
- tlgr/daemon/transfers.py +219 -0
- tlgr/daemon/webhook.py +390 -0
- tlgr/data/catalog_index.json +1 -0
- tlgr/data/parity_waivers.toml +90 -0
- tlgr/filters/__init__.py +42 -0
- tlgr/filters/compose.py +121 -0
- tlgr/filters/content.py +85 -0
- tlgr/filters/context.py +114 -0
- tlgr/filters/message.py +161 -0
- tlgr/filters/temporal.py +87 -0
- tlgr/filters/user.py +36 -0
- tlgr/gateway/__init__.py +1 -0
- tlgr/gateway/config.py +161 -0
- tlgr/gateway/engine.py +215 -0
- tlgr/gateway/event.py +22 -0
- tlgr/jobs/__init__.py +0 -0
- tlgr/jobs/base.py +81 -0
- tlgr/jobs/client.py +37 -0
- tlgr/models/__init__.py +1220 -0
- tlgr/models/admin.py +744 -0
- tlgr/models/auth.py +510 -0
- tlgr/models/base.py +81 -0
- tlgr/models/bot.py +576 -0
- tlgr/models/business.py +265 -0
- tlgr/models/call.py +586 -0
- tlgr/models/config.py +101 -0
- tlgr/models/contact.py +481 -0
- tlgr/models/daemon.py +336 -0
- tlgr/models/dialog.py +626 -0
- tlgr/models/envelope.py +68 -0
- tlgr/models/error.py +30 -0
- tlgr/models/event.py +79 -0
- tlgr/models/export.py +66 -0
- tlgr/models/gift.py +275 -0
- tlgr/models/inline.py +84 -0
- tlgr/models/location.py +115 -0
- tlgr/models/media.py +507 -0
- tlgr/models/message.py +584 -0
- tlgr/models/net.py +232 -0
- tlgr/models/notify.py +105 -0
- tlgr/models/page.py +32 -0
- tlgr/models/payment.py +172 -0
- tlgr/models/peer.py +400 -0
- tlgr/models/poll.py +119 -0
- tlgr/models/premium.py +161 -0
- tlgr/models/privacy.py +93 -0
- tlgr/models/profile.py +217 -0
- tlgr/models/reaction.py +160 -0
- tlgr/models/resolve.py +175 -0
- tlgr/models/settings.py +103 -0
- tlgr/models/stars.py +101 -0
- tlgr/models/sticker.py +243 -0
- tlgr/models/story.py +467 -0
- tlgr/models/sync.py +105 -0
- tlgr/models/todo.py +36 -0
- tlgr/models/webapp.py +89 -0
- tlgr/ops/__init__.py +63 -0
- tlgr/ops/_admin.py +313 -0
- tlgr/ops/_auth.py +599 -0
- tlgr/ops/_bots.py +586 -0
- tlgr/ops/_calls.py +535 -0
- tlgr/ops/_common.py +160 -0
- tlgr/ops/_layer.py +46 -0
- tlgr/ops/_media.py +592 -0
- tlgr/ops/_params.py +212 -0
- tlgr/ops/_rights.py +402 -0
- tlgr/ops/_send.py +593 -0
- tlgr/ops/_serialize.py +667 -0
- tlgr/ops/_settings.py +306 -0
- tlgr/ops/_spec.py +167 -0
- tlgr/ops/_story.py +743 -0
- tlgr/ops/account.py +2604 -0
- tlgr/ops/agent.py +937 -0
- tlgr/ops/auth.py +1282 -0
- tlgr/ops/bot.py +4880 -0
- tlgr/ops/business.py +1520 -0
- tlgr/ops/call.py +1610 -0
- tlgr/ops/chat.py +4025 -0
- tlgr/ops/chat_admin.py +929 -0
- tlgr/ops/chat_extra.py +1061 -0
- tlgr/ops/chat_invite.py +716 -0
- tlgr/ops/chat_manage.py +1691 -0
- tlgr/ops/chat_member.py +1357 -0
- tlgr/ops/chat_stats.py +902 -0
- tlgr/ops/chat_topic.py +905 -0
- tlgr/ops/conference.py +791 -0
- tlgr/ops/config.py +1698 -0
- tlgr/ops/contact.py +2330 -0
- tlgr/ops/daemon.py +1397 -0
- tlgr/ops/draft.py +299 -0
- tlgr/ops/emoji.py +343 -0
- tlgr/ops/events.py +1327 -0
- tlgr/ops/export.py +596 -0
- tlgr/ops/folder.py +1322 -0
- tlgr/ops/gif.py +522 -0
- tlgr/ops/gift.py +1546 -0
- tlgr/ops/giveaway.py +541 -0
- tlgr/ops/inline.py +773 -0
- tlgr/ops/job.py +799 -0
- tlgr/ops/location.py +917 -0
- tlgr/ops/media.py +4495 -0
- tlgr/ops/message.py +3769 -0
- tlgr/ops/net.py +536 -0
- tlgr/ops/notify.py +840 -0
- tlgr/ops/passport.py +464 -0
- tlgr/ops/payment.py +907 -0
- tlgr/ops/poll.py +1078 -0
- tlgr/ops/premium.py +488 -0
- tlgr/ops/privacy.py +794 -0
- tlgr/ops/profile.py +1481 -0
- tlgr/ops/proxy.py +750 -0
- tlgr/ops/reaction.py +1475 -0
- tlgr/ops/resolve.py +1140 -0
- tlgr/ops/search.py +521 -0
- tlgr/ops/settings.py +1066 -0
- tlgr/ops/stars.py +594 -0
- tlgr/ops/sticker.py +1602 -0
- tlgr/ops/story.py +3216 -0
- tlgr/ops/sync.py +788 -0
- tlgr/ops/todo.py +514 -0
- tlgr/ops/user.py +1406 -0
- tlgr/ops/vc.py +2351 -0
- tlgr/ops/webapp.py +717 -0
- tlgr/ops/webhook.py +418 -0
- tlgr/parity.py +386 -0
- tlgr/processors/__init__.py +125 -0
- tlgr/processors/regex.py +26 -0
- tlgr/processors/text.py +56 -0
- tlgr/registry.py +519 -0
- tlgr/schema.py +173 -0
- tlgr/transport/__init__.py +30 -0
- tlgr/transport/autostart.py +293 -0
- tlgr/transport/client.py +805 -0
- tlgr/transport/ndjson.py +44 -0
- tlgr/version.py +31 -0
- tlgr_cli-2.0.1.dist-info/METADATA +957 -0
- tlgr_cli-2.0.1.dist-info/RECORD +192 -0
- tlgr_cli-2.0.1.dist-info/WHEEL +5 -0
- tlgr_cli-2.0.1.dist-info/entry_points.txt +2 -0
- tlgr_cli-2.0.1.dist-info/licenses/LICENSE +21 -0
- 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
|