Показаны сообщения с ярлыком laravel. Показать все сообщения
Показаны сообщения с ярлыком laravel. Показать все сообщения

среда, 9 февраля 2022 г.

Использование Telegram Core API (MTProto) на PHP

Эта заметка не про Bot API, а про Core API Telegram, с помощью которого можно создавать полноценные клиенты для месседжера, и конечно же любой другой софт, например для сбора данных из Телеграма. Основная проблема заключается в том, что общение с серверами Telegram осуществляется по специальному протоколу разработанным внутри компании — MTProto. Именно благодаря этому протоколу данный месседжер и славится своей безопасностью и шифрование данных.

Вас мучают вопросы: как использовать Telegram Api на PHP? Как вызывать функции? Очень много примеров использования telegram api для бота, а как использовать обычное api telegram? Зарегистрировал приложение, получил api_id и api_hash, как получить все сообщения из телеграм-канала? https://core.telegram.org/method/messages.getHistory
Как вызвать этот метод? Как реализовать авторизацию с помощью API Telegram? Тогда эта статья для вас!

Естественно, разбирать нюансы протокола MTProto в данной заметке я не буду. Для работы с ним буду пользоваться PHP-библиотекой MadelineProto, доступной всем желающим на GitHub. Однако, нельзя просто так взять и воспользоваться библиотекой. Есть как минимум три нюанса, которые нужно решить.

Подготовка к установке MadelineProto

Во-первых, нужен установленный Python, будет достаточно версии 2.7.

Во-вторых, библиотека не помечена как стабильная, поэтому для подключения её через composer к существующему проекту нужно немного отредактировать composer.json:

"minimum-stability": "dev",

Для того, чтобы composer не ругался на отсутствие стабильных версий зависимостей. Без указания этой директивы во время установки библиотеки будет получена примерно такая ошибка:

Your requirements could not be resolved to an installable set of packages.

Problem 1
 - Installation request for danog/madelineproto ^2.0 -> satisfiable by danog/madelineproto[2.0].
 - danog/madelineproto 2.0 requires danog/primemodule dev-master -> satisfiable by danog/primemodule[dev-master] but these conflict with your requirements or minimum-stability.


Installation failed, reverting ./composer.json to its original content.

Затем нужно указать git-репозиторий библиотеки:

"repositories": [
    {
        "type": "git",
        "url": "https://github.com/danog/phpseclib"
    }
],

и только затем можно устанавливать саму либу:

composer require danog/madelineproto

Если во время установки зависимостей появится ошибка на подобии такой:

github Failed to clone via https, ssh protocols, aborting.
error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version

То вам нужно обновить версию git.

Регистрация приложения для Telegram API

Теперь нужно зарегистрировать приложение в разделе API development tools и получить App api_id и App api_hash.

Как правильно использовать MadelineProto с Laravel

В-третьих, на сегодняшний день (2017-02-10) мне не удалось запустить MadelineProto из коробки, т.к. начинали сыпаться ошибки типа:

DataCenter: Connecting to DC 2 (main server, ipv4, tcp_full)...
Exception: stream_set_timeout() expects parameter 1 to be resource, null given in Socket.php:153
DataCenter: Connection failed, retrying connection on port 443...
Exception: stream_set_timeout() expects parameter 1 to be resource, null given in Socket.php:153
DataCenter: Connection failed, retrying connection on port 80...
Exception: stream_set_timeout() expects parameter 1 to be resource, null given in Socket.php:153
DataCenter: Connection failed, retrying connection on port 88...
Exception: stream_set_timeout() expects parameter 1 to be resource, null given in Socket.php:153
DataCenter: Connection failed, retrying connection on port 443 without the proxy...
Exception: stream_set_timeout() expects parameter 1 to be resource, null given in Socket.php:153
DataCenter: Connection failed, retrying connection on port 80 without the proxy...
Exception: stream_set_timeout() expects parameter 1 to be resource, null given in Socket.php:153
DataCenter: Connection failed, retrying connection on port 88 without the proxy...
Exception: stream_set_timeout() expects parameter 1 to be resource, null given in Socket.php:153
Exception: Undefined offset: 2 in MsgIdHandler.php:77
CallHandler: An error occurred while calling method help.getNearestDc: Undefined offset: 2 in MsgIdHandler on line 77. Recreating connection and retrying to call method...
Exception: Undefined offset: 2 in MTProto.php:641

In MTProto.php line 641:

Undefined offset: 2

