Как добавить Webmentions в блог на Laravel

Веб-упоминания в Ларавел

В разделе комментариев этого блога раньше использовался Disqus. По сути он работал довольно хорошо. Но мне не нравится, что он тащит с собой слишком много JavaScript. К тому же у него не самый красивый интерфейс. А недавно я заменил Disqus на Webmentions (веб-упоминания). Если вы ответите на эту статью, добавите лайк в Твиттере или сделаете ретвит, это взаимодействие через несколько минут появится в разделе комментариев ниже. Попробуйте!

В этой стать я хотел бы объяснить, почему я перешел к webmentions и как они реализованы в этом блоге.

Что такое Webmentions?

Недавно мой коллега Seb сделал очень интересную запись в блоге о webmentions. После чего я очень сильно заинтересовался ими. Seb объясняет, что же это такое.

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

Отправка webmentions

Всякий раз, когда я публикую запись в блоге — автоматически публикуется твит со ссылкой. В идеале я хотел бы получать уведомления от Twitter, когда на этот твит (или любой твит, содержащий ссылку на мой блог) отвечают, лайкают или ретвиттят.

К сожалению, в самом Твиттере нет поддержки webmentions. Но, к счастью, некоторые службы могут помочь с этим.

Одна из них это Bridgy. После настройки она будет сканировать Twitter на наличие твитов, содержащих ссылку на https://freek.dev. Она также будет сканировать новые взаимодействия (ответы, ретвиты, лайки) с этими твитами.

Каждый раз, когда она находит ссылку на мой блог, то ищет тег link на этой странице. Если находит, то отправляет webmention на указанный в href адрес. Подробнее об этом позже.

<link rel="webmention" href="...">

Вот так выглядит интерфейс Bridgy

Возможно это не самый красивый сайт, но здесь вы можете увидеть, когда он будет сканировать Твиттер на наличие новых упоминаний и проверять мой блог на предмет новых или обновленных целей для webmentions. Через интерфейс вы можете вручную запустить эти действия. Вы также можете повторно отправить webmentions. Эти возможности оказались очень полезными при разработке веб-упоминаний для этого блога.

Получение webmentions

Итак, Bridgy может отправлять webmentions, ну а мы должны позаботиться об их получении. Конечно, вы можете самостоятельно накодить сервер, который будет их получать, но для этого есть и специальные службы. Используя службу, вы можете быть уверены, что даже если ваш сервер по какой-то причине не работает, webmentions все равно будут записываться.

Я использую Webmention.io. Она может фильтровывать спам от ботов. Чтобы Bridgy отправлял все веб-упоминания на webmention.io, мне нужно указать этот href на каждой странице моего блога.

<link rel="webmention" href="https://webmention.io/freek.dev/webmention" />

В интерфейсе webmention.io вы можете видеть каждое упоминание, которое отправил Bridgy.

Webmention.io поддерживает отправку веб-хуков. Мы можем настроить его так, чтобы, как только он получат webmention (и считает, что это не спам), то отправляет её по указанному адресу.

Webmention Laravel

Разобравшись с этим, давайте посмотрим, как мы обрабатываем входящие веб-хуки из webmention.ui в нашем приложении Laravel.

Обработка webmentions

Несколько недель назад моя команда Spatie выпустила пакет под названием laravel-webhooks-client. Этот пакет позволяет легко обработать любой входящий веб-хук в Laravel.

После установки пакета в первую очередь необходимо убедиться, что есть адрес, который может принять вызов, сделанный из webmention.io. Вам нужно использовать макрос webhooks в Route. Я поместил эту строку в мой файл маршрутов:

Route::webhooks('webhook-webmentions', 'webmentions');

Первый параметр — это URL. Второй параметр — это ключ, используемый в файле конфигурации. Перейдите к этому разделу в файле Readme пакета на GitHub, чтобы узнать больше об этом.

На скриншоте выше вы, вероятно, заметили поле callback secret. Webmention.io добавит его в запрос, отправляемый к нам. Я также создал поле webmentions.webhook_secret в файле config/services.php и поместил туда значение этого секретного слова.

При использовании пакета laravel-webhook-client вы можете указать класс, который будет определять валидность веб-хука.

namespace App\Services\Webmentions;

use Illuminate\Http\Request;
use Spatie\WebhookClient\SignatureValidator\SignatureValidator;
use Spatie\WebhookClient\WebhookConfig;

