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/privacy.py ADDED
@@ -0,0 +1,93 @@
1
+ """Privacy rules, the global privacy switches, and paid-message revenue.
2
+
3
+ `account.setPrivacy` **replaces** the whole ordered rule vector, and
4
+ `account.setGlobalPrivacySettings` replaces the whole constructor. Both are
5
+ therefore read-modify-write operations, and both need a model that survives
6
+ the round trip without losing anything:
7
+
8
+ * `PrivacySettings` splits the vector into the four exception lists a human
9
+ edits *and* keeps `raw_rules` — the constructors in server order — so a
10
+ rule tlgr does not recognise is still sent back unchanged.
11
+ * `GlobalPrivacy` names every field of the constructor, so patching one and
12
+ writing the rest back cannot silently clear a switch nobody mentioned.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from tlgr.models.base import Model
18
+ from tlgr.models.peer import Peer
19
+
20
+ __all__ = [
21
+ "GlobalPrivacy",
22
+ "PaidMessageRevenue",
23
+ "PrivacyExceptions",
24
+ "PrivacyRule",
25
+ "PrivacySettings",
26
+ ]
27
+
28
+
29
+ class PrivacyRule(Model):
30
+ """One `privacyValue*` constructor, in tlgr's vocabulary."""
31
+
32
+ #: allow | disallow
33
+ action: str = "allow"
34
+ #: all | contacts | close-friends | premium | bots | users | chats
35
+ scope: str = "all"
36
+ ids: list[int] = []
37
+
38
+
39
+ class PrivacyExceptions(Model):
40
+ """The four lists the GUI calls "Always allow" / "Never allow"."""
41
+
42
+ allow_users: list[int] = []
43
+ deny_users: list[int] = []
44
+ allow_chats: list[int] = []
45
+ deny_chats: list[int] = []
46
+
47
+
48
+ class PrivacySettings(Model):
49
+ """One privacy key, read or written.
50
+
51
+ `base` is the headline the GUI shows; the exception lists are what it
52
+ puts under it. `raw_rules` is the server's own ordered vector, kept so a
53
+ write can reproduce it exactly.
54
+ """
55
+
56
+ key: str
57
+ #: everybody | contacts | close-friends | premium | bots | nobody. No
58
+ #: default: it is the headline, and `omit_defaults` would hide the most
59
+ #: restrictive answer exactly when a caller most wants to see it.
60
+ base: str
61
+ allow_users: list[int] = []
62
+ deny_users: list[int] = []
63
+ allow_chats: list[int] = []
64
+ deny_chats: list[int] = []
65
+ raw_rules: list[PrivacyRule] = []
66
+ #: Resolved names for the exception ids, when `--resolve` was given.
67
+ peers: list[Peer] = []
68
+
69
+
70
+ class GlobalPrivacy(Model):
71
+ """`globalPrivacySettings`, every field named.
72
+
73
+ Nothing here defaults to a value the server might disagree with: each
74
+ flag is a tri-state so "the server did not report it" stays different
75
+ from "off", which is what makes the read-modify-write safe.
76
+ """
77
+
78
+ hide_read_marks: bool | None = None
79
+ archive_and_mute_new_noncontact_peers: bool | None = None
80
+ new_noncontact_peers_require_premium: bool | None = None
81
+ noncontact_peers_paid_stars: int | None = None
82
+ keep_archived_unmuted: bool | None = None
83
+ keep_archived_folders: bool | None = None
84
+ display_gifts_button: bool | None = None
85
+ #: unlimited | limited | unique | premium | from-channels
86
+ disallowed_gifts: list[str] = []
87
+ changed: list[str] = []
88
+ already: bool = False
89
+
90
+
91
+ class PaidMessageRevenue(Model):
92
+ user_id: int
93
+ stars_amount: int = 0
tlgr/models/profile.py ADDED
@@ -0,0 +1,217 @@
1
+ """My own profile — the Settings ▸ Edit Profile screen, as data.
2
+
3
+ Everything here describes *this* account rather than somebody else's: the
4
+ name and bio the world sees, the usernames (including the Fragment
5
+ collectibles), the avatar history, the accent colours, the emoji status and
6
+ the presence switch.
7
+
8
+ Two shapes are deliberate.
9
+
10
+ * **`ProfileFull` is one object, not a union of three RPCs.** `users.getUsers`
11
+ answers the name, `users.getFullUser` the bio, birthday, personal channel
12
+ and gift counters. v1 fetched only the first and hard-coded `bio` to `""`,
13
+ which is a wrong answer dressed as a real one. One model, both calls.
14
+ * **A username is a row, not a string.** A collectible bought on Fragment can
15
+ be active or parked, and only the *first* active one is the main handle;
16
+ flattening the vector to `username` is how a script deactivates the wrong
17
+ name.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import Any
23
+
24
+ from tlgr.models.base import Model
25
+ from tlgr.models.peer import Peer
26
+
27
+ __all__ = [
28
+ "AdminedChannel",
29
+ "ColorPalette",
30
+ "ColorSet",
31
+ "EmojiStatusItem",
32
+ "EmojiStatusSet",
33
+ "PhotosDeleted",
34
+ "PresenceSet",
35
+ "ProfileFull",
36
+ "ProfileLink",
37
+ "ProfilePhotoSet",
38
+ "ProfileUpdated",
39
+ "ProfileUsername",
40
+ "UsernameSet",
41
+ ]
42
+
43
+
44
+ class ProfileUsername(Model):
45
+ """One entry of `user.usernames`.
46
+
47
+ `main` is derived rather than reported: the server marks the basic
48
+ username with `editable`, and the first *active* entry is what a link
49
+ resolves to. Both facts matter and neither is the other.
50
+ """
51
+
52
+ username: str
53
+ active: bool = True
54
+ editable: bool = False
55
+ main: bool = False
56
+
57
+
58
+ class ProfileFull(Model):
59
+ """My profile as the Edit Profile screen shows it."""
60
+
61
+ id: int = 0
62
+ first_name: str = ""
63
+ last_name: str = ""
64
+ username: str | None = None
65
+ usernames: list[ProfileUsername] = []
66
+ phone: str | None = None
67
+ premium: bool = False
68
+ bot: bool = False
69
+ #: Only present with `--full`; `""` means "fetched and empty", `None`
70
+ #: means "not fetched", which v1 could not tell apart.
71
+ bio: str | None = None
72
+ birthday: str | None = None
73
+ personal_channel_id: int | None = None
74
+ personal_channel: Peer | None = None
75
+ emoji_status: int | None = None
76
+ emoji_status_collectible_id: int | None = None
77
+ emoji_status_until: str | None = None
78
+ color: int | None = None
79
+ color_collectible_id: int | None = None
80
+ background_emoji_id: int | None = None
81
+ profile_color: int | None = None
82
+ profile_background_emoji_id: int | None = None
83
+ #: Which tab the profile page opens on (`stargifts`, `posts`, …).
84
+ main_tab: str | None = None
85
+ stargifts_count: int | None = None
86
+ stars_rating: int | None = None
87
+ ttl_period: int | None = None
88
+ sponsored_enabled: bool | None = None
89
+ #: Gift categories this account refuses, from `disallowed_gifts`.
90
+ disallowed_gifts: list[str] = []
91
+ photo_id: int | None = None
92
+ fallback_photo_id: int | None = None
93
+ contacts_count: int | None = None
94
+ common_chats_count: int | None = None
95
+
96
+
97
+ class ProfileUpdated(Model):
98
+ """What `profile update` actually changed, field by field.
99
+
100
+ Only the fields the caller named appear, because the command spans three
101
+ RPCs and "I asked for a birthday and got a name back" is a report nobody
102
+ can act on.
103
+ """
104
+
105
+ first_name: str | None = None
106
+ last_name: str | None = None
107
+ bio: str | None = None
108
+ birthday: str | None = None
109
+ personal_channel_id: int | None = None
110
+ photo_id: int | None = None
111
+ changed: list[str] = []
112
+ already: bool = False
113
+
114
+
115
+ class UsernameSet(Model):
116
+ """`profile username set`, in all five of its moods."""
117
+
118
+ username: str | None = None
119
+ active: bool | None = None
120
+ #: Only for `--check`: whether the name may be taken right now.
121
+ available: bool | None = None
122
+ #: Set when the server says `USERNAME_PURCHASE_AVAILABLE`: the name is
123
+ #: free only on Fragment, which is a different answer from "taken".
124
+ purchasable: bool = False
125
+ usernames: list[ProfileUsername] = []
126
+ already: bool = False
127
+
128
+
129
+ class ColorPalette(Model):
130
+ """One entry of `help.getPeerColors` / `getPeerProfileColors`."""
131
+
132
+ color_id: int
133
+ colors: list[str] = []
134
+ dark_colors: list[str] = []
135
+ min_level: int = 0
136
+ hidden: bool = False
137
+ channel_min_level: int | None = None
138
+ group_min_level: int | None = None
139
+ #: True for palettes 0-6, whose colours every client hard-codes.
140
+ builtin: bool = False
141
+
142
+
143
+ class ColorSet(Model):
144
+ """`profile color set`: the palette now in force."""
145
+
146
+ color: int | None = None
147
+ collectible_id: int | None = None
148
+ background_emoji_id: int | None = None
149
+ for_profile: bool = False
150
+ already: bool = False
151
+
152
+
153
+ class EmojiStatusItem(Model):
154
+ """A wearable emoji status, from any of the four suggestion lists."""
155
+
156
+ document_id: int = 0
157
+ collectible_id: int | None = None
158
+ title: str | None = None
159
+ slug: str | None = None
160
+ group: str = ""
161
+ until: str | None = None
162
+
163
+
164
+ class EmojiStatusSet(Model):
165
+ document_id: int | None = None
166
+ collectible_id: int | None = None
167
+ until: str | None = None
168
+ until_unix: int | None = None
169
+ cleared: bool = False
170
+ already: bool = False
171
+
172
+
173
+ class PresenceSet(Model):
174
+ """`profile presence set`. `online` is what was *reported*, not measured."""
175
+
176
+ online: bool
177
+ already: bool = False
178
+
179
+
180
+ class ProfilePhotoSet(Model):
181
+ photo_id: int | None = None
182
+ is_video: bool = False
183
+ fallback: bool = False
184
+ #: Set when the avatar was built server-side from a custom emoji.
185
+ emoji_markup: bool = False
186
+
187
+
188
+ class PhotosDeleted(Model):
189
+ deleted: int = 0
190
+ photo_ids: list[int] = []
191
+ already: bool = False
192
+
193
+
194
+ class ProfileLink(Model):
195
+ """`profile link`: the public handle, and what Fragment knows about it."""
196
+
197
+ #: No default: `omit_defaults` would drop the reassuring answer and
198
+ #: leave a caller unable to tell "public" from "not reported".
199
+ resolvable_by_strangers: bool
200
+ link: str = ""
201
+ username: str | None = None
202
+ user_id: int | None = None
203
+ #: A unicode-block QR, when `--qr` was given.
204
+ qr: str | None = None
205
+ qr_path: str | None = None
206
+ #: `fragment.getCollectibleInfo`, when `--collectible` was given.
207
+ collectible: dict[str, Any] | None = None
208
+
209
+
210
+ class AdminedChannel(Model):
211
+ """A public channel eligible to be shown on the profile."""
212
+
213
+ id: int
214
+ title: str = ""
215
+ username: str | None = None
216
+ participants_count: int | None = None
217
+ current: bool = False
@@ -0,0 +1,160 @@
1
+ """Reactions: what is on a message, who put it there, and what a chat allows.
2
+
3
+ One spelling for a reaction across the whole group: a unicode emoji is itself
4
+ (`"👍"`) and a custom (Premium) emoji is `"custom:<document_id>"`. The pair
5
+ `(emoji, custom)` is accepted on input and the same string comes back on
6
+ output, so a reaction read from `reaction list` can be handed straight back to
7
+ `reaction remove` without the caller knowing which kind it was.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Literal
13
+
14
+ from tlgr.models.base import Model
15
+ from tlgr.models.message import ReactionSummary
16
+
17
+ __all__ = [
18
+ "AvailableReaction",
19
+ "ChatReactions",
20
+ "MessageReactionState",
21
+ "PaidReactionResult",
22
+ "ReactionPrivacy",
23
+ "ReactionPurge",
24
+ "ReactionReport",
25
+ "ReactionResult",
26
+ "ReactionTag",
27
+ "ReactionUser",
28
+ "TopReactor",
29
+ ]
30
+
31
+
32
+ class ReactionResult(Model):
33
+ """What `reaction add` / `reaction remove` did.
34
+
35
+ `emoji` and `reacted` are v1's `message react` keys and stay spelled that
36
+ way; `mine` is the full set my account now holds, because `sendReaction`
37
+ takes the whole desired state and a caller that wants to add a second
38
+ reaction has to know what the first one was.
39
+ """
40
+
41
+ chat_id: int
42
+ msg_id: int
43
+ emoji: str = ""
44
+ reacted: bool = False
45
+ already: bool = False
46
+ mine: list[str] = []
47
+ reactions: ReactionSummary | None = None
48
+
49
+
50
+ class TopReactor(Model):
51
+ """A row of the Star-reaction leaderboard on a channel post."""
52
+
53
+ user_id: int | None = None
54
+ stars: int = 0
55
+ anonymous: bool = False
56
+ mine: bool = False
57
+
58
+
59
+ class MessageReactionState(Model):
60
+ """The reaction state of one message, refreshed from the server."""
61
+
62
+ chat_id: int = 0
63
+ msg_id: int = 0
64
+ reactions: ReactionSummary | None = None
65
+ can_see_list: bool = False
66
+ as_tags: bool = False
67
+ top_reactors: list[TopReactor] = []
68
+
69
+
70
+ class ReactionUser(Model):
71
+ """Who reacted, with what, and when."""
72
+
73
+ user_id: int
74
+ reaction: str = ""
75
+ date: str | None = None
76
+ date_unix: int | None = None
77
+ big: bool = False
78
+ unread: bool = False
79
+ mine: bool = False
80
+
81
+
82
+ class AvailableReaction(Model):
83
+ """One entry of the reaction catalogue.
84
+
85
+ `source` says which list it came from, because the standard catalogue,
86
+ the featured list and my own recent reactions are three different
87
+ questions with three different endpoints and the same row shape.
88
+ """
89
+
90
+ emoticon: str
91
+ source: Literal["available", "top", "recent"]
92
+ title: str = ""
93
+ premium: bool = False
94
+ inactive: bool = False
95
+ static_icon_id: int | None = None
96
+ select_animation_id: int | None = None
97
+
98
+
99
+ class ChatReactions(Model):
100
+ """What a chat (or one message in it) allows.
101
+
102
+ `mode` is the `chatReactions*` variant flattened to a word, because
103
+ `setChatAvailableReactions` demands the whole value on every write: a
104
+ caller changing only `reactions_limit` still has to resend the mode.
105
+ """
106
+
107
+ mode: Literal["all", "some", "none"]
108
+ chat_id: int = 0
109
+ msg_id: int | None = None
110
+ reactions: list[str] = []
111
+ allow_custom: bool = False
112
+ reactions_limit: int | None = None
113
+ paid_enabled: bool | None = None
114
+
115
+
116
+ class ReactionTag(Model):
117
+ """A Saved Messages reaction tag, and the name I gave it."""
118
+
119
+ reaction: str
120
+ title: str | None = None
121
+ count: int = 0
122
+ suggested: bool = False
123
+
124
+
125
+ class ReactionPrivacy(Model):
126
+ """How my paid reactions are attributed."""
127
+
128
+ privacy: Literal["default", "anonymous", "peer"]
129
+ peer_id: int | None = None
130
+ msg_id: int | None = None
131
+
132
+
133
+ class PaidReactionResult(Model):
134
+ """A Star reaction that was actually paid for."""
135
+
136
+ chat_id: int
137
+ msg_id: int
138
+ stars_sent: int = 0
139
+ privacy: Literal["default", "anonymous", "peer"] = "default"
140
+ top_reactors: list[TopReactor] = []
141
+ #: `reaction pay --senders`: the peers I may pay as, listed without paying.
142
+ senders: list[int] = []
143
+
144
+
145
+ class ReactionPurge(Model):
146
+ """A moderator's removal of somebody else's reactions."""
147
+
148
+ chat_id: int
149
+ user_id: int
150
+ scope: Literal["message", "chat"]
151
+ msg_id: int | None = None
152
+ deleted: bool = False
153
+
154
+
155
+ class ReactionReport(Model):
156
+ ok: bool = False
157
+ banned: bool = False
158
+ chat_id: int = 0
159
+ msg_id: int = 0
160
+ user_id: int = 0
tlgr/models/resolve.py ADDED
@@ -0,0 +1,175 @@
1
+ """Resolution results: what a reference, a link or a cache entry turns into.
2
+
3
+ `resolve` is the one group whose whole job is to be honest about *how* an
4
+ answer was reached, so every shape here carries provenance:
5
+
6
+ * `ResolvedRef.source` says which strategy answered (cache, username, phone,
7
+ dialog scan, arithmetic on a `t.me/c/` link).
8
+ * `ResolvedPhone.reason` exists because `PHONE_NOT_OCCUPIED` is genuinely
9
+ ambiguous — no account, or an owner who refuses phone lookups — and the op
10
+ exits 13 rather than claiming "not found".
11
+ * `ResolvedLink.delegated_to` names the command that would *act* on a link.
12
+ Resolution never joins, starts, installs, boosts or redeems anything; it
13
+ says what the link is and which verb would.
14
+
15
+ No access hash is ever emitted, only `access_hash_cached`.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import Any, Literal
21
+
22
+ from tlgr.models.base import Model
23
+ from tlgr.models.peer import Peer
24
+
25
+ __all__ = [
26
+ "CachedPeerRow",
27
+ "LinkKind",
28
+ "ResolvedLink",
29
+ "ResolvedPhone",
30
+ "ResolvedRef",
31
+ "ResolvedUsername",
32
+ ]
33
+
34
+ #: Every shape a t.me / tg:// reference can take. `unknown` is a real answer:
35
+ #: Telegram adds deep links faster than any client learns them, and reporting
36
+ #: `unknown` with the raw path beats guessing wrong.
37
+ LinkKind = Literal[
38
+ "public-username",
39
+ "phone",
40
+ "invite",
41
+ "chatlist-invite",
42
+ "message",
43
+ "private-post",
44
+ "story",
45
+ "bot-start",
46
+ "bot-startgroup",
47
+ "bot-startchannel",
48
+ "webapp",
49
+ "business-chat-link",
50
+ "contact-token",
51
+ "proxy",
52
+ "boost",
53
+ "giftcode",
54
+ "unique-gift",
55
+ "stars-topup",
56
+ "wallpaper",
57
+ "theme",
58
+ "stickerset",
59
+ "emojiset",
60
+ "share-url",
61
+ "settings-section",
62
+ "contacts-section",
63
+ "login-code",
64
+ "confirm-phone",
65
+ "invoice",
66
+ "premium-offer",
67
+ "folder",
68
+ "unknown",
69
+ ]
70
+
71
+
72
+ class ResolvedRef(Model, omit_defaults=False):
73
+ """One `resolve peer` answer, with the strategy that produced it."""
74
+
75
+ ref: str = ""
76
+ kind: str = ""
77
+ id: int | None = None
78
+ marked_id: int | None = None
79
+ botapi_id: int | None = None
80
+ type: str = ""
81
+ title: str = ""
82
+ username: str | None = None
83
+ access_hash_cached: bool = False
84
+ min: bool = False
85
+ source: str = ""
86
+ resolved: bool = False
87
+ reason: str | None = None
88
+
89
+
90
+ class ResolvedUsername(Model, omit_defaults=False):
91
+ kind: str = ""
92
+ peer: Peer | None = None
93
+ username: str = ""
94
+ access_hash_cached: bool = False
95
+
96
+
97
+ class ResolvedPhone(Model, omit_defaults=False):
98
+ """A phone lookup. `resolved=false` with a `reason` is exit 13, not 5."""
99
+
100
+ phone: str = ""
101
+ e164: str = ""
102
+ country: str | None = None
103
+ prefix: str | None = None
104
+ pattern: str | None = None
105
+ resolved: bool = False
106
+ peer: Peer | None = None
107
+ reason: str | None = None
108
+ countries: list[dict[str, Any]] = []
109
+
110
+
111
+ class ResolvedLink(Model):
112
+ """A t.me / tg:// link, classified and (optionally) read.
113
+
114
+ One discriminated shape rather than one command per link type: a human
115
+ pasting a link does not know which of twenty kinds it is, and that is
116
+ precisely the question.
117
+ """
118
+
119
+ kind: LinkKind
120
+ raw_url: str = ""
121
+ scheme: str = ""
122
+ peer: Peer | None = None
123
+ username: str | None = None
124
+ phone: str | None = None
125
+ invite_hash: str | None = None
126
+ chatlist_slug: str | None = None
127
+ msg_id: int | None = None
128
+ thread_id: int | None = None
129
+ comment_id: int | None = None
130
+ story_id: int | None = None
131
+ bot: str | None = None
132
+ start_param: str | None = None
133
+ start_target: str | None = None
134
+ boost: bool | None = None
135
+ gift: str | None = None
136
+ stars: int | None = None
137
+ proxy: dict[str, Any] | None = None
138
+ theme: str | None = None
139
+ wallpaper: str | None = None
140
+ stickerset: str | None = None
141
+ share: dict[str, Any] | None = None
142
+ contact_token: str | None = None
143
+ section: str | None = None
144
+ deeplink_info: str | None = None
145
+ title: str | None = None
146
+ #: Set only with `--open`: the follow-up read for the classified kind.
147
+ opened: dict[str, Any] | None = None
148
+ requires_action: bool = False
149
+ #: The command that would act on this link. Naming it is the alternative
150
+ #: to acting: joining, starting a bot or redeeming a gift are separate,
151
+ #: confirmed verbs in their own groups.
152
+ delegated_to: str | None = None
153
+ draft_saved: bool = False
154
+
155
+
156
+ class CachedPeerRow(Model, omit_defaults=False):
157
+ """One entry of the per-account resolver cache.
158
+
159
+ `min_context` is the `(chat, message)` where a `min` user was seen —
160
+ Telethon stores none, and without it a stranger who posted in a channel
161
+ is unaddressable.
162
+ """
163
+
164
+ id: int = 0
165
+ marked_id: int = 0
166
+ type: str = ""
167
+ username: str | None = None
168
+ phone: str | None = None
169
+ access_hash_cached: bool = False
170
+ min: bool = False
171
+ min_context: str | None = None
172
+ seen_at: str | None = None
173
+ seen_at_unix: int | None = None
174
+ refreshed: bool = False
175
+ purged: bool = False