На самом деле здесь нет ничего фатального, просто фреймворк Laravel по-умолчанию перехватывает все ошибки и при отсуствии должных обработчиков завершает скрипт даже при наличии не критичных ошибок. Возможно такое поведение присутствует и в других фреймворках. Можно изменить уровень ошибок, добавив в метод \App\Providers\AppServiceProvider::boot() строку:

error_reporting(0);

Но тогда есть вероятность пропустить некритичные ошибки своего приложения.

Вторым способом устранения ошибок будет правка исходника /vendor/danog/madelineproto/src/danog/MadelineProto/Connection.php, а именно нужно закомментировать 3 строки в конструкторе в условии

case 'tcp_full':
//                $this->sock->setOption(\SOL_SOCKET, \SO_RCVTIMEO, $timeout);
//                $this->sock->setOption(\SOL_SOCKET, \SO_SNDTIMEO, $timeout);
//                $this->sock->setBlocking(true);

В коммите 56c0d431768c04009ae9aa3151715b5e6399ec4d эти строки находятся на 105-107 строках файла. Источник проблемы был найден с помощью отладчика xDebug. Проблема заключалась в том, что методы $this->sock->setOption() и  $this->sock->setBlocking() пытались работать с ещё не созданным объектом $this->sock->sock. Если у вас возникнут другие ошибки, то с помощью отладчика вы их легко обнаружите и исправите.

Также в библиотеку могут быть зашиты устаревшие или не актуальные IP-адреса серверов Телеграма. Их всегда можно посмотреть на странице API development tools и передать в ModelineProto через конструктор \danog\MadelineProto\API().

Список всех параметров которые можно изменить в этой библиотеке можно посмотреть в массиве $default_settings метода \danog\MadelineProto\MTProto::parse_settings().

В идеале нужно зарегистрировать для приложения новую чистую учётную запись, но для тестирования и отладки вполне сгодится любая уже имеющаяся учётка Телеграма.

Пример работы MadelineProto на Laravel

Как делать запросы к Telegram API на PHP?

Приведу простой пример кода на базе консольной команды для Laravel:

public function handle() {

        // Если файл с сессией уже существует, использовать его
        if(file_exists( env('TELEGRAM_SESSION_FILE') ) ) {
            $madeline = new API( env('TELEGRAM_SESSION_FILE') );
        } else {
        // Иначе создать новую сессию
            $madeline = new API([
                'app_info' => [
                    'api_id' => env('TELEGRAM_API_ID'),
                    'api_hash' => env('TELEGRAM_API_HASH'),
                ]
            ]);

            // Задать имя сессии
            $madeline->session = env('TELEGRAM_SESSION_FILE');

            // Принудительно сохранить сессию
            $madeline->serialize();

            // Начать авторизацию по номеру мобильного телефона
            $madeline->phone_login( env('TELEGRAM_PHONE') );
            // Запросить код с помощью консоли
            $code = readline('Enter the code you received: ');
            $madeline->complete_phone_login($code);
        }

        $messages = $madeline->messages->getHistory(['peer' => '@ANY_CHANNEL_ID', 'offset_id' => 0, 'offset_date' => 0, 'add_offset' => 0, 'limit' => 10, 'max_id' => 0, 'min_id' => 0, 'hash' => 0, ]);

        foreach($messages['messages'] as $msg) {
            dump($msg);
        }

    }

Для тех, кто не умеет в Laravel, кратко поясню. Вызовы env() — это запросы значений из файла конфигурации, можно заменить их на константы или захардкодить. Собственно:

TELEGRAM_SESSION_FILE — любое значение, которое можно использовать в качестве имени файла.

TELEGRAM_API_ID и TELEGRAM_API_HASH — Данные из API development tools.

TELEGRAM_PHONE — мобильный номер существующий учётки, например, +7XXXXXXXXXX.

Теперь пояснения о происходящем в коде. Сессия — достаточно важный объект клиента, без него при каждом запуске скрипта авторизовываться и вводить код из сообщения, который Telegram высылает либо в смс либо через сам месседжер. Также на этапе авторизации происходит вся знаменитая шифровочная телеграм-магия. Процесс не быстрый, на моём тестовом стенде он мог затягиваться на минуты.

В результате работы скрипта в консоль будет выведено огромное количество отладочной информации, на которую можно не обращать внимания. При первом запуске сессии нужно будет авторизоваться в месседжере:

API: Running APIFactory...
API: MadelineProto is ready!
API: Serializing MadelineProto...
Login: Sending code...
Login: Code sent successfully! Once you receive the code you should use the complete_phone_login function.
Enter the code you received: ...
Login: Logging in as a normal user...
MTProto: Trying to copy authorization from dc 2 to dc 1
MTProto: Trying to copy authorization from dc 2 to dc 3
MTProto: Trying to copy authorization from dc 2 to dc 4
ResponseHandler: Parsing updates received via the socket...

