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/dialog.py ADDED
@@ -0,0 +1,626 @@
1
+ """Dialogs, drafts and folders — the chat-list side of the model.
2
+
3
+ Everything the chat list shows is here: the dialog row itself, the chat
4
+ folders that re-slice it, the notification exception behind the little
5
+ bell, the action bar Telegram puts above a stranger's chat, and the
6
+ per-chat decorations (theme, wallpaper, auto-delete timer).
7
+
8
+ Two shapes are deliberate.
9
+
10
+ * **A dialog carries its peer, not a flattened copy of it.** v1 spelled the
11
+ chat three different ways depending on which command answered; here every
12
+ row embeds the same `Peer`, so `chat.id` means one thing everywhere.
13
+ * **A notification setting is a tri-state.** `peerNotifySettings` fields are
14
+ *unset* when the chat inherits the scope default, and `None` here means
15
+ exactly that — not "off". Collapsing the two is how a client ends up
16
+ reporting a muted chat as unmuted.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from typing import Any
22
+
23
+ from tlgr.models.base import Model
24
+ from tlgr.models.message import Message, MessageEntity
25
+ from tlgr.models.peer import Peer
26
+
27
+ __all__ = [
28
+ "ActionBar",
29
+ "ArchiveResult",
30
+ "ArchiveSettings",
31
+ "Badge",
32
+ "Catchup",
33
+ "CatchupChat",
34
+ "ChatInfo",
35
+ "ChatSwitches",
36
+ "ChatTheme",
37
+ "ChatWallpaper",
38
+ "ChatlistInvite",
39
+ "ChatlistJoin",
40
+ "ChatlistUpdates",
41
+ "ClearResult",
42
+ "DeleteChatResult",
43
+ "Dialog",
44
+ "Draft",
45
+ "Folder",
46
+ "FolderBadge",
47
+ "FolderDeleted",
48
+ "FolderList",
49
+ "FolderOrder",
50
+ "ImportState",
51
+ "LeaveResult",
52
+ "MuteResult",
53
+ "NotifySettings",
54
+ "NotifyView",
55
+ "OpenResult",
56
+ "PeerResult",
57
+ "PinnedDialogs",
58
+ "Poster",
59
+ "PosterReport",
60
+ "Promo",
61
+ "ReadChats",
62
+ "SavedDialog",
63
+ "SecretChat",
64
+ "ShareDeleted",
65
+ "SuggestedFolder",
66
+ "ThemeResult",
67
+ "TranslateResult",
68
+ "TtlResult",
69
+ "TypingResult",
70
+ "UnreadResult",
71
+ "WallpaperResult",
72
+ ]
73
+
74
+
75
+ class NotifySettings(Model):
76
+ """One chat's notification exception.
77
+
78
+ `muted` is the answer to the question a human asks; the fields under it
79
+ are what Telegram actually stores, and a `None` among them means "not
80
+ set here, inherit the scope default".
81
+ """
82
+
83
+ muted: bool = False
84
+ mute_until: str | None = None
85
+ mute_until_unix: int | None = None
86
+ silent: bool | None = None
87
+ show_previews: bool | None = None
88
+ sound: str | None = None
89
+ stories_muted: bool | None = None
90
+ stories_hide_sender: bool | None = None
91
+ stories_sound: str | None = None
92
+
93
+
94
+ class NotifyView(Model):
95
+ """`chat notify get`: the exception, and what it resolves to.
96
+
97
+ Both halves are reported because they answer different questions. The
98
+ exception is what you must send back to change one field without
99
+ clobbering the others; the effective value is what the user is asking
100
+ about when they say "is this chat muted?".
101
+ """
102
+
103
+ chat_id: int
104
+ settings: NotifySettings
105
+ effective: NotifySettings | None = None
106
+ scope: str = ""
107
+ scope_default: NotifySettings | None = None
108
+ #: Field names taken from the scope default rather than set on the chat.
109
+ inherited: list[str] = []
110
+
111
+
112
+ class Draft(Model):
113
+ chat_id: int
114
+ chat: Peer | None = None
115
+ text: str = ""
116
+ entities: list[MessageEntity] = []
117
+ reply_to_msg_id: int | None = None
118
+ top_msg_id: int | None = None
119
+ no_webpage: bool = False
120
+ effect_id: int | None = None
121
+ date: str | None = None
122
+ empty: bool = False
123
+
124
+
125
+ class Dialog(Model):
126
+ """One row of the chat list."""
127
+
128
+ chat: Peer
129
+ unread_count: int = 0
130
+ unread_mentions_count: int = 0
131
+ unread_reactions_count: int = 0
132
+ unread_poll_votes_count: int = 0
133
+ unread_mark: bool = False
134
+ read_inbox_max_id: int = 0
135
+ # The highest message of OURS the other side has read — the only honest
136
+ # answer to "have they seen it?", and not derivable from read_inbox_max_id.
137
+ read_outbox_max_id: int = 0
138
+ top_message_id: int | None = None
139
+ pinned: bool = False
140
+ pinned_order: int | None = None
141
+ folder_id: int = 0
142
+ archived: bool = False
143
+ #: Ids of the chat *folders* (dialog filters) this chat falls into. Not
144
+ #: the same thing as `folder_id`, which is Telegram's peer-folder (0 or 1).
145
+ folders: list[int] = []
146
+ notify: NotifySettings | None = None
147
+ draft: Draft | None = None
148
+ ttl_period: int | None = None
149
+ view_forum_as_messages: bool | None = None
150
+ requests_pending: int | None = None
151
+ restricted: bool = False
152
+ restriction_reason: list[str] = []
153
+ participants_count: int | None = None
154
+ #: `channels.getInactiveChannels` only: when the chat was last active.
155
+ inactive_since: str | None = None
156
+ last_message: Message | None = None
157
+
158
+
159
+ class SavedDialog(Model):
160
+ """A Saved-Messages sublist, or a channel direct-message topic.
161
+
162
+ The same TL family answers both, which is why one model does: a monoforum
163
+ topic *is* a saved dialog whose parent peer is the channel.
164
+ """
165
+
166
+ origin_peer: Peer | None = None
167
+ origin_id: int = 0
168
+ parent_peer: int | None = None
169
+ top_message_id: int | None = None
170
+ pinned: bool = False
171
+ unread_count: int = 0
172
+ unread_mark: bool = False
173
+ date: str | None = None
174
+ date_unix: int | None = None
175
+
176
+
177
+ class Folder(Model):
178
+ """A chat folder (`dialogFilter`), or a shared one (`dialogFilterChatlist`).
179
+
180
+ A shared folder carries no type flags at all — Telegram will not accept
181
+ them on one — so `is_chatlist` is the flag that says which half of this
182
+ model is meaningful.
183
+ """
184
+
185
+ id: int
186
+ title: str
187
+ emoticon: str | None = None
188
+ color: int | None = None
189
+ title_noanimate: bool = False
190
+ include_peers: list[int] = []
191
+ exclude_peers: list[int] = []
192
+ pinned_peers: list[int] = []
193
+ contacts: bool = False
194
+ non_contacts: bool = False
195
+ groups: bool = False
196
+ broadcasts: bool = False
197
+ bots: bool = False
198
+ exclude_muted: bool = False
199
+ exclude_read: bool = False
200
+ exclude_archived: bool = False
201
+ is_chatlist: bool = False
202
+ has_my_invites: bool = False
203
+ is_default: bool = False
204
+ #: Populated by `folder list --with-counts`.
205
+ chats: int | None = None
206
+ unread_chats: int | None = None
207
+ unread_messages: int | None = None
208
+
209
+
210
+ class FolderList(Model):
211
+ tags_enabled: bool = False
212
+ folders: list[Folder] = []
213
+
214
+
215
+ class FolderOrder(Model):
216
+ order: list[int] = []
217
+
218
+
219
+ class FolderDeleted(Model):
220
+ id: int
221
+ deleted: bool = False
222
+ left_chats: list[int] = []
223
+ suggested: list[int] = []
224
+
225
+
226
+ class SuggestedFolder(Model):
227
+ title: str
228
+ description: str = ""
229
+ emoticon: str | None = None
230
+ contacts: bool = False
231
+ non_contacts: bool = False
232
+ groups: bool = False
233
+ broadcasts: bool = False
234
+ bots: bool = False
235
+ exclude_muted: bool = False
236
+ exclude_read: bool = False
237
+ exclude_archived: bool = False
238
+ #: Set when `--add` created the folder from this suggestion.
239
+ added_id: int | None = None
240
+
241
+
242
+ class ChatlistInvite(Model):
243
+ slug: str
244
+ url: str = ""
245
+ title: str = ""
246
+ peers: list[int] = []
247
+
248
+
249
+ class ChatlistJoin(Model):
250
+ slug: str
251
+ title: str = ""
252
+ emoticon: str | None = None
253
+ already_member: bool = False
254
+ filter_id: int | None = None
255
+ peers: list[int] = []
256
+ missing_peers: list[int] = []
257
+ already_peers: list[int] = []
258
+ joined: list[int] = []
259
+
260
+
261
+ class ChatlistUpdates(Model):
262
+ id: int
263
+ missing_peers: list[int] = []
264
+ joined: list[int] = []
265
+ dismissed: bool = False
266
+
267
+
268
+ class ShareDeleted(Model):
269
+ slug: str
270
+ deleted: bool = False
271
+
272
+
273
+ # ---------------------------------------------------------------------------
274
+ # Per-chat state
275
+ # ---------------------------------------------------------------------------
276
+
277
+
278
+ class ActionBar(Model):
279
+ """`peerSettings` — the bar Telegram shows above a chat with a stranger.
280
+
281
+ The four date-ish fields at the bottom are the strongest cold-outreach
282
+ triage signals a client is given, and no other call reports them.
283
+ """
284
+
285
+ chat_id: int
286
+ report_spam: bool = False
287
+ add_contact: bool = False
288
+ block_contact: bool = False
289
+ share_contact: bool = False
290
+ need_contacts_exception: bool = False
291
+ report_geo: bool = False
292
+ autoarchived: bool = False
293
+ invite_members: bool = False
294
+ request_chat_title: str | None = None
295
+ request_chat_date: str | None = None
296
+ request_chat_broadcast: bool = False
297
+ business_bot_id: int | None = None
298
+ business_bot_paused: bool = False
299
+ business_bot_can_reply: bool = False
300
+ charge_paid_message_stars: int | None = None
301
+ registration_month: str | None = None
302
+ phone_country: str | None = None
303
+ name_change_date: str | None = None
304
+ photo_change_date: str | None = None
305
+ geo_distance: int | None = None
306
+ hidden: bool = False
307
+
308
+
309
+ class ChatTheme(Model):
310
+ emoticon: str | None = None
311
+ gift_id: int | None = None
312
+ gift_slug: str | None = None
313
+ title: str = ""
314
+ premium_required: bool = False
315
+
316
+
317
+ class ChatWallpaper(Model):
318
+ id: int | None = None
319
+ slug: str | None = None
320
+ dark: bool = False
321
+ pattern: bool = False
322
+ blur: bool = False
323
+ intensity: int | None = None
324
+ background_color: int | None = None
325
+ second_background_color: int | None = None
326
+ emoticon: str | None = None
327
+
328
+
329
+ class ChatInfo(Model):
330
+ """`chat get`: the dialog row, the peer, and whatever `--full` added.
331
+
332
+ One flat object rather than three nested ones because this is the command
333
+ a script runs to answer a single question, and `--select` is how it picks
334
+ the field it wants.
335
+ """
336
+
337
+ id: int
338
+ raw_id: int = 0
339
+ type: str = "unknown"
340
+ title: str = ""
341
+ username: str | None = None
342
+ usernames: list[str] = []
343
+ # -- dialog record --
344
+ folder_id: int | None = None
345
+ pinned: bool | None = None
346
+ unread: int | None = None
347
+ unread_mentions: int | None = None
348
+ unread_reactions: int | None = None
349
+ unread_mark: bool | None = None
350
+ read_inbox_max_id: int | None = None
351
+ read_outbox_max_id: int | None = None
352
+ top_message_id: int | None = None
353
+ notify_settings: NotifySettings | None = None
354
+ draft: Draft | None = None
355
+ ttl_period: int | None = None
356
+ theme: ChatTheme | None = None
357
+ wallpaper: ChatWallpaper | None = None
358
+ translations_disabled: bool | None = None
359
+ view_forum_as_messages: bool | None = None
360
+ settings: ActionBar | None = None
361
+ # -- full info --
362
+ blocked: bool | None = None
363
+ blocked_my_stories_from: bool | None = None
364
+ requests_pending: int | None = None
365
+ recent_requesters: list[int] = []
366
+ common_chats_count: int | None = None
367
+ personal_channel_id: int | None = None
368
+ online_count: int | None = None
369
+ restricted: bool | None = None
370
+ restriction_reason: list[str] = []
371
+ default_send_as: int | None = None
372
+ noforwards: bool | None = None
373
+ about: str | None = None
374
+ participants_count: int | None = None
375
+ admins_count: int | None = None
376
+ kicked_count: int | None = None
377
+ banned_count: int | None = None
378
+ creator: bool | None = None
379
+ left: bool | None = None
380
+ forum: bool | None = None
381
+ gigagroup: bool | None = None
382
+ join_to_send: bool | None = None
383
+ join_request: bool | None = None
384
+ slowmode_seconds: int | None = None
385
+ hidden_prehistory: bool | None = None
386
+ participants_hidden: bool | None = None
387
+ antispam: bool | None = None
388
+ linked_chat_id: int | None = None
389
+ linked_monoforum_id: int | None = None
390
+ location: str | None = None
391
+ level: int | None = None
392
+ boosts_applied: int | None = None
393
+ sticker_set: str | None = None
394
+ emoji_set: str | None = None
395
+ available_reactions: list[str] | None = None
396
+ default_banned_rights: dict[str, Any] | None = None
397
+ admin_rights: dict[str, Any] | None = None
398
+ my_rank: str | None = None
399
+ stats_dc: int | None = None
400
+ can_view_stats: bool | None = None
401
+ can_view_participants: bool | None = None
402
+ can_set_stickers: bool | None = None
403
+ can_set_location: bool | None = None
404
+ can_delete_channel: bool | None = None
405
+ can_view_revenue: bool | None = None
406
+ can_view_stars_revenue: bool | None = None
407
+ paid_reactions_available: bool | None = None
408
+ has_welcome_messages: bool | None = None
409
+ exported_invite: str | None = None
410
+ pending_suggestions: list[str] = []
411
+ main_tab: str | None = None
412
+ #: v1's `chat get` spelled the title `name`; kept so a v1 script still reads.
413
+ name: str = ""
414
+
415
+
416
+ class ChatSwitches(Model):
417
+ chat_id: int
418
+ noforwards: bool | None = None
419
+ view_forum_as_messages: bool | None = None
420
+ default_send_as: int | None = None
421
+ send_as_options: list[Peer] = []
422
+
423
+
424
+ class TtlResult(Model):
425
+ chat_id: int
426
+ ttl_period: int | None = None
427
+ set: bool = False
428
+
429
+
430
+ class ThemeResult(Model):
431
+ chat_id: int
432
+ theme: ChatTheme | None = None
433
+
434
+
435
+ class WallpaperResult(Model):
436
+ chat_id: int
437
+ wallpaper: ChatWallpaper | None = None
438
+ for_both: bool = False
439
+ overridden: bool = False
440
+
441
+
442
+ class TranslateResult(Model):
443
+ chat_id: int
444
+ translations_disabled: bool = False
445
+
446
+
447
+ class TypingResult(Model):
448
+ chat_id: int
449
+ action: str = "typing"
450
+ duration: float = 0.0
451
+ #: v1 answered `{"typing": true, "chat_id": …}`; AGENT.md documents it.
452
+ typing: bool = False
453
+
454
+
455
+ class UnreadResult(Model):
456
+ chat_id: int
457
+ unread: bool = False
458
+
459
+
460
+ class PeerResult(Model):
461
+ """One peer's outcome inside a bulk operation."""
462
+
463
+ chat_id: int
464
+ ok: bool = False
465
+ error: str | None = None
466
+
467
+
468
+ class MuteResult(Model):
469
+ muted: bool = False
470
+ chat_id: int | None = None
471
+ chat_ids: list[int] = []
472
+ mute_until: str | None = None
473
+ mute_until_unix: int | None = None
474
+ stories: bool = False
475
+ results: list[PeerResult] = []
476
+
477
+
478
+ class ArchiveResult(Model):
479
+ archived: bool = False
480
+ chat_id: int | None = None
481
+ chat_ids: list[int] = []
482
+ bar_hidden: bool = False
483
+
484
+
485
+ class ArchiveSettings(Model):
486
+ archive_and_mute_new_noncontact_peers: bool = False
487
+ keep_archived_unmuted: bool = False
488
+ keep_archived_folders: bool = False
489
+
490
+
491
+ class PinnedDialogs(Model):
492
+ pinned: bool = False
493
+ chat_id: int | None = None
494
+ chat_ids: list[int] = []
495
+ folder: str = "main"
496
+ order: list[int] = []
497
+
498
+
499
+ class ReadChats(Model):
500
+ read: bool = False
501
+ chat_id: int | None = None
502
+ results: list[PeerResult] = []
503
+ mentions_read: int | None = None
504
+ reactions_read: int | None = None
505
+ polls_read: bool | None = None
506
+
507
+
508
+ class ClearResult(Model):
509
+ chat_id: int
510
+ cleared: bool = False
511
+ messages_affected: int = 0
512
+
513
+
514
+ class DeleteChatResult(Model):
515
+ chat_id: int
516
+ deleted: bool = False
517
+ #: `me` | `both` | `everyone` — who the chat is gone for.
518
+ scope: str = "me"
519
+ left: bool = False
520
+
521
+
522
+ class LeaveResult(Model):
523
+ left: bool = False
524
+ chat_id: int | None = None
525
+ chat_ids: list[int] = []
526
+ errors: list[PeerResult] = []
527
+ #: `messages.getFutureChatCreatorAfterLeave` — who inherits a basic group.
528
+ successor: int | None = None
529
+
530
+
531
+ class OpenResult(Model):
532
+ chat_id: int
533
+ marked_read: bool = False
534
+ messages: list[Message] = []
535
+
536
+
537
+ class CatchupChat(Model):
538
+ id: int
539
+ chat: Peer | None = None
540
+ name: str = ""
541
+ unread_count: int = 0
542
+ unread_mark: bool = False
543
+ messages: list[Message] = []
544
+
545
+
546
+ class Catchup(Model):
547
+ chats: list[CatchupChat] = []
548
+
549
+
550
+ class FolderBadge(Model):
551
+ id: int
552
+ title: str = ""
553
+ chats: int = 0
554
+ messages: int = 0
555
+
556
+
557
+ class Badge(Model):
558
+ """The number on the app icon, computed the way a client computes it."""
559
+
560
+ chats: int = 0
561
+ messages: int = 0
562
+ muted_chats: int = 0
563
+ muted_messages: int = 0
564
+ mentions: int = 0
565
+ reactions: int = 0
566
+ folders: list[FolderBadge] = []
567
+ limits: dict[str, Any] | None = None
568
+
569
+
570
+ class Poster(Model):
571
+ """One sender in a chat's recent history."""
572
+
573
+ user_id: int
574
+ #: v1 spelled it `id`; both are emitted so a v1 script keeps working.
575
+ id: int = 0
576
+ username: str | None = None
577
+ name: str = ""
578
+ count: int = 0
579
+ is_bot: bool = False
580
+ is_deleted: bool = False
581
+ last_msg_id: int | None = None
582
+ date: str | None = None
583
+ date_unix: int | None = None
584
+
585
+
586
+ class PosterReport(Model):
587
+ posters: list[Poster] = []
588
+ scanned_messages: int = 0
589
+ distinct_posters: int = 0
590
+ #: True when a flood wait cut the scan short: the harvest is a prefix of
591
+ #: the truth, and reporting it as complete is the bug this flag prevents.
592
+ partial: bool = False
593
+ flood_wait: int | None = None
594
+
595
+
596
+ class Promo(Model):
597
+ pending_suggestions: list[str] = []
598
+ dismissed_suggestions: list[str] = []
599
+ promo_peer: int | None = None
600
+ psa_type: str | None = None
601
+ psa_message: str | None = None
602
+ birthdays_today: list[int] = []
603
+ hidden: bool = False
604
+
605
+
606
+ class SecretChat(Model):
607
+ id: int
608
+ peer: int | None = None
609
+ state: str = "unknown"
610
+ layer: int | None = None
611
+ created: str | None = None
612
+ key_fingerprint: str | None = None
613
+ ttl: int | None = None
614
+ discarded: bool = False
615
+
616
+
617
+ class ImportState(Model):
618
+ chat_id: int
619
+ import_id: int | None = None
620
+ media_count: int = 0
621
+ started: bool = False
622
+ messages: int = 0
623
+ media: int = 0
624
+ #: `checked` | `started`. Defaulted to the empty string so the field a
625
+ #: caller branches on is always present in the JSON.
626
+ state: str = ""
@@ -0,0 +1,68 @@
1
+ """Request and response envelopes — data only; the transport lives elsewhere.
2
+
3
+ These are defined in `models/` (not `transport/`) because both ends of the
4
+ socket and the CLI renderer need the same shape, and because a test should be
5
+ able to build one without opening a socket.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from tlgr.models.base import Model, Request
13
+ from tlgr.models.error import ErrorBody
14
+ from tlgr.models.page import PageInfo
15
+
16
+ __all__ = ["ErrEnvelope", "Meta", "OkEnvelope", "OpRequest"]
17
+
18
+
19
+ class OpRequest(Request):
20
+ """`POST /v1/op` body.
21
+
22
+ `limit`/`cursor`/`all` sit beside the request rather than inside it so
23
+ that pagination is uniform across every op and no request struct has to
24
+ redeclare it (registry lint L5 forbids those field names).
25
+ """
26
+
27
+ op: str
28
+ account: str = ""
29
+ request: dict[str, Any] = {}
30
+ dry_run: bool = False
31
+ flood_wait_max: int | None = None
32
+ request_id: str = ""
33
+ client_version: str = ""
34
+ protocol: int = 2
35
+ stream: bool = False
36
+ limit: int | None = None
37
+ cursor: str | None = None
38
+ all: bool = False
39
+
40
+
41
+ class Meta(Model):
42
+ request_id: str = ""
43
+ elapsed_ms: int = 0
44
+ flood_wait_slept: int = 0
45
+ warnings: list[str] = []
46
+ # `already: true` is how an idempotent no-op is reported. It is not an
47
+ # error: MESSAGE_NOT_MODIFIED means the world already looks the way the
48
+ # caller asked for, which is success.
49
+ already: bool = False
50
+ daemon_version: str = ""
51
+ protocol: int = 2
52
+
53
+
54
+ class OkEnvelope(Model):
55
+ ok: bool = True
56
+ op: str = ""
57
+ account: str | None = None
58
+ result: Any = None
59
+ page: PageInfo | None = None
60
+ meta: Meta | None = None
61
+
62
+
63
+ class ErrEnvelope(Model):
64
+ error: ErrorBody
65
+ ok: bool = False
66
+ op: str = ""
67
+ account: str | None = None
68
+ meta: Meta | None = None
tlgr/models/error.py ADDED
@@ -0,0 +1,30 @@
1
+ """The wire error shape and the stable machine names that go in it."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from tlgr.models.base import Model
8
+
9
+ __all__ = ["ErrorBody"]
10
+
11
+
12
+ class ErrorBody(Model):
13
+ """One error, everywhere: daemon response body, CLI JSON output, event.
14
+
15
+ `code` is the stable machine name and `exit_code` is what the CLI exits
16
+ with; the two are looked up together from a single table (`core.errors`)
17
+ so a new error can never arrive with a plausible-looking wrong exit.
18
+ """
19
+
20
+ code: str
21
+ message: str
22
+ exit_code: int
23
+ retryable: bool = False
24
+ wait_seconds: int | None = None
25
+ field: str | None = None
26
+ hint: str | None = None
27
+ rpc: dict[str, Any] | None = None
28
+ account: str | None = None
29
+ request_id: str | None = None
30
+ reason: str | None = None