Skip to content

Commit 496a9fe

Browse files
authored
Merge pull request #1 from lexfrei/refactor/code-quality-overhaul
refactor: type-safe rewrite with tests, packaging, container and CI
2 parents 1639eb6 + 2bbc092 commit 496a9fe

13 files changed

Lines changed: 1614 additions & 240 deletions

File tree

.containerignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.git
2+
.github
3+
.venv
4+
__pycache__/
5+
*.py[cod]
6+
*.session
7+
*.session-journal
8+
.env
9+
tests/
10+
.mypy_cache/
11+
.pytest_cache/
12+
.ruff_cache/
13+
.coverage
14+
htmlcov/
15+
README*.md
16+
Containerfile
17+
.containerignore
18+
.gitignore

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ API_ID=12345678
55
API_HASH=1a2b3c4d5e6f1239983abcdef
66
MUTE_TIMEOUT=3600
77
SUMMARY_PREFIX="I've put together your %d messages:\n"
8+
# Optional. Session name or path; Telethon stores auth at $SESSION.session.
9+
# Leave unset to use the default ("userbot"; the container image uses /data/userbot).
10+
# SESSION=userbot

.github/workflows/ci.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
check:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Install uv
18+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
19+
with:
20+
enable-cache: true
21+
22+
- name: Install dependencies
23+
run: uv sync --locked
24+
25+
- name: Lint
26+
run: uv run ruff check .
27+
28+
- name: Format
29+
run: uv run ruff format --check .
30+
31+
- name: Type-check
32+
run: uv run mypy bot.py
33+
34+
- name: Test
35+
run: uv run pytest

.gitignore

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
# Telethon session
1+
# Telethon session files (contain auth secrets — never commit)
22
*.session
33
*.session-journal
44

5-
# Environment
5+
# Local environment
66
.env
77

88
# Python
99
__pycache__/
10-
*.pyc
11-
*.pyo
10+
*.py[cod]
11+
.venv/
12+
13+
# Tooling caches
14+
.mypy_cache/
15+
.pytest_cache/
16+
.ruff_cache/
17+
.coverage
18+
htmlcov/

Containerfile

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# syntax=docker/dockerfile:1
2+
3+
# Build stage: resolve and install dependencies into a virtual environment.
4+
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS builder
5+
6+
ENV UV_COMPILE_BYTECODE=1 \
7+
UV_LINK_MODE=copy \
8+
UV_PYTHON_DOWNLOADS=0
9+
10+
WORKDIR /app
11+
12+
# Install dependencies from the lockfile in a cached, reproducible layer.
13+
# The project itself is not a package (tool.uv.package = false), so only
14+
# third-party dependencies land in the virtual environment.
15+
RUN --mount=type=cache,target=/root/.cache/uv \
16+
--mount=type=bind,source=uv.lock,target=uv.lock \
17+
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
18+
uv sync --locked --no-dev
19+
20+
COPY bot.py /app/bot.py
21+
22+
# Runtime stage: a slim image without uv. The interpreter path must match the
23+
# builder image, hence the same python:3.13 base.
24+
FROM python:3.13-slim-bookworm
25+
26+
RUN groupadd --system --gid 999 nonroot \
27+
&& useradd --system --gid 999 --uid 999 --create-home nonroot
28+
29+
COPY --from=builder --chown=nonroot:nonroot /app /app
30+
31+
# Put the virtual environment's executables first on PATH.
32+
ENV PATH="/app/.venv/bin:$PATH" \
33+
PYTHONUNBUFFERED=1 \
34+
SESSION=/data/userbot
35+
36+
# Telethon persists its session at $SESSION.session; mount a volume on /data to
37+
# keep authentication across restarts.
38+
RUN install --directory --owner=nonroot --group=nonroot /data
39+
VOLUME ["/data"]
40+
41+
USER nonroot
42+
WORKDIR /app
43+
44+
ENTRYPOINT ["python", "bot.py"]

README.md

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,89 @@
11
# Telegram Abridger Userbot
22