После чего можно полноценно использовать все возможности Telegram Core API, например,  $messages = $madeline->messages->getHistory().

Данный метод возвращает сообщения из канала в обратном хронологическом порядке, т.е. начиная с самых свежих. Подробнее о параметрах этого метода можно узнать на страницах официальной документации MadelineProto. Заметьте, параметры MadelineProto могут отличаться от параметров официальной документации самого Telegram.

Отблагодарить можно через форму справа "Donate" ... )

To reward you via the form on the right "Donate" ... )

:)

четверг, 15 июля 2021 г.

воскресенье, 31 января 2021 г.

Cоглашения сообщества об именовании

 


ЧтоПравилоПринятоНе принято
Контроллеред. ч.ArticleControllerArticlesController
Маршрутымн. ч.articles/1article/1
Имена маршрутовsnake_caseusers.show_activeusers.show-active, show-active-users
Модельед. ч.UserUsers
Отношения hasOne и belongsToед. ч.articleCommentarticleComments, article_comment
Все остальные отношениямн. ч.articleCommentsarticleComment, article_comments
Таблицамн. ч.article_commentsarticle_comment, articleComments
Pivot таблицаимена моделей в алфавитном порядке в ед. ч.article_useruser_article, articles_users
Столбец в таблицеsnake_case без имени моделиmeta_titleMetaTitle; article_meta_title
Свойство моделиsnake_case$model->created_at$model->createdAt
Внешний ключимя модели ед. ч. и _idarticle_idArticleId, id_article, articles_id
Первичный ключ-idcustom_id
Миграция-2017_01_01_000000_create_articles_table2017_01_01_000000_articles
МетодcamelCasegetAllget_all
Метод в контроллере ресурсовтаблицаstoresaveArticle
Метод в тестеcamelCasetestGuestCannotSeeArticletest_guest_cannot_see_article
ПеременныеcamelCase$articlesWithAuthor$articles_with_author
Коллекцияописательное, мн. ч.$activeUsers = User::active()->get()$active, $data
Объектописательное, ед. ч.$activeUser = User::active()->first()$users, $obj
Индексы в конфиге и языковых файлахsnake_casearticles_enabledArticlesEnabled; articles-enabled
Представлениеkebab-caseshow-filtered.blade.phpshowFiltered.blade.php, show_filtered.blade.php
Конфигурационный файлsnake_casegoogle_calendar.phpgoogleCalendar.php, google-calendar.php
Контракт (интерфейс)прилагательное или существительноеAuthenticationInterfaceAuthenticatable, IAuthentication
ТрейтприлагательноеNotifiableNotificationTrait

Отблагодарить можно через форму справа "Donate" ... )

To reward you via the form on the right "Donate" ... )

:)

понедельник, 11 ноября 2019 г.

Хорошие практики Laravel

Принцип единственной ответственности (Single responsibility principle)
Каждый класс и метод должны выполнять лишь одну функцию.

Плохо:

public function getFullNameAttribute()
{
    if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
        return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
    } else {
        return $this->first_name[0] . '. ' . $this->last_name;
    }
}
Хорошо:

public function getFullNameAttribute()
{
    return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}

public function isVerifiedClient()
{
    return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}

public function getFullNameLong()
{
    return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}

public function getFullNameShort()
{
    return $this->first_name[0] . '. ' . $this->last_name;
}
🔝 Наверх

Тонкие контроллеры, толстые модели
По своей сути, это лишь один из частных случаев принципа единой ответственности. Выносите работу с данными в модели при работе с Eloquent или в репозитории при работе с Query Builder или "сырыми" SQL запросами.

Плохо:

public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($q) {
            $q->where('created_at', '>', Carbon::today()->subWeek());
        }])
        ->get();

    return view('index', ['clients' => $clients]);
}
Хорошо:

public function index()
{
    return view('index', ['clients' => $this->client->getWithNewOrders()]);
}

class Client extends Model
{
    public function getWithNewOrders()
    {
        return $this->verified()
            ->with(['orders' => function ($q) {
                $q->where('created_at', '>', Carbon::today()->subWeek());
            }])
            ->get();
    }
}
🔝 Наверх

Валидация
Следуя принципам тонкого контроллера и SRP, выносите валидацию из контроллера в Request классы.

Плохо:

public function store(Request $request)
{
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ]);

    ....
}
Хорошо:

public function store(PostRequest $request)

    ....
}