class WebmentionWebhookSignatureValidator implements SignatureValidator
{
    public function isValid(Request $request, WebhookConfig $config): bool
    {
        if (! $request->has('secret')) {
            return false;
        }

        return $request->secret === config('services.webmentions.webhook_secret');
    }
}

Всякий раз, как webmention.io вызывает наше приложение через веб-хук, я хотел бы преобразовать его запрос, связав с соответствующим постом в блоге и добавить в базу данных в таблицу webmentions .

Все это делается в ProcessWebhookJob:

namespace App\Services\Webmentions

use App\Models\Post;
use App\Models\Webmention;
use Illuminate\Support\Arr;
use Spatie\Url\Url;
use Spatie\WebhookClient\ProcessWebhookJob as SpatieProcessWebhookJob;

class ProcessWebhookJob extends SpatieProcessWebhookJob
{
    public function handle()
    {
        $payload = $this->webhookCall->payload;

        if ($this->payloadHasBeenReceivedBefore($payload)) {
            return;
        }

        if (!$type = $this->getType($payload)) {
            return;
        }

        if (!$post = $this->getPost($payload)) {
            return;
        }

        Webmention::create([
            'post_id' => $post->id,
            'type' => $type,
            'webmention_id' => Arr::get($payload, 'post.wm-id'),
            'author_name' => Arr::get($payload, 'post.author.name'),
            'author_photo_url' => Arr::get($payload, 'post.author.photo'),
            'author_url' => Arr::get($payload, 'post.author.url'),
            'interaction_url' => Arr::get($payload, 'post.url'),
            'text' => Arr::get($payload, 'post.content.text'),
        ]);
    }
  
    private function payloadHasBeenReceivedBefore(array $payload): bool
    {
        $webmentionId = Arr::get($payload, 'post.wm-id');

        return Webmention::where('webmention_id', $webmentionId)->exists();
    }

    private function getType(array $payload): ?string
    {
        $types = [
            'in-reply-to' => Webmention::TYPE_REPLY,
            'like-of' => Webmention::TYPE_LIKE,
            'repost-of' => Webmention::TYPE_RETWEET,
        ];

        $wmProperty = Arr::get($payload, 'post.wm-property');

        if (!array_key_exists($wmProperty, $types)) {
            return null;
        }

        return $types[$wmProperty];
    }

    private function getPost(array $payload): ?Post
    {
        $url = Arr::get($payload, 'post.wm-target');

        if (!$url) {
            return null;
        }

        $postIdSlug = Url::fromString($url)->getSegment(1);

        [$id] = explode('-', $postIdSlug);

        return Post::find($id);
    }
}

Я не хочу объяснять этот код пошагово. Большая часть должна быть понятна и так.

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

Использование webmentions в качестве системы комментариев по сравнению с Disqus

С Disqus у меня отношения любовь-ненависть. Основные функции работают очень хорошо, есть поддержка вложенных комментариев, вход в систему для размещения комментариев возможен через различные системы.

С другой стороны, интерфейс, добавляемый Disqus на наш сайт, просто уродлив (imho) и нет большых возможностей для его настройки. Из-за того, как он рендерится на странице, Google не будет индексировать наши комментарии. Disqus также может вставлять рекламу в комментарии.

Использование твиттерных webmentions в качестве системы комментариев также является смешанным вариантом. Приятно, что вы полностью контролируете отображение комментарии. Вам придется хранить комментарии где-то самим, чтобы не зависеть от служб хранения контента.

С другой стороны, вы должны иметь в виду, что веб-упоминания появляются не сразу. Комментаторам нужна учетная запись на другом сайте (в моем случае им нужен аккаунт в Twitter). Комментарии не могут быть длинными, так как есть ограничение на количество символов твита (вы, разумеется, можете использовать несколько твитов). Вложенные твиты не поддерживается вообще.

Сейчас я думаю, что мне больше нравятся компромиссы webmentions. У меня не так много комментариев в этом блоге, но большая часть моей аудитории, кажется, активна в Твиттере. Возможно, мои мнение изменится в будущем. Посмотрим…

В заключение

Я надеюсь, вам понравился мой рассказ о реализации веб-упоминаний в этом блоге. Вот несколько ресурсов, если вы хотите узнать больше:

 

Автор: Freek Van der Herten
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.