3-
[English version](README_en.md)
3+
**English** · [中文](README.zh-CN.md) · [Русский](README.ru.md)
44

5-
## Что делает бот
6-
7-
Юзербот входит в Telegram от имени пользователя и следит за входящими сообщениями во всех чатах, которые не заглушены и не находятся в архиве (чаты с topics игнорируются).
8-
9-
Для каждого отправителя ведётся скользящее окно `COOLDOWN_INTERVAL` секунд:
10-
11-
- Все входящие сообщения автоматически отмечаются прочитанными и накапливаются в буфер
12-
- Когда окно истекает, буфер склеивается через `MESSAGE_CONCAT_STRING` и отправляется в тот же чат без уведомления (silently); если задан `SUMMARY_PREFIX`, он добавляется в начало (`%d` → число сообщений)
13-
- Если отправитель превышает `MESSAGE_FREQUENCY_LIMIT` сообщений за окно, он сразу заглушается на `MUTE_TIMEOUT` секунд, а накопленный буфер отправляется досрочно
14-
- Если за `COOLDOWN_INTERVAL` в том же чате были исходящие сообщения (т.е. пользователь сам отвечал), суммари не отправляется — это нормальный ход разговора
5+
A self-account (MTProto) userbot that watches incoming messages, buffers them per sender over a sliding window, and—when a sender floods the window—joins the buffered messages into one silent summary and mutes the sender's notifications. If you reply in the chat yourself, the summary is suppressed: the conversation is clearly active.
156

167
## Requirements
178

