Биб­лиоте­ка MadelineProto поз­воля­ет с лег­костью написать собс­твен­ный Telegram-кли­ент, который мож­но исполь­зовать по‑раз­ному — от отправ­ки сооб­щения все­му кон­такт‑лис­ту до взло­ма Telegram-акка­унтов. При этом биб­лиоте­ка не исполь­зует Bot API, что зна­читель­но упро­щает раз­работ­ку. В этой статье мы раз­берем базовые фун­кции кли­ента: авто­риза­цию, получе­ние спис­ка кон­тактов, извле­чение информа­ции о поль­зовате­лях и отправ­ку им сооб­щений.

Подготовительные мероприятия

Са­му биб­лиоте­ку MadelineProto уста­нав­ливать и обновлять не нуж­но. Скрип­ты устро­ены таким обра­зом, что, если файл биб­лиоте­ки отсутс­тву­ет, он будет заг­ружен авто­мати­чес­ки. Одна­ко тебе нуж­но позабо­тить­ся о двух вещах:

С регис­тра­цией при­ложе­ния раз­берешь­ся сам, там все прос­то, а вот для обновле­ния PHP до нуж­ной вер­сии тебе надо под­клю­чить репози­торий Ondřej Surý PPA. Вве­ди сле­дующие коман­ды:

sudo apt install software-properties-common -y && sudo add-apt-repository ppa:ondrej/php -y
sudo apt update && sudo apt upgrade

Пос­ле это­го вве­ди коман­ду уста­нов­ки PHP 8.2:

sudo apt install php8.2

Ес­ли у тебя раз­вернут Apache, то уста­нови еще один пакет и переза­пус­ти веб‑сер­вер:

sudo apt install libapache2-mod-php8.2
sudo systemctl restart apache2

За­тем вве­ди коман­ду php -v. Ты дол­жен уви­деть при­мер­но такой вывод:

PHP 8.2.0 (cli) (built: Dec 10 2022 10:52:42) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.0, Copyright (c) Zend Technologies
with Zend OPcache v8.2.0, Copyright (c), by Zend Technologies

Как видишь, ничего слож­ного.

Авторизация

На­пишем прос­тей­ший сце­нарий, выпол­няющий авто­риза­цию поль­зовате­ля. Для это­го нам понадо­бит­ся Telegram-акка­унт. Можешь тре­ниро­вать­ся на сво­ем акка­унте: не бес­покой­ся, ты всег­да смо­жешь поменять пароль, и биб­лиоте­ка точ­но не уго­нит твою учет­ку.

<?php
$userDir = '';
$api_id = 11111; // Добавь сюда api_id
$api_hash = 'xxxxxxx'; // Добавь сюда api_hash
$phone = 'твой номер телефона'; // Номер телефона
if (!file_exists($userDir . '/madeline.php')) {
copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
}
include_once $userDir . '/madeline.php';
$settings = (new \danog\MadelineProto\Settings\AppInfo)
->setApiId($api_id)
->setApiHash($api_hash);
use danog\MadelineProto\Tools;
$MadelineProto = new \danog\MadelineProto\API($userDir . "/session", $settings);
$MadelineProto->phoneLogin($phone);
$authorization = $MadelineProto->completePhoneLogin(Tools::readLine('Enter the phone code: '));
if ($authorization['_'] === 'account.needSignup') {
echo "needSignup for $phone";
die();
}
if ($authorization['_'] === 'account.password') {
$authorization = $MadelineProto->complete2falogin(Tools::readLine('Please enter your password (hint '.$authorization['hint'].'): '));
}

Те­перь раз­берем­ся, что мы дела­ем. Для начала мы зада­ем необ­ходимые перемен­ные: $api_id, $api_hash, $userDir. С пер­выми дву­мя все ясно, а $userDir ты можешь исполь­зовать, если нуж­но, что­бы madeline.php заг­ружал­ся в какой‑то дру­гой, а не текущий каталог. Если эта перемен­ная содер­жит пус­тое зна­чение, madeline.php будет заг­ружать­ся в текущий каталог и отту­да инклу­дить­ся.

Да­лее мы соз­даем объ­ект с парамет­рами. В ста­рых вер­сиях MadelineProto для ука­зания парамет­ров при­ложе­ний исполь­зовал­ся ассо­циатив­ный мас­сив, поэто­му в интерне­те ты смо­жешь най­ти уста­рев­шие скрип­ты с исполь­зовани­ем этой биб­лиоте­ки, которые работать не будут как раз по этой самой при­чине — вмес­то мас­сива теперь исполь­зует­ся объ­ект с парамет­рами.

