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/daemon.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""Daemon, job, flood and dead-letter shapes.
|
|
2
|
+
|
|
3
|
+
Everything an operator reads when something is wrong. Two of these deserve a
|
|
4
|
+
note:
|
|
5
|
+
|
|
6
|
+
* `AccountHealth.state` is a **state machine**, not a boolean. v1 reported
|
|
7
|
+
which clients the daemon held, so an account whose connection had died
|
|
8
|
+
still appeared in `accounts` and the daemon still called itself healthy
|
|
9
|
+
(COR-13/COR-37). `state` plus `behind_seconds` is what makes "alive" and
|
|
10
|
+
"working" separable.
|
|
11
|
+
* `FloodRecord` exists because a flood deadline outlives a process. Telethon
|
|
12
|
+
remembers one in memory and forgets it on exit, so v1 re-hit every wait
|
|
13
|
+
after a restart — and re-hitting a wait is how a short one becomes long.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from tlgr.models.base import Model
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"AccountHealth",
|
|
24
|
+
"DaemonStatus",
|
|
25
|
+
"DeadLetter",
|
|
26
|
+
"DeadLetterResult",
|
|
27
|
+
"EventBusStatus",
|
|
28
|
+
"FloodRecord",
|
|
29
|
+
"FloodResult",
|
|
30
|
+
"HealthSummary",
|
|
31
|
+
"Job",
|
|
32
|
+
"JobState",
|
|
33
|
+
"JobTestFrame",
|
|
34
|
+
"LifecycleResult",
|
|
35
|
+
"LogLine",
|
|
36
|
+
"ReconnectResult",
|
|
37
|
+
"ReconnectedAccount",
|
|
38
|
+
"SaveStateResult",
|
|
39
|
+
"SavedState",
|
|
40
|
+
"ServiceResult",
|
|
41
|
+
"WebhookProbe",
|
|
42
|
+
"WebhookSettings",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AccountHealth(Model):
|
|
47
|
+
"""One account's connection and sync health."""
|
|
48
|
+
|
|
49
|
+
alias: str
|
|
50
|
+
state: str = "unknown"
|
|
51
|
+
user_id: int | None = None
|
|
52
|
+
username: str | None = None
|
|
53
|
+
dc_id: int | None = None
|
|
54
|
+
proxy: str | None = None
|
|
55
|
+
ping_ms: float | None = None
|
|
56
|
+
pts: int | None = None
|
|
57
|
+
qts: int | None = None
|
|
58
|
+
seq: int | None = None
|
|
59
|
+
date: str | None = None
|
|
60
|
+
behind_seconds: int | None = None
|
|
61
|
+
catching_up: bool = False
|
|
62
|
+
channels_tracked: int = 0
|
|
63
|
+
last_update_at: str | None = None
|
|
64
|
+
connected_since: str | None = None
|
|
65
|
+
reconnects: int = 0
|
|
66
|
+
in_flight: int = 0
|
|
67
|
+
resync_needed: list[int] = []
|
|
68
|
+
flood_waits: int = 0
|
|
69
|
+
circuit: str = "closed"
|
|
70
|
+
frozen: bool = False
|
|
71
|
+
error: str | None = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class EventBusStatus(Model):
|
|
75
|
+
buffered: int = 0
|
|
76
|
+
oldest_seq: int | None = None
|
|
77
|
+
last_seq: int = 0
|
|
78
|
+
subscribers: int = 0
|
|
79
|
+
dropped: int = 0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class DaemonStatus(Model, omit_defaults=False):
|
|
83
|
+
"""`tlgr daemon status`. `running` and `healthy` are different questions.
|
|
84
|
+
|
|
85
|
+
`omit_defaults=False` for the whole struct: the answer this operation
|
|
86
|
+
exists to give is "no", and `omit_defaults` drops every false and every
|
|
87
|
+
zero — so a stopped daemon serialised to `{}`, which printed nothing at
|
|
88
|
+
all in human mode and made `status["running"]` a KeyError for the exact
|
|
89
|
+
caller the flag is for. v1 printed `RUNNING false`; a field here is
|
|
90
|
+
always present, whichever way the answer went.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
running: bool = False
|
|
94
|
+
ready: bool = False
|
|
95
|
+
healthy: bool = False
|
|
96
|
+
pid: int | None = None
|
|
97
|
+
uptime_seconds: int = 0
|
|
98
|
+
#: `None` rather than `""` when the daemon is not answering: nobody read
|
|
99
|
+
#: a version off a process that is not there.
|
|
100
|
+
version: str | None = None
|
|
101
|
+
protocol: int = 0
|
|
102
|
+
layer: int = 0
|
|
103
|
+
socket: str = ""
|
|
104
|
+
socket_owner: int | None = None
|
|
105
|
+
managed_by: str | None = None
|
|
106
|
+
accounts: list[AccountHealth] = []
|
|
107
|
+
events: EventBusStatus | None = None
|
|
108
|
+
webhook: dict[str, Any] = {}
|
|
109
|
+
jobs: list[dict[str, Any]] = []
|
|
110
|
+
# v1's `/daemon/status` carried these two, and AGENT.md documents them.
|
|
111
|
+
connections: dict[str, bool] = {}
|
|
112
|
+
disconnected: list[str] = []
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class HealthSummary(Model, omit_defaults=False):
|
|
116
|
+
"""`tlgr status`: one screen, the states where everything else fails.
|
|
117
|
+
|
|
118
|
+
`omit_defaults=False` for the same reason as `DaemonStatus`: this screen
|
|
119
|
+
is read when nothing works, which is precisely when every answer on it is
|
|
120
|
+
false — and a screen that prints only the fields that happened to be true
|
|
121
|
+
is at its least useful exactly then.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
account: str = ""
|
|
125
|
+
user_id: int | None = None
|
|
126
|
+
username: str | None = None
|
|
127
|
+
authorized: bool = False
|
|
128
|
+
connected: bool = False
|
|
129
|
+
dc_id: int | None = None
|
|
130
|
+
proxy: str | None = None
|
|
131
|
+
ping_ms: float | None = None
|
|
132
|
+
layer: int = 0
|
|
133
|
+
behind_seconds: int | None = None
|
|
134
|
+
daemon_running: bool = False
|
|
135
|
+
daemon_healthy: bool = False
|
|
136
|
+
jobs_running: int = 0
|
|
137
|
+
webhook_enabled: bool = False
|
|
138
|
+
flood_waits: int = 0
|
|
139
|
+
frozen: dict[str, Any] = {}
|
|
140
|
+
unconfirmed_sessions: int = 0
|
|
141
|
+
terms_pending: bool = False
|
|
142
|
+
problems: list[str] = []
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class LifecycleResult(Model):
|
|
146
|
+
"""`daemon start` / `stop` / `restart`."""
|
|
147
|
+
|
|
148
|
+
started: bool = False
|
|
149
|
+
stopped: bool = False
|
|
150
|
+
restarted: bool = False
|
|
151
|
+
already: bool = False
|
|
152
|
+
pid: int | None = None
|
|
153
|
+
socket: str = ""
|
|
154
|
+
ready: bool = False
|
|
155
|
+
accounts: list[str] = []
|
|
156
|
+
catch_up: bool = True
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class ServiceResult(Model):
|
|
160
|
+
"""`daemon install` / `uninstall`."""
|
|
161
|
+
|
|
162
|
+
installed: bool = False
|
|
163
|
+
uninstalled: bool = False
|
|
164
|
+
already: bool = False
|
|
165
|
+
supervisor: str = ""
|
|
166
|
+
unit: str = ""
|
|
167
|
+
path: str = ""
|
|
168
|
+
stopped: bool = False
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class LogLine(Model):
|
|
172
|
+
ts: str = ""
|
|
173
|
+
level: str = ""
|
|
174
|
+
account: str | None = None
|
|
175
|
+
logger: str = ""
|
|
176
|
+
message: str = ""
|
|
177
|
+
raw: str = ""
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class ReconnectedAccount(Model):
|
|
181
|
+
alias: str
|
|
182
|
+
reconnected: bool = False
|
|
183
|
+
dc_id: int | None = None
|
|
184
|
+
caught_up: bool = False
|
|
185
|
+
error: str | None = None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class ReconnectResult(Model):
|
|
189
|
+
accounts: list[ReconnectedAccount] = []
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class SavedState(Model):
|
|
193
|
+
alias: str
|
|
194
|
+
pts: int | None = None
|
|
195
|
+
qts: int | None = None
|
|
196
|
+
seq: int | None = None
|
|
197
|
+
date: str | None = None
|
|
198
|
+
channels: int = 0
|
|
199
|
+
entities: int = 0
|
|
200
|
+
error: str | None = None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class SaveStateResult(Model):
|
|
204
|
+
accounts: list[SavedState] = []
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class FloodRecord(Model):
|
|
208
|
+
"""One remembered rate-limit deadline."""
|
|
209
|
+
|
|
210
|
+
account: str
|
|
211
|
+
#: No default: `Model` omits a field equal to its default, and a record
|
|
212
|
+
#: whose kind is absent reads as one that has no kind.
|
|
213
|
+
kind: str
|
|
214
|
+
method: str = ""
|
|
215
|
+
chat: str | None = None
|
|
216
|
+
wait_seconds: int = 0
|
|
217
|
+
until: str | None = None
|
|
218
|
+
hits: int = 1
|
|
219
|
+
circuit_open: bool = False
|
|
220
|
+
expired: bool = False
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class FloodResult(Model):
|
|
224
|
+
cleared: int = 0
|
|
225
|
+
circuit_open: bool = False
|
|
226
|
+
accounts: list[str] = []
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class DeadLetter(Model):
|
|
230
|
+
"""An event a consumer could not be given."""
|
|
231
|
+
|
|
232
|
+
id: str
|
|
233
|
+
seq: int = 0
|
|
234
|
+
source: str = "webhook"
|
|
235
|
+
event: str = ""
|
|
236
|
+
account: str = ""
|
|
237
|
+
chat_id: int | None = None
|
|
238
|
+
attempts: int = 1
|
|
239
|
+
last_error: str = ""
|
|
240
|
+
first_failed_at: str = ""
|
|
241
|
+
last_failed_at: str = ""
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class DeadLetterResult(Model):
|
|
245
|
+
attempted: int = 0
|
|
246
|
+
delivered: int = 0
|
|
247
|
+
failed: int = 0
|
|
248
|
+
deleted: int = 0
|
|
249
|
+
remaining: int = 0
|
|
250
|
+
dry_run: bool = False
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class JobState(Model):
|
|
254
|
+
"""One gateway job."""
|
|
255
|
+
|
|
256
|
+
name: str
|
|
257
|
+
account: str = ""
|
|
258
|
+
enabled: bool = True
|
|
259
|
+
running: bool = False
|
|
260
|
+
events: list[str] = []
|
|
261
|
+
filters: dict[str, Any] = {}
|
|
262
|
+
processors: list[str] = []
|
|
263
|
+
actions: list[dict[str, Any]] = []
|
|
264
|
+
matched: int = 0
|
|
265
|
+
skipped: int = 0
|
|
266
|
+
actions_run: int = 0
|
|
267
|
+
errors: int = 0
|
|
268
|
+
last_match_at: str | None = None
|
|
269
|
+
last_error: str | None = None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class Job(Model):
|
|
273
|
+
"""The result of a job mutation."""
|
|
274
|
+
|
|
275
|
+
name: str
|
|
276
|
+
#: No default at all: this is the answer `job enable`/`job disable` was
|
|
277
|
+
#: asked for, and `Model` drops a field equal to its default — so either
|
|
278
|
+
#: value would sometimes be missing from the reply.
|
|
279
|
+
enabled: bool
|
|
280
|
+
account: str = ""
|
|
281
|
+
events: list[str] = []
|
|
282
|
+
removed: bool = False
|
|
283
|
+
reloaded: bool = False
|
|
284
|
+
already: bool = False
|
|
285
|
+
loaded: int = 0
|
|
286
|
+
added: list[str] = []
|
|
287
|
+
removed_names: list[str] = []
|
|
288
|
+
changed: list[str] = []
|
|
289
|
+
errors: list[str] = []
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class JobTestFrame(Model):
|
|
293
|
+
"""One event fed through a job, and what the pipeline decided."""
|
|
294
|
+
|
|
295
|
+
seq: int = 0
|
|
296
|
+
event: str = ""
|
|
297
|
+
matched: bool = False
|
|
298
|
+
filter_trace: list[str] = []
|
|
299
|
+
processed_text: str | None = None
|
|
300
|
+
actions: list[dict[str, Any]] = []
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class WebhookSettings(Model):
|
|
304
|
+
"""`tlgr webhook get` / `set`. Secrets are redacted unless asked for."""
|
|
305
|
+
|
|
306
|
+
enabled: bool = False
|
|
307
|
+
url: str = ""
|
|
308
|
+
events: list[str] = []
|
|
309
|
+
filters: dict[str, Any] = {}
|
|
310
|
+
sign: str = "hmac-sha256"
|
|
311
|
+
secret: str | None = None
|
|
312
|
+
token: str | None = None
|
|
313
|
+
max_attempts: int = 5
|
|
314
|
+
backoff: int = 2
|
|
315
|
+
timeout: int = 30
|
|
316
|
+
queue: int = 10000
|
|
317
|
+
on_lag: str = "drop"
|
|
318
|
+
batch: int = 1
|
|
319
|
+
last_delivery_at: str | None = None
|
|
320
|
+
last_status: int | None = None
|
|
321
|
+
delivered: int = 0
|
|
322
|
+
failed: int = 0
|
|
323
|
+
dead_letters: int = 0
|
|
324
|
+
queue_depth: int = 0
|
|
325
|
+
last_seq: int = 0
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class WebhookProbe(Model):
|
|
329
|
+
"""`tlgr webhook test`: the exact request a receiver would have to verify."""
|
|
330
|
+
|
|
331
|
+
url: str = ""
|
|
332
|
+
status: int | None = None
|
|
333
|
+
latency_ms: int = 0
|
|
334
|
+
request_headers: dict[str, str] = {}
|
|
335
|
+
body: str = ""
|
|
336
|
+
error: str | None = None
|