Перейти к содержанию

Мониторинг доменов в Telegram: бот и скрипт для Keitaro

Мониторинг доменов в Telegram: бот для Keitaro следит за сроками регистрации, SSL, доступностью и блокировками РКН. Плюс скрипт на PHP для своего сервера…

Мониторинг доменов в Telegram: бот и скрипт для Keitaro

Мониторинг доменов в арбитраже нужен, чтобы связка не умерла посреди открутки: не истекла регистрация, не кончился SSL-сертификат, сервер не перестал отвечать, домен не попал в блокировку. Проще всего поручить это Telegram-боту. Он берёт список доменов из Keitaro по API, проверяет их каждый день и пишет, когда пора продлевать или чинить.

Ниже: почему мы сделали своего бота, как подключить @keboostclicksru_bot, как проверить домен вручную и готовый скрипт на PHP, если хотите держать мониторинг на своём сервере.

Четыре шага мониторинга доменов и уведомления в Telegram
Схема мониторинга: список доменов, несколько проверок, подтверждение сбоя и доставка тревоги.

Зачем мониторить домены

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

Что обычно ломается и как это заметить:

Что случилосьЧем грозитКак заметить
Истекла регистрацияДомен перестаёт открываться, клики уходят в пустотуДата окончания в RDAP или WHOIS
Истёк SSL-сертификатБраузер показывает предупреждение вместо лендингаДата окончания сертификата
Сервер не отвечаетТрафик оплачен, а страницы нетРегулярный HTTP-запрос
Блокировка в РоссииПользователи из РФ не откроют сайтРеестр Роскомнадзора
Бан домена в FacebookСсылки с доменом блокируются или открываются с предупреждениемПроверка домена в Facebook
Таблица прокручивается вбок

Keitaro сам выписывает SSL-сертификат после правильной парковки домена (документация по доменам). Но если DNS домена поменяли или регистрация истекла, сертификат может перестать обновляться, поэтому его дату тоже стоит проверять.

Бот @keboostclicksru_bot: как подключить

Бот написан на PHP с базой MariaDB и работает на нашем сервере. Домены в него можно загрузить двумя путями: из трекера по API или списком вручную. Второй путь работает с любым трекером и без трекера вообще.

  1. Откройте бота и нажмите «Запустить».
  2. Выберите «Вставить домены списком» или «Подключить трекер».
  3. Для Keitaro укажите адрес, по которому открываете админку (домен или IP сервера), и ключ Admin API.
  4. Бот загрузит домены, проверит их и дальше будет сам синхронизировать список с трекером.

Где взять ключ: в Keitaro откройте «Пользователи», нажмите «Править» напротив нужного логина и выберите «Создать новый ключ». Второй путь: значок аккаунта в правом верхнем углу, «Профиль → API ключи» (инструкция Keitaro). Учтите, что Admin API есть только на тарифах Expert, Team и Enterprise. На Starter и Advanced загружайте домены списком. Чем тарифы Keitaro отличаются по цене и возможностям, мы разбирали в сравнении трекеров для арбитража.

Что проверяет бот

ПроверкаКогда бот напишет
Срок регистрации (RDAP, для зон без RDAP: WHOIS)За 30, 14, 7, 3 и 1 день до окончания
Домен пропал из реестраПосле трёх проверок подряд с одним результатом
SSL-сертификатЗа 14, 7, 3 и 1 день и после истечения
Доступность сайтаПосле трёх неудачных проверок подряд и при восстановлении
Смена IP-адреса в DNSПри изменении
Блокировка РоскомнадзораПри попадании в реестр и при снятии
Таблица прокручивается вбок

Чтобы не превращаться в спам, бот по умолчанию присылает одну сводку в день. Срочные события он отправляет сразу, но не чаще раза в четыре часа, ночью молчит и пишет не больше шести сообщений в сутки. Частоту можно поменять в /settings. Остальные команды: /domains (список), /check (проверить сейчас), /sync (синхронизировать с трекером), /add (добавить домены), /help.

Как проверить домен вручную

Иногда нужно быстро проверить один домен, не подключая бота. Вот что для этого есть.

Срок регистрации

Большинство доменных зон отвечают по протоколу RDAP. Адрес сервера для каждой зоны публикует IANA в файле dns.json, а сервис rdap.org сам перенаправляет запрос нужному реестру:

curl -sL https://rdap.org/domain/ВАШ_ДОМЕН.com | grep -o '"eventAction": *"expiration", *"eventDate": *"[^"]*"'

У зон .ru, .su и .рф RDAP в списке IANA нет. Для них работает обычный WHOIS, дата окончания стоит в строке paid-till:

whois -h whois.tcinet.ru ВАШ_ДОМЕН.ru | grep paid-till

SSL-сертификат