Этот объ­ект нуж­но передать конс­трук­тору \danog\MadelineProto\API(). Пер­вый его параметр — наз­вание катало­га с сес­сией вхо­дяще­го в учет­ку поль­зовате­ля, а вто­рой — это как раз объ­ект с парамет­рами. Обра­ти вни­мание: мы так­же исполь­зуем $userDir, что­бы каталог с сес­сией мог находить­ся в дру­гой пап­ке, если тебе так будет удоб­нее. Ког­да ты пишешь Telegram-кли­ент для одно­го поль­зовате­ля, то никаких проб­лем не воз­ника­ет. Но если ты пла­ниру­ешь исполь­зовать кли­ент для вхо­да мно­гих поль­зовате­лей, для под­держа­ния поряд­ка нуж­но будет дер­жать сес­сии в раз­ных катало­гах — для каж­дого поль­зовате­ля при­дет­ся соз­давать отдель­ную пап­ку. Думаю, это понят­но.

Ме­тод phoneLogin() при­меня­ется для вхо­да с исполь­зовани­ем номера телефо­на. Ука­жи номер телефо­на в том фор­мате, в котором ты ука­зыва­ешь его при вхо­де в Telegram, но без +.

Пос­ле это­го на твой телефон будет отправ­лено сооб­щение с 2FA-кодом. Как пра­вило, оно отправ­ляет­ся уже не по SMS, а в твой Telegram, поэто­му открой при­ложе­ние на телефо­не и перей­ди к чату с наз­вани­ем Telegram.

Ес­ли мес­сен­джер на тво­ем телефо­не не уста­нов­лен, скрипт сооб­щит об этом и умрет — сра­бота­ет вызов die().

Ес­ли для тво­его акка­унта уста­нов­лен пароль, скрипт зап­росит его. Разуме­ется, если пароль пра­виль­ный, то все хорошо, а вот если нет, ты получишь соот­ветс­тву­ющее сооб­щение от самой биб­лиоте­ки. Отдель­но обра­баты­вать исклю­чение неп­равиль­но вве­ден­ного пароля не ста­ну — ты при желании спра­вишь­ся сам.

Пос­ле вхо­да в акка­унт ты смо­жешь выпол­нять все­воз­можные дей­ствия, нап­ример зап­рашивать спи­сок кон­тактов, отправ­лять им сооб­щения, читать диало­ги (чаты), уда­лять сооб­щения и диало­ги. Биб­лиоте­ка MadelineProto поз­воля­ет в этом пла­не выпол­нять мно­го раз­личных дей­ствий. Для начала поп­робу­ем получить спи­сок кон­тактов.

Получаем список контактов

Ме­тод getContacts() получа­ет спи­сок кон­тактов из тво­его Telegram-акка­унта. Рас­смот­рим фраг­мент кода, выводя­щий кон­такты на экран:

$contacts = $MadelineProto->contacts->getContacts();
$users = $contacts['users'];
foreach ($users as $user) {
$firstname = $user['first_name'] ?? '';
$lastname = $user['last_name'] ?? '';
$username = $user['username'] ?? '';
$uphone = $user['phone'] ?? '';
echo "$user[id]\t$firstname\t$lastname\t$username\t$uphone\n";
}

Что делать с кон­такта­ми даль­ше? Да что угод­но. Нап­ример, мож­но сох­ранить все эти кон­такты в таб­лице базы дан­ных, что­бы они никуда не потеря­лись. А еще мож­но отпра­вить каж­дому кон­такту сооб­щение.

Сохраняем контакты в базу данных

Не будем изоб­ретать велоси­пед и уста­новим MySQL для работы с базой дан­ных:

sudo apt install mysql-server mysql-client

От­крой кли­ент MySQL, нап­ример так:

mysql -u root

Ес­ли ты уже успел задать пароль для поль­зовате­ля root, добавь к пре­дыду­щей коман­де параметр -p, что­бы прог­рамма зап­росила пароль поль­зовате­ля.

Мы не будем соз­давать новую базу дан­ных, а ста­нем исполь­зовать уже име­ющуюся с име­нем test:

use test;

Соз­дай таб­лицу contacts:

