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/core/timefmt.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Every timestamp and duration tlgr reads or writes goes through here.
|
|
2
|
+
|
|
3
|
+
v1 called `str(datetime)` in a dozen places and produced
|
|
4
|
+
`"2025-03-06 12:00:00+00:00"` — a format nothing parses without a custom rule
|
|
5
|
+
(COR-35). v2 emits RFC-3339 UTC with a `Z`, ships a `*_unix` sibling for
|
|
6
|
+
anything an agent is likely to compare, and renders local time only in human
|
|
7
|
+
tables. There is one function per direction, and no other module formats a
|
|
8
|
+
date.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from datetime import date, datetime, timedelta, timezone
|
|
15
|
+
|
|
16
|
+
# `datetime.UTC` is 3.11+; we support 3.10.
|
|
17
|
+
UTC = timezone.utc
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"fmt_dt",
|
|
21
|
+
"fmt_local",
|
|
22
|
+
"fmt_unix",
|
|
23
|
+
"legacy_dates_enabled",
|
|
24
|
+
"parse_dt",
|
|
25
|
+
"parse_duration",
|
|
26
|
+
"to_unix",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
_DURATION_RE = re.compile(r"^(\d+(?:\.\d+)?)\s*([smhdw]?)$", re.IGNORECASE)
|
|
30
|
+
_RELATIVE_RE = re.compile(r"^([+-])(\d+(?:\.\d+)?)\s*([smhdw])$", re.IGNORECASE)
|
|
31
|
+
_UNIT_SECONDS = {"s": 1, "m": 60, "h": 3600, "d": 86400, "w": 604800}
|
|
32
|
+
|
|
33
|
+
FOREVER = frozenset({"forever", "never", "none", "unlimited"})
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TimeFormatError(ValueError):
|
|
37
|
+
"""A duration or timestamp that cannot be understood.
|
|
38
|
+
|
|
39
|
+
A plain ValueError subclass so `core.timefmt` keeps its dependencies to
|
|
40
|
+
the standard library; `core.errors.classify` maps it to USAGE.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Out
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def fmt_dt(value: datetime | None, *, legacy: bool = False) -> str | None:
|
|
50
|
+
"""RFC-3339 UTC with a `Z` suffix, or None.
|
|
51
|
+
|
|
52
|
+
*legacy* restores v1's `str(datetime)` spelling for the one minor release
|
|
53
|
+
that `[defaults] legacy_dates` covers (§12.4).
|
|
54
|
+
"""
|
|
55
|
+
if value is None:
|
|
56
|
+
return None
|
|
57
|
+
if value.tzinfo is None:
|
|
58
|
+
# Telethon hands back aware datetimes; anything naive reaching here is
|
|
59
|
+
# a local wall-clock value, and guessing UTC would silently shift it.
|
|
60
|
+
value = value.astimezone()
|
|
61
|
+
utc = value.astimezone(UTC)
|
|
62
|
+
if legacy:
|
|
63
|
+
return str(utc)
|
|
64
|
+
return utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def fmt_unix(value: int | float | None, *, legacy: bool = False) -> str | None:
|
|
68
|
+
"""A Unix timestamp as RFC-3339 UTC.
|
|
69
|
+
|
|
70
|
+
The mirror of `to_unix`, for the values Telegram gives as integers
|
|
71
|
+
(`mute_until`, `freeze_until_date`, `slowmode_next_send_date`): they have
|
|
72
|
+
to come back out as a timestamp a human can check, because "muted: true"
|
|
73
|
+
with a deadline in 1970 looks exactly like a mute that worked (COR-01).
|
|
74
|
+
"""
|
|
75
|
+
if value is None:
|
|
76
|
+
return None
|
|
77
|
+
return fmt_dt(datetime.fromtimestamp(float(value), UTC), legacy=legacy)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def to_unix(value: datetime | None) -> int | None:
|
|
81
|
+
"""Seconds since the epoch, for the `*_unix` sibling of every timestamp."""
|
|
82
|
+
if value is None:
|
|
83
|
+
return None
|
|
84
|
+
if value.tzinfo is None:
|
|
85
|
+
value = value.astimezone()
|
|
86
|
+
return int(value.timestamp())
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def legacy_dates_enabled() -> bool:
|
|
90
|
+
"""`[defaults] legacy_dates` — v1 date spelling, for one minor release."""
|
|
91
|
+
try:
|
|
92
|
+
from tlgr.core.config import load_app_config
|
|
93
|
+
|
|
94
|
+
return bool(getattr(load_app_config().defaults, "legacy_dates", False))
|
|
95
|
+
except Exception:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def fmt_local(value: str | datetime | None, *, now: datetime | None = None) -> str:
|
|
100
|
+
"""Human rendering: local time, precision by recency.
|
|
101
|
+
|
|
102
|
+
`today 14:02` for today, `Mon 09:14` inside the last week, and the full
|
|
103
|
+
`2026-09-02 09:14` beyond that — a table of timestamps is read for "when
|
|
104
|
+
relative to me", not for the year.
|
|
105
|
+
"""
|
|
106
|
+
if value is None:
|
|
107
|
+
return "-"
|
|
108
|
+
dt = parse_dt(value) if isinstance(value, str) else value
|
|
109
|
+
if dt is None:
|
|
110
|
+
return "-"
|
|
111
|
+
local = dt.astimezone()
|
|
112
|
+
reference = (now or datetime.now(UTC)).astimezone()
|
|
113
|
+
delta = reference.date() - local.date()
|
|
114
|
+
if delta.days == 0:
|
|
115
|
+
return f"today {local:%H:%M}"
|
|
116
|
+
if delta.days == 1:
|
|
117
|
+
return f"yesterday {local:%H:%M}"
|
|
118
|
+
if 0 < delta.days < 7:
|
|
119
|
+
return f"{local:%a %H:%M}"
|
|
120
|
+
return f"{local:%Y-%m-%d %H:%M}"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# In
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def parse_dt(text: str | datetime | None, *, now: datetime | None = None) -> datetime | None:
|
|
129
|
+
"""Parse RFC-3339, `YYYY-MM-DD`, `YYYY-MM-DDTHH:MM` or a relative offset.
|
|
130
|
+
|
|
131
|
+
Naive values are read in the **local** zone and converted to UTC: a user
|
|
132
|
+
typing `2026-09-02 09:00` means nine in their own morning, and reading it
|
|
133
|
+
as UTC silently moves the appointment (COR-23).
|
|
134
|
+
"""
|
|
135
|
+
if text is None:
|
|
136
|
+
return None
|
|
137
|
+
if isinstance(text, datetime):
|
|
138
|
+
return text if text.tzinfo else text.astimezone()
|
|
139
|
+
|
|
140
|
+
raw = text.strip()
|
|
141
|
+
if not raw:
|
|
142
|
+
return None
|
|
143
|
+
reference = now or datetime.now(UTC)
|
|
144
|
+
|
|
145
|
+
low = raw.lower()
|
|
146
|
+
if low == "now":
|
|
147
|
+
return reference
|
|
148
|
+
if low in ("today", "midnight"):
|
|
149
|
+
local = reference.astimezone()
|
|
150
|
+
return local.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
151
|
+
|
|
152
|
+
relative = _RELATIVE_RE.match(raw)
|
|
153
|
+
if relative:
|
|
154
|
+
sign, amount, unit = relative.groups()
|
|
155
|
+
seconds = float(amount) * _UNIT_SECONDS[unit.lower()]
|
|
156
|
+
return reference + timedelta(seconds=seconds if sign == "+" else -seconds)
|
|
157
|
+
|
|
158
|
+
if raw.isdigit() and len(raw) >= 9:
|
|
159
|
+
# A bare unix timestamp; 9 digits keeps `20260902` out of this branch.
|
|
160
|
+
return datetime.fromtimestamp(int(raw), tz=UTC)
|
|
161
|
+
|
|
162
|
+
candidate = raw.replace("Z", "+00:00").replace("z", "+00:00")
|
|
163
|
+
try:
|
|
164
|
+
parsed = datetime.fromisoformat(candidate)
|
|
165
|
+
except ValueError:
|
|
166
|
+
try:
|
|
167
|
+
parsed = datetime.combine(date.fromisoformat(raw), datetime.min.time())
|
|
168
|
+
except ValueError as exc:
|
|
169
|
+
raise TimeFormatError(
|
|
170
|
+
f"{text!r} is not a timestamp: expected RFC-3339, YYYY-MM-DD, "
|
|
171
|
+
"or a relative offset such as -90m or +3d"
|
|
172
|
+
) from exc
|
|
173
|
+
if parsed.tzinfo is None:
|
|
174
|
+
parsed = parsed.astimezone()
|
|
175
|
+
return parsed
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def parse_duration(text: str | int | None) -> int | None:
|
|
179
|
+
"""`30s 5m 2h 7d 1w forever` → seconds, or None for "forever".
|
|
180
|
+
|
|
181
|
+
`0` is legal and means "now/immediately"; None means "no end", which is
|
|
182
|
+
why the return type cannot simply be an int.
|
|
183
|
+
"""
|
|
184
|
+
if text is None:
|
|
185
|
+
return None
|
|
186
|
+
if isinstance(text, int):
|
|
187
|
+
return text
|
|
188
|
+
raw = text.strip().lower()
|
|
189
|
+
if not raw:
|
|
190
|
+
return None
|
|
191
|
+
if raw in FOREVER:
|
|
192
|
+
return None
|
|
193
|
+
match = _DURATION_RE.match(raw)
|
|
194
|
+
if not match:
|
|
195
|
+
raise TimeFormatError(
|
|
196
|
+
f"{text!r} is not a duration: expected 30s, 5m, 2h, 7d, 1w or 'forever'"
|
|
197
|
+
)
|
|
198
|
+
amount, unit = match.groups()
|
|
199
|
+
return int(float(amount) * _UNIT_SECONDS.get(unit or "s", 1))
|
tlgr/core/tl.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""TL object → JSON-safe builtins, and peer arithmetic.
|
|
2
|
+
|
|
3
|
+
Two functions, in `core/` because three layers need them and none may import
|
|
4
|
+
another: the bus normalises updates with them, `ops/` reads `help.*` replies
|
|
5
|
+
with them, and neither is allowed to reach into the other (§2.2).
|
|
6
|
+
|
|
7
|
+
`tl_to_builtins` is the COR-07 fix stated once. v1 delivered a raw `to_dict()`
|
|
8
|
+
through `json.dumps(default=str)`, so a `datetime` became a string in one
|
|
9
|
+
place and a `bytes` blew up in another — and a message with media could fail
|
|
10
|
+
to serialise *at delivery time*, far from the cause, counted as a delivery
|
|
11
|
+
failure rather than as the bug it was.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
__all__ = ["CHANNEL_MARK", "peer_marked_id", "tl_to_builtins"]
|
|
20
|
+
|
|
21
|
+
#: Telegram's channel id offset. A channel's marked id is this minus its id,
|
|
22
|
+
#: which is what makes `-100…` recognisable at a glance.
|
|
23
|
+
CHANNEL_MARK = -1000000000000
|
|
24
|
+
|
|
25
|
+
#: How deep `tl_to_builtins` walks before it stops. A `Message` inside a
|
|
26
|
+
#: `Story` inside a `WebPage` is real; anything past this is a cycle, or a
|
|
27
|
+
#: payload nobody wanted in a stream frame.
|
|
28
|
+
_MAX_DEPTH = 8
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def tl_to_builtins(value: Any, *, depth: int = 0) -> Any:
|
|
32
|
+
"""A TL object tree → JSON-safe builtins, with the class name kept.
|
|
33
|
+
|
|
34
|
+
Datetimes become RFC-3339, bytes become hex, and the constructor name
|
|
35
|
+
survives as `_` so a consumer can still branch on it.
|
|
36
|
+
"""
|
|
37
|
+
if value is None or isinstance(value, (bool, int, float, str)):
|
|
38
|
+
return value
|
|
39
|
+
if isinstance(value, (bytes, bytearray, memoryview)):
|
|
40
|
+
return bytes(value).hex()
|
|
41
|
+
if isinstance(value, datetime):
|
|
42
|
+
return value.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
43
|
+
if depth >= _MAX_DEPTH:
|
|
44
|
+
return type(value).__name__
|
|
45
|
+
if isinstance(value, (list, tuple, set, frozenset)):
|
|
46
|
+
return [tl_to_builtins(item, depth=depth + 1) for item in value]
|
|
47
|
+
if isinstance(value, dict):
|
|
48
|
+
return {str(k): tl_to_builtins(v, depth=depth + 1) for k, v in value.items()}
|
|
49
|
+
|
|
50
|
+
out: dict[str, Any] = {"_": type(value).__name__}
|
|
51
|
+
attributes = getattr(value, "__dict__", None)
|
|
52
|
+
names: list[str]
|
|
53
|
+
if isinstance(attributes, dict) and attributes:
|
|
54
|
+
names = [name for name in attributes if not name.startswith("_")]
|
|
55
|
+
else:
|
|
56
|
+
names = [
|
|
57
|
+
name
|
|
58
|
+
for klass in type(value).__mro__
|
|
59
|
+
for name in getattr(klass, "__slots__", ())
|
|
60
|
+
if not str(name).startswith("_")
|
|
61
|
+
]
|
|
62
|
+
if not names:
|
|
63
|
+
# A TLObject with nothing set, or an object we have no handle on. Its
|
|
64
|
+
# class name is the honest answer; `str()` would call Telethon's
|
|
65
|
+
# pretty-printer, which re-enters `to_dict()` and can raise.
|
|
66
|
+
return out
|
|
67
|
+
for name in names:
|
|
68
|
+
out[str(name)] = tl_to_builtins(getattr(value, name, None), depth=depth + 1)
|
|
69
|
+
return out
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def peer_marked_id(peer: Any) -> int | None:
|
|
73
|
+
"""A TL `Peer*` → the marked id tlgr uses everywhere else.
|
|
74
|
+
|
|
75
|
+
Four lines of arithmetic rather than a call into Telethon's `utils`,
|
|
76
|
+
because the bus runs this on the update loop's hot path for every event.
|
|
77
|
+
"""
|
|
78
|
+
if peer is None:
|
|
79
|
+
return None
|
|
80
|
+
if isinstance(peer, int):
|
|
81
|
+
return peer
|
|
82
|
+
name = type(peer).__name__
|
|
83
|
+
if name == "PeerUser":
|
|
84
|
+
return _int(getattr(peer, "user_id", None))
|
|
85
|
+
if name == "PeerChat":
|
|
86
|
+
chat_id = _int(getattr(peer, "chat_id", None))
|
|
87
|
+
return -chat_id if chat_id is not None else None
|
|
88
|
+
if name == "PeerChannel":
|
|
89
|
+
channel_id = _int(getattr(peer, "channel_id", None))
|
|
90
|
+
return CHANNEL_MARK - channel_id if channel_id is not None else None
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _int(value: Any) -> int | None:
|
|
95
|
+
try:
|
|
96
|
+
return int(value)
|
|
97
|
+
except (TypeError, ValueError):
|
|
98
|
+
return None
|
tlgr/daemon/__init__.py
ADDED
|
File without changes
|