class PostRequest extends Request
{
    public function rules()
    {
        return [
            'title' => 'required|unique:posts|max:255',
            'body' => 'required',
            'publish_at' => 'nullable|date',
        ];
    }
}
🔝 Наверх

Бизнес логика в сервис-классах
Контроллер должен выполнять только свои прямые обязанности, поэтому выносите всю бизнес логику в отдельные классы и сервис классы.

Плохо:

public function store(Request $request)
{
    if ($request->hasFile('image')) {
        $request->file('image')->move(public_path('images') . 'temp');
    }
 
    ....
}
Хорошо:

public function store(Request $request)
{
    $this->articleService->handleUploadedImage($request->file('image'));

    ....
}

class ArticleService
{
    public function handleUploadedImage($image)
    {
        if (!is_null($image)) {
            $image->move(public_path('images') . 'temp');
        }
    }
}
🔝 Наверх

Не повторяйся (DRY)
Этот принцип призывает вас переиспользовать код везде, где это возможно. Если вы следуете принципу SRP, вы уже избегаете повторений, но Laravel позволяет вам также переиспользовать представления, части Eloquent запросов и т.д.

Плохо:

public function getActive()
{
    return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
}

public function getArticles()
{
    return $this->whereHas('user', function ($q) {
            $q->where('verified', 1)->whereNotNull('deleted_at');
        })->get();
}
Хорошо:

public function scopeActive($q)
{
    return $q->where('verified', 1)->whereNotNull('deleted_at');
}

public function getActive()
{
    return $this->active()->get();
}

public function getArticles()
{
    return $this->whereHas('user', function ($q) {
            $q->active();
        })->get();
}
🔝 Наверх

Предпочитайте Eloquent конструктору запросов (query builder) и сырым запросам в БД. Предпочитайте работу с коллекциями работе с массивами
Eloquent позволяет писать максимально читаемый код, а изменять функционал приложения несоизмеримо легче. У Eloquent также есть ряд удобных и мощных инструментов.

Плохо:

SELECT *
FROM `articles`
WHERE EXISTS (SELECT *
              FROM `users`
              WHERE `articles`.`user_id` = `users`.`id`
              AND EXISTS (SELECT *
                          FROM `profiles`
                          WHERE `profiles`.`user_id` = `users`.`id`)
              AND `users`.`deleted_at` IS NULL)
AND `verified` = '1'
AND `active` = '1'
ORDER BY `created_at` DESC
Хорошо:

Article::has('user.profile')->verified()->latest()->get();
🔝 Наверх

Используйте массовое заполнение (mass assignment)
Плохо:

$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
// Привязать статью к категории.
$article->category_id = $category->id;
$article->save();
Хорошо:

$category->article()->create($request->validated());
🔝 Наверх

Не выполняйте запросы в представлениях и используйте нетерпеливую загрузку (проблема N + 1)
Плохо (будет выполнен 101 запрос в БД для 100 пользователей):

@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach
Хорошо (будет выполнено 2 запроса в БД для 100 пользователей):

$users = User::with('profile')->get();

...

@foreach ($users as $user)
    {{ $user->profile->name }}
@endforeach
🔝 Наверх

Комментируйте код, предпочитайте читаемые имена методов комментариям
Плохо:

if (count((array) $builder->getQuery()->joins) > 0)
Лучше:

// Determine if there are any joins.
if (count((array) $builder->getQuery()->joins) > 0)
Хорошо:

if ($this->hasJoins())
🔝 Наверх

Выносите JS и CSS из шаблонов Blade и HTML из PHP кода
Плохо:

let article = `{{ json_encode($article) }}`;
Лучше:

<input id="article" type="hidden" value='@json($article)'>

Или

<button class="js-fav-article" data-article='@json($article)'>{{ $article->name }}<button>
В Javascript файле:

let article = $('#article').val();
Еще лучше использовать специализированный пакет для передачи данных из бэкенда во фронтенд.

🔝 Наверх

Конфиги, языковые файлы и константы вместо текста в коде
Непосредственно в коде не должно быть никакого текста.

Плохо:

public function isNormal()
{
    return $article->type === 'normal';
}

return back()->with('message', 'Ваша статья была успешно добавлена');
Хорошо:

public function isNormal()
{
    return $article->type === Article::TYPE_NORMAL;
}

return back()->with('message', __('app.article_added'));
🔝 Наверх

Используйте инструменты и практики принятые сообществом