CREATE TABLE `contacts` (
`id` bigint NOT NULL AUTO_INCREMENT,
`uid` varchar(50) NOT NULL,
`fname` varchar(200) DEFAULT NULL,
`lname` varchar(200) DEFAULT NULL,
`username` varchar(200) DEFAULT NULL,
`tid` varchar(250) DEFAULT NULL,
`phone` varchar(50) DEFAULT NULL,
PRIMARY KEY(id)
);

Наз­начение полей:

Те­перь нуж­но вый­ти из MySQL и про­дол­жить кодинг:

exit

Вер­немся к нашему фраг­менту вывода кон­тактов. Рас­ширим его, добавив код встав­ки кон­тактов в таб­лицу contacts:

$contacts = $MadelineProto->contacts->getContacts();
$users = $contacts['users'];
foreach ($users as $user) {
$firstname = $user['first_name'] ?? '';
$lastname = $user['last_name'] ?? '';
$username = $user['username'] ?? '';
$uphone = $user['phone'] ?? '';
// Настоятельно рекомендуется перед добавлением в БД:
$firstname = $mysqli->real_escape_string($firstname);
$lastname = $mysqli->real_escape_string($lastname);
$username = $mysqli->real_escape_string($username);
$uphone = $mysqli->real_escape_string($uphone);
echo "$user[id]\t$firstname\t$lastname\t$username\t$uphone\n";
// Проверяем, есть ли контакт в списке
$sql = "select * from contacts where uid = "$phone" and tid = $user[id]";
$result = $mysqli->query($sql) or die($mysqli->error);
if ($result->num_rows == 0) {
// Создаем новый контакт
$sql = "insert into contacts values(0, "$phone", "$firstname", "$lastname", "$username", "$user[id]", "$uphone")";
$mysqli->query($sql) or die($mysqli->error);
}
}

Ра­зуме­ется, где‑то в скрип­те до это­го цик­ла ты дол­жен под­клю­чить­ся к БД, но я все‑таки рас­счи­тываю, что минималь­ные навыки прог­рамми­рова­ния на PHP у тебя есть:

$mysqli = new mysqli($dbhost,$dbuser,$dbpass,$db);
if ($mysqli->connect_errno) {
die($mysqli->connect_error);
}

Ког­да ты взгля­нешь на этот код, ста­нет понят­но, зачем мы сох­раняли Telegram ID в спис­ке кон­тактов, — по нему мы про­веря­ем, добав­лен такой кон­такт для текуще­го поль­зовате­ля или нет. А поле uid мы добави­ли с одной целью — что­бы наша таб­личка мог­ла хра­нить кон­такты раз­ных акка­унтов: не соз­давать же для это­го отдель­ные таб­лицы (хотя при необ­ходимос­ти тоже мож­но).

Так вот, если для текуще­го поль­зовате­ля кон­такт с задан­ным Telegram ID не сущес­тву­ет в таб­лице contacts, мы выпол­няем SQL-опе­ратор insert для встав­ки кон­такта в таб­лицу.

Отправляем сообщения пользователям из контакт-списка

Пер­вым делом хочу пре­дос­теречь: что­бы твой акка­унт не заб­локиро­вали за спам, луч­ше не устра­ивать мас­совую рас­сылку всем сра­зу. Для отправ­ки сооб­щения исполь­зует­ся метод sendMessage, которо­му нуж­но передать два парамет­ра — ID поль­зовате­ля Telegram, которо­му отправ­ляет­ся сооб­щение (обра­ти вни­мание: не номер телефо­на), а так­же само сооб­щение:

$MadelineProto->messages->sendMessage([
'peer' => $user['id'],
'message' => "$firstname, с Новым годом!"
]);

Ес­ли кон­тактов в тво­ем спис­ке не очень мно­го, можешь запус­кать этот код без проб­лем. А вот если кон­тактов хотя бы нес­коль­ко десят­ков, при­думай, как изме­нить алго­ритм, что­бы он отправ­лял сооб­щения не всем сра­зу, а пооче­ред­но — при пер­вом запус­ке пер­вым десяти кон­тактам, при вто­ром — сле­дующим десяти и так далее. Делай переры­вы меж­ду запус­ками кли­ента. В общем, все инди­виду­аль­но: иног­да ты можешь отпра­вить тысячу сооб­щений и тебя не заб­локиру­ют за спам, а можешь отправ­лять с переры­вами, осто­рож­ничать — и твою учет­ку заб­локиру­ют на пятисо­том сооб­щении.