echo | openssl s_client -connect ВАШ_ДОМЕН:443 -servername ВАШ_ДОМЕН 2>/dev/null | openssl x509 -noout -enddate

Блокировки

Свой мониторинг доменов: скрипт на PHP

Если не хотите отдавать ключ от трекера стороннему боту, поставьте скрипт на свой сервер. Он забирает домены из Keitaro через Admin API (метод GET /admin_api/v1/domains из документации Admin API, ключ передаётся в заголовке Api-Key), проверяет срок регистрации через RDAP, а для зон без RDAP через WHOIS, смотрит дату SSL-сертификата и отвечает ли сайт. Итог приходит в Telegram одним сообщением.

Нужен PHP 8 с расширениями curl, openssl и mbstring. Расширение intl необязательно: с ним скрипт понимает кириллические домены.

<?php
// domain-monitor.php: сводка по доменам Keitaro в Telegram.
// Проверяет срок регистрации (RDAP, для зон без RDAP: WHOIS),
// срок SSL-сертификата и отвечает ли домен вообще.
// Запуск: php domain-monitor.php (раз в сутки по cron).

declare(strict_types=1);

const KEITARO_URL   = 'https://ВАШ_ТРЕКЕР';   // адрес, где открывается админка Keitaro
const KEITARO_KEY   = 'API_KEY';              // Профиль → API ключи; пусто = без Keitaro
const EXTRA_DOMAINS = [];                     // домены вне трекера: ['example.com']
const TG_TOKEN      = '';                     // токен от @BotFather; пусто = печать в консоль
const TG_CHAT       = '';                     // ID чата или группы
const WARN_DOMAIN_DAYS = 30;                  // предупреждать за N дней до конца регистрации
const WARN_SSL_DAYS    = 14;                  // и за N дней до конца сертификата

function http_get(string $url, array $headers = [], bool $verify = true, bool $follow = true): array
{
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => $follow,
        CURLOPT_MAXREDIRS      => 5,
        CURLOPT_PROTOCOLS      => CURLPROTO_HTTP | CURLPROTO_HTTPS,
        CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT        => 20,
        CURLOPT_SSL_VERIFYPEER => $verify,
        CURLOPT_SSL_VERIFYHOST => $verify ? 2 : 0,
        CURLOPT_USERAGENT      => 'domain-monitor/1.0',
    ]);
    $body = curl_exec($ch);
    $result = [(int) curl_getinfo($ch, CURLINFO_HTTP_CODE), is_string($body) ? $body : '', curl_error($ch)];
    curl_close($ch);
    return $result;
}

function keitaro_domains(): array
{
    $url = rtrim(KEITARO_URL, '/') . '/admin_api/v1/domains';
    $parts = parse_url($url);
    if (($parts['scheme'] ?? '') !== 'https' || isset($parts['user']) || isset($parts['pass'])) {
        throw new RuntimeException('Admin API требует HTTPS без логина и пароля в URL');
    }
    // Api-Key не пересылаем вслед за редиректом; TLS всегда проверяется.
    [$code, $body] = http_get($url, ['Api-Key: ' . KEITARO_KEY], true, false);
    if ($code !== 200) {
        throw new RuntimeException("Keitaro Admin API ответил HTTP $code");
    }
    $names = [];
    foreach (json_decode($body, true, 512, JSON_THROW_ON_ERROR) as $domain) {
        if (($domain['state'] ?? 'active') === 'active') {
            $name = strtolower($domain['name']);
            if (function_exists('idn_to_ascii') && preg_match('/[^\x00-\x7F]/', $name)) {
                $name = idn_to_ascii($name) ?: $name; // кириллические домены в punycode
            }
            $names[] = $name;
        }
    }
    return $names;
}

// land.example.com → example.com. Для зон вида co.uk допишите исключения сами.
function base_domain(string $host): string
{
    return implode('.', array_slice(explode('.', $host), -2));
}

function rdap_base(string $tld): ?string
{
    static $map = null;
    if ($map === null) {
        $map = [];
        [, $body] = http_get('https://data.iana.org/rdap/dns.json');
        foreach (json_decode($body, true)['services'] ?? [] as [$tlds, $urls]) {
            foreach ($tlds as $t) {
                $map[$t] = rtrim($urls[0], '/') . '/';
            }
        }
    }
    return $map[$tld] ?? null;
}

function whois_query(string $server, string $query): string
{
    $fp = @fsockopen($server, 43, $errno, $errstr, 10);
    if (!$fp) {
        return '';
    }
    stream_set_timeout($fp, 10);
    fwrite($fp, $query . "\r\n");
    $out = (string) stream_get_contents($fp);
    fclose($fp);
    return $out;
}

