telegram-assistant 0.2.1__tar.gz
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.
- telegram_assistant-0.2.1/LICENSE +21 -0
- telegram_assistant-0.2.1/PKG-INFO +201 -0
- telegram_assistant-0.2.1/README.md +162 -0
- telegram_assistant-0.2.1/pyproject.toml +92 -0
- telegram_assistant-0.2.1/setup.cfg +4 -0
- telegram_assistant-0.2.1/src/telegram_assistant/__init__.py +3 -0
- telegram_assistant-0.2.1/src/telegram_assistant/cli/__init__.py +5 -0
- telegram_assistant-0.2.1/src/telegram_assistant/cli/main.py +3164 -0
- telegram_assistant-0.2.1/src/telegram_assistant/config/__init__.py +33 -0
- telegram_assistant-0.2.1/src/telegram_assistant/config/loader.py +139 -0
- telegram_assistant-0.2.1/src/telegram_assistant/config/models.py +190 -0
- telegram_assistant-0.2.1/src/telegram_assistant/domain/__init__.py +1 -0
- telegram_assistant-0.2.1/src/telegram_assistant/folders/__init__.py +37 -0
- telegram_assistant-0.2.1/src/telegram_assistant/folders/service.py +229 -0
- telegram_assistant-0.2.1/src/telegram_assistant/folders/telethon_backend.py +128 -0
- telegram_assistant-0.2.1/src/telegram_assistant/groups/__init__.py +37 -0
- telegram_assistant-0.2.1/src/telegram_assistant/groups/service.py +731 -0
- telegram_assistant-0.2.1/src/telegram_assistant/groups/telethon_backend.py +299 -0
- telegram_assistant-0.2.1/src/telegram_assistant/health.py +202 -0
- telegram_assistant-0.2.1/src/telegram_assistant/http_api/__init__.py +5 -0
- telegram_assistant-0.2.1/src/telegram_assistant/http_api/app.py +333 -0
- telegram_assistant-0.2.1/src/telegram_assistant/http_api/auth.py +45 -0
- telegram_assistant-0.2.1/src/telegram_assistant/http_api/folders.py +133 -0
- telegram_assistant-0.2.1/src/telegram_assistant/http_api/groups.py +233 -0
- telegram_assistant-0.2.1/src/telegram_assistant/http_api/members.py +272 -0
- telegram_assistant-0.2.1/src/telegram_assistant/http_api/messages.py +296 -0
- telegram_assistant-0.2.1/src/telegram_assistant/http_api/topics.py +406 -0
- telegram_assistant-0.2.1/src/telegram_assistant/members/__init__.py +57 -0
- telegram_assistant-0.2.1/src/telegram_assistant/members/service.py +1071 -0
- telegram_assistant-0.2.1/src/telegram_assistant/members/telethon_backend.py +151 -0
- telegram_assistant-0.2.1/src/telegram_assistant/messages/__init__.py +33 -0
- telegram_assistant-0.2.1/src/telegram_assistant/messages/service.py +528 -0
- telegram_assistant-0.2.1/src/telegram_assistant/observability/__init__.py +43 -0
- telegram_assistant-0.2.1/src/telegram_assistant/observability/alerts.py +298 -0
- telegram_assistant-0.2.1/src/telegram_assistant/observability/factory.py +19 -0
- telegram_assistant-0.2.1/src/telegram_assistant/observability/logging.py +205 -0
- telegram_assistant-0.2.1/src/telegram_assistant/persistence/__init__.py +35 -0
- telegram_assistant-0.2.1/src/telegram_assistant/persistence/idempotency.py +98 -0
- telegram_assistant-0.2.1/src/telegram_assistant/persistence/models.py +86 -0
- telegram_assistant-0.2.1/src/telegram_assistant/persistence/schema.py +103 -0
- telegram_assistant-0.2.1/src/telegram_assistant/persistence/store.py +572 -0
- telegram_assistant-0.2.1/src/telegram_assistant/plugins/__init__.py +19 -0
- telegram_assistant-0.2.1/src/telegram_assistant/plugins/base.py +127 -0
- telegram_assistant-0.2.1/src/telegram_assistant/plugins/planfix.py +182 -0
- telegram_assistant-0.2.1/src/telegram_assistant/telegram_client/__init__.py +27 -0
- telegram_assistant-0.2.1/src/telegram_assistant/telegram_client/errors.py +28 -0
- telegram_assistant-0.2.1/src/telegram_assistant/telegram_client/proxy.py +52 -0
- telegram_assistant-0.2.1/src/telegram_assistant/telegram_client/session.py +272 -0
- telegram_assistant-0.2.1/src/telegram_assistant/topics/__init__.py +59 -0
- telegram_assistant-0.2.1/src/telegram_assistant/topics/service.py +835 -0
- telegram_assistant-0.2.1/src/telegram_assistant/topics/telethon_backend.py +159 -0
- telegram_assistant-0.2.1/src/telegram_assistant/worker/__init__.py +19 -0
- telegram_assistant-0.2.1/src/telegram_assistant/worker/queue.py +221 -0
- telegram_assistant-0.2.1/src/telegram_assistant.egg-info/PKG-INFO +201 -0
- telegram_assistant-0.2.1/src/telegram_assistant.egg-info/SOURCES.txt +90 -0
- telegram_assistant-0.2.1/src/telegram_assistant.egg-info/dependency_links.txt +1 -0
- telegram_assistant-0.2.1/src/telegram_assistant.egg-info/entry_points.txt +2 -0
- telegram_assistant-0.2.1/src/telegram_assistant.egg-info/requires.txt +20 -0
- telegram_assistant-0.2.1/src/telegram_assistant.egg-info/top_level.txt +1 -0
- telegram_assistant-0.2.1/tests/test_app_skeleton.py +86 -0
- telegram_assistant-0.2.1/tests/test_cli_groups_layout.py +374 -0
- telegram_assistant-0.2.1/tests/test_cli_operations.py +251 -0
- telegram_assistant-0.2.1/tests/test_config_loader.py +405 -0
- telegram_assistant-0.2.1/tests/test_docker_image.py +120 -0
- telegram_assistant-0.2.1/tests/test_dry_run_contract.py +287 -0
- telegram_assistant-0.2.1/tests/test_dry_run_folders_operations.py +485 -0
- telegram_assistant-0.2.1/tests/test_dry_run_groups_topics.py +658 -0
- telegram_assistant-0.2.1/tests/test_dry_run_members_messages.py +896 -0
- telegram_assistant-0.2.1/tests/test_folders.py +592 -0
- telegram_assistant-0.2.1/tests/test_groups.py +1477 -0
- telegram_assistant-0.2.1/tests/test_groups_layout.py +558 -0
- telegram_assistant-0.2.1/tests/test_health.py +445 -0
- telegram_assistant-0.2.1/tests/test_http_groups_layout.py +381 -0
- telegram_assistant-0.2.1/tests/test_http_lifespan.py +165 -0
- telegram_assistant-0.2.1/tests/test_members.py +716 -0
- telegram_assistant-0.2.1/tests/test_members_remove.py +698 -0
- telegram_assistant-0.2.1/tests/test_messages.py +793 -0
- telegram_assistant-0.2.1/tests/test_observability_alerts.py +300 -0
- telegram_assistant-0.2.1/tests/test_observability_logging.py +151 -0
- telegram_assistant-0.2.1/tests/test_persistence.py +493 -0
- telegram_assistant-0.2.1/tests/test_plugins.py +238 -0
- telegram_assistant-0.2.1/tests/test_proxy.py +82 -0
- telegram_assistant-0.2.1/tests/test_skill_errors_scope.py +148 -0
- telegram_assistant-0.2.1/tests/test_skill_inventory.py +164 -0
- telegram_assistant-0.2.1/tests/test_skill_scenarios.py +202 -0
- telegram_assistant-0.2.1/tests/test_skill_structure.py +116 -0
- telegram_assistant-0.2.1/tests/test_telegram_client.py +351 -0
- telegram_assistant-0.2.1/tests/test_topics.py +577 -0
- telegram_assistant-0.2.1/tests/test_topics_bulk.py +780 -0
- telegram_assistant-0.2.1/tests/test_topics_close.py +519 -0
- telegram_assistant-0.2.1/tests/test_topics_telethon_backend.py +344 -0
- telegram_assistant-0.2.1/tests/test_worker_queue.py +393 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stanislav Popov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: telegram-assistant
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Telegram automation service (MTProto/Telethon) with an optional Planfix integration plugin.
|
|
5
|
+
Author-email: Stanislav Popov <popstas@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/popstas/telegram-assistant
|
|
8
|
+
Project-URL: Repository, https://github.com/popstas/telegram-assistant
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/popstas/telegram-assistant/issues
|
|
10
|
+
Keywords: telegram,telethon,automation,mtproto,planfix
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: Topic :: Communications :: Chat
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: telethon<2.0,>=1.36
|
|
20
|
+
Requires-Dist: fastapi<1.0,>=0.115
|
|
21
|
+
Requires-Dist: uvicorn[standard]<1.0,>=0.30
|
|
22
|
+
Requires-Dist: pydantic<3.0,>=2.7
|
|
23
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
24
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
25
|
+
Requires-Dist: structlog<25.0,>=24.1
|
|
26
|
+
Requires-Dist: SQLAlchemy<3.0,>=2.0
|
|
27
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
28
|
+
Requires-Dist: python-socks[asyncio]<3.0,>=2.4
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff<1.0,>=0.5; extra == "dev"
|
|
33
|
+
Requires-Dist: git-cliff>=2.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pre-commit<5.0,>=3.7; extra == "dev"
|
|
35
|
+
Requires-Dist: build<2.0,>=1.2; extra == "dev"
|
|
36
|
+
Requires-Dist: twine<7.0,>=5.0; extra == "dev"
|
|
37
|
+
Requires-Dist: bump-my-version<1.0,>=0.28; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# telegram-assistant
|
|
41
|
+
|
|
42
|
+
Telegram automation service for the Planfix ↔ Telegram integration.
|
|
43
|
+
|
|
44
|
+
Three interfaces share one domain layer:
|
|
45
|
+
|
|
46
|
+
- HTTP API (FastAPI) on port `8085` with bearer-token auth — primary entry point for Planfix and automations.
|
|
47
|
+
- CLI (`telegram-assistant`) — mirrors every HTTP endpoint plus admin commands (`auth`, `operations status`, `operations retry`).
|
|
48
|
+
- Worker/queue — performs Telegram operations with throttling and `FLOOD_WAIT` handling.
|
|
49
|
+
|
|
50
|
+
Runs on MTProto via Telethon under a technical Telegram user account.
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python3.12 -m venv .venv
|
|
56
|
+
source .venv/bin/activate
|
|
57
|
+
pip install -e ".[dev]"
|
|
58
|
+
|
|
59
|
+
# Place a config file at data/config.yml (see Configuration below)
|
|
60
|
+
telegram-assistant auth # interactive Telethon login
|
|
61
|
+
telegram-assistant health # show current health
|
|
62
|
+
uvicorn telegram_assistant.http_api.app:create_app --factory --port 8085
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Commands
|
|
66
|
+
|
|
67
|
+
Every CLI subcommand maps 1:1 to an HTTP endpoint (except the admin-only commands `auth`, `operations status`, and `operations retry`). Run any command with `--help` for full flag documentation.
|
|
68
|
+
|
|
69
|
+
Top-level:
|
|
70
|
+
|
|
71
|
+
- `auth` — interactive Telethon login for the technical account.
|
|
72
|
+
- `health` — report service health (Telegram session, database, default folder).
|
|
73
|
+
- `version` — print the installed version.
|
|
74
|
+
|
|
75
|
+
`groups` — manage Telegram supergroups:
|
|
76
|
+
|
|
77
|
+
- `groups create` — create a Telegram supergroup for a Planfix client. Accepts `--topics-layout list|tabs` to pick the forum layout for this group (defaults to `telegram.defaults.topics_layout`).
|
|
78
|
+
- `groups set-layout` — set the topics layout (`list` vs `tabs`) for an existing forum chat.
|
|
79
|
+
- `groups get-layout` — read the current topics layout (`list` or `tabs`) for a forum chat.
|
|
80
|
+
|
|
81
|
+
`topics` — manage forum topics:
|
|
82
|
+
|
|
83
|
+
- `topics create` — create a single forum topic in an existing supergroup.
|
|
84
|
+
- `topics bulk-create` — bulk-create topics from a CSV or JSON file.
|
|
85
|
+
- `topics close` — close an existing forum topic (the topic and its history are kept).
|
|
86
|
+
|
|
87
|
+
`members` — manage group membership:
|
|
88
|
+
|
|
89
|
+
- `members bulk-add` — bulk-add members to an existing supergroup, optionally promoting to admin.
|
|
90
|
+
- `members bulk-remove` — bulk-remove members from a supergroup (kick or permanently ban).
|
|
91
|
+
|
|
92
|
+
`messages` — send messages and service commands:
|
|
93
|
+
|
|
94
|
+
- `messages send` — send a message or service command (targeted or folder-wide mass mode).
|
|
95
|
+
|
|
96
|
+
`folders` — inspect and manage chat folders:
|
|
97
|
+
|
|
98
|
+
- `folders inspect` — inspect a chat folder and list its chats.
|
|
99
|
+
- `folders add-chat` — move an existing chat into a folder.
|
|
100
|
+
|
|
101
|
+
`operations` — inspect and retry queued operations:
|
|
102
|
+
|
|
103
|
+
- `operations status` — show the status of an operation, including per-item summary.
|
|
104
|
+
- `operations retry` — reset a failed/`needs_review` operation (and its items) back to pending.
|
|
105
|
+
|
|
106
|
+
Updating this list: descriptions are sourced from each Typer command's docstring in `src/telegram_assistant/cli/main.py`. When you add or rename a command, update this section, `skills/telegram-assistant/SKILL.md`, and re-run `pytest tests/test_skill_inventory.py` — the inventory guard fails if the README/skill catalog drifts from the CLI.
|
|
107
|
+
|
|
108
|
+
## Configuration
|
|
109
|
+
|
|
110
|
+
Config is read from `data/config.yml` by default. The `data/` directory is excluded from version control and holds the Telethon session, SQLite database, and secrets.
|
|
111
|
+
|
|
112
|
+
If `./data/config.yml` is absent, the loader falls back to `~/.config/telegram-assistant/config.yml`. On a clean machine, running any CLI command without `--config` will create a template at that path with `REPLACE_ME` placeholders for `api_id`, `api_hash`, and `bearer_token` — fill them in and re-run.
|
|
113
|
+
|
|
114
|
+
To reach Telegram through a proxy, set `telegram.proxy_url` to a single URL — supported schemes are `socks5`, `socks4`, `http`, and `https`. Credentials and explicit ports are optional:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
telegram:
|
|
118
|
+
proxy_url: "socks5://user:pass@host:1080" # or http://host:8080, socks4://host, ...
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Leave it unset (or remove the line) to connect directly.
|
|
122
|
+
|
|
123
|
+
Defaults applied to new supergroups live under `telegram.defaults`:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
telegram:
|
|
127
|
+
defaults:
|
|
128
|
+
enable_topics: true
|
|
129
|
+
create_invite_link: true
|
|
130
|
+
topics_layout: "list" # "list" | "tabs" — applied after groups create
|
|
131
|
+
default_member_permissions:
|
|
132
|
+
create_topics: true # let ordinary members create forum topics
|
|
133
|
+
pin_messages: true # let ordinary members pin messages
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`topics_layout` controls how the forum opens after `groups create`: `"list"` shows topics as a vertical list (Telegram's default), `"tabs"` shows them as horizontal tabs. The CLI `groups create --topics-layout` and `groups set-layout --layout` flags, and the `POST /telegram/groups` / `POST /telegram/groups/layout` bodies (`topics_layout`), override the default per call.
|
|
137
|
+
|
|
138
|
+
`default_member_permissions` sets the new group's default banned rights so ordinary members can `create_topics` and `pin_messages`. Other default rights are left untouched.
|
|
139
|
+
|
|
140
|
+
### Idempotency anchor
|
|
141
|
+
|
|
142
|
+
Group/topic creation is idempotent on a generic `external_ref` (CLI `--external-ref`, HTTP `external_ref`). For backward compatibility the CLI `--planfix-task-id` flag and the HTTP `planfix_task_id` field are accepted as aliases that map onto `external_ref`. With no `external_ref`, groups key on the exact title and topics key on `chat_id + topic_name`.
|
|
143
|
+
|
|
144
|
+
### Planfix plugin (optional, off by default)
|
|
145
|
+
|
|
146
|
+
Planfix-specific behavior lives behind an opt-in plugin. With it disabled the core has **zero Planfix knowledge** — `external_ref` still anchors idempotency, but there is no `/task <id>` service message, no `@planfix_bot` welcome cleanup, and `@planfix_bot` is not treated as a protected account. Enable it under `plugins`:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
plugins:
|
|
150
|
+
planfix:
|
|
151
|
+
enabled: true # turn on Planfix-specific behavior
|
|
152
|
+
bot_username: "@planfix_bot" # group member that receives the /task command
|
|
153
|
+
group_title_postfix: "" # appended to the Telegram chat title at creation
|
|
154
|
+
cleanup_messages: false # delete welcome / /task / bot-reply after creation (opt-in)
|
|
155
|
+
task_reply_wait_seconds: 5 # how long to poll for the bot's /task reply
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
When enabled and `external_ref` is set on a group whose members include `bot_username`, the plugin sends `/task <external_ref>` after creation. `group_title_postfix` is appended to the Telegram chat title at creation time but deliberately kept out of the idempotency key, so a replay of the same `external_ref` still matches on the raw title. `cleanup_messages` (default `false`) deletes the bot's welcome message, the `/task <id>` command, and the bot's reply to it; `task_reply_wait_seconds` is how long to poll for that reply before deleting only the welcome + command. All cleanup is best-effort: failures are recorded in the operation's `skipped` list and never fail the create.
|
|
159
|
+
|
|
160
|
+
See `docs/plans/20260518-telegram-assistant-mvp.md` for the full configuration schema and feature scope.
|
|
161
|
+
|
|
162
|
+
## Docker
|
|
163
|
+
|
|
164
|
+
The service ships as a slim Python 3.12 image. Runtime state (Telethon session, SQLite database, `data/config.yml`, bearer token) lives in `/data`, which must be mounted as a volume — nothing sensitive is baked into the image.
|
|
165
|
+
|
|
166
|
+
Build and run with `docker compose`:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
mkdir -p data
|
|
170
|
+
cp path/to/your/config.yml data/config.yml # fill in api_id, api_hash, bearer_token, etc.
|
|
171
|
+
docker compose up -d
|
|
172
|
+
curl http://127.0.0.1:8085/health
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Run a one-shot CLI invocation against the same volume:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
docker compose run --rm telegram-assistant \
|
|
179
|
+
telegram-assistant health
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The `auth` CLI is interactive (it prompts for phone, code, and optional 2FA password), so run it with a TTY attached:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
docker compose run --rm -it telegram-assistant \
|
|
186
|
+
telegram-assistant auth
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The Telethon session is written to `/data` and persists across container restarts.
|
|
190
|
+
|
|
191
|
+
A self-contained smoke script lives at `scripts/docker-smoke.sh`. It builds the image, starts a throwaway container with a temporary `data/config.yml`, polls `GET /health` until it returns `200`, and tears everything down.
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
bash scripts/docker-smoke.sh
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Tests
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pytest
|
|
201
|
+
```
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# telegram-assistant
|
|
2
|
+
|
|
3
|
+
Telegram automation service for the Planfix ↔ Telegram integration.
|
|
4
|
+
|
|
5
|
+
Three interfaces share one domain layer:
|
|
6
|
+
|
|
7
|
+
- HTTP API (FastAPI) on port `8085` with bearer-token auth — primary entry point for Planfix and automations.
|
|
8
|
+
- CLI (`telegram-assistant`) — mirrors every HTTP endpoint plus admin commands (`auth`, `operations status`, `operations retry`).
|
|
9
|
+
- Worker/queue — performs Telegram operations with throttling and `FLOOD_WAIT` handling.
|
|
10
|
+
|
|
11
|
+
Runs on MTProto via Telethon under a technical Telegram user account.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
python3.12 -m venv .venv
|
|
17
|
+
source .venv/bin/activate
|
|
18
|
+
pip install -e ".[dev]"
|
|
19
|
+
|
|
20
|
+
# Place a config file at data/config.yml (see Configuration below)
|
|
21
|
+
telegram-assistant auth # interactive Telethon login
|
|
22
|
+
telegram-assistant health # show current health
|
|
23
|
+
uvicorn telegram_assistant.http_api.app:create_app --factory --port 8085
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
Every CLI subcommand maps 1:1 to an HTTP endpoint (except the admin-only commands `auth`, `operations status`, and `operations retry`). Run any command with `--help` for full flag documentation.
|
|
29
|
+
|
|
30
|
+
Top-level:
|
|
31
|
+
|
|
32
|
+
- `auth` — interactive Telethon login for the technical account.
|
|
33
|
+
- `health` — report service health (Telegram session, database, default folder).
|
|
34
|
+
- `version` — print the installed version.
|
|
35
|
+
|
|
36
|
+
`groups` — manage Telegram supergroups:
|
|
37
|
+
|
|
38
|
+
- `groups create` — create a Telegram supergroup for a Planfix client. Accepts `--topics-layout list|tabs` to pick the forum layout for this group (defaults to `telegram.defaults.topics_layout`).
|
|
39
|
+
- `groups set-layout` — set the topics layout (`list` vs `tabs`) for an existing forum chat.
|
|
40
|
+
- `groups get-layout` — read the current topics layout (`list` or `tabs`) for a forum chat.
|
|
41
|
+
|
|
42
|
+
`topics` — manage forum topics:
|
|
43
|
+
|
|
44
|
+
- `topics create` — create a single forum topic in an existing supergroup.
|
|
45
|
+
- `topics bulk-create` — bulk-create topics from a CSV or JSON file.
|
|
46
|
+
- `topics close` — close an existing forum topic (the topic and its history are kept).
|
|
47
|
+
|
|
48
|
+
`members` — manage group membership:
|
|
49
|
+
|
|
50
|
+
- `members bulk-add` — bulk-add members to an existing supergroup, optionally promoting to admin.
|
|
51
|
+
- `members bulk-remove` — bulk-remove members from a supergroup (kick or permanently ban).
|
|
52
|
+
|
|
53
|
+
`messages` — send messages and service commands:
|
|
54
|
+
|
|
55
|
+
- `messages send` — send a message or service command (targeted or folder-wide mass mode).
|
|
56
|
+
|
|
57
|
+
`folders` — inspect and manage chat folders:
|
|
58
|
+
|
|
59
|
+
- `folders inspect` — inspect a chat folder and list its chats.
|
|
60
|
+
- `folders add-chat` — move an existing chat into a folder.
|
|
61
|
+
|
|
62
|
+
`operations` — inspect and retry queued operations:
|
|
63
|
+
|
|
64
|
+
- `operations status` — show the status of an operation, including per-item summary.
|
|
65
|
+
- `operations retry` — reset a failed/`needs_review` operation (and its items) back to pending.
|
|
66
|
+
|
|
67
|
+
Updating this list: descriptions are sourced from each Typer command's docstring in `src/telegram_assistant/cli/main.py`. When you add or rename a command, update this section, `skills/telegram-assistant/SKILL.md`, and re-run `pytest tests/test_skill_inventory.py` — the inventory guard fails if the README/skill catalog drifts from the CLI.
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
Config is read from `data/config.yml` by default. The `data/` directory is excluded from version control and holds the Telethon session, SQLite database, and secrets.
|
|
72
|
+
|
|
73
|
+
If `./data/config.yml` is absent, the loader falls back to `~/.config/telegram-assistant/config.yml`. On a clean machine, running any CLI command without `--config` will create a template at that path with `REPLACE_ME` placeholders for `api_id`, `api_hash`, and `bearer_token` — fill them in and re-run.
|
|
74
|
+
|
|
75
|
+
To reach Telegram through a proxy, set `telegram.proxy_url` to a single URL — supported schemes are `socks5`, `socks4`, `http`, and `https`. Credentials and explicit ports are optional:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
telegram:
|
|
79
|
+
proxy_url: "socks5://user:pass@host:1080" # or http://host:8080, socks4://host, ...
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Leave it unset (or remove the line) to connect directly.
|
|
83
|
+
|
|
84
|
+
Defaults applied to new supergroups live under `telegram.defaults`:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
telegram:
|
|
88
|
+
defaults:
|
|
89
|
+
enable_topics: true
|
|
90
|
+
create_invite_link: true
|
|
91
|
+
topics_layout: "list" # "list" | "tabs" — applied after groups create
|
|
92
|
+
default_member_permissions:
|
|
93
|
+
create_topics: true # let ordinary members create forum topics
|
|
94
|
+
pin_messages: true # let ordinary members pin messages
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`topics_layout` controls how the forum opens after `groups create`: `"list"` shows topics as a vertical list (Telegram's default), `"tabs"` shows them as horizontal tabs. The CLI `groups create --topics-layout` and `groups set-layout --layout` flags, and the `POST /telegram/groups` / `POST /telegram/groups/layout` bodies (`topics_layout`), override the default per call.
|
|
98
|
+
|
|
99
|
+
`default_member_permissions` sets the new group's default banned rights so ordinary members can `create_topics` and `pin_messages`. Other default rights are left untouched.
|
|
100
|
+
|
|
101
|
+
### Idempotency anchor
|
|
102
|
+
|
|
103
|
+
Group/topic creation is idempotent on a generic `external_ref` (CLI `--external-ref`, HTTP `external_ref`). For backward compatibility the CLI `--planfix-task-id` flag and the HTTP `planfix_task_id` field are accepted as aliases that map onto `external_ref`. With no `external_ref`, groups key on the exact title and topics key on `chat_id + topic_name`.
|
|
104
|
+
|
|
105
|
+
### Planfix plugin (optional, off by default)
|
|
106
|
+
|
|
107
|
+
Planfix-specific behavior lives behind an opt-in plugin. With it disabled the core has **zero Planfix knowledge** — `external_ref` still anchors idempotency, but there is no `/task <id>` service message, no `@planfix_bot` welcome cleanup, and `@planfix_bot` is not treated as a protected account. Enable it under `plugins`:
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
plugins:
|
|
111
|
+
planfix:
|
|
112
|
+
enabled: true # turn on Planfix-specific behavior
|
|
113
|
+
bot_username: "@planfix_bot" # group member that receives the /task command
|
|
114
|
+
group_title_postfix: "" # appended to the Telegram chat title at creation
|
|
115
|
+
cleanup_messages: false # delete welcome / /task / bot-reply after creation (opt-in)
|
|
116
|
+
task_reply_wait_seconds: 5 # how long to poll for the bot's /task reply
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
When enabled and `external_ref` is set on a group whose members include `bot_username`, the plugin sends `/task <external_ref>` after creation. `group_title_postfix` is appended to the Telegram chat title at creation time but deliberately kept out of the idempotency key, so a replay of the same `external_ref` still matches on the raw title. `cleanup_messages` (default `false`) deletes the bot's welcome message, the `/task <id>` command, and the bot's reply to it; `task_reply_wait_seconds` is how long to poll for that reply before deleting only the welcome + command. All cleanup is best-effort: failures are recorded in the operation's `skipped` list and never fail the create.
|
|
120
|
+
|
|
121
|
+
See `docs/plans/20260518-telegram-assistant-mvp.md` for the full configuration schema and feature scope.
|
|
122
|
+
|
|
123
|
+
## Docker
|
|
124
|
+
|
|
125
|
+
The service ships as a slim Python 3.12 image. Runtime state (Telethon session, SQLite database, `data/config.yml`, bearer token) lives in `/data`, which must be mounted as a volume — nothing sensitive is baked into the image.
|
|
126
|
+
|
|
127
|
+
Build and run with `docker compose`:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
mkdir -p data
|
|
131
|
+
cp path/to/your/config.yml data/config.yml # fill in api_id, api_hash, bearer_token, etc.
|
|
132
|
+
docker compose up -d
|
|
133
|
+
curl http://127.0.0.1:8085/health
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Run a one-shot CLI invocation against the same volume:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
docker compose run --rm telegram-assistant \
|
|
140
|
+
telegram-assistant health
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The `auth` CLI is interactive (it prompts for phone, code, and optional 2FA password), so run it with a TTY attached:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
docker compose run --rm -it telegram-assistant \
|
|
147
|
+
telegram-assistant auth
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The Telethon session is written to `/data` and persists across container restarts.
|
|
151
|
+
|
|
152
|
+
A self-contained smoke script lives at `scripts/docker-smoke.sh`. It builds the image, starts a throwaway container with a temporary `data/config.yml`, polls `GET /health` until it returns `200`, and tears everything down.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
bash scripts/docker-smoke.sh
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Tests
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
pytest
|
|
162
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "telegram-assistant"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
description = "Telegram automation service (MTProto/Telethon) with an optional Planfix integration plugin."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Stanislav Popov", email = "popstas@gmail.com" }]
|
|
13
|
+
keywords = ["telegram", "telethon", "automation", "mtproto", "planfix"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Framework :: FastAPI",
|
|
17
|
+
"Topic :: Communications :: Chat",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"telethon>=1.36,<2.0",
|
|
23
|
+
"fastapi>=0.115,<1.0",
|
|
24
|
+
"uvicorn[standard]>=0.30,<1.0",
|
|
25
|
+
"pydantic>=2.7,<3.0",
|
|
26
|
+
"pyyaml>=6.0,<7.0",
|
|
27
|
+
"typer>=0.12,<1.0",
|
|
28
|
+
"structlog>=24.1,<25.0",
|
|
29
|
+
"SQLAlchemy>=2.0,<3.0",
|
|
30
|
+
"httpx>=0.27,<1.0",
|
|
31
|
+
"python-socks[asyncio]>=2.4,<3.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8.0,<9.0",
|
|
37
|
+
"pytest-asyncio>=0.23,<1.0",
|
|
38
|
+
"ruff>=0.5,<1.0",
|
|
39
|
+
"git-cliff>=2.0.0",
|
|
40
|
+
"pre-commit>=3.7,<5.0",
|
|
41
|
+
"build>=1.2,<2.0",
|
|
42
|
+
"twine>=5.0,<7.0",
|
|
43
|
+
"bump-my-version>=0.28,<1.0",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[project.scripts]
|
|
47
|
+
telegram-assistant = "telegram_assistant.cli.main:app"
|
|
48
|
+
|
|
49
|
+
[project.urls]
|
|
50
|
+
Homepage = "https://github.com/popstas/telegram-assistant"
|
|
51
|
+
Repository = "https://github.com/popstas/telegram-assistant"
|
|
52
|
+
"Bug Tracker" = "https://github.com/popstas/telegram-assistant/issues"
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.packages.find]
|
|
55
|
+
where = ["src"]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
asyncio_mode = "auto"
|
|
60
|
+
addopts = "-ra"
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
line-length = 100
|
|
64
|
+
target-version = "py312"
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = ["E", "F", "W", "I", "B", "UP"]
|
|
68
|
+
ignore = ["E501"]
|
|
69
|
+
|
|
70
|
+
# Version bumping: `bump-my-version bump patch|minor|major` updates both files,
|
|
71
|
+
# commits as `chore(release): vX.Y.Z`, and creates the `vX.Y.Z` tag. Pushing the
|
|
72
|
+
# tag triggers .github/workflows/release.yml (GitHub release + PyPI publish).
|
|
73
|
+
[tool.bumpversion]
|
|
74
|
+
current_version = "0.2.1"
|
|
75
|
+
allow_dirty = false
|
|
76
|
+
commit = true
|
|
77
|
+
message = "chore(release): v{new_version}"
|
|
78
|
+
tag = true
|
|
79
|
+
tag_name = "v{new_version}"
|
|
80
|
+
tag_message = "v{new_version}"
|
|
81
|
+
|
|
82
|
+
[[tool.bumpversion.files]]
|
|
83
|
+
filename = "pyproject.toml"
|
|
84
|
+
search = '''name = "telegram-assistant"
|
|
85
|
+
version = "{current_version}"'''
|
|
86
|
+
replace = '''name = "telegram-assistant"
|
|
87
|
+
version = "{new_version}"'''
|
|
88
|
+
|
|
89
|
+
[[tool.bumpversion.files]]
|
|
90
|
+
filename = "src/telegram_assistant/__init__.py"
|
|
91
|
+
search = '__version__ = "{current_version}"'
|
|
92
|
+
replace = '__version__ = "{new_version}"'
|