Собираем все вместе

А вот пол­ный код кли­ента, получа­юще­го спи­сок кон­тактов и сох­раня­юще­го его в таб­лицу contacts:

<?php
$userDir = '';
$api_id = 11111; // Добавь сюда api_id
$api_hash = 'xxxxxxx'; // Добавь сюда api_hash
$phone = 'твой номер телефона'; // Номер телефона
$dbhost = 'localhost';
$dbuser = 'root';
$dbpass = '';
$db = 'test';
// Подключаемся к БД
$mysqli = new mysqli($dbhost, $dbuser, $dbpass, $db);
if ($mysqli->connect_errno) {
die($mysqli->connect_error);
}
// Загружаем библиотеку
if (!file_exists($userDir . '/madeline.php')) {
copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
}
include_once $userDir . '/madeline.php';
// Формируем объект с параметрами
$settings = (new \danog\MadelineProto\Settings\AppInfo)
->setApiId($api_id)
->setApiHash($api_hash);
use danog\MadelineProto\Tools;
$MadelineProto = new \danog\MadelineProto\API($userDir . "/session", $settings);
// Вход под именем пользователя
$MadelineProto->phoneLogin($phone);
$authorization = $MadelineProto->completePhoneLogin(Tools::readLine('Enter the phone code: '));
if ($authorization['_'] === 'account.needSignup') {
echo "needSignup for $phone";
die();
}
if ($authorization['_'] === 'account.password') {
$authorization = $MadelineProto->complete2falogin(Tools::readLine('Please enter your password (hint '.$authorization['hint'].'): '));
}
// Получаем список контактов
$contacts = $MadelineProto->contacts->getContacts();
$users = $contacts['users'];
foreach ($users as $user) {
$firstname = $user['first_name'] ?? '';
$lastname = $user['last_name'] ?? '';
$username = $user['username'] ?? '';
$uphone = $user['phone'] ?? '';
// Настоятельно рекомендуется перед добавлением в БД:
$firstname = $mysqli->real_escape_string($firstname);
$lastname = $mysqli->real_escape_string($lastname);
$username = $mysqli->real_escape_string($username);
$uphone = $mysqli->real_escape_string($uphone);
echo "$user[id]\t$firstname\t$lastname\t$username\t$uphone\n";
// Проверяем, есть ли контакт в списке
$sql = "select * from contacts where uid = "$phone" and tid = $user[id]";
$result = $mysqli->query($sql) or die($mysqli->error);
if ($result->num_rows == 0) {
// Создаем новый контакт
$sql = "insert into contacts values(0, "$phone", "$firstname", "$lastname", "$username", "$user[id]", "$uphone")";
$mysqli->query($sql) or die($mysqli->error);
}
}

Итак, наш скрипт впол­не работос­пособен, но воз­можнос­тей у него пока еще не слиш­ком мно­го. В сле­дующий раз мы про­дол­жим изу­чение биб­лиоте­ки и рас­смот­рим, как мож­но получить и сох­ранить в базу дан­ных диало­ги (чаты) из Telegram.

Те­перь давай раз­берем­ся, как извлечь спи­сок диало­гов и получить из них отдель­ные сооб­щения.

Метод getDialogs()

Пос­ле того как ты залоги­нил­ся, мож­но получить из Telegram спи­сок диало­гов. Сде­лать это лег­ко — дос­таточ­но выз­вать метод getDialogs():

$dialogs = $MadelineProto->messages->getDialogs();

Те­перь в цик­ле for мож­но прой­тись по этим диало­гам, что­бы вытащить отту­да что‑нибудь цен­ное:

$n = count($dialogs);
for ($i = 0; $i < $n; $i++)
{
// Обрабатываем диалоги
}

Но не все так прос­то, как кажет­ся, ина­че не нуж­но было бы писать целую статью. Я не знаю, что ты будешь делать с вытащен­ными из «Телег­рама» диало­гами, но пред­ста­вим, что тебе нуж­но вывес­ти их на экран (ты же можешь сох­ранить их в файл или в БД — как тебе захочет­ся).

Пользовательские диалоги и чаты

