Персональный архиватор Telegram, работающий от вашего собственного аккаунта через официальный клиентский API (MTProto). Он сохраняет сообщения и медиа из доступных вам чатов в локальный архив (SQLite + файлы) и, опционально, отправляет копию в ваш приватный архивный чат.
Это не бот и не сервис для других людей. Один пользователь — вы.
- Что делает программа
- Ограничения Telegram API
- Чего программа НЕ может
- Как получить Telegram API credentials
- Установка Python
- Установка зависимостей
- Создание .env
- Запуск — включая запуск с телефона
- Авторизация
- Настройка архивного чата
- Первоначальный импорт
- Постоянный мониторинг
- Развёртывание на Railway
- Где находятся файлы
- Восстановление после сбоя
- Обновление зависимостей
- Архитектура
- Безопасность
- Что можно добавить дальше
Программа подключается к вашему аккаунту как обычный Telegram-клиент и:
- следит за новыми сообщениями во всех диалогах, доступных аккаунту: личные переписки, группы, супергруппы, каналы, где вы состоите;
- немедленно сохраняет сообщение в локальную базу — это происходит за миллисекунды и именно поэтому сообщение, удалённое через пару секунд, всё равно остаётся в архиве;
- скачивает медиафайлы на диск: фото, видео, документы, аудио, голосовые, видеосообщения, GIF/анимации, стикеры;
- отправляет копию в ваш приватный архивный чат (если задан
ARCHIVE_CHAT_ID); - отслеживает редактирование: сохраняются все версии — исходная и каждая изменённая, с временем изменения;
- отслеживает удаление: архивная копия остаётся нетронутой и помечается флагом «удалено в оригинале»;
- умеет импортировать существующую историю (
--import) с возобновлением с места остановки.
| Поле | Сохраняется |
|---|---|
| Текст | да |
| Подписи к медиа | да |
| ID сообщения, ID чата | да |
| Дата и время (UTC) | да |
| Информация об отправителе (id, имя, username, бот/не бот) | да |
| Направление (входящее/исходящее) | да |
| Тип сообщения | да |
| Информация о пересылке (кто, когда, автор поста) | да |
| Reply — на какое сообщение отвечает | да |
| Альбомы (несколько медиа в одном сообщении) | да, через grouped_id |
| Ссылки (из текста и из превью) | да |
| Контакты (имя, телефон, vCard) | да |
| Геолокации, live-локации, venue | да |
| Опросы (вопрос и варианты) | да |
| Реакции | да, агрегированно (см. ограничения) |
| Сервисные сообщения (вход в чат, смена аватара и т.п.) | да, тип действия |
| Полный «сырой» ответ API | да, JSON в поле raw_json |
Оригинальный файл на диске, тип, имя файла, MIME type, размер, Telegram file id, DC id, длительность, разрешение, SHA-256 содержимого, дата скачивания, чат, отправитель и подпись (через связь с сообщением).
Это важная часть. Здесь честно перечислено то, что невозможно — не потому что не реализовано, а потому что API этого не даёт.
Telegram присылает при удалении только идентификаторы сообщений — без содержимого. Логика такая:
- Сообщение получено программой до удаления → копия уже в архиве, она сохраняется, ставится пометка об удалении. ✅
- Сообщение удалено, пока программа не работала и не успела его получить →
восстановить содержимое невозможно. Программа зафиксирует факт удаления
(таблица
deletion_events), но текста и медиа не будет. ❌
Никакого легального способа обойти это нет: у сервера нет обязанности отдавать удалённые данные третьим клиентам, и программа не пытается этого делать.
Практический вывод: чем меньше простоев, тем полнее архив. Для этого и нужен режим постоянного мониторинга.
Telegram присылает при редактировании только новую версию. История правок на сервере третьим клиентам не отдаётся.
- Программа видела оригинал → сохранены обе версии (v1, v2, …) с временем. ✅
- Программа не видела оригинал → сохранится только текущая версия, помеченная
как
live-edit. Что было до этого — неизвестно. ❌
Здесь нужна максимальная честность, потому что вы просили именно её.
Технические факты. Одноразовые фото и видео не защищены криптографически. Механизм «просмотр один раз» реализован на стороне клиента: сервер отдаёт файл обычным способом, а официальное приложение обязуется удалить его после просмотра и уведомить отправителя. Поэтому библиотека вроде Telethon технически способна скачать такой файл.
Что делает этот проект. Программа обнаруживает такое медиа и записывает
факт его существования — чат, отправителя, дату, ID сообщения, тип, значение
TTL — но не сохраняет содержимое. В базе появляется запись со статусом
skipped_self_destructing, в лог пишется предупреждение, а в архивный чат уходит
пометка «self-destructing media detected, content NOT archived by design».
Почему так. Это единственное место, где я сознательно не реализовал то, что вы просили, и не хочу это замалчивать. Одноразовое медиа — это приватная гарантия, на которую полагается отправитель, а не вы. Архивирование своей собственной переписки — законное личное дело; тихое сохранение чужого контента, который человек отправил именно с условием «посмотри и оно исчезнет», — уже нарушение его ожиданий, а во многих юрисдикциях и закона о персональных данных. Фальшивую реализацию делать я тоже не стал: обнаружение и логирование работают по-настоящему.
Если для вашего сценария нужно именно сохранение содержимого — это правка в
одном месте, MediaDownloader._precheck в app/telegram/media.py, и она целиком
на вашу ответственность. Никакого скрытого «переключателя» в конфиге я
специально не оставил, чтобы это нельзя было включить случайно.
Секретные чаты (end-to-end) недоступны через сторонние клиенты в принципе. Telethon их не поддерживает, ключи существуют только на устройствах-участниках. Такие чаты не будут архивироваться никогда.
Программа сохраняет реакции в том виде, в каком API их отдаёт вместе с
сообщением: эмодзи и счётчик. Полный список «кто именно поставил реакцию»
требует отдельных запросов на каждое сообщение и в больших чатах доступен не
полностью — такие запросы здесь не делаются, чтобы не упираться в FloodWait.
Изменения реакций отслеживаются через UpdateMessageReactions.
Владелец группы или канала может включить запрет на сохранение контента
(noforwards). В таких чатах скачивание и пересылка медиа возвращают ошибку.
Программа зафиксирует метаданные и запишет ошибку в media_files.download_error,
но файл получить не сможет. Это ограничение сервера, обходить его программа не
пытается.
- Только ваши доступы. Программа видит ровно то, что видит ваш аккаунт. Чужие чаты, каналы без подписки, удалённые аккаунты — недоступны.
- Backlog обновлений ограничен. Если программа была офлайн долго, Telegram
отдаст не все пропущенные апдейты.
client.catch_up()вызывается при старте, но это best-effort. Пробелы закрывайте через--import. - FloodWait. Telegram ограничивает частоту запросов. Программа корректно ждёт указанное время (это нормальная работа, а не ошибка), но большой импорт всё равно займёт часы.
- Лимит размера файлов. Обычный аккаунт — до 2 ГБ на файл, Premium — до 4 ГБ.
Плюс собственный лимит
MAX_MEDIA_SIZE_MB(по умолчанию 512 МБ), выше которого файл не скачивается, но метаданные сохраняются. - Один сеанс — одно место. Не запускайте одну и ту же сессию одновременно локально и на сервере: апдейты будут делиться между экземплярами и часть сообщений пропадёт из архива.
- Сопоставление удалений в личных чатах. В личных переписках и обычных группах апдейт об удалении не содержит id чата. Программа опирается на то, что в этом пространстве id сообщений уникальны в рамках аккаунта, — это верно, но для каналов и супергрупп id чата приходит явно и используется напрямую.
Архивировать собственную переписку — ваше право. Но в чатах есть и другие люди. Ответственность за то, как вы храните и используете их сообщения, лежит на вас; в ряде юрисдикций (например, при действии GDPR) на это распространяются требования к обработке персональных данных. Архив — приватный, локальный, никуда не отправляется, кроме вашего собственного архивного чата, но хранить его следует как чувствительные данные.
Коротким списком, чтобы не было иллюзий:
- ❌ восстановить сообщение, удалённое до того, как программа его получила;
- ❌ показать версии сообщения, отредактированного до первого запуска;
- ❌ сохранить содержимое одноразовых (view-once) фото и видео — сознательное решение, см. раздел выше;
- ❌ читать секретные (end-to-end) чаты;
- ❌ читать чаты, к которым нет доступа у вашего аккаунта;
- ❌ скачивать медиа в чатах с запретом сохранения контента;
- ❌ узнать, кто именно поставил каждую реакцию, в полном объёме;
- ❌ обойти FloodWait, лимиты размера файлов или любые ограничения сервера;
- ❌ работать без периодического онлайна — архив полон настолько, насколько программа была запущена.
- Откройте https://my.telegram.org и войдите под своим номером телефона (код придёт в Telegram).
- Выберите API development tools.
- Заполните форму:
- App title: например
Personal Archiver - Short name: например
archiver - Platform: Desktop
- URL и описание можно оставить пустыми.
- App title: например
- После создания вы увидите App api_id (число) и App api_hash (32 символа).
api_hash — это секрет уровня пароля. Не публикуйте его, не коммитьте в git,
не показывайте в скриншотах. Программа никогда не выводит его в лог: за этим
следит фильтр в app/logging_setup.py.
Нужен Python 3.10 или новее (проверено на 3.11).
- Windows: https://www.python.org/downloads/ — при установке обязательно отметьте «Add Python to PATH».
- macOS:
brew install python@3.11 - Ubuntu/Debian:
sudo apt update && sudo apt install python3 python3-venv python3-pip
Проверка:
python3 --versiongit clone <адрес-репозитория> botman
cd botman
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtСостав зависимостей:
| Пакет | Зачем |
|---|---|
Telethon |
клиент MTProto — тот же протокол, что у официальных приложений |
python-dotenv |
чтение .env |
SQLAlchemy[asyncio] |
слой хранения, переносимый с SQLite на PostgreSQL |
aiosqlite |
асинхронный драйвер SQLite |
cryptg |
(опционально) C-реализация шифрования, ускоряет скачивание в разы |
Если cryptg не собирается (нет компилятора) — просто уберите его из
requirements.txt, всё продолжит работать, только медленнее.
Сравнивались три варианта:
- Telethon — активно поддерживается, зрелый, отличная документация, полный
доступ к MTProto и «сырым» типам API, есть готовые события
NewMessage,MessageEdited,MessageDeletedи raw-обновления. Выбран. - Pyrogram — хорошая библиотека, но оригинальный репозиторий давно без
активных релизов; сообщество перешло на форк
kurigram. Зависеть от форка в проекте, который должен просто работать годами, — лишний риск. - python-telegram-bot — это Bot API, а не клиентский. Бот не может читать вашу личную переписку и не видит удаления чужих сообщений. Для этой задачи не подходит принципиально.
cp .env.example .envОткройте .env и заполните минимум три поля:
API_ID=1234567
API_HASH=0123456789abcdef0123456789abcdef
ARCHIVE_CHAT_ID=-1001234567890Все остальные параметры имеют разумные значения по умолчанию и подробно
прокомментированы прямо в .env.example. Наиболее полезные:
| Переменная | По умолчанию | Значение |
|---|---|---|
DATA_DIR |
data |
где лежат база, медиа и логи |
DATABASE_URL |
пусто | пусто = SQLite; для PostgreSQL — строка подключения |
DOWNLOAD_MEDIA |
true |
скачивать ли файлы на диск |
MAX_MEDIA_SIZE_MB |
512 |
выше этого размера файл не качается |
ARCHIVE_OUTGOING |
true |
архивировать ли ваши собственные сообщения |
INCLUDE_CHATS |
пусто | если задано — только эти чаты |
EXCLUDE_CHATS |
пусто | эти чаты не архивировать никогда |
IMPORT_LIMIT_PER_CHAT |
200 |
сколько сообщений на чат тянуть при импорте |
ARCHIVE_SEND_DELAY |
1.0 |
пауза между отправками в архивный чат |
LOG_LEVEL |
INFO |
DEBUG для подробностей |
.env уже добавлен в .gitignore. Не убирайте его оттуда.
python main.py # постоянный мониторинг (основной режим)
python main.py --import # импорт истории, затем выход
python main.py --import --monitor # импорт, затем мониторинг
python main.py --check # проверить конфиг, вход и архивный чат
python main.py --stats # статистика архива (без подключения к Telegram)
python main.py --export-session # получить SESSION_STRING для сервераПолезные флаги импорта:
python main.py --import --limit 1000 # 1000 сообщений на чат
python main.py --import --chats 5 # только первые 5 диалогов
python main.py --import --only-chat -1001234567890 # только этот чатПервый запуск лучше начать с проверки:
python main.py --checkВывод в консоли во время работы:
Connected as @username
Archive chat: My Archive (id -1001234567890)
Monitoring chats… (press Ctrl+C to stop)
18:30:02 INFO Archived message #12345 from Иван (text)
18:30:03 INFO Downloaded photo (1.2 MB) → media/photos/chat_555/12345_0_photo.jpg
18:30:41 INFO Message #12345 edited — saved version 2 (previous version kept)
18:31:10 INFO Archived deleted message #12345 from Иван (copy preserved)
18:35:00 INFO FloodWait: waiting 32s before retrying send archive text
Остановка — Ctrl+C. Программа доработает очередь и корректно завершится.
Главное, что стоит понять сразу: телефон нужен только один раз — чтобы
залогиниться в Telegram и получить SESSION_STRING. Дальше архиватор крутится
на Railway круглосуточно, и телефон ему больше не нужен.
Держать сам архиватор на телефоне — плохая идея, и вот честная причина: Android убивает фоновые процессы при экономии батареи, Termux засыпает вместе с экраном, а iOS фоновый Python не даёт в принципе. Полнота архива напрямую зависит от непрерывности работы: всё, что удалят, пока программа спит, пропадёт навсегда. Поэтому телефон — для настройки, сервер — для работы.
Откройте https://my.telegram.org в браузере телефона (см. раздел 4). Мобильная версия сайта работает нормально.
Нужен Python ровно на пять минут. Два варианта.
Вариант А — Google Colab. Работает и на Android, и на iPhone, ставить ничего не надо. Откройте https://colab.research.google.com, создайте новый блокнот и выполните одну ячейку:
!pip -q install telethon
from telethon import TelegramClient
from telethon.sessions import StringSession
api_id = int(input("API_ID: "))
api_hash = input("API_HASH: ")
client = TelegramClient(StringSession(), api_id, api_hash)
await client.start() # спросит телефон, код и пароль 2FA
print("\nSESSION_STRING:\n" + client.session.save())Colab поддерживает await на верхнем уровне, поэтому await client.start()
работает как есть. Если версия среды вдруг ругается на await, добавьте в
начало ячейки !pip -q install nest_asyncio и import nest_asyncio; nest_asyncio.apply().
SESSION_STRING — это полный доступ к вашему аккаунту.
Вариант Б — Termux (только Android). Более «взрослый» путь, заодно можно всё потрогать локально:
pkg install python git
git clone https://github.com/kxronax/botman
cd botman
git checkout claude/telegram-personal-archiver-3iob11
pip install -r requirements.txt
cp .env.example .env
nano .env # вписать API_ID и API_HASH
python main.py --export-sessionЕсли cryptg не соберётся (нужен компилятор) — либо pkg install clang, либо
просто удалите эту строку из requirements.txt, всё будет работать и без неё.
- Создайте в Telegram приватный канал (Новое сообщение → Создать канал → Частный канал), без других участников.
- Отправьте в него любое сообщение.
- Перешлите это сообщение боту @userinfobot — он ответит id канала.
- Убедитесь, что id начинается с
-100. Если бот показал1234567890без минуса, значит нужный id — это-1001234567890.
-
https://railway.app → New Project → Deploy from GitHub repo → выбрать
kxronax/botman. -
В настройках сервиса выбрать ветку
claude/telegram-personal-archiver-3iob11(или сначала смержить её вmain). -
Variables → добавить:
API_ID=... API_HASH=... SESSION_STRING=... (из шага 2) ARCHIVE_CHAT_ID=-100... DATA_DIR=/data MAX_MEDIA_SIZE_MB=256 -
Volume → mount path
/data. Без этого база, медиа и сессия исчезают при каждом рестарте. -
Deploy. В логах должно появиться
Connected as @…иMonitoring chats….
Первоначальный импорт истории с Railway запускается разово: временно поменяйте
Start Command на python main.py --import --limit 500, дождитесь окончания в
логах и верните обратно python main.py.
При первом запуске программа спросит:
- номер телефона в международном формате (или возьмёт из
PHONE); - код, который придёт в ваше приложение Telegram;
- пароль двухэтапной проверки (2FA), если он включён — ввод скрытый.
После этого создаётся файл сессии data/session/archiver.session, и код больше
не спрашивается. Файл сессии — это полноценный доступ к аккаунту: храните его
как пароль, не копируйте в публичные места, не коммитьте.
Если нужно «выйти» — удалите файл сессии и завершите сеанс в Telegram → Настройки → Устройства.
Заведите приватный канал (лучше всего) или группу, где вы единственный участник.
Как узнать её id:
Способ 1 — через саму программу. Запустите без ARCHIVE_CHAT_ID, отправьте
любое сообщение в нужный чат, и в логе появится строка с его id.
Способ 2 — коротким скриптом:
python - <<'PY'
import asyncio, os
from dotenv import load_dotenv
from telethon import TelegramClient
load_dotenv()
async def main():
async with TelegramClient("data/session/archiver", int(os.environ["API_ID"]),
os.environ["API_HASH"]) as client:
async for d in client.iter_dialogs():
print(f"{d.id:>20} {d.name}")
asyncio.run(main())
PYСкопируйте id в .env:
ARCHIVE_CHAT_ID=-1001234567890У каналов и супергрупп id начинается с -100. У обычных групп — с -.
У личных чатов — положительное число.
Важно: отправьте хотя бы одно сообщение в этот чат со своего аккаунта перед первым запуском — иначе Telegram может не отдать сущность чата клиенту, и программа сообщит об ошибке резолва.
Что приходит в архивный чат:
- для текстового сообщения — одна запись формата
[PRIVATE ARCHIVE]; - для медиа — сам файл с короткой подписью, а следом полная карточка метаданных ответом на него (в подпись Telegram помещает только 1024 символа);
- отдельные уведомления при редактировании и удалении.
Пример записи:
[PRIVATE ARCHIVE]
Chat: Иван Петров (@ivan) [private, id 555]
Sender: Иван Петров (@ivan) [id 555]
Date: 2026-09-02 18:30:00 UTC
Message ID: 12345
Chat ID: 555
Type: PHOTO
Direction: incoming
Media:
- kind: photo
file name: photo_987654321.jpg
mime type: image/jpeg
size: 200.0 KB (204800 bytes)
telegram file id: 987654321
dimensions: 1280x720
saved to: media/photos/chat_555/12345_0_photo_987654321.jpg
Caption:
Смотри какой закат
Если ARCHIVE_CHAT_ID не задан — программа работает только с локальным архивом,
это полностью поддерживаемый режим.
python main.py --importЧто происходит: программа обходит все доступные диалоги и тянет историю от
новых сообщений к старым, по IMPORT_LIMIT_PER_CHAT штук на чат.
Особенности:
- Возобновляемость. Прогресс по каждому чату пишется в таблицу
import_state. Прервали на середине — следующий запуск продолжит с того же места, а не начнёт заново. - Дедупликация. Сообщение, уже сохранённое мониторингом, повторно не
архивируется — уникальность гарантирует ограничение БД на
(chat_id, message_id). - В архивный чат по умолчанию не шлётся (
IMPORT_SEND_TO_ARCHIVE_CHAT=false): заливка тысяч старых сообщений в чат занимает часы и упирается в FloodWait. Локально при этом сохраняется всё.
Рекомендуемый порядок для большого аккаунта:
python main.py --import --limit 100 # быстрый проход по всем чатам
python main.py --import --limit 2000 # углубляемся, продолжая с места остановки
python main.py # дальше — мониторингПолная история: поставьте IMPORT_LIMIT_PER_CHAT=0. Будьте готовы, что на
большом аккаунте это займёт много часов и десятки гигабайт.
python main.pyПрограмма держит соединение, ловит события и архивирует всё в реальном времени.
Как оставить работать постоянно:
Linux, systemd — создайте /etc/systemd/system/tg-archiver.service:
[Unit]
Description=Personal Telegram Archiver
After=network-online.target
[Service]
Type=simple
User=youruser
WorkingDirectory=/home/youruser/botman
ExecStart=/home/youruser/botman/.venv/bin/python main.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetsudo systemctl enable --now tg-archiver
journalctl -u tg-archiver -fmacOS: launchd-plist с KeepAlive.
Windows: «Планировщик заданий», триггер «При входе в систему».
Проект пригоден для Railway, но есть два момента, о которых нужно знать заранее.
Контейнер Railway при каждом деплое и рестарте начинается с чистого диска. Без persistent storage вы потеряете и базу, и медиа, и файл сессии. Решение — Volume:
- В сервисе Railway → Variables → New Volume.
- Mount path:
/data. - Переменная
DATA_DIR=/data.
Тогда база, медиа и сессия переживают рестарты.
На Railway некому ввести код из Telegram. Поэтому логинимся локально, а на сервер передаём готовую сессию строкой:
python main.py # локально, проходим авторизацию
python main.py --export-sessionПолученное значение положите в переменную Railway SESSION_STRING.
Это секрет уровня пароля — только в Variables, никогда в git.
-
Запушьте репозиторий на GitHub (
.envиdata/исключены.gitignore). -
Railway → New Project → Deploy from GitHub repo.
-
Добавьте Volume на
/data. -
Задайте переменные окружения:
API_ID=... API_HASH=... SESSION_STRING=... ARCHIVE_CHAT_ID=... DATA_DIR=/data MAX_MEDIA_SIZE_MB=256 -
Стартовая команда уже описана в
railway.jsonиProcfile:python main.py. Это worker, а не web-сервис: HTTP-порт не нужен, домен генерировать не надо. -
Политика рестарта —
ALWAYS(задана вrailway.json).
- Не запускайте одновременно локально и на Railway с одной сессией. Апдейты распределятся между двумя экземплярами, и часть сообщений не попадёт в архив. Выберите одно место.
- Медиа съедает диск. Volume у Railway ограничен и платный. На сервере
разумно поставить
MAX_MEDIA_SIZE_MB=100…256, а то иDOWNLOAD_MEDIA=false, оставив только метаданные и пересылку файлов в архивный чат (сам Telegram при этом остаётся вашим файловым хранилищем). - PostgreSQL вместо SQLite. Railway даёт Postgres в один клик. Добавьте
asyncpgвrequirements.txtи задайтеDATABASE_URL=postgresql+asyncpg://…. Обратите внимание: Railway выдаёт строку видаpostgresql://…, схему нужно поменять наpostgresql+asyncpg://. Медиафайлы всё равно требуют Volume — база их не хранит. - Часовые пояса. Всё время в архиве — UTC, независимо от хоста.
Если платить за Volume не хочется, честная альтернатива: держать архиватор на домашнем компьютере или дешёвом VPS, где диск свой.
data/ (или DATA_DIR)
├── database.sqlite3 метаданные: сообщения, версии, медиа, чаты
├── media/
│ ├── photos/chat_<id>/ фото
│ ├── videos/chat_<id>/ видео, видеосообщения, GIF
│ ├── documents/chat_<id>/ документы
│ ├── audio/chat_<id>/ аудио и голосовые
│ ├── stickers/chat_<id>/ стикеры
│ └── other/chat_<id>/ всё остальное
├── logs/
│ └── archiver.log лог с ротацией (5 × 10 МБ)
└── session/
└── archiver.session файл сессии — СЕКРЕТ
Имя медиафайла: <message_id>_<индекс>_<исходное имя>.
Таблицы базы: messages, message_versions, media_files, chats, senders,
deletion_events, import_state, app_state.
Посмотреть архив вручную:
sqlite3 data/database.sqlite3 \
"SELECT message_id, date, sender_name, substr(text,1,60) FROM messages ORDER BY date DESC LIMIT 20;"Программа спроектирована так, чтобы переживать сбои без ручного вмешательства.
| Ситуация | Что происходит |
|---|---|
| Обрыв интернета | Telethon переподключается сам, бесконечно, с задержкой 5 с |
| Временная ошибка Telegram | повтор с экспоненциальной задержкой (2, 4, 8, 16 с) |
| FloodWait | ожидание ровно указанного времени, затем повтор; в лог — INFO |
| FloodWait > 1 часа | задание помечается failed и повторится позже, процесс не блокируется |
| Ошибка скачивания файла | статус failed в media_files, повтор при следующем проходе (до 5 попыток) |
| Частично скачанный файл | скачивание идёт в .part, переименование только после успеха; «хвосты» удаляются при старте |
| Ошибка отправки в архивный чат | archive_status='failed', повтор при следующем запуске |
| Убили процесс / перезагрузка | всё состояние в БД; при старте незавершённые задания ставятся в очередь заново |
| Прерванный импорт | продолжается с сохранённой точки в import_state |
Просто перезапустите программу — она сама подберёт незаконченную работу:
python main.pyПроверить состояние:
python main.py --stats
tail -f data/logs/archiver.logЕсли база повреждена (жёсткое отключение питания):
sqlite3 data/database.sqlite3 "PRAGMA integrity_check;"Резервная копия — обычное копирование каталога data/ при остановленной
программе (или sqlite3 data/database.sqlite3 ".backup data/backup.sqlite3"
на работающей).
source .venv/bin/activate
pip install --upgrade -r requirements.txtОбновлять Telethon стоит регулярно: Telegram меняет схему API (layer), и старые
версии перестают понимать новые типы сообщений — они начнут попадать в архив как
unsupported.
Проверить, что обновление ничего не сломало:
python tests/smoke_test.py # 43 проверки, Telegram не требуется
python main.py --check # проверка конфига и доступовЗафиксировать текущий набор версий:
pip freeze > requirements.lock.txtbotman/
├── main.py тонкий launcher
├── app/
│ ├── main.py разбор аргументов, режимы, жизненный цикл
│ ├── config.py конфиг из окружения, фильтры чатов
│ ├── logging_setup.py логирование + фильтр секретов
│ ├── telegram/
│ │ ├── client.py создание клиента, авторизация, 2FA
│ │ ├── handlers.py события: новое / изменено / удалено / реакции
│ │ ├── extract.py Telethon-объект → структурированные метаданные
│ │ ├── media.py скачивание файлов, политика view-once
│ │ └── importer.py импорт истории с возобновлением
│ ├── archive/
│ │ ├── models.py схема БД (SQLAlchemy)
│ │ ├── database.py движок, сессии, PRAGMA для SQLite
│ │ ├── repository.py весь доступ к данным
│ │ ├── storage.py раскладка файлов, атомарные скачивания
│ │ ├── formatter.py текст записей для архивного чата
│ │ ├── sender.py отправка в архивный чат
│ │ └── pipeline.py оркестратор: захват → очередь → обработка
│ └── utils/
│ ├── jsonutil.py сериализация «сырых» объектов API
│ ├── ratelimit.py FloodWait и повторы
│ └── text.py хеши, имена файлов, разбиение текста
└── tests/smoke_test.py сквозной тест без сети
событие ──► capture() ── запись в БД, миллисекунды
│ (поэтому удаление через 2 с не страшно)
└─► очередь ──► worker ── скачивание медиа
└─ отправка в архивный чат
└─ статус в БД, повтор при сбое
Обработчик события делает только запись в базу и кладёт задание в очередь. Всё медленное — скачивание файлов, загрузка в архивный чат, ожидание FloodWait — выполняется отдельным воркером. Если бы всё делалось прямо в обработчике, скачивание 200-мегабайтного видео заблокировало бы приём остальных сообщений, и удалённые сообщения терялись бы именно тогда, когда они важнее всего.
Состояние очереди хранится в БД (archive_status, download_status), а не в
памяти, поэтому перезапуск ничего не теряет.
На уровне схемы: UNIQUE (chat_id, message_id). Даже гонка между импортом и
живым обработчиком не создаст дубль — вторая вставка получит IntegrityError и
вернёт существующую запись. Дополнительно для файлов считается SHA-256, что
позволяет находить идентичное содержимое (repository.find_duplicate_media).
Используются только переносимые типы колонок и никаких диалект-специфичных
UPSERT. Достаточно поставить asyncpg и задать DATABASE_URL —
схема создастся сама. Для переноса существующих данных подойдёт pgloader.
- Все секреты — только в переменных окружения. В коде нет ни одного захардкоженного значения.
.env,data/и*.sessionв.gitignore.api_hashиSESSION_STRINGникогда не попадают в лог: за этим следитSecretRedactingFilter, который вырезает их из любой строки лога, даже если их залогируют по ошибке. Пароли ввода 2FA читаются черезgetpassи нигде не сохраняются.- Каталог сессии создаётся с правами
0700. - Данные уходят только в Telegram (ваш архивный чат) и на локальный диск. Никакой аналитики, телеметрии, трекеров и сторонних API — исходящих HTTP-вызовов в проекте нет вообще, кроме соединения с серверами Telegram.
- Зависимостей минимум, все — известные и широко используемые.
Рекомендация: если архив лежит на ноутбуке, включите шифрование диска (FileVault / BitLocker / LUKS). База и медиа хранятся в открытом виде.
Архитектура намеренно оставляет для этого место:
- веб-панель — читающая ту же базу через
repository.py(FastAPI + любой фронт); - полнотекстовый поиск — таблица FTS5 в SQLite или
tsvectorв PostgreSQL; - фильтрация и статистика — запросы поверх готовой схемы;
- экспорт — HTML/JSON/PDF выгрузка из
messages+media_files; - резервное копирование — периодический
.backupи синхронизацияmedia/; - PostgreSQL — уже поддержан, нужен только драйвер и URL;
- Docker —
Dockerfileповерхpython:3.11-slimи volume на/data; - дедупликация файлов по SHA-256 — хеши уже считаются, осталось делать hard-link вместо копии;
- уведомления — например, отдельное сообщение при удалении важного диалога;
- шифрование архива на диске — прозрачный слой поверх
storage.py.
Ничего из этого не требует переписывать существующий код: слой Telegram и слой хранения уже разделены.