// Дата окончания регистрации (unix time) или null, если узнать не удалось
function domain_expiry(string $domain): ?int
{
    $tld  = substr((string) strrchr($domain, '.'), 1);
    $base = rdap_base($tld);
    if ($base !== null) {
        usleep(300000); // не частим: реестры ограничивают число запросов
        [$code, $body] = http_get($base . 'domain/' . $domain, ['Accept: application/rdap+json']);
        if ($code === 200) {
            foreach (json_decode($body, true)['events'] ?? [] as $event) {
                if (($event['eventAction'] ?? '') === 'expiration') {
                    return strtotime($event['eventDate']) ?: null;
                }
            }
        }
        return null;
    }
    // Зоны без RDAP (например .ru): спрашиваем у IANA адрес WHOIS-сервера зоны
    if (!preg_match('/^whois:\s*(\S+)/mi', whois_query('whois.iana.org', $tld), $m)) {
        return null;
    }
    $raw = whois_query($m[1], $domain);
    if (preg_match('/^\s*(?:Registry Expiry Date|Registrar Registration Expiration Date|Expiration Date|Expiry Date|paid-till)\s*:\s*(\S+)/mi', $raw, $m)) {
        return strtotime($m[1]) ?: null;
    }
    return null;
}

function ssl_expiry(string $host): ?int
{
    $ctx = stream_context_create(['ssl' => [
        'capture_peer_cert' => true,
        'peer_name'         => $host,
        'verify_peer'       => false, // хотим увидеть и просроченный сертификат
        'verify_peer_name'  => false,
    ]]);
    $fp = @stream_socket_client("ssl://$host:443", $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $ctx);
    if (!$fp) {
        return null;
    }
    $cert = stream_context_get_params($fp)['options']['ssl']['peer_certificate'] ?? null;
    fclose($fp);
    $info = $cert ? openssl_x509_parse($cert) : false;
    return $info ? (int) $info['validTo_time_t'] : null;
}

function days_left(int $ts): int
{
    return (int) floor(($ts - time()) / 86400);
}

function send_telegram(string $text): void
{
    if (TG_TOKEN === '') {
        echo $text, "\n\n";
        return;
    }
    $ch = curl_init('https://api.telegram.org/bot' . TG_TOKEN . '/sendMessage');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => http_build_query(['chat_id' => TG_CHAT, 'text' => $text, 'parse_mode' => 'HTML']),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 15,
    ]);
    $answer = json_decode((string) curl_exec($ch), true);
    if (empty($answer['ok'])) {
        fwrite(STDERR, 'Telegram: ' . ($answer['description'] ?? 'нет ответа') . "\n");
    }
}

try {
    $hosts = KEITARO_KEY === '' ? [] : keitaro_domains(); // без ключа: только EXTRA_DOMAINS
} catch (Throwable $e) {
    send_telegram('⚠️ Мониторинг доменов: ' . htmlspecialchars($e->getMessage(), ENT_NOQUOTES, 'UTF-8'));
    exit(1);
}
$hosts = array_values(array_unique(array_merge($hosts, EXTRA_DOMAINS)));
$problems = [];
$checkedBase = [];

foreach ($hosts as $host) {
    $h = htmlspecialchars($host, ENT_NOQUOTES, 'UTF-8');

    $base = base_domain($host);
    if (!isset($checkedBase[$base])) {
        $checkedBase[$base] = true;
        $exp = domain_expiry($base);
        if ($exp === null) {
            $problems[] = "❔ $base: не удалось узнать срок регистрации, проверьте вручную";
        } elseif (days_left($exp) <= WARN_DOMAIN_DAYS) {
            $problems[] = "⏳ $base: регистрация до " . date('d.m.Y', $exp) . ' (осталось ' . days_left($exp) . ' дн.)';
        }
    }

    $ssl = ssl_expiry($host);
    if ($ssl === null) {
        $problems[] = "🚫 $h: нет ответа по HTTPS";
        continue;
    }
    if (days_left($ssl) <= WARN_SSL_DAYS) {
        $problems[] = "🔒 $h: сертификат до " . date('d.m.Y', $ssl) . ' (осталось ' . days_left($ssl) . ' дн.)';
    }

    // 4xx не считаем ошибкой: домен Keitaro без Index-кампании может отвечать 404
    [$code, , $error] = http_get("https://$host/", [], false);
    if ($code === 0 || $code >= 500) {
        $problems[] = "🚫 $h: сайт не открывается (" . ($code ? "HTTP $code" : htmlspecialchars($error, ENT_NOQUOTES, 'UTF-8')) . ')';
    }
}

$header = '<b>Домены: ' . count($hosts) . ', проблем: ' . count($problems) . "</b>\n";
$chunk = $header;
foreach ($problems as $line) {
    if (mb_strlen($chunk . $line) > 3800) { // лимит Telegram 4096 символов
        send_telegram($chunk);
        $chunk = '';
    }
    $chunk .= $line . "\n";
}
send_telegram($problems ? $chunk : $header . 'Всё в порядке ✅');