Ди­ало­ги быва­ют двух видов: поль­зователь­ские и чаты. К пер­вому типу отно­сят­ся диало­ги с поль­зовате­лями, ботами, самим Telegram (ког­да сис­тема отправ­ляет тебе сооб­щения, нап­ример о попыт­ке вхо­да), а так­же каналы пред­став­лены в фор­мате поль­зователь­ских диало­гов. В общем, если потен­циаль­ных учас­тни­ков беседы не боль­ше двух (ты и еще кто‑то), то это поль­зователь­ский диалог. А вот чат — это ког­да отпра­вите­лей сооб­щений нес­коль­ко, при­чем в эту катего­рию вхо­дят и диало­ги при­ват­ных групп. Это ког­да ты соз­даешь груп­пу мамочек 3Б клас­са и добав­ляешь туда всех жела­ющих. Затем каж­дая из мамочек в семь часов утра начина­ет отправ­лять в эту груп­пу сооб­щение о том, что ее чадо заболе­ло или будет отсутс­тво­вать по семей­ным обсто­ятель­ствам. Зна­комо?

Ко­роче, струк­тура чатов отли­чает­ся от струк­туры поль­зователь­ских диало­гов, и, что­бы не запутать­ся, мы напишем отдель­ные фун­кции для обра­бот­ки диало­гов раз­ных типов.

Я буду исполь­зовать про­цедур­ный стиль прог­рамми­рова­ния: если хочешь исполь­зовать ООП — пожалуй­ста, но лич­но я не вижу смыс­ла городить клас­сы для решения столь прос­той задачи.

Пер­вым делом в нашем цик­ле for нам нуж­но получить информа­цию о собесед­нике, что мож­но сде­лать методом getInfo():

$userinfo = $MadelineProto->getInfo($dialogs["dialogs"][$i]);

Ес­ли в мас­сиве $userInfo уста­нов­лен эле­мент User, это поль­зователь­ский диалог, а если там есть эле­мент Chat — диалог при­ват­ной груп­пы. При­мер обра­бот­ки:

if (isset($userinfo['User'])) {
// Обрабатываем пользовательский диалог
$peerId = $userinfo['user_id'];
$username = $userinfo['User']['username'] ?? 'n/a';
if (isset($userinfo['User']['first_name']))
$fullname = $userinfo['User']['first_name'];
else $fullname = '';
if(isset($userinfo['User']['last_name'])){
$fullname .= ' '. $userinfo['User']['last_name'];
}
}
echo("Full Name: $fullname UserId: $peerId Username @: $username\n");
// getRegularMessages($library, $peerId, $fullname, $username);
}
if (isset($userinfo['Chat'])) {
// Обрабатываем диалог группы
$peerId = $peers["dialogs"][$i]["peer"];
$chatname = "Группа " . $userinfo['Chat']['title'];
echo("PeerId: $peerId $chatname\n");
// getGroupMessages($library, $peerId, $chatname);
}

Об­рати вни­мание на одну осо­бен­ность биб­лиоте­ки. Если поль­зователь не запол­нил о себе какую‑то информа­цию, нап­ример написал в про­филе толь­ко свое имя, но не фамилию, то соот­ветс­тву­ющий эле­мент — в дан­ном слу­чае last_name — будет поп­росту отсутс­тво­вать в мас­сиве userinfo. А что про­изой­дет, если PHP попыта­ется обра­тить­ся к несущес­тву­юще­му эле­мен­ту? Пра­виль­но, ты получишь ошиб­ку и не смо­жешь про­читать весь спи­сок диало­гов, пос­коль­ку выпол­нение скрип­та будет прер­вано рань­ше. Поэто­му обя­затель­но про­веряй с помощью isset() эле­мен­ты, которые ты пыта­ешь­ся получить из $userinfo и дру­гих струк­тур, воз­вра­щаемых биб­лиоте­кой MadelineProto.

Код, который ты помес­тишь в наш цикл for, будет выводить спи­сок диало­гов. Прос­то спи­сок диало­гов, но не спи­сок сооб­щений для каж­дого из них. Наз­вание диало­га будет пред­варять­ся стро­кой "Группа ", если выводит­ся диалог для груп­пы.

Так­же обра­ти вни­мание, что у нас заком­менти­рова­ны вызовы фун­кций getRegularMessages() и getGroupMessages(). Пока эти фун­кции не готовы, и я заком­менти­ровал их вызов, что­бы ты смог запус­тить код и уви­деть спи­сок чатов.

Выводим сообщения из пользовательского диалога

В эту фун­кцию мы переда­ем перемен­ную‑объ­ект MadelineProto, а так­же информа­цию о поль­зовате­ле, получен­ную ранее: она при­годит­ся, если ты надума­ешь не прос­то выводить сооб­щения на экран, а сох­ранять их в БД или файл.

