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/story.py ADDED
@@ -0,0 +1,467 @@
1
+ """Stories: the item, its audience, its overlays, and who watched it.
2
+
3
+ Four shapes carry most of this group and each exists because the API makes a
4
+ distinction the GUI hides.
5
+
6
+ * **`Story`** is one story item. Telegram returns three different TL classes
7
+ for one id — the full `storyItem`, a `storyItemSkipped` placeholder in a
8
+ feed, and `storyItemDeleted` for one that is gone — so the model carries
9
+ `skipped` and `deleted` flags rather than pretending a placeholder is a
10
+ story with no caption.
11
+ * **`StoryPrivacy`** is the audience as a *base rule plus exceptions*, which
12
+ is how `inputPrivacyRule*` vectors actually work and how the GUI's
13
+ "Contacts, except Bob" is expressed. Flattening it into a single string
14
+ would make that sentence inexpressible.
15
+ * **`MediaArea`** is one overlay pill. All eight TL variants are flattened
16
+ into one struct with a `type` discriminator, because `story get --areas-out`
17
+ has to write JSON that `story post --areas` reads back verbatim: a nested
18
+ one-of would make that round trip depend on the caller knowing the variant.
19
+ * **`StoryViewer`** is one row of the viewers screen, which mixes plain views,
20
+ public forwards and public reposts — `kind` says which, so a caller counting
21
+ "people who watched" is not silently counting reposts too.
22
+
23
+ Coordinates on a media area are percentages of the media (0–100), which is
24
+ what the TL type stores; they are never pixels, because a story's rendered
25
+ size is a client decision.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from typing import Any, Literal
31
+
32
+ from tlgr.models.base import Model
33
+ from tlgr.models.message import MediaSummary, Message, MessageEntity
34
+ from tlgr.models.peer import Peer, Photo, User
35
+
36
+ __all__ = [
37
+ "AlbumDeleted",
38
+ "AlbumOrder",
39
+ "BlockedStoryUser",
40
+ "BlocklistChange",
41
+ "LiveStory",
42
+ "MediaArea",
43
+ "StealthMode",
44
+ "StoriesDeleted",
45
+ "Story",
46
+ "StoryAlbum",
47
+ "StoryEvent",
48
+ "StoryExport",
49
+ "StoryFeedPeer",
50
+ "StoryHidden",
51
+ "StoryLimits",
52
+ "StoryPinned",
53
+ "StoryPostCheck",
54
+ "StoryPrivacy",
55
+ "StoryReactionResult",
56
+ "StoryRead",
57
+ "StoryReply",
58
+ "StoryReport",
59
+ "StoryShared",
60
+ "StoryStats",
61
+ "StoryViewer",
62
+ "StoryViews",
63
+ ]
64
+
65
+ #: The eight `mediaArea*` variants, as one flat vocabulary.
66
+ MediaAreaKind = Literal[
67
+ "geo",
68
+ "venue",
69
+ "reaction",
70
+ "channel_post",
71
+ "url",
72
+ "weather",
73
+ "star_gift",
74
+ "unknown",
75
+ ]
76
+
77
+ #: The base audience rule. `selected` is Telegram's "Only these people": an
78
+ #: empty allow list with this rule shows the story to nobody.
79
+ PrivacyBase = Literal["everyone", "contacts", "close-friends", "selected"]
80
+
81
+
82
+ class MediaArea(Model):
83
+ """One overlay pill on a story, in the round-trippable JSON form.
84
+
85
+ `x`/`y` are the centre of the area and `w`/`h` its size, all as
86
+ percentages of the media, exactly as `mediaAreaCoordinates` stores them.
87
+ """
88
+
89
+ type: MediaAreaKind = "unknown"
90
+ x: float = 0.0
91
+ y: float = 0.0
92
+ w: float = 0.0
93
+ h: float = 0.0
94
+ rotation: float = 0.0
95
+ radius: float | None = None
96
+ # geo / venue
97
+ latitude: float | None = None
98
+ longitude: float | None = None
99
+ address: dict[str, str] | None = None
100
+ title: str | None = None
101
+ address_text: str | None = None
102
+ provider: str | None = None
103
+ venue_id: str | None = None
104
+ venue_type: str | None = None
105
+ # suggested reaction
106
+ reaction: str | None = None
107
+ dark: bool = False
108
+ flipped: bool = False
109
+ # channel post
110
+ chat_id: int | None = None
111
+ msg_id: int | None = None
112
+ # url
113
+ url: str | None = None
114
+ # weather
115
+ emoji: str | None = None
116
+ temperature_c: float | None = None
117
+ color: int | None = None
118
+ # collectible star gift
119
+ slug: str | None = None
120
+
121
+
122
+ class StoryPrivacy(Model):
123
+ """Who may see a story: a base rule, then the exceptions on top of it.
124
+
125
+ The lists are *marked* ids, like every other id tlgr emits. They are only
126
+ populated on your own stories — the server never tells you the audience of
127
+ somebody else's.
128
+ """
129
+
130
+ base: PrivacyBase = "everyone"
131
+ allow_users: list[int] = []
132
+ allow_chats: list[int] = []
133
+ disallow_users: list[int] = []
134
+ disallow_chats: list[int] = []
135
+
136
+
137
+ class StoryViews(Model):
138
+ """The counters under a story.
139
+
140
+ `has_viewers` is the honest half: a non-Premium account loses the viewer
141
+ list `story_viewers_expire_period` seconds after the story expires, and
142
+ an empty list then means "no longer available", not "nobody watched".
143
+ """
144
+
145
+ views_count: int = 0
146
+ forwards_count: int | None = None
147
+ reactions_count: int | None = None
148
+ has_viewers: bool = False
149
+ recent_viewers: list[int] = []
150
+ reactions: dict[str, int] = {}
151
+
152
+
153
+ class StoryFwdHeader(Model):
154
+ """Where a reposted story came from."""
155
+
156
+ from_id: int | None = None
157
+ from_name: str | None = None
158
+ story_id: int | None = None
159
+ modified: bool = False
160
+
161
+
162
+ class Story(Model):
163
+ """One story.
164
+
165
+ `skipped` and `deleted` are the two placeholder states the API has:
166
+ a feed hands back `storyItemSkipped` (id, dates and the close-friends flag
167
+ only) and a gone story comes back as `storyItemDeleted`. Reporting either
168
+ as an ordinary story with empty fields is how a caller ends up believing a
169
+ deleted story is a caption-less one.
170
+ """
171
+
172
+ id: int
173
+ peer_id: int = 0
174
+ peer: Peer | None = None
175
+ date: str | None = None
176
+ date_unix: int | None = None
177
+ expire_date: str | None = None
178
+ expire_date_unix: int | None = None
179
+ caption: str = ""
180
+ entities: list[MessageEntity] = []
181
+ media: MediaSummary | None = None
182
+ media_areas: list[MediaArea] = []
183
+ privacy: StoryPrivacy | None = None
184
+ public: bool = False
185
+ close_friends: bool = False
186
+ contacts: bool = False
187
+ selected_contacts: bool = False
188
+ pinned: bool = False
189
+ noforwards: bool = False
190
+ edited: bool = False
191
+ out: bool = False
192
+ min: bool = False
193
+ #: A placeholder row in a feed: only id, dates and `close_friends` are set.
194
+ skipped: bool = False
195
+ #: The story is gone; nothing but `id` is meaningful.
196
+ deleted: bool = False
197
+ #: A live story (an ongoing broadcast rather than a recorded item).
198
+ live: bool = False
199
+ fwd_from: StoryFwdHeader | None = None
200
+ sent_reaction: str | None = None
201
+ albums: list[int] = []
202
+ music: MediaSummary | None = None
203
+ views: StoryViews | None = None
204
+ link: str | None = None
205
+ translation: str | None = None
206
+
207
+
208
+ class StoryAlbum(Model):
209
+ """A profile album — the named chips above the story grid."""
210
+
211
+ id: int
212
+ title: str = ""
213
+ icon: Photo | None = None
214
+ stories: list[int] = []
215
+ stories_count: int | None = None
216
+
217
+
218
+ class AlbumDeleted(Model):
219
+ peer: int = 0
220
+ album_id: int = 0
221
+ ok: bool = True
222
+
223
+
224
+ class AlbumOrder(Model):
225
+ peer: int = 0
226
+ order: list[int] = []
227
+ ok: bool = True
228
+
229
+
230
+ class BlockedStoryUser(Model):
231
+ """A row of "Hide my stories from" — a blocklist of its own."""
232
+
233
+ user_id: int
234
+ username: str | None = None
235
+ name: str = ""
236
+ date: str | None = None
237
+ date_unix: int | None = None
238
+
239
+
240
+ class BlocklistChange(Model):
241
+ added: list[int] = []
242
+ removed: list[int] = []
243
+ total: int | None = None
244
+ already: bool = False
245
+
246
+
247
+ class StoryLimits(Model):
248
+ """The app-config numbers that decide what a story may be.
249
+
250
+ Every one of them is read from `help.getAppConfig`; none is hardcoded,
251
+ because Telegram changes them without changing the layer.
252
+ """
253
+
254
+ expiring_limit: int | None = None
255
+ sent_weekly_limit: int | None = None
256
+ sent_monthly_limit: int | None = None
257
+ caption_length_limit: int | None = None
258
+ suggested_reactions_limit: int | None = None
259
+ area_url_max: int | None = None
260
+ albums_limit: int | None = None
261
+ album_stories_limit: int | None = None
262
+ pinned_to_top_max: int | None = None
263
+ viewers_expire_period: int | None = None
264
+ stealth_past_period: int | None = None
265
+ stealth_future_period: int | None = None
266
+ stealth_cooldown_period: int | None = None
267
+ #: Which of the above this account's Premium status unlocks.
268
+ premium_unlocks: list[str] = []
269
+
270
+
271
+ class StoryPostCheck(Model):
272
+ """`story can-post`: the pre-flight the GUI runs before opening the camera."""
273
+
274
+ can_post: bool = False
275
+ reason: str = ""
276
+ #: Seconds to wait, for a weekly/monthly flood; boosts missing, for a channel.
277
+ retry_after: int | None = None
278
+ boosts_required: int | None = None
279
+ free_slots: int | None = None
280
+ count_remains: int | None = None
281
+ premium: bool = False
282
+ limits: StoryLimits | None = None
283
+ chats: list[Peer] = []
284
+
285
+
286
+ class StoryHiddenPeer(Model):
287
+ """One row of a bulk `story hide`, in the same keys the single call uses.
288
+
289
+ `hidden` and `already` carry no default on purpose: they are the answer,
290
+ and `omit_defaults` would drop the false ones (see `StoryHidden`).
291
+ """
292
+
293
+ hidden: bool
294
+ already: bool
295
+ user_id: int = 0
296
+ username: str | None = None
297
+ peer_id: int = 0
298
+
299
+
300
+ class StoryHidden(Model):
301
+ """The per-account "Hide Stories" toggle, in v1's keys.
302
+
303
+ `user_id`/`username` are what `tlgr user hide-stories` printed and stay
304
+ spelled that way; `peer_id` is the marked id, for the channels the same
305
+ RPC accepts. A single target answers exactly as v1 did; extra targets
306
+ appear in `peers`, so a bulk pass stays one command without changing the
307
+ shape the documented single-peer call returns.
308
+ """
309
+
310
+ #: Neither has a default: `hidden: false` and `already: false` are the
311
+ #: answer AGENT.md publishes for `user hide-stories`, and `omit_defaults`
312
+ #: would drop exactly the two values a caller has to read.
313
+ hidden: bool
314
+ already: bool
315
+ user_id: int = 0
316
+ username: str | None = None
317
+ peer_id: int = 0
318
+ #: Set instead of the peer fields when `--all` collapsed the whole bar.
319
+ all: bool = False
320
+ peers: list[StoryHiddenPeer] = []
321
+
322
+
323
+ class StoriesDeleted(Model):
324
+ peer: int = 0
325
+ deleted_ids: list[int] = []
326
+
327
+
328
+ class StoryPinned(Model):
329
+ peer: int = 0
330
+ ids: list[int] = []
331
+ pinned: bool = False
332
+ pinned_to_top: list[int] = []
333
+
334
+
335
+ class StoryRead(Model):
336
+ peer: int = 0
337
+ max_id: int = 0
338
+ ids: list[int] = []
339
+ ok: bool = True
340
+ already: bool = False
341
+ #: Ids whose view counter was incremented (`--register-view`).
342
+ viewed_ids: list[int] = []
343
+
344
+
345
+ class StoryReactionResult(Model):
346
+ peer: int = 0
347
+ story_id: int = 0
348
+ reaction: str = ""
349
+ removed: bool = False
350
+ #: Set when `--as-message` sent an ordinary reply instead.
351
+ msg_id: int | None = None
352
+
353
+
354
+ class StoryReply(Model):
355
+ chat_id: int = 0
356
+ msg_id: int = 0
357
+ reply_to_story: int = 0
358
+ text: str = ""
359
+ message: Message | None = None
360
+
361
+
362
+ class StoryShared(Model):
363
+ sent: list[Message] = []
364
+ story_id: int = 0
365
+ peer: int = 0
366
+
367
+
368
+ class StoryReport(Model):
369
+ """One step of the multi-step report flow."""
370
+
371
+ result: str = ""
372
+ title: str = ""
373
+ options: list[dict[str, str]] = []
374
+ comment_required: bool = False
375
+ reported: bool = False
376
+
377
+
378
+ class StealthMode(Model):
379
+ active_until_date: str | None = None
380
+ active_until_unix: int | None = None
381
+ cooldown_until_date: str | None = None
382
+ cooldown_until_unix: int | None = None
383
+ past: bool = False
384
+ future: bool = False
385
+ active: bool = False
386
+
387
+
388
+ class StoryViewer(Model):
389
+ """One row of the viewers screen."""
390
+
391
+ kind: Literal["view", "forward", "repost"] = "view"
392
+ user_id: int = 0
393
+ user: User | None = None
394
+ peer: Peer | None = None
395
+ date: str | None = None
396
+ date_unix: int | None = None
397
+ reaction: str | None = None
398
+ blocked: bool = False
399
+ blocked_my_stories_from: bool = False
400
+ #: For a forward/repost row: where it landed.
401
+ msg_id: int | None = None
402
+ story_id: int | None = None
403
+
404
+
405
+ class StoryFeedPeer(Model):
406
+ """One peer in the stories bar."""
407
+
408
+ peer_id: int = 0
409
+ peer: Peer | None = None
410
+ max_read_id: int = 0
411
+ stories: list[Story] = []
412
+ unread_count: int = 0
413
+ has_unread: bool = False
414
+ live: bool = False
415
+ hidden: bool = False
416
+ #: Set by `--peers`: the compact `stories.getPeerMaxIDs` answer.
417
+ max_id: int | None = None
418
+
419
+
420
+ class LiveStory(Model):
421
+ """A live story: the ongoing-broadcast form of a story."""
422
+
423
+ story_id: int = 0
424
+ peer: int = 0
425
+ live: bool = True
426
+ date: str | None = None
427
+ expire_date: str | None = None
428
+ call_id: int | None = None
429
+ participants_count: int | None = None
430
+ streamer: int | None = None
431
+ rtmp_stream: bool = False
432
+ rtmp_url: str | None = None
433
+ rtmp_key: str | None = None
434
+ stream_dc_id: int | None = None
435
+ messages_enabled: bool | None = None
436
+ send_paid_messages_stars: int | None = None
437
+ record_start_date: str | None = None
438
+ listeners_hidden: bool | None = None
439
+ pinned: bool = False
440
+ noforwards: bool = False
441
+
442
+
443
+ class StoryExport(Model):
444
+ count: int = 0
445
+ out_dir: str = ""
446
+ files: list[str] = []
447
+ stories: list[Story] = []
448
+
449
+
450
+ class StoryStats(Model):
451
+ views_graph: dict[str, Any] | None = None
452
+ reactions_by_emotion_graph: dict[str, Any] | None = None
453
+ forwards: list[dict[str, Any]] = []
454
+
455
+
456
+ class StoryEvent(Model):
457
+ """One frame of `story watch`."""
458
+
459
+ event: str = "story"
460
+ kind: str = ""
461
+ peer: int = 0
462
+ story_id: int | None = None
463
+ ids: list[int] = []
464
+ reaction: str | None = None
465
+ max_read_id: int | None = None
466
+ stealth_mode: StealthMode | None = None
467
+ at: str = ""
tlgr/models/sync.py ADDED
@@ -0,0 +1,105 @@
1
+ """Update-state shapes: pts/qts/seq, per-channel boxes, and difference runs.
2
+
3
+ `sync` is deliberately a different noun from `catchup`. `chat catchup` is the
4
+ unread digest a human reads; `sync catch-up` is `updates.getDifference`, the
5
+ plumbing that decides whether an event ever existed for the daemon at all.
6
+ Confusing them is how v1 ended up with an idle timeout that guaranteed a
7
+ permanent sync hole and a `catchup` command that could not close it.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+
14
+ from tlgr.models.base import Model
15
+
16
+ __all__ = [
17
+ "BackfillPage",
18
+ "CatchUpResult",
19
+ "ChannelState",
20
+ "DifferenceResult",
21
+ "ResetResult",
22
+ "SyncStatus",
23
+ ]
24
+
25
+
26
+ class ChannelState(Model):
27
+ chat_id: int
28
+ pts: int = 0
29
+ #: False means catch-up will silently *skip* this channel: Telethon needs
30
+ #: an access hash in the session to call getChannelDifference at all.
31
+ access_hash_known: bool = False
32
+ last_difference_at: str | None = None
33
+ title: str | None = None
34
+
35
+
36
+ class SyncStatus(Model):
37
+ account: str = ""
38
+ pts: int | None = None
39
+ qts: int | None = None
40
+ seq: int | None = None
41
+ date: str | None = None
42
+ date_unix: int | None = None
43
+ unread_count: int | None = None
44
+ server_pts: int | None = None
45
+ server_seq: int | None = None
46
+ behind_pts: int | None = None
47
+ behind_seconds: int | None = None
48
+ phase: str = "unknown"
49
+ getting_difference: bool = False
50
+ channels: list[ChannelState] = []
51
+ last_update_at: str | None = None
52
+ no_updates_for_seconds: int | None = None
53
+
54
+
55
+ class CatchUpResult(Model):
56
+ account: str = ""
57
+ events_replayed: int = 0
58
+ pts_before: int | None = None
59
+ pts_after: int | None = None
60
+ duration_ms: int = 0
61
+ too_long: bool = False
62
+ timed_out: bool = False
63
+
64
+
65
+ class DifferenceResult(Model):
66
+ """One `updates.getDifference` / `getChannelDifference` run, reported raw.
67
+
68
+ Without `--apply` this is a *probe*: it does not advance the stored pts,
69
+ so running it cannot create the gap it was meant to diagnose.
70
+ """
71
+
72
+ #: `common` or `channel`. No default: the discriminator must never be the
73
+ #: field `omit_defaults` drops.
74
+ kind: str
75
+ #: Nor this one: "was that the whole difference" is what a caller loops on.
76
+ final: bool
77
+ new_pts: int | None = None
78
+ new_qts: int | None = None
79
+ new_seq: int | None = None
80
+ new_date: str | None = None
81
+ messages: int = 0
82
+ other_updates: int = 0
83
+ users: int = 0
84
+ chats: int = 0
85
+ timeout: int | None = None
86
+ too_long: bool = False
87
+ applied: bool = False
88
+ dry_run: bool = False
89
+ requests: list[dict[str, Any]] = []
90
+
91
+
92
+ class ResetResult(Model):
93
+ account: str = ""
94
+ reset: bool = False
95
+ pts_before: int | None = None
96
+ pts_after: int | None = None
97
+ channels_reset: list[int] = []
98
+ dry_run: bool = False
99
+
100
+
101
+ class BackfillPage(Model):
102
+ """The extra field a plain `Page[Message]` cannot carry: what was missing."""
103
+
104
+ fetched: int = 0
105
+ missing_ids: list[int] = []
tlgr/models/todo.py ADDED
@@ -0,0 +1,36 @@
1
+ """Checklists.
2
+
3
+ A task's `id` is assigned by the client and is the only thing that ties a
4
+ completion to a task: `messages.editMessage` rewrites the whole `todoList`, so
5
+ renumbering the survivors while deleting one would silently move every tick to
6
+ the wrong row. Nothing in this group ever renumbers.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from tlgr.models.base import Model
12
+ from tlgr.models.message import MessageEntity
13
+
14
+ __all__ = ["Todo", "TodoTask"]
15
+
16
+
17
+ class TodoTask(Model):
18
+ id: int
19
+ title: str = ""
20
+ entities: list[MessageEntity] = []
21
+ done: bool = False
22
+ completed_by: int | None = None
23
+ completed_date: str | None = None
24
+ completed_date_unix: int | None = None
25
+
26
+
27
+ class Todo(Model):
28
+ chat_id: int = 0
29
+ msg_id: int = 0
30
+ title: str = ""
31
+ entities: list[MessageEntity] = []
32
+ others_can_add: bool = False
33
+ others_can_complete: bool = False
34
+ tasks: list[TodoTask] = []
35
+ done_count: int = 0
36
+ already: bool = False
tlgr/models/webapp.py ADDED
@@ -0,0 +1,89 @@
1
+ """Mini apps: the manifest, the signed session URL, and the session's lifetime.
2
+
3
+ `WebAppSession.url` is a short-lived *credential*, not a link: it carries the
4
+ user's signed init data, and anyone holding it can act as the app on that
5
+ user's behalf until it expires. It is therefore printed once, with that
6
+ warning in human output, and never opened — tlgr has no browser and hosting
7
+ the `window.Telegram.WebApp` bridge is not a CLI's job.
8
+
9
+ `needs_prolong` exists because the two request families differ in a way that
10
+ is invisible from the URL: a session that came back with a `query_id` dies in
11
+ about a minute unless `webapp watch` keeps prolonging it, and one that did not
12
+ simply has no session to lose.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Any
18
+
19
+ from tlgr.models.base import Model
20
+
21
+ __all__ = [
22
+ "WebAppDownload",
23
+ "WebAppInfo",
24
+ "WebAppProlong",
25
+ "WebAppSent",
26
+ "WebAppSession",
27
+ ]
28
+
29
+
30
+ class WebAppInfo(Model):
31
+ """A mini app's manifest, as `messages.botApp` and `botAppSettings` hold it."""
32
+
33
+ bot: str | None = None
34
+ short_name: str | None = None
35
+ title: str | None = None
36
+ description: str | None = None
37
+ photo: int | None = None
38
+ document: int | None = None
39
+ inactive: bool = False
40
+ request_write_access: bool = False
41
+ has_settings: bool = False
42
+ terms_url: str | None = None
43
+ privacy_policy_url: str | None = None
44
+ link: str | None = None
45
+ installed_in_attach_menu: bool = False
46
+ installed_in_side_menu: bool = False
47
+ #: The placeholder is an SVG-like path blob; its length is reported
48
+ #: rather than its bytes, because nothing on a terminal can render it.
49
+ placeholder_path: int | None = None
50
+ bg_color: int | None = None
51
+ bg_dark_color: int | None = None
52
+ header_color: int | None = None
53
+ header_dark_color: int | None = None
54
+ button_request: dict[str, Any] | None = None
55
+
56
+
57
+ class WebAppSession(Model):
58
+ bot: str | None = None
59
+ kind: str = ""
60
+ url: str = ""
61
+ query_id: str | None = None
62
+ expires_at: str | None = None
63
+ fullsize: bool = False
64
+ fullscreen: bool = False
65
+ same_origin: bool = False
66
+ needs_prolong: bool = False
67
+ prolong_every: int | None = None
68
+ write_allowed: bool = False
69
+ inactive_confirmed: bool = False
70
+
71
+
72
+ class WebAppSent(Model):
73
+ bot_id: int = 0
74
+ sent: bool = False
75
+
76
+
77
+ class WebAppDownload(Model):
78
+ allowed: bool | None = None
79
+ file_name: str = ""
80
+ url: str = ""
81
+ downloaded: bool | None = None
82
+ path: str | None = None
83
+
84
+
85
+ class WebAppProlong(Model):
86
+ query_id: str = ""
87
+ prolonged_at: str | None = None
88
+ alive: bool = True
89
+ reason: str | None = None