Всем привет, начиная с прошлого лета я потихоньку клепал свою версию Telegram Wrapped.
Началось всё максимально тупо. Я увидел в TikTok рекламу очередного сервиса, который обещал красиво проанализировать мой Telegram. Проверять такое на основном аккаунте мне показалось сомнительной идеей, поэтому я зашёл туда с фейка, на котором было буквально три переписки.
В итоге фейк угнали.
Да, аккаунт с тремя чатами.
После этого я задумался: почему вообще ради красивой статистики человек должен отдавать кому‑то код входа, сессию Telegram или доступ ко всему аккаунту?
Так появилась идея сделать свой Wrapped, который не заходит в аккаунт и не отправляет переписки на чужой сервер.
Самое смешное, что сначала я всё равно пошёл по очевидному пути и сделал вход через Telegram. Технически это работало (с оговоркой того что api телеги очень капризный, выгрузка переписок могла идти 5 — 6 часов). Но потом до меня дошло, что для нормальной работы такой версии мне пришлось бы держать собственный VDS, через который проходили бы пользовательские сессии и переписки.
То есть после истории с угнанным фейком я буквально начал строить ровно тот сервис, которому сам не хотел бы доверять. Короче, эту идею я откинул очень быстро.
Потом я узнал, что Telegram Desktop умеет официально выгружать историю переписок в JSON или HTML. Без сторонних ботов, без передачи сессии и без подключения непонятного приложения к аккаунту. С этого момента проект нормально поехал. Плюс я учусь в вузе и за показ подобного проекта мне дали 10 баллов по предмету (МИРЭА, привет)
Как этим пользоваться
Сначала человек сам открывает Telegram Desktop и заходит в Настройки, затем в Продвинутые настройки и выбирает Экспорт данных из Telegram.
Там нужно выбрать личные чаты и машиночитаемый JSON. HTML тоже поддерживается, но JSON надёжнее сохраняет Telegram ID и направление сообщений.
После завершения экспорта пользователь открывает TGWR и выбирает получившуюся папку. Дальше приложение локально читает архив, отделяет личные диалоги от групп, каналов и служебных событий, создаёт SQLite‑базу, предлагает подходящий год и собирает Wrapped.
Никакого логина через Telegram нет. Приложение не просит номер телефона, пароль, код подтверждения, токен или файл сессии.
Что получилось
Сейчас TGWR умеет собрать статистику за выбранный год или за весь архив.
Он показывает общее количество сообщений, сколько было отправлено и получено, самый активный месяц, час пик, долю ночных сообщений, частые слова и эмодзи, фотографии, видео, голосовые, стикеры и файлы, самые длинные сообщения, стрики общения и паузы.
Есть отдельные таблицы с топами по объёму сообщений, длительности общения и взаимности. Есть раздел Люди, где можно открыть конкретного собеседника и посмотреть активные дни, месяцы, баланс, частые слова, медиа, часы общения и медианное время ответа.
Я не хотел делать психологический гороскоп по семи сообщениям и одному стикеру
Самая сложная часть проекта оказалась не в том, чтобы посчитать сообщения. Сложнее было не начать уверенно рассказывать человеку какую‑нибудь чушь на слабых данных.
Например, ранняя версия могла назвать победителем в категории Самая длинная пауза чат, в котором было два сообщения с разницей в несколько лет. Математически пауза действительно самая длинная. По смыслу результат бесполезный.
Поэтому сейчас для такой метрики рассматриваются только крупные диалоги минимум с 3000 сообщений. Для скорости ответа используются медианы и минимальное количество замеров. Более сложные выводы проверяют объём, активные дни, месяцы, баланс и устойчивость сигнала.
Если никто не прошёл пороги качества, приложение не выдумывает победителя, а прямо пишет, что данных недостаточно.
Итоговый Wrapped тоже стал динамическим. Приложение не заполняет историю пустыми экранами ради красивого числа. Оно выбирает только подходящие для конкретного архива слайды. В насыщенном отчёте получается до 14 экранов истории и отдельный финальный Credits, то есть максимум 15 экранов.
LONGEST_SILENCE_MIN_MESSAGES = 3000def _longest_silence_gap( conn: sqlite3.Connection, start_ts: int, end_ts: int, minimum_messages_required: int = LONGEST_SILENCE_MIN_MESSAGES,) -> Dict[str, Any]: chat_message_counts: Dict[int, int] = {} for row in conn.execute(count_q, params): chat_pk = int(row[0]) message_count = int(row[1] or 0) if chat_pk in banned_pks or message_count < minimum_messages_required: continue chat_message_counts[chat_pk] = message_count
Это небольшой реальный фрагмент проверки для самой длинной паузы. Маленькие заброшенные чаты отсекаются до выбора победителя.
В разделе Люди остаётся более подробный слой аналитики. Там сейчас 14 типов сигналов: главный человек, стабильный диалог, камбэк, рост и затухание общения, дневной и ночной собеседник, плотные сессии, ритм ответов, взаимность, инициирование контакта, возвращение разговора после паузы и медиа‑связь.
Это не диагнозы и не выдуманные выводы, о которых невозможно молчать с семантическими ловушками. Это объяснимые эвристики, рядом с которыми показываются объём выборки и фактические доказательства.
Что здесь означает «полностью локально»
TGWR состоит из Electron и React для интерфейса, Python‑модуля для импорта и расчётов и SQLite для локального хранения сообщений.
В установленной версии Python‑модуль уже собран в отдельный бинарник и лежит внутри приложения. Пользователю не нужно отдельно ставить Python, Node.js или запускать команды из терминала.
Electron запускает этот модуль как обычный локальный процесс и общается с ним через stdin и stdout. Переписки для анализа не отправляются на HTTP‑сервер. В проекте нет удалённой аналитики, загрузки архива или телеметрии сообщений.
if (app.isPackaged) { const executableName = process.platform === 'win32' ? 'tgwr-worker.exe' : 'tgwr-worker' const bundledPath = join( process.resourcesPath, 'worker-bin', `${process.platform}-${process.arch}`, executableName ) candidates.push({ command: bundledPath, args: [], label: 'bundled', cwd: dirname(bundledPath) })}const proc = spawn(candidate.command, candidate.args, { cwd: candidate.cwd, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true})
Нормализованные сообщения хранятся в tgwr.db в стандартном локальном каталоге приложения. Готовый отчёт лежит рядом в report.json.
При этом локально не означает магически неуязвимо. Если кто‑то уже имеет доступ к разблокированной учётной записи компьютера или на устройстве находится вредоносная программа, TGWR не сможет защитить от неё локальные файлы.
То есть я не хочу продавать обещание невозможной безопасности. Граница проще: приложение не просит доступ к аккаунту и не отправляет архив на чужую инфраструктуру.
Что произойдёт, если импорт сломается
Telegram Export считается недоверенным входом. Он может оказаться неполным, повреждённым или просто огромным.
Поэтому новый импорт не пишется сразу поверх рабочей базы. Сначала приложение создаёт временную базу с окончанием importing. Только после полного успешного разбора она атомарно заменяет предыдущую базу.
Если пользователь отменил импорт или парсер встретил ошибку, временные файлы удаляются, а предыдущий нормальный Wrapped остаётся на месте.
def import_staging_db_path(db_path: str) -> str: return db_path + ".importing"def promote_imported_database(staging_db_path: str, db_path: str) -> None: ensure_removed(db_path + "-wal") ensure_removed(db_path + "-shm") os.replace(staging_db_path, db_path)def do_import(export_dir: str, mode: str, db_path: str) -> None: staging_db_path = import_staging_db_path(db_path) remove_sqlite_artifacts(staging_db_path) conn = recreate_db(staging_db_path) # Telegram JSON and HTML are parsed into the staging database here. conn.execute("PRAGMA wal_checkpoint(TRUNCATE);") conn.close() promote_imported_database(staging_db_path, db_path)
Как не выложить переписку вместе с красивой картинкой
В какой‑то момент я понял ещё одну вещь. Даже если приложение само ничего никуда не отправляет, пользователь может сохранить красивый слайд и случайно опубликовать имя человека, цитату сообщения или точную дату.
Поэтому перед экспортом TGWR показывает Share Preview. Это не декоративный макет, а точный предварительный просмотр тех данных, которые попадут в PNG или PDF.
Имена можно заменить последовательными псевдонимами. Текстовые фрагменты можно скрыть. Точные даты можно убрать отдельно. Все три защиты по умолчанию включены.
Оригинальный локальный report.json при этом не переписывается. Очищается только копия, подготовленная для публикации.
Что с платформами и установкой
Сейчас публичная версия проекта 0.2.1 распространяется как pre‑release через GitHub Releases.
Есть установщик для Windows x64, DMG для macOS Intel и Apple Silicon, а также AppImage, DEB и RPM для Linux x64.
Windows ARM, Linux ARM, мобильные системы и BSD пока не поддерживаются. Интерфейс сейчас русскоязычный, а календарные метрики считаются по московскому времени UTC+3. Группы и каналы не участвуют в персональных рейтингах.
Windows SmartScreen и macOS Gatekeeper могут показать предупреждение о неизвестном издателе, потому что текущие сборки не подписаны коммерческими сертификатами. Поэтому скачивать приложение стоит только со страницы Releases в официальном репозитории и при желании сверять SHA-256.
Зачем я вообще это продолжаю делать
За это время проект прошёл путь от идеи залогинить пользователя через Telegram до отдельного локального приложения с безопасным импортом, базой, метриками, предпросмотром публикации, очисткой данных, тестами и нативными сборками под три системы.
Я не хочу делать ещё один сервис, который говорит: доверьте нам все свои переписки, мы обязательно удалим их потом.
Хочется сделать штуку, которой можно пользоваться без необходимости кому‑то верить. Скачал открытый код или готовую сборку, при желании отключил интернет, выбрал собственный экспорт и получил результат у себя на компьютере.
Буду рад обратной связи, особенно если у вас большой, старый или какой‑нибудь странный Telegram Export.
Интересно понять, где импорт ломается, какие метрики реально хочется видеть и что в интерфейсе пока непонятно обычному человеку. Ну и если кто‑то захочет посмотреть код, поспорить с формулами или помочь с платформами, буду только рад.
Репозиторий и готовые сборки:https://github.com/iwannasome/TGWRDSKTP
Спасибо за внимание <3
ссылка на оригинал статьи https://habr.com/ru/articles/1061370/