По­лучить спи­сок сооб­щений в том или ином диало­ге мож­но с помощью вызова getHistory():

$messages = $library->messages->getHistory(peer:$peerId,limit:10);

Пер­вый параметр — это $peerId, то есть ID поль­зовате­ля, с которым у тебя ведет­ся диалог. Для чатов — это содер­жимое эле­мен­та мас­сива peer. Вто­рой параметр — количес­тво пос­ледних сооб­щений из диало­га. В нашем слу­чае мы получа­ем десять пос­ледних сооб­щений.

Са­ми сооб­щения будут хра­нить­ся в эле­мен­те messages:

$msgs = $messages['messages'];
foreach ($msgs as $item) {
// На всякий случай проверяем, есть ли элемент message внутри
// Это нормальная практика при работе с MadelineProto
if (isset($item['message'])) {
// Обработка каждого отдельного сообщения $item['message']
}
}

Нам при­годят­ся сле­дующие эле­мен­ты мас­сива $item:

Вы­ведем текст сооб­щения, дату и приз­нак out:

$msgs = $messages['messages'];
foreach ($msgs as $item) {
// На всякий случай проверяем, есть ли элемент message внутри
// Это нормальная практика при работе с MadelineProto
if (isset($item['message'])) {
echo "==========================\n";
echo "Диалог с $fullname @ $username\n"
echo "Дата: " . date('m/d/Y H:i', $item['date']);
if ($item['out'] === false) echo "Входящее сообщение:\n";
else echo "Исходящее сообщение:\n";
echo $item["message"] . "\n";
echo "==========================\n";
}
}

Те­перь раз­берем­ся с эле­мен­том $item["media"]["_"]. Он содер­жит тип при­соеди­нен­ного меди­афай­ла. Нас инте­ресу­ют два типа — messageMediaDocument и messageMediaPhoto. С осталь­ными ты смо­жешь озна­комить­ся самос­тоятель­но в докумен­тации: эти типы поз­воля­ют, нап­ример, сох­ранять вся­кие сти­керы.

Пер­вый из инте­ресу­ющих нас типов — это какой‑то меди­афайл. Сюда отно­сят­ся все типы фай­лов, которые ты можешь пересы­лать через Telegram: видео, архи­вы, PDF и так далее. Вто­рой тип выделен исклю­читель­но для кар­тинок (фотог­рафий).

Ска­чать меди­афайл мож­но с помощью метода $library->downloadToDir(). Пер­вый параметр — это все сооб­щение целиком (перемен­ная $item), а вто­рой — каталог, в который нуж­но ска­чать дан­ный файл. Исполь­зуя тип докумен­та, мы можем сох­ранять кар­тинки в один каталог, а осталь­ные фай­лы — в дру­гой. Метод getDownloadInfo() поз­воля­ет узнать имя фай­ла и его рас­ширение: при желании ты можешь рас­пихать фай­лы по раз­ным пап­кам в зависи­мос­ти от рас­ширения.

Вот при­мер кода заг­рузки фай­ла:

if (isset($item["media"]["_"])) {
$mediaType = $item["media"]["_"];
switch ($mediaType) {
case "messageMediaDocument":
// Получаем инфо о файле. Потом можешь использовать по своему усмотрению
$info = $library->getDownloadInfo($item);
// name имя, ext расширение
$fileName = $info['name'] . $info['ext'];
$outFileName = $library->downloadToDir($item, "files");
break;
case "messageMediaPhoto":
$info = $MadelineProto->getDownloadInfo($item);
$fileName = $info['name'] . $info['ext'];
$outFileName = $library->downloadToDir($item, "pics");
break;
}
}

info

При сох­ранении фай­лов неп­лохо бы про­верять дос­тупное дис­ковое прос­транс­тво. Если ты вдруг передал дру­гу фильм объ­емом нес­коль­ко гигабайт, то он заг­рузит­ся в пап­ку files в пол­ном сос­таве. А если таких филь­мов будет мно­го, ты рис­куешь запол­нить все дос­тупное прос­транс­тво.

Ме­тод downloadToDir() воз­вра­щает резуль­тиру­ющее имя фай­ла. Как видишь, мы все фай­лы помеща­ем в пап­ку files (которая дол­жна сущес­тво­вать в рабочем катало­ге и иметь соот­ветс­тву­ющим обра­зом уста­нов­ленные пра­ва дос­тупа), а кар­тинки — в отдель­ный каталог pics. Что­бы наш код работал, не забудь соз­дать эти катало­ги:

