Discord бот для ежедневной публикации статистики числа новых случаев заражения коронавирусом (COVID-19)

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

Реализация оказалась весьма тривиальна.

Первое, что нам понадобится, это доступ к самому дискорд серверу. Создадим новый канал под названием «Коронавирус». В настройках канала закрываем доступ к постингу сообщений, т.к. канал будет чисто информативный.

Далее опять же в настройках создаём вебхук с названием covid-bot:

На этом подготовительная часть в плане настроек Discord сервера завершена.

Переходим к кодингу.

Решение будет работать на основе данных с сайта стопкоронавирус.рф

Допустим, нам нужна статистика по городу Санкт-Петербург. На карте оперативной информации тыкаем на Питер и в отладчике замечаем GET запрос:

В ответ нам присылаются данные за последний месяц:

Воспользуемся этим веб-сервисом для получения статистики. Создаём два класса — первый (назовём его Data.php) для формирования самого сообщения с статистикой за текущий день, а второй (назовём его Discord.php) — для отправки этого сообщения в канал «Коронавирус» нашего Discord сервера.

Листинг классов:

<?php


namespace Covid;


class Data
{
    public static function GetTodayStats($regionCode, $regionName)
    {
        $R = false;
        if (!$regionCode || !$regionName){
            throw new \Exception('Не указан регион');
        }
        $jsonData = file_get_contents("https://xn--80aesfpebagmfblc0a.xn--p1ai/covid_data.json?do=region_stats&code={$regionCode}");
        if ($jsonData){
            $arData = json_decode($jsonData, true);
            if ($arData[0]['date'] == date('d.m.Y')){
                $R = self::FormatMessage($arData[0], $arData[1], $regionName);
            }
        }

        return $R;
    }

    protected static function FormatMessage($todayData = [], $yesterdayData = [], $regionName)
    {
        $R = false;

        if (!$todayData || !$yesterdayData || !$regionName){
            throw new \Exception('Не указаны данные для формирования сообщения');
        }

        $R  = "{$regionName}: статистика от **{$todayData['date']}**".PHP_EOL;
        $R .= "Новых случаев: **".((int)$todayData['sick'] - (int)$yesterdayData['sick']).'**'.PHP_EOL;
        $R .= "Выявлено всего: **{$todayData['sick']}".'**'.PHP_EOL;
        $R .= "Выздоровело: **{$todayData['healed']}".'**'.PHP_EOL;
        $R .= "Умерло: **{$todayData['died']}".'**'.PHP_EOL;

        return $R;
    }
}
<?php


namespace Covid;


class Discord
{
    public static function Send($params)
    {
        if (!$params['regionCode'] || !$params['regionName'] || !$params['hookUrl']){
            throw new \Exception('Не указан один из параметов вызова функции');
        }

        //Lock file
        $lockFile = __DIR__.'/'.md5($params['hookUrl'].$params['regionCode'] );
        $lastRunDate = file_get_contents($lockFile);
        if ($lastRunDate == date('d.m.Y')){
            return false;
        }

        $todayStats = Data::GetTodayStats($params['regionCode'], $params['regionName']);

        if (!$todayStats){
            return false;
        }

        $postData = [
            'content' => $todayStats,
        ];

        $curl = curl_init();

        $postData = json_encode($postData, JSON_UNESCAPED_UNICODE);

        curl_setopt_array($curl, [
            CURLOPT_URL => $params['hookUrl'],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 5,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_POSTFIELDS => $postData,
            CURLOPT_HTTPHEADER => [
                "Content-Type: application/json",
            ],
        ]);

        $response = curl_exec($curl);
        curl_close($curl);

        file_put_contents($lockFile, date('d.m.Y'));

        return $response;
    }
}

Теперь создаём третий файл (run-spe.php), на который будем обращаться по CRON-у каждую минуту для того, чтобы инициировать отправку статистики заражённых:

<?php

namespace Covid;

header('Content-type: text/html; charset=utf-8');
ini_set('display_errors', 1);

include __DIR__ . '/Data.php';
include __DIR__ . '/Discord.php';

$params = [
    'hookUrl' => "{сюда пишем url вебхука канала, который мы создали}",
    'regionCode' => 'RU-SPE',
    'regionName' => 'Санкт-Петербург',
];

Discord::Send($params);

Собственно всё. Механика работы получается такой:

  • каждую минуту сron обращается к run-spe.php
  • в нём определяются параметры отправки сообщения (по какому вебхуку и с какого региона брать статистику)
  • в классе Discord получаем статистику и если эта статистика ещё не была отправлена в канал дискорд сервера — отправляем
  • здесь фиксатором факта отправки сообщения является дата, которая записывается в файл с названием, хранящимся в переменной $lockFile (смотри класс Discord)

В итоге мы получаем что-то вроде такой статистики, которая отправляется ежедневно: