clitg 0.1.0__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.
- clitg/__init__.py +12 -0
- clitg/__main__.py +5 -0
- clitg/catalog.py +242 -0
- clitg/cli.py +1164 -0
- clitg/errors.py +50 -0
- clitg/models.py +147 -0
- clitg/raw.py +139 -0
- clitg/serialization.py +108 -0
- clitg/service.py +890 -0
- clitg/storage.py +396 -0
- clitg/telegram.py +583 -0
- clitg-0.1.0.dist-info/METADATA +359 -0
- clitg-0.1.0.dist-info/RECORD +16 -0
- clitg-0.1.0.dist-info/WHEEL +4 -0
- clitg-0.1.0.dist-info/entry_points.txt +3 -0
- clitg-0.1.0.dist-info/licenses/LICENSE +21 -0
clitg/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""clitg package."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("clitg")
|
|
7
|
+
except PackageNotFoundError:
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
SCHEMA_VERSION = "0.1"
|
|
11
|
+
|
|
12
|
+
__all__ = ["SCHEMA_VERSION", "__version__"]
|
clitg/__main__.py
ADDED
clitg/catalog.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Generate machine-readable command, schema, and MTProto capability catalogs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Literal
|
|
11
|
+
|
|
12
|
+
import telethon
|
|
13
|
+
from telethon.tl import alltlobjects
|
|
14
|
+
from telethon.tl.tlobject import TLRequest
|
|
15
|
+
|
|
16
|
+
from clitg import SCHEMA_VERSION, __version__
|
|
17
|
+
from clitg.models import (
|
|
18
|
+
Capability,
|
|
19
|
+
CapabilityCatalog,
|
|
20
|
+
Confirmation,
|
|
21
|
+
Envelope,
|
|
22
|
+
ErrorInfo,
|
|
23
|
+
JsonlRecord,
|
|
24
|
+
LoginState,
|
|
25
|
+
Meta,
|
|
26
|
+
ProfileView,
|
|
27
|
+
)
|
|
28
|
+
from clitg.serialization import to_jsonable
|
|
29
|
+
|
|
30
|
+
HIGH_LEVEL_METHODS: dict[str, str] = {
|
|
31
|
+
"messages.getForumTopics": "topics.list",
|
|
32
|
+
"contacts.getContacts": "contacts.list",
|
|
33
|
+
"messages.deleteMessages": "messages.delete",
|
|
34
|
+
"messages.deleteScheduledMessages": "scheduled.cancel",
|
|
35
|
+
"messages.editMessage": "messages.edit",
|
|
36
|
+
"messages.forwardMessages": "messages.forward",
|
|
37
|
+
"messages.getHistory": "messages.list",
|
|
38
|
+
"messages.getMessages": "messages.get",
|
|
39
|
+
"messages.getScheduledHistory": "scheduled.list",
|
|
40
|
+
"messages.readHistory": "messages.read",
|
|
41
|
+
"messages.search": "messages.search",
|
|
42
|
+
"messages.sendMedia": "messages.send",
|
|
43
|
+
"messages.sendMessage": "messages.send",
|
|
44
|
+
"messages.sendMultiMedia": "messages.send",
|
|
45
|
+
"messages.sendReaction": "messages.react",
|
|
46
|
+
"messages.updatePinnedMessage": "messages.pin",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
UNSUPPORTED_METHODS: dict[str, str] = {
|
|
50
|
+
"messages.acceptEncryption": "Secret-chat encryption state is not implemented",
|
|
51
|
+
"messages.discardEncryption": "Secret-chat encryption state is not implemented",
|
|
52
|
+
"messages.requestEncryption": "Secret-chat encryption state is not implemented",
|
|
53
|
+
"messages.sendEncrypted": "Secret-chat encryption state is not implemented",
|
|
54
|
+
"messages.sendEncryptedFile": "Secret-chat encryption state is not implemented",
|
|
55
|
+
"messages.sendEncryptedService": "Secret-chat encryption state is not implemented",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
CRITICAL_PATTERNS = (
|
|
59
|
+
re.compile(r"^account\.(delete|updatePassword|resetPassword|changePhone)"),
|
|
60
|
+
re.compile(r"^auth\."),
|
|
61
|
+
re.compile(r"^channels\.editCreator"),
|
|
62
|
+
)
|
|
63
|
+
DESTRUCTIVE_WORDS = ("delete", "discard", "revoke", "terminate")
|
|
64
|
+
WRITE_WORDS = (
|
|
65
|
+
"accept",
|
|
66
|
+
"add",
|
|
67
|
+
"cancel",
|
|
68
|
+
"create",
|
|
69
|
+
"edit",
|
|
70
|
+
"forward",
|
|
71
|
+
"import",
|
|
72
|
+
"invite",
|
|
73
|
+
"join",
|
|
74
|
+
"leave",
|
|
75
|
+
"mark",
|
|
76
|
+
"read",
|
|
77
|
+
"save",
|
|
78
|
+
"send",
|
|
79
|
+
"set",
|
|
80
|
+
"start",
|
|
81
|
+
"toggle",
|
|
82
|
+
"update",
|
|
83
|
+
"upload",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
COMMAND_CATALOG: dict[str, dict[str, Any]] = {
|
|
88
|
+
"auth": {"commands": ["request-code", "verify", "status", "logout"]},
|
|
89
|
+
"capabilities": {"commands": ["list", "get"]},
|
|
90
|
+
"contacts": {"commands": ["list", "search", "resolve"]},
|
|
91
|
+
"dialogs": {"commands": ["list", "get", "search"]},
|
|
92
|
+
"help": {"commands": []},
|
|
93
|
+
"media": {"commands": ["download"]},
|
|
94
|
+
"messages": {
|
|
95
|
+
"commands": [
|
|
96
|
+
"list",
|
|
97
|
+
"get",
|
|
98
|
+
"search",
|
|
99
|
+
"send",
|
|
100
|
+
"reply",
|
|
101
|
+
"forward",
|
|
102
|
+
"edit",
|
|
103
|
+
"delete",
|
|
104
|
+
"read",
|
|
105
|
+
"react",
|
|
106
|
+
"pin",
|
|
107
|
+
"unpin",
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
"polls": {"commands": ["create", "vote", "close"]},
|
|
111
|
+
"profiles": {"commands": ["create", "list", "get", "set-default", "remove"]},
|
|
112
|
+
"raw": {"commands": ["invoke"]},
|
|
113
|
+
"scheduled": {"commands": ["list", "cancel"]},
|
|
114
|
+
"schema": {"commands": ["list", "get", "export"]},
|
|
115
|
+
"state": {"commands": ["get", "prune"]},
|
|
116
|
+
"topics": {"commands": ["list"]},
|
|
117
|
+
"version": {"commands": []},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
SCHEMA_MODELS = {
|
|
121
|
+
cls.__name__: cls
|
|
122
|
+
for cls in (
|
|
123
|
+
Capability,
|
|
124
|
+
CapabilityCatalog,
|
|
125
|
+
Confirmation,
|
|
126
|
+
Envelope,
|
|
127
|
+
ErrorInfo,
|
|
128
|
+
JsonlRecord,
|
|
129
|
+
LoginState,
|
|
130
|
+
Meta,
|
|
131
|
+
ProfileView,
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def method_name(request_class: type[TLRequest]) -> str:
|
|
137
|
+
"""Convert a generated Telethon request class to its canonical TL method name."""
|
|
138
|
+
|
|
139
|
+
namespace = request_class.__module__.rsplit(".", maxsplit=1)[-1]
|
|
140
|
+
class_name = request_class.__name__.removesuffix("Request")
|
|
141
|
+
return f"{namespace}.{class_name[0].lower()}{class_name[1:]}"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def risk_for(method: str) -> Literal["read", "write", "destructive", "critical", "unknown"]:
|
|
145
|
+
"""Conservatively classify one raw method."""
|
|
146
|
+
|
|
147
|
+
if any(pattern.search(method) for pattern in CRITICAL_PATTERNS):
|
|
148
|
+
return "critical"
|
|
149
|
+
leaf = method.split(".", maxsplit=1)[1]
|
|
150
|
+
lowered = leaf.lower()
|
|
151
|
+
if lowered.startswith(DESTRUCTIVE_WORDS):
|
|
152
|
+
return "destructive"
|
|
153
|
+
if leaf.startswith(("get", "search")):
|
|
154
|
+
return "read"
|
|
155
|
+
if lowered.startswith(WRITE_WORDS):
|
|
156
|
+
return "write"
|
|
157
|
+
return "unknown"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def request_registry() -> dict[str, type[TLRequest]]:
|
|
161
|
+
"""Return every request exposed by the installed Telethon layer."""
|
|
162
|
+
|
|
163
|
+
registry: dict[str, type[TLRequest]] = {}
|
|
164
|
+
for candidate in alltlobjects.tlobjects.values():
|
|
165
|
+
if inspect.isclass(candidate) and issubclass(candidate, TLRequest):
|
|
166
|
+
registry[method_name(candidate)] = candidate
|
|
167
|
+
return dict(sorted(registry.items()))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def capability_catalog() -> CapabilityCatalog:
|
|
171
|
+
"""Generate the capability manifest from the live Telethon registry."""
|
|
172
|
+
|
|
173
|
+
capabilities: list[Capability] = []
|
|
174
|
+
for method, request_class in request_registry().items():
|
|
175
|
+
reason = UNSUPPORTED_METHODS.get(method)
|
|
176
|
+
if reason:
|
|
177
|
+
status = "unsupported"
|
|
178
|
+
elif method in HIGH_LEVEL_METHODS:
|
|
179
|
+
status = "high-level"
|
|
180
|
+
else:
|
|
181
|
+
status = "raw-only"
|
|
182
|
+
capabilities.append(
|
|
183
|
+
Capability(
|
|
184
|
+
method=method,
|
|
185
|
+
python_class=f"{request_class.__module__}.{request_class.__name__}",
|
|
186
|
+
status=status,
|
|
187
|
+
risk=risk_for(method),
|
|
188
|
+
command=HIGH_LEVEL_METHODS.get(method),
|
|
189
|
+
reason=reason,
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
return CapabilityCatalog(
|
|
193
|
+
telethon_version=telethon.__version__,
|
|
194
|
+
telegram_layer=alltlobjects.LAYER,
|
|
195
|
+
generated_at=datetime.now(UTC),
|
|
196
|
+
capabilities=capabilities,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def command_catalog() -> dict[str, Any]:
|
|
201
|
+
"""Return the machine-readable top-level command catalog."""
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
"schema_version": SCHEMA_VERSION,
|
|
205
|
+
"cli_version": __version__,
|
|
206
|
+
"global_options": {
|
|
207
|
+
"profile": "string|null",
|
|
208
|
+
"output": ["json", "jsonl"],
|
|
209
|
+
"timeout_seconds": "positive integer",
|
|
210
|
+
"verbose": "boolean",
|
|
211
|
+
},
|
|
212
|
+
"groups": COMMAND_CATALOG,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def schema_catalog() -> dict[str, Any]:
|
|
217
|
+
"""Return JSON Schema documents for stable public models."""
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
"schema_version": SCHEMA_VERSION,
|
|
221
|
+
"models": {name: model.model_json_schema() for name, model in SCHEMA_MODELS.items()},
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def write_catalogs(root: Path) -> None:
|
|
226
|
+
"""Write deterministic checked-in catalogs."""
|
|
227
|
+
|
|
228
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
229
|
+
capabilities = capability_catalog().model_dump(mode="json")
|
|
230
|
+
capabilities["generated_at"] = "generated"
|
|
231
|
+
(root / "capabilities.json").write_text(
|
|
232
|
+
json.dumps(to_jsonable(capabilities), indent=2, sort_keys=True) + "\n",
|
|
233
|
+
encoding="utf-8",
|
|
234
|
+
)
|
|
235
|
+
(root / "schemas.json").write_text(
|
|
236
|
+
json.dumps(schema_catalog(), indent=2, sort_keys=True) + "\n",
|
|
237
|
+
encoding="utf-8",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
write_catalogs(Path("schemas"))
|