Packagist 2 Telegram notifier

- Pubblicato in coding di - Permalink

Una precisazione importante

Questo sistema non significa che una persona abbia installato la libreria. Il contatore di Packagist misura i download e non identifica il singolo utilizzatore. Inoltre un singolo progetto/CI può effettuare più download.

Quindi la notifica va interpretata come:

“Packagist ha registrato X download in più dall'ultimo controllo”

Alla prima esecuzione si vedrà qualcosa come:

Prima esecuzione.
Download attuali: 00001

Non si riceveranno notifiche: lo script sta semplicemente memorizzando il valore iniziale.

Dalla volta successiva, se Packagist è passato da 00001 a 00002, lo script invierà il messaggio al Bot specificato nel codice.

Script:

<?php

declare(strict_types=1);

/*
 * Packagist 2 Telegram notifier
 *
 * Requisiti:
 * - PHP 8+
 * - cURL
 * - un bot Telegram
 *
 * Esecuzione:
 * php packagist-telegram.php
 */

$package = 'vendor/nome-pacchetto';

$telegramBotToken = 'BOT_TOKEN';
$telegramChatId   = 'CHAT_ID';

$stateFile = __DIR__ . '/packagist-state.json';

function queryJson(string $url): array
{
    $ch = curl_init($url);

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER  =>  true,
        CURLOPT_FOLLOWLOCATION  =>  true,
        CURLOPT_TIMEOUT         =>  15,
        CURLOPT_HTTPHEADER      =>  [
            'Accept: application/json',
            'User-Agent: PackagistTelegramNotifier/1.0',
        ],
    ]);

    $response = curl_exec($ch);

    if ($response === false) {
        $error = curl_error($ch);
        curl_close($ch);

        throw new RuntimeException('cURL error: ' . $error);
    }

    $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($statusCode < 200 OR $statusCode >= 300) throw new RuntimeException("HTTP error {$statusCode} ricevuto da Packagist");

    $data = json_decode($response, true);

    if (!is_array($data)) throw new RuntimeException('Risposta JSON non valida.');

    return $data;
}

function send2Telegram(string $botToken, string $chatId, string $message): void
{
    $url = "https://api.telegram.org/bot{$botToken}/sendMessage";

    $ch = curl_init($url);

    curl_setopt_array($ch, [
        CURLOPT_POST            =>  true,
        CURLOPT_RETURNTRANSFER  =>  true,
        CURLOPT_TIMEOUT         =>  15,
        CURLOPT_POSTFIELDS      =>  [
            'chat_id'       =>  $chatId,
            'text'          =>  $message,
            'parse_mode'    =>  'HTML',
        ]
    ]);

    $response = curl_exec($ch);

    if ($response === false) {
        $error = curl_error($ch);
        curl_close($ch);

        throw new RuntimeException('Errore Telegram: ' . $error);
    }

    $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($statusCode < 200 OR $statusCode >= 300) throw new RuntimeException("Telegram ha restituito HTTP {$statusCode}: {$response}");
}

function loadState(string $file): ?int
{
    if (!file_exists($file)) return;

    $content = file_get_contents($file);

    if ($content === false) return;

    $data = json_decode($content, true);

    if (!is_array($data) OR !isset($data['downloads'])) return;

    return (int)$data['downloads'];
}

function saveState(string $file, int $downloads): void
{
    $data = [
        'downloads'     =>  $downloads,
        'updated_at'    =>  date(DATE_ATOM),
    ];

    file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX);
}

try {
    $url = 'https://packagist.org/packages/' . rawurlencode($package) . '.json';

    $data = queryJson($url);

    if (!isset($data['package']['downloads']['total'])) throw new RuntimeException('Il campo downloads.total non è presente nella risposta di Packagist.');

    $currentDownloads = (int)$data['package']['downloads']['total'];

    $previousDownloads = loadState($stateFile);

    if ($previousDownloads === null) {
        saveState($stateFile, $currentDownloads);

        echo "Prima esecuzione.\n";
        echo "Download attuali: {$currentDownloads}\n";

        exit(0);
    }

    if ($currentDownloads <= $previousDownloads) {
        echo sprintf("[%s] Nessun nuovo download. Totale: %d\n", date('Y-m-d H:i:s'), $currentDownloads);

        saveState($stateFile, $currentDownloads);

        exit(0);
    }

    $newDownloads = $currentDownloads - $previousDownloads;

    $message = sprintf("<b>%s</b>\n\n<b>+%d nuovi download</b>\nTotale: <b>%d</b>\n %s", htmlspecialchars($package, ENT_QUOTES, 'UTF-8'), $newDownloads, $currentDownloads, date('d/m/Y H:i:s'));

    send2Telegram($telegramBotToken, $telegramChatId, $message);

    saveState($stateFile, $currentDownloads);

    echo sprintf("[%s] Notifica inviata: +%d download. Totale: %d\n", date('Y-m-d H:i:s'), $newDownloads, $currentDownloads);
} catch (Throwable $e) {
    fwrite(STDERR, '[' . date('Y-m-d H:i:s') . '] ERRORE: ' . $e->getMessage() . PHP_EOL);

    exit(1);
}
Etichette:
→