Laravel имеет встроенные инструменты для решения часто встречаемых задач. Предпочитайте пользоваться ими использованию сторонних пакетов и инструментов. Laravel разработчику, пришедшему в проект после вас, придется изучать и работать с новым для него инструментом, со всеми вытекающими последствиями. Получить помощь от сообщества будет также гораздо труднее. Не заставляйте клиента или работодателя платить за ваши велосипеды.
ЗадачаСтандартные инструментНестандартные инструмент
АвторизацияПолитикиEntrust, Sentinel и др. пакеты, собственное решение
Работа с JS, CSS и пр.Laravel MixGrunt, Gulp, сторонние пакеты
Среда разработкиHomesteadDocker
Разворачивание приложенийLaravel ForgeDeployer и многие другие
ТестированиеPhpunit, MockeryPhpspec
e2e тестированиеLaravel DuskCodeception
Работа с БДEloquentSQL, построитель запросов, Doctrine
ШаблоныBladeTwig
Работа с даннымиКоллекции LaravelМассивы
Валидация формRequest классыСторонние пакеты, валидация в контроллере
АутентификацияВстроенный функционалСторонние пакеты, собственное решение
Аутентификация APILaravel PassportСторонние пакеты, использующие JWT, OAuth
Создание APIВстроенный функционалDingo API и другие пакеты
Работа со структурой БДМиграцииРабота с БД напрямую
ЛокализацияВстроенный функционалСторонние пакеты
Обмен данными в реальном времениLaravel Echo, PusherПакеты и работа с веб сокетами напрямую
Генерация тестовых данныхSeeder классы, фабрики моделей, FakerРучное заполнение и пакеты
Планирование задачПланировщик задач LaravelСкрипты и сторонние пакеты
БДMySQL, PostgreSQL, SQLite, SQL ServerMongoDb

Соблюдайте соглашения сообщества об именовании

Следуйте стандартам PSR при написании кода.
Также, соблюдайте другие cоглашения об именовании:
ЧтоПравилоПринятоНе принято
Контроллеред. ч.ArticleControllerArticlesController
Маршрутымн. ч.articles/1article/1
Имена маршрутовsnake_caseusers.show_activeusers.show-active, show-active-users
Модельед. ч.UserUsers
Отношения hasOne и belongsToед. ч.articleCommentarticleComments, article_comment
Все остальные отношениямн. ч.articleCommentsarticleComment, article_comments
Таблицамн. ч.article_commentsarticle_comment, articleComments
Pivot таблицаимена моделей в алфавитном порядке в ед. ч.article_useruser_article, articles_users
Столбец в таблицеsnake_case без имени моделиmeta_titleMetaTitle; article_meta_title
Внешний ключимя модели ед. ч. и _idarticle_idArticleId, id_article, articles_id
Первичный ключ-idcustom_id
Миграция-2017_01_01_000000_create_articles_table2017_01_01_000000_articles
МетодcamelCasegetAllget_all
Метод в контроллере ресурсовтаблицаstoresaveArticle
Метод в тестеcamelCasetestGuestCannotSeeArticletest_guest_cannot_see_article
ПеременныеcamelCase$articlesWithAuthor$articles_with_author
Коллекцияописательное, мн. ч.$activeUsers = User::active()->get()$active, $data
Объектописательное, ед. ч.$activeUser = User::active()->first()$users, $obj
Индексы в конфиге и языковых файлахsnake_casearticles_enabledArticlesEnabled; articles-enabled
Представлениеsnake_caseshow_filtered.blade.phpshowFiltered.blade.php, show-filtered.blade.php
Конфигурационный файлsnake_casegoogle_calendar.phpgoogleCalendar.php, google-calendar.php
Контракт (интерфейс)прилагательное или существительноеAuthenticatableAuthenticationInterface, IAuthentication
ТрейтприлагательноеNotifiableNotificationTrait

Короткий и читаемый синтаксис там, где это возможно

Плохо:
Хорошо:
Еще примеры:
Часто используемый синтаксисБолее короткий и читаемый синтаксис
Session::get('cart')session('cart')
$request->session()->get('cart')session('cart')
Session::put('cart', $data)session(['cart' => $data])
$request->input('name')$request->name
Request::get('name')request('name')
return Redirect::back()return back()
return view('index')->with('title', $title)->with('client', $client)return view('index', compact('title', 'client'))

Используйте IoC или фасады вместо new Class

Внедрение классов через синтаксис new Class создает сильное сопряжение между частями приложения и усложняет тестирование. Используйте контейнер или фасады.
Плохо:
Хорошо:

Другие советы и практики

Не размещайте логику в маршрутах.
Старайтесь не использовать сырой PHP в шаблонах Blade.

Отблагодарить можно через форму справа "Donate" ... )

To reward you via the form on the right "Donate" ... )

:)

друзья )

Сохраняйте и делитесь желаниями, и не забывайте о важных датах! парсинг центр