18-
- Python 3.10+
19-
- `telethon`
9+
- Python 3.11+
10+
- [uv](https://docs.astral.sh/uv/)
2011
- Telegram API credentials (`API_ID`, `API_HASH`)
2112

22-
### Получение API_ID и API_HASH
13+
### Getting API_ID and API_HASH
2314

24-
1. Открыть https://my.telegram.org и войти по номеру телефона
25-
2. Перейти в **API development tools**
26-
3. Создать приложение (название и платформа — любые)
27-
4. Скопировать `App api_id``API_ID` и `App api_hash``API_HASH`
15+
1. Go to https://my.telegram.org and sign in with your phone number
16+
2. Navigate to **API development tools**
17+
3. Create an application (name and platform can be anything)
18+
4. Copy `App api_id``API_ID` and `App api_hash``API_HASH`
2819

29-
### Config (env vars or `.env`)
20+
### Configuration (environment variables or `.env`)
3021

3122
| Variable | Default | Description |
32-
|---|---|---|
33-
| `API_ID` || Telegram API ID |
34-
| `API_HASH` || Telegram API Hash |
35-
| `COOLDOWN_INTERVAL` | `300` | Окно наблюдения в секундах |
36-
| `MESSAGE_FREQUENCY_LIMIT` | `5` | Макс. сообщений за окно до mute |
37-
| `MESSAGE_CONCAT_STRING` | `, ` | Разделитель при склейке сообщений |
38-
| `MUTE_TIMEOUT` | `3600` | Длительность mute в секундах |
39-
| `SUMMARY_PREFIX` | _(нет)_ | Префикс перед склеенным сообщением; `%d` заменяется числом сообщений (например, `"Собрано %d сообщений:\n"`) |
23+
| --- | --- | --- |
24+
| `API_ID` || Telegram API ID (required) |
25+
| `API_HASH` || Telegram API hash (required) |
26+
| `COOLDOWN_INTERVAL` | `300` | Observation window in seconds |
27+
| `MESSAGE_FREQUENCY_LIMIT` | `5` | Max messages per window before muting |
28+
| `MESSAGE_CONCAT_STRING` | `, ` | Delimiter used to join buffered messages |
29+
| `MUTE_TIMEOUT` | `3600` | Mute duration in seconds |
30+
| `SUMMARY_PREFIX` | _(none)_ | Optional prefix prepended to the summary; `%d` is replaced with the message count (e.g. `"I've put together your %d messages:\n"`) |
31+
| `SESSION` | `userbot` | Telethon session name or path; auth is stored at `$SESSION.session` |
4032

4133
## Usage
4234

4335
```bash
44-
cp .env.example .env # заполни API_ID и API_HASH
36+
cp .env.example .env # fill in API_ID and API_HASH
4537
uv run bot.py
4638
```
4739

48-
`uv` сам установит зависимости из inline-метаданных скрипта (PEP 723) в изолированное окружение.
40+
`uv run` resolves and installs the dependencies from `pyproject.toml`/`uv.lock` into an isolated environment automatically — no manual setup is needed.
41+
42+
On first run, Telethon will prompt for your phone number and a confirmation code, then write a `*.session` file. That file holds your authentication secret — keep it private and never commit it.
43+
44+
## Running in a container
45+
46+
The image builds dependencies in a `uv` stage and ships a slim, non-root runtime. The session is persisted on the `/data` volume so authentication survives restarts.
47+
48+
```bash
49+
# Build
50+
docker build --tag telegram-abridger --file Containerfile .
51+
52+
# First run: authenticate interactively, persisting the session to a named volume
53+
docker run --interactive --tty \
54+
--env-file .env \
55+
--volume abridger-session:/data \
56+
telegram-abridger
57+
58+
# Subsequent runs (already authenticated)
59+
docker run --detach --restart unless-stopped \
60+
--env-file .env \
61+
--volume abridger-session:/data \
62+
telegram-abridger
63+
```
64+
65+
The image sets `SESSION=/data/userbot`; leave `SESSION` unset in your `.env` so the session lands on the volume.
66+
67+
## How it works
68+
69+
The userbot monitors incoming messages from non-muted, non-archived chats (forum/topics chats are excluded). For each sender it maintains a sliding window of `COOLDOWN_INTERVAL` seconds:
4970

50-
При первом запуске Telethon запросит номер телефона и код подтверждения.
71+
- Incoming messages are marked as read and added to a per-sender buffer
72+
- If a sender exceeds `MESSAGE_FREQUENCY_LIMIT` messages within the window, they are muted for `MUTE_TIMEOUT` seconds via Telegram's notification settings and their buffer is flushed immediately
73+
- Buffered messages are concatenated with `MESSAGE_CONCAT_STRING` and sent silently in the same chat; if `SUMMARY_PREFIX` is set, it is prepended to the summary (`%d` → message count)
74+
- Buffers are also flushed periodically once a sender's window goes quiet
75+
- If you sent any message in the same chat within `COOLDOWN_INTERVAL`, the summary is suppressed — the conversation is already active
76+
77+
## Development
78+
79+
```bash
80+
uv sync # install runtime + dev dependencies
81+
uv run pytest # run the test suite
82+
uv run ruff check . && uv run ruff format --check .
83+
uv run mypy bot.py
84+
```
5185

52-
## Output / Result Files
86+
## Output / result files
5387

54-
- `userbot.session`файл сессии Telethon (не коммитить)
55-
- Логи выводятся в stdout
88+
- `$SESSION.session` (default `userbot.session`)Telethon session file (do not commit)
89+
- Logs go to stdout

README.ru.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Telegram Abridger Userbot
2+
3+
[English](README.md) · [中文](README.zh-CN.md) · **Русский**
4+
5+
Юзербот (MTProto, от имени аккаунта), который следит за входящими сообщениями, копит их по отправителю в скользящем окне и — когда отправитель заваливает окно — склеивает накопленные сообщения в одно беззвучное саммари и заглушает уведомления от него. Если ты сам ответил в этом чате, саммари не отправляется: разговор очевидно идёт.
6+
7+
## Требования
8+
9+
- Python 3.11+
10+
- [uv](https://docs.astral.sh/uv/)
11+
- Учётные данные Telegram API (`API_ID`, `API_HASH`)
12+
13+
### Получение API_ID и API_HASH
14+
15+
1. Открыть https://my.telegram.org и войти по номеру телефона
16+
2. Перейти в **API development tools**
17+
3. Создать приложение (название и платформа — любые)
18+
4. Скопировать `App api_id``API_ID` и `App api_hash``API_HASH`
19+
20+
### Конфигурация (переменные окружения или `.env`)
21+
22+
| Переменная | По умолчанию | Описание |
23+
| --- | --- | --- |
24+
| `API_ID` || Telegram API ID (обязательно) |
25+
| `API_HASH` || Telegram API hash (обязательно) |
26+
| `COOLDOWN_INTERVAL` | `300` | Окно наблюдения в секундах |
27+
| `MESSAGE_FREQUENCY_LIMIT` | `5` | Максимум сообщений за окно до mute |
28+
| `MESSAGE_CONCAT_STRING` | `, ` | Разделитель при склейке сообщений |
29+
| `MUTE_TIMEOUT` | `3600` | Длительность mute в секундах |
30+
| `SUMMARY_PREFIX` | _(нет)_ | Необязательный префикс перед саммари; `%d` заменяется числом сообщений (например, `"Собрано %d сообщений:\n"`) |
31+
| `SESSION` | `userbot` | Имя или путь сессии Telethon; авторизация хранится в `$SESSION.session` |
32+
33+
## Запуск
34+
35+
```bash
36+
cp .env.example .env # заполни API_ID и API_HASH
37+
uv run bot.py
38+
```
39+
40+
`uv run` сам разрешает и устанавливает зависимости из `pyproject.toml`/`uv.lock` в изолированное окружение — ручная настройка не нужна.
41+
42+
При первом запуске Telethon запросит номер телефона и код подтверждения, затем создаст файл `*.session`. Этот файл содержит секрет авторизации — храни его в тайне и никогда не коммить.
43+
44+
## Запуск в контейнере
45+
46+
Образ собирает зависимости в стадии `uv` и поставляется как тонкий non-root рантайм. Сессия хранится на томе `/data`, поэтому авторизация переживает перезапуски.
47+
48+
```bash
49+
# Сборка
50+
docker build --tag telegram-abridger --file Containerfile .
51+
52+
# Первый запуск: интерактивная авторизация с сохранением сессии в именованный том
53+
docker run --interactive --tty \
54+
--env-file .env \
55+
--volume abridger-session:/data \
56+
telegram-abridger
57+
58+
# Последующие запуски (авторизация уже выполнена)
59+
docker run --detach --restart unless-stopped \
60+
--env-file .env \
61+
--volume abridger-session:/data \
62+
telegram-abridger
63+
```
64+
65+
Образ задаёт `SESSION=/data/userbot`; не задавай `SESSION` в своём `.env`, чтобы сессия попадала на том.
66+
67+
## Как это работает
68+
69+
Юзербот отслеживает входящие сообщения из не заглушённых и не архивных чатов (чаты с форумами/топиками исключаются). Для каждого отправителя ведётся скользящее окно `COOLDOWN_INTERVAL` секунд:
70+
71+
- Входящие сообщения отмечаются прочитанными и добавляются в буфер по отправителю
72+
- Если отправитель превышает `MESSAGE_FREQUENCY_LIMIT` сообщений за окно, он заглушается на `MUTE_TIMEOUT` секунд через настройки уведомлений Telegram, а его буфер отправляется досрочно
73+
- Сообщения буфера склеиваются через `MESSAGE_CONCAT_STRING` и беззвучно отправляются в тот же чат; если задан `SUMMARY_PREFIX`, он добавляется в начало саммари (`%d` → число сообщений)
74+
- Буферы также периодически сбрасываются, когда окно отправителя затихает
75+
- Если ты отправил любое сообщение в этом же чате за `COOLDOWN_INTERVAL`, саммари подавляется — разговор уже активен
76+
77+
## Разработка
78+
79+
```bash
80+
uv sync # установка runtime- и dev-зависимостей
81+
uv run pytest # запуск тестов
82+
uv run ruff check . && uv run ruff format --check .
83+
uv run mypy bot.py
84+
```
85+
86+
## Выходные файлы
87+
88+
- `$SESSION.session` (по умолчанию `userbot.session`) — файл сессии Telethon (не коммитить)
89+
- Логи выводятся в stdout

0 commit comments

Comments
 (0)