mkdir files
chmod 666 files
mkdir pics
chmod 666 pics

Пол­ный код фун­кции getRegularMessages() при­веден ниже:

Вы­вод обыч­ного сооб­щения getRegularMessages()
function getRegularMessages($library, $peerId, $fullname, $username) {
$messages = $library->messages->getHistory(peer:$peerId,limit:10);
$msgs = $messages['messages'];
foreach ($msgs as $item) {
// На всякий случай проверяем, есть ли элемент message внутри
// Это нормальная практика при работе с MadelineProto
if (isset($item['message'])) {
echo "==========================\n";
echo "Диалог с $fullname @ $username\n"
echo "Дата: " . date('m/d/Y H:i', $item['date']);
if ($item['out'] === false) echo "Входящее сообщение:\n";
else echo "Исходящее сообщение:\n";
echo $item["message"] . "\n";
echo "==========================\n";
}
}
}

Код обра­бот­ки меди­афай­лов ты можешь добавить в цикл foreach() по желанию.

Выводим сообщения из чата

Эта фун­кция очень похожа на getRegularMessages(), но есть важ­ные отли­чия. Во‑пер­вых, нам нуж­но получить спи­сок всех учас­тни­ков чата:

$users = $chatInfo['users'];

ID поль­зовате­ля, написав­шего сооб­щение в груп­пу, хра­нит­ся в эле­мен­те мас­сива сооб­щения $item["peer_id"]. Но, как понима­ешь, ID поль­зовате­ля нам мало­инте­ресен, тебе захочет­ся знать его имя, фамилию или номер телефо­на (если эти дан­ные откры­ты и ука­заны). Для это­го мож­но написать вот такой код, опре­деля­ющий всю перечис­ленную информа­цию:

$userId = $item["peer_id"];
foreach ($users as $user) {
if ($user['id'] == $userId) {
if (isset($user["first_name"]))
$userInfo .= $user["first_name"] . " ";
if (isset($user["last_name"]))
$userInfo .= $user["last_name"] . " ";
if (isset($user["phone"])) $userInfo .= $user["phone"] . " ";
if (isset($user["username"])) $userInfo .= $user["username"];
}
}

Мы добав­ляем все све­дения о поль­зовате­ле в перемен­ную $userInfo для ком­пак­тнос­ти.

Код фун­кции getGroupMessages()
function getGroupMessages($library, $chat_id, $chatname) {
$messages = $library->messages->getHistory(peer:$chat_id,limit:10);
$msgs = $messages['messages'];
foreach ($msgs as $item) {
// На всякий случай проверяем, есть ли элемент message внутри
// Это нормальная практика при работе с MadelineProto
if (isset($item['message'])) {
$userId = $item["peer_id"]; $userInfo = "";
foreach ($users as $user) {
if ($user['id'] == $userId) {
if (isset($user["first_name"]))
$userInfo .= $user["first_name"] . " ";
if (isset($user["last_name"]))
$userInfo .= $user["last_name"] . " ";
if (isset($user["phone"])) $userInfo .= $user["phone"] . " ";
if (isset($user["username"])) $userInfo .= $user["username"];
}
}
echo "==========================\n";
echo "Чат $chatname\n"
echo "Дата: " . date('m/d/Y H:i', $item['date']);
echo "Пользователь: $userInfo\n"
if ($item['out'] === false) echo "Входящее сообщение:\n";
else echo "Исходящее сообщение:\n";
echo $item["message"] . "\n";
echo "==========================\n";
}
}
}

Ме­диав­ложения обра­баты­вают­ся ана­логич­ным обра­зом.

Выводы

Как видишь, получить спи­сок поль­зовате­лей, вытащить из Telegram все дан­ные о них, отпра­вить им сооб­щения или сох­ранить текст и фай­лы из чатов — доволь­но прос­то. На осно­ве этой тех­нологии мож­но соз­дать при­ложе­ние, которое будет, нап­ример, собирать информа­цию о юзе­рах Telegram, которые добави­лись в соз­данную тобой груп­пу или чат. При­мене­ний подоб­ному боту мож­но най­ти мно­жес­тво, и я уве­рен, ты смо­жешь исполь­зовать его с умом.