Как запустить

  1. Создайте бота у @BotFather командой /newbot и сохраните токен.
  2. Напишите боту любое сообщение (или добавьте его в рабочую группу), откройте в браузере https://api.telegram.org/botТОКЕН/getUpdates и возьмите значение chat.id. У групп оно отрицательное. Этот способ описан и в документации Keitaro.
  3. Впишите в константы адрес трекера и ключ API. Для Admin API используйте доверенное доменное имя с действительным сертификатом. Не отключайте проверку TLS на постоянной основе ради доступа по IP: с этим запросом передаётся ключ трекера.
  4. Запустите php domain-monitor.php с пустым TG_TOKEN: отчёт напечатается в консоль. Когда всё устроит, впишите токен и ID чата.
  5. Добавьте задание в cron, например каждый день в 10:00: 0 10 * * * php /opt/monitor/domain-monitor.php >> /var/log/domain-monitor.log 2>&1

Без Keitaro скрипт тоже работает: оставьте KEITARO_KEY пустым и перечислите домены в EXTRA_DOMAINS.

Храните скрипт с ключами вне публичной папки сайта. В примере запрос Admin API разрешён только по HTTPS с проверкой сертификата и без перехода по редиректам: заголовок Api-Key не уйдёт на другой адрес. При ответе 3xx задайте конечный доверенный HTTPS-адрес трекера, а не отключайте проверку. Проверка срока сертификата здесь не подтверждает правильность его цепочки и имени, а игнорирование 4xx допустимо только для выбранного технического URL, не для контроля рабочего лендинга.

Что учтено в коде:

  • Ответы 4xx не считаются ошибкой: домен Keitaro без Index-кампании может отвечать 404, и это нормально. Тревога поднимается, если сайт не ответил совсем или вернул 5xx.
  • Сертификат читается без проверки цепочки, иначе просроченный сертификат выглядел бы как «нет ответа», а не как «истёк».
  • Поддомены проверяются по основному домену: land.example.com продлевается вместе с example.com. Для зон вида co.uk правило base_domain нужно дописать.
  • Длинный отчёт режется на части: у Telegram лимит 4096 символов на сообщение (Bot API, метод sendMessage).

Встроенный мониторинг в Keitaro

У Keitaro есть свой мониторинг потоков на тарифах Expert, Team и Enterprise. Он проверяет страницы, на которые ведёт поток: лендинги и офферы по внешней ссылке или любую другую страницу. Условия: страница недоступна (ответ 4xx или 5xx), содержит или не содержит заданный текст. Действия: отключить поток, заменить домен или URL, отправить вебхук. Минимальный интервал проверки одна минута.

Этот мониторинг следит за тем, куда ведёт поток, но не за сроками регистрации и сертификатов ваших доменов. Поэтому он хорошо дополняет бота, а не заменяет его. Покупать и парковать домены прямо из трекера позволяет интеграция с Namecheap, она тоже доступна с тарифа Expert.

Пожелания по боту присылайте в наш Telegram-канал. Другой наш бот для Keitaro ищет нулевые апрувы и пишет о них в чат команды.

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

Частые вопросы

Как проверить, заблокирован ли домен?

Для России Keitaro советует официальный сервис Роскомнадзора blocklist.rkn.gov.ru. Бан в Facebook покажет проверка домена в Facebook, а пометку об опасном сайте в Chrome покажет отчёт Google Safe Browsing.

Где взять API-ключ Keitaro для бота?

«Пользователи» → «Править» → «Создать новый ключ» или «Профиль → API ключи». Admin API доступен на тарифах Expert, Team и Enterprise.

Можно ли мониторить домены без Keitaro?

Да. В боте выберите «Вставить домены списком», а в скрипте оставьте ключ пустым и перечислите домены в EXTRA_DOMAINS.

Почему не определяется срок регистрации домена .ru?

У зон .ru, .su и .рф нет RDAP-сервера в списке IANA. Скрипт в этом случае спрашивает WHOIS-сервер зоны и берёт дату из строки paid-till.

Как часто бот присылает уведомления?

По умолчанию одна сводка в день, срочные события сразу, но не чаще раза в четыре часа. Ночью бот молчит и пишет не больше шести сообщений в сутки. Режим меняется в /settings.

Чем мониторинг доменов отличается от мониторинга потоков Keitaro?

Мониторинг потоков проверяет страницы, на которые ведёт поток, и умеет подменить URL. Мониторинг доменов следит за регистрацией, сертификатами и доступностью самих доменов.

Редакция BoostClicks

Материалы о настройке рекламы, трекинге и автоматизации: инструкции, примеры кода и расчёты с указанием условий проверки.