Это вторая, техническая часть истории про TgDrive, приложение, которое превращает закрытую Telegram-группу в общий облачный диск команды. В первой части мы рассказали, как оно устроено на верхнем уровне и почему делимся чертежами, а не дистрибутивом. Здесь весь путь сборки: 6 фаз, от первого запроса к API до установщика, со схемами данных, константами и, главное, граблями, на которые мы уже наступили за вас. Кода в статье нет намеренно. Все решения описаны так, что по ним соберёт приложение и живой разработчик, и ИИ-ассистент, которому вы скормите этот текст.
Что мы строим и что понадобится
Windows-приложение в трее; общие папки команды, физически живущие в Telegram-группах; автосинхронизация в обе стороны; полная история версий; честные конфликты; «умная папка» с файлами по требованию. Что всё это даёт пользователю, подробно разобрано в первой статье. Здесь сосредоточимся на «как» и соберём своё облако для хранения файлов с нуля.
Понадобится:
- Windows 10 1903+. На ней живёт Cloud Files API, основа «умной папки»;
- .NET 8 SDK и любая IDE;
- аккаунт Telegram и личные ключи API
api_id/api_hashс my.telegram.org/apps (бесплатно, одна минута); - закрытая супергруппа, будущее хранилище (или создадите её из кода);
- на финал Inno Setup 6.3+ для установщика.
Стек, который мы выбрали и советуем (подробное «почему» есть в первой статье) C# / .NET 8 · WTelegramClient (MTProto) · SQLite · Cloud Files API (через P/Invoke-обёртку Vanara и WinRT StorageProviderSyncRootManager) · WPF с треем · xUnit · Inno Setup.
Решение №0, которое определяет всё (не Bot API)
| Bot API | MTProto (user-аккаунт) | |
|---|---|---|
| Загрузка файла | ~50 МБ | до 2 ГБ (4 ГБ с Premium) |
| Скачивание | ~20 МБ | без этого лимита |
| История чата | ограничена | полная |
| Кто действует | бот | сам пользователь, поэтому «кто залил» достаётся бесплатно |
Для файлового хранилища Bot API мёртв на старте. Работаем по MTProto от имени обычного аккаунта; в .NET это библиотека WTelegramClient. Бонус! она отдаёт прогресс загрузки каждые ~512 КБ, отсюда бесплатные индикаторы скорости.
Архитектура прежде кода
Полчаса на этот раздел сэкономят недели. Три конструкции держат весь проект.
Слои и один шов
Всё общение с Telegram прячется за интерфейсом IRemoteStorage. Это примерно десяток методов, проверка авторизации, «кто я», список и создание хранилищ, подписка на обновления, чтение и публикация манифеста, чтение id закреплённого сообщения, загрузка и скачивание чанка, удаление сообщений. Типы Telegram-библиотеки не выходят за пределы адаптера. Такой шов даёт два свойства, без которых проект не доехал бы: вся sync-логика тестируется без сети (облако подменяется фейком в памяти), а бэкенд в принципе заменяем.
Модель хранения
1 файл → M версий → каждая версия = N чанков (документов в группе) + запись в манифесте
- Чанк = 64 МиБ. Меньше нельзя, получите море сообщений и rate-лимиты. Больше тоже плохо, страдает докачка.
- Каждый чанк уходит document-сообщением с косметическим именем
{имя}.{номер:00000}.chunk. Связь «чанк ↔ файл ↔ версия» держит только манифест, а не имена сообщений. - У каждого чанка есть SHA-256 это и целостность, и дедупликация (одинаковое не грузится дважды ни внутри файла, ни между версиями), и докачка после обрыва.
- Каждое сохранение изменённого файла даёт новую версию, то есть новые сообщения. Старые не трогаем никогда! Telegram хранит их бесплатно, это и есть история версий.
- Вложенные папки живут просто как
pathв манифесте (docs/2026/report.pdf), отдельные чаты для них не нужны. Одна группа = один манифест = одно дерево.
Манифест единственный источник правды об облаке
Это JSON-файл, который сам лежит в группе, а закреплённое сообщение указывает на его актуальную версию.
{
"schema_version": 2,
"manifest_version": 42,
"generated_by": "device-uuid",
"generated_at": "2026-06-10T12:00:00Z",
"files": [
{
"path": "docs/report.pdf",
"current_version": 2,
"deleted": false,
"conflict_versions": [3],
"versions": [
{
"version": 1,
"uploaded_by_name": "Алиса (PC-1)",
"uploaded_by_id": "1234567",
"uploaded_at": "2026-06-10T09:00:00Z",
"sha256": "…",
"size": 123456789,
"chunks": [
{ "index": 0, "sha256": "…", "message_id": 1015, "size": 67108864 }
]
}
]
}
]
}
Железные правила манифеста!
- Никогда не редактируется на месте. Публикуется новый файл с
manifest_version + 1, затем перезакрепляется pin. Старые манифесты не удаляем! Это бесплатная история и защита от гонок. - Удаление файла записывается как
deleted: true(tombstone) история остаётся, восстановление сводится к снятию пометки. Без tombstones удалённые файлы «воскресают» с отставших машин. schema_versionзадаёт жёсткий контракт — клиент, встретивший схему новее своей, останавливается с просьбой обновиться. Молчаливое «почти работает» опаснее простоя. Старый клиент при своей публикации сотрёт незнакомые ему поля.
Локальное состояние в три снимка
Сравнение трёх снимков каждого пути.
| Снимок | Где живёт | Смысл |
|---|---|---|
| Local | файловая система | что на диске сейчас |
| Base | индекс SQLite | каким файл был при последней успешной синхронизации |
| Remote | манифест | что в облаке сейчас |
Сравнение Local с Base выявляет мои изменения, а Remote с Base чужие. Без Base невозможно отличить «я изменил» от «кто-то изменил», поэтому индекс появляется в первой же фазе синхронизации. Не откладывайте его.
Индексом служит SQLite-база на каждую подключённую папку. Таблица files: путь (ключ), размер, mtime, sha256, версия манифеста последней синхронизации, версия файла локальной копии. Таблица meta: id устройства, id чата, последняя виденная версия манифеста, версия схемы индекса, id последнего закреплённого сообщения. Изменения детектируются в два прохода: сначала дёшево по размеру и mtime, а SHA-256 считается только при расхождении.
Раскладка данных на машине (всё в профиле пользователя):
%LOCALAPPDATA%TgDrive
WTelegram.session — сессия Telegram (= пароль аккаунта!)
device_id — стабильный идентификатор машины
roots<chatId> — метаданные каждой папки:
config.json — привязка к чату, лимит кэша, режим (mirror / on_demand)
index.db — индекс SQLite
backup<ts> — копии, спасённые перед перезаписью при pull
access.json — времена доступа для LRU-эвикции
Дорожная карта (6 фаз)
Главное правило процесса — фазы не перепрыгиваются. Каждая заканчивается работающим сценарием и тестами, и только потом начинается следующая. Виртуальный диск подключается к уже оттестированному sync-ядру, а не наоборот. Второе правило! каждая облачная операция возобновляема и идемпотентна. Обрыв сети или процесса в любой точке безопасен при повторе.
Фаза 0. Telegram-ядро
Консольное приложение, которое умеет войти в аккаунт по номеру телефона (код приходит в Telegram; поддержка 2FA-пароля), разрезать файл на чанки по 64 МиБ, залить их в группу, собрать манифест с версиями, закрепить его и скачать файл любой версии обратно. CLI-команды: login / ls / upload / download / history.
Что заложить сразу:
- Считайте FLOOD_WAIT нормой, а не ошибкой. Сервер отвечает «подожди X секунд»? Ждём X плюс случайный джиттер и повторяем. Прочие сетевые ошибки лечатся пятью попытками с экспоненциальной задержкой. Чанки грузим последовательно, максимум 2 параллельно, агрессивный параллелизм гарантированно ловит лимиты и рискует аккаунтом.
- Сообщение могли удалить руками. При скачивании проверяем существование message_id и сверяем SHA-256; расхождение превращается во внятную ошибку.
- Session-файл и ключи API не попадают в репозиторий никогда (переменные окружения,
.gitignore; перед каждым коммитом проверяйте diff на секреты).
✅ Фаза готова, когда файл больше 64 МиБ уезжает в группу и скачивается обратно бит-в-бит; history показывает версии с авторами; обрыв загрузки на середине докачивается без повторной заливки готовых чанков.
Фаза 1. Папка-зеркало
Классическая синхронизируемая папка (как ранний Dropbox), пока от одного аккаунта. Появляются индекс SQLite с тремя снимками и операции init / status / push / pull / restore.
statusвычисляет diff и ничего не меняет. Любая операция начинается с него.push: скачать актуальный манифест → залить чанки изменённых файлов (только те SHA-256, которых ещё нет в облаке) → опубликовать манифест+1и перезакрепить pin → обновить Base.pullзатирает только файлы без локальных изменений. Для изменённых нужно явное подтверждение, и перед перезаписью локальная копия откладывается вbackup/{timestamp}/. Молча уничтожать работу пользователя нельзя никогда; это принцип, а не пожелание.- Конфликт (файл изменён и там, и там относительно Base) решается сохранением обеих сторон. Автослияния нет сознательно, файлы бинарные.
- В паре «удалил против изменил» побеждает изменение, файл воскресает. Потеря данных всегда хуже лишнего файла.
Здесь же начинается тестирование, которое довезёт проект до конца. Облако подменяется in-memory-фейком (IRemoteStorage это позволяет), фейк считает загрузки, и так проверяются дедупликация с докачкой. Минимальный набор сценариев: чистый push, чистый pull, конфликт, «удалил против изменил», обрыв push с повтором.
✅ Фаза готова, когда два каталога на одной машине сходятся через облако в любом порядке операций, а повторный push после обрыва не загружает ни одного лишнего чанка.
Фаза 2. Команда и гонки
Самая концептуально важная фаза! Несколько человек пишут в одно хранилище, а Telegram не даёт транзакций. Наивная реализация теряет данные. Вот как не потерять.
- Оптимистичная блокировка.
manifest_versionмонотонно растёт. Если при push облако оказалось впереди, срабатывает авто-pull + retry подмешиваем чужие изменения и повторяем, до 5 попыток. - Гонка публикаций. Два ПК могут опубликовать манифест с одним номером версии, и различить их можно только по id сообщения. Поэтому публикация возвращает message id; id текущего pin читается дёшево (из информации о чате, без скачивания JSON); перед публикацией pin id перечитывается (изменился? отмена и новый круг слияния); после публикации выдерживается пауза 1,5 с и делается контрольное чтение. Закреплён ли наш манифест? Если нет, гонку выиграл другой ПК, и наши изменения вливаются в его манифест повторным проходом. Это не серверная атомарность (её тут не существует), а сужение окна гонки до безопасного. Проигравший ничего не теряет, только повторяет.
- Чанки, залитые перед отменённой публикацией, становятся «сиротами». Они безвредны и копятся тихо; их будет подчищать отдельная сборка мусора.
- Право на pin. Закрепление сообщений в супергруппе требует прав администратора. Каждому пишущему участнику нужно право «Закреплять сообщения», а приложению нужна понятная ошибка об этом.
- В каждую версию пишется, кто и с какой машины её загрузил (имя из аккаунта плюс имя устройства). Потом это станет колонкой «кто изменил» в интерфейсе.
✅ Фаза готова, когда два клиента (в тестах два «ПК» с общим фейком облака) одновременно пушат один файл, и в истории оказываются обе версии; ни один сценарий гонки не теряет ни версии, ни файла.
Фаза 3. Приложение с треем
Логика готова, одеваем её в интерфейс. WPF-приложение, живущее в трее.
- Окна входа: телефон → код → 2FA-пароль. Список файлов со статусами; кнопки «Сохранить в облако» и «Получить из облака» с прогрессом; окно «Версии файла» с восстановлением любой версии; уведомления из трея.
- Тихое восстановление сессии. Эта грабля стоила нам многих итераций. Проверяйте сохранённую сессию запросом «кто я» (
Users_GetUsers(self)с таймаутом) живая сессия ответит, мёртвая бросит 401, и тогда показываем вход. Не пытайтесь «просто логиниться без номера» клиентская библиотека без номера телефона уходит в QR-flow, а повторный QR на уже привязанной сессии Telegram отвергает (AUTH_TOKEN_ALREADY_ACCEPTED).
✅ Фаза готова, когда: человек без консоли устанавливает соединение, пушит, пуллит и откатывает версию. Мышкой.
Фаза 4. Виртуальный диск, она же «умная папка»
Файлы видны все, а место занимают только нужные. На Windows это Cloud Files API (CfAPI), родной механизм OneDrive. Альтернатива в виде WinFsp даёт настоящую букву диска, но CfAPI даёт ровно тот UX «как у больших облаков» плейсхолдеры, значки статуса, нативные «Всегда сохранять» / «Освободить место». Букву диска при желании добавляет subst.
Папка регистрируется как sync root; файлы из манифеста создаются плейсхолдерами (имя и размер видны, места нет); при первом чтении Windows зовёт callback, мы качаем чанки и отдаём данные по мере скачивания; «освободить место» дегидратирует обратно.
Правила, нарушение которых означает сломанный Проводник или потерянные данные:
- Никакой сети синхронно из callback файловой системы. Данные отдаются из фоновой задачи по мере скачивания чанков, иначе заморозите Проводник всем пользователям.
- Запись не идёт в облако напрямую. Записанный файл остаётся обычным локальным, помечается «изменённым», дальше работает штатный push. FS-слой тонкий, логика в движке.
- LRU-кэш. Лимит локального содержимого (у нас 5 ГБ по умолчанию, настраивается); при переполнении дольше всех не открывавшиеся файлы дегидратируются до ~90 % лимита. Никогда не выгружаются закреплённые и изменённые-неотправленные. Времена доступа трекайте своим журналом, штатный last-access в Windows ненадёжен.
- Источником правды остаются локальные файлы и индекс, а не память FS-слоя, падение процесса не должно терять ничего.
- Тестируйте реальными программами. Офисные пакеты и редакторы с паттерном «rename + replace» и lock-файлами ломают наивные реализации. Большие медиа гидратируйте целиком при первом открытии, иначе видеоредактор устроит лавину мелких чтений по плейсхолдеру.
✅ Фаза готова, когда свежая папка показывает всё облако, не занимая места; двойной клик по любому файлу открывает его (с ожиданием скачивания); «освободить место» работает; заполненный кэш чистится сам, не трогая закреплённое и изменённое.
Фаза 5. Продукт
Превращаем инструмент в приложение для команды:
- Мульти-папки, несколько хранилищ = несколько групп, сайдбар в окне.
- Оркестратор автосинхронизации. Файловый вотчер с дебаунсом, push через 3 с тишины (плюс джиттер до 1,5 с, чтобы два ПК не столкнулись). Подписка на события Telegram даёт pull за секунды (с дебаунсом 2 с). Страховочный поллинг каждые 5 мин; ретраи от 15 с экспоненциально; уведомления «какой файл, где и кто».
- Эксклюзивная блокировка операций на папку, push, pull, монтирование и служебные перестановки не пересекаются. Иначе вотчер примет ваши же служебные действия за действия пользователя.
- Бейджи состояния и контекстное меню в Проводнике (per-user, через реестр HKCU; команды в уже запущенное приложение идут через именованный канал).
- Установщик: self-contained publish (не требует .NET на машине) плюс Inno Setup per-user, без прав администратора. И CfAPI-провайдер, и меню регистрируются per-user. Установщик не несёт ни сессий, ни ключей. Первая страница объясняет, как получить api_id/api_hash.
- К установщику приложите чек-лист живой проверки для теста на реальных ПК. Он окупится в первый же день.
✅ Фаза готова, когда человек не из IT ставит приложение по установщику, входит, подключает папку, и файлы ездят сами.
Фаза 5.1. То, что найдёт только живой тест
Мы прогнали всё на живых ПК командой и собрали урожай, который не находят юнит-тесты. Забирайте бесплатно:
- Отключение диска не равно выходу из приложения. Наш единственный инцидент с потерей данных. Если разрегистрировать sync root и оставить Base-записи, следующий скан увидит исчезнувшие плейсхолдеры как «пользователь удалил файлы» и разошлёт удаления всей команде. Правильно — при явном отключении гидратированные файлы остаются обычными файлами, а online-only плейсхолдеры удаляются вместе со своими Base-записями; при выходе из приложения только рвётся соединение, а регистрация и плейсхолдеры остаются до следующего запуска.
- Предохранитель массового удаления. Исчезновение дерева файлов чаще артефакт (отвалившийся том, переименованная папка), чем намерение. Push с удалениями больше 10 штук или больше 20 % папки проходит только с явным подтверждением списка.
- Регистрируйте sync root через WinRT (
StorageProviderSyncRootManager.Register), а не через низкоуровневыйCfRegisterSyncRoot. Только так появляются значки в Проводнике и нативные пункты меню, а антивирус, индексатор и панель предпросмотра начинают уважать плейсхолдеры, а не скачивать их. - «Файлы сами скачиваются»? Ищите виновника в собственном сканере. Хеширование «нового» файла читает плейсхолдер, а значит гидратирует его. Сканер никогда не читает файлы с recall-атрибутами; при монтировании Base плейсхолдеров пишется в индекс заранее.
- После подключения провайдера явно выставьте статус «IDLE», после отдачи данных «IN_SYNC». Иначе получите вечный значок синхронизации и серое «Освободить место».
- Изменённые файлы неприкосновенны! не конвертировать в плейсхолдеры, не дегидратировать, LRU пропускает, Base не перезаписывается.
- Файл не выгружается? Закройте панель предпросмотра Проводника, она держит файл.
- Конфликты храните как версии одного файла с пометкой в манифесте (наш формат v2, поле
conflict_versions), а разрешение отдайте людям. «оставить выбранную» или «разъехаться на два файла» (второе мгновенно, чанки переиспользуются, ничего не перезаливается). - Бамп схемы манифеста касается всей команды. Обновляйте все ПК разом, а старые клиенты обязаны громко остановиться.
Цифры одним экраном
| Параметр | Значение |
|---|---|
| Чанк | 64 МиБ |
| Лимит сообщения Telegram | 2 ГБ (4 ГБ с Premium) |
| Параллельных загрузок | 1–2 |
| Попыток push при гонке и сбоях | до 5 |
| Пауза перед верификацией публикации | 1,5 с |
| Авто-push после изменения | 3 с тишины + джиттер до 1,5 с |
| Дебаунс авто-pull / поллинг | 2 с / 5 мин |
| Ретраи оркестратора | от 15 с, экспоненциально |
| Предохранитель удалений | только с подтверждением: больше 10 файлов или 20 % дерева |
| Кэш «умной папки» | 5 ГБ по умолчанию, эвикция до ~90 % |
| Юнит-тестов | 93 |
Финальный чек-лист
- Файл 300 МБ уезжает и возвращается бит-в-бит; обрыв докачивается без перезаливки
- Два ПК одновременно пушат один файл → обе версии в истории, ноль потерь
- Удаление на одном ПК видно на другом; восстановление возвращает файл всем
- «Умная папка»: всё видно, место только под нужное; кэш чистится сам
- Отключение диска не рассылает «удаления» команде
- Установщик ставится без прав администратора; секретов внутри нет
- Чек-лист живого теста пройден минимум на двух ПК
Частые вопросы про сборку своего облака
Нужно ли уметь программировать, чтобы собрать TgDrive?
Желательно, но быть профи не обязательно. Статья написана так, что её можно скармливать ИИ-ассистенту фаза за фазой и собирать приложение вместе с ним. Понимание основ C# сильно ускорит дело. А если собирать не хочется вовсе, готовый дистрибутив бесплатно раздают в нашем Telegram-канале.
Сколько стоят ключи Telegram API?
Нисколько. api_id и api_hash выдаются бесплатно любому аккаунту на my.telegram.org/apps, занимает это около минуты.
Почему не Bot API? Он же проще
У ботов лимит загрузки около 50 МБ и нет полной истории чата. Для файлового хранилища это приговор. MTProto от имени обычного аккаунта даёт файлы до 2 ГБ (4 ГБ с Premium) и бесплатную атрибуцию «кто залил».
Можно ли собрать не на C#?
Да. Архитектура к языку не привязана: MTProto-библиотеки есть для Python (Telethon), Go (gotd), TypeScript (GramJS). Windows-специфична только «умная папка» на Cloud Files API; папку-зеркало можно собрать на чём угодно и под любую ОС.
Сколько времени займёт сборка?
Мы прошли путь за несколько недель вечерами, вместе с ИИ-ассистентом. Фазы 0–1 (консольное ядро и папка-зеркало) реально поднять за пару выходных. Больше всего времени съедают гонки Фазы 2 и «умная папка» Фазы 4.
Если собирать не хочется
Мы честно отдали все чертежи. Но если нужен готовый вариант, заходите в наш Telegram-канал: бесплатно поделимся дистрибутивом и поможем с установкой. Там же выходят анонсы следующих инструментов на базе Telegram API (облачная галерея для телефона и видеогалерея уже в планах), а разборы публикуются на winzardy.com.
Соберёте своё, покажите. Нам правда интересно.

