Робимо інструмент перевірки аптайму сайтів з сповіщеннями у Telegram
Зміст
- Таблиця з переліком сайтів для перевірки
- Власний чат-бот в Telegram
- Скрипт перевірки
- Налаштовуємо періодичність виконання скрипта
Був в мене клієнт в якого постійно лягав сайт, через що частина бюджету на рекламу уходила в нікуди. Звісно, гугл рано чи пізно відхиляє оголошення, що ведуть на непрацюючий сайт, але робить він це за дивною логікою і не дуже оперативно + не в усіх форматах реклами
Тоді я знайшов безкоштовний аптайм чекер, додав туди 1 сайт і забув про цю проблему, поки в мене не з’явився інший клієнт з постійно падаючим сайтом
Оскільки безкоштовний тариф більшості сервісів дозволяє додати лише 1 сайт, виникла ідея зробити рішення, яке буде автоматично перевіряти велику кількість сайтів за логікою:
додали пару десятків сайтів в гугл док > скрипт перевіряє всі сайти зі списку кожні N-хвилин > якщо знайшов помилку завантаження сторінки – відправляє сповіщення у телеграм
Власне саме таке рішення ми і будемо зараз розгортати
1. Таблиця з переліком сайтів
Спочатку нам потрібно створити просту гугл таблицю з двома вкладками “Сайти” та “Лог”
Тут важливо зберегти порядок та назви вкладок, вони зашиті у скрипт, назва самого документу не принципова. Можете просто скопіювати собі готовий шаблон
На даному етапі нам потрібно скопіювати ID саме вашої копії документу – частину адреси сторінки що виділено жирним
docs.google.com/spreadsheets/d/ 1czRW1o2… /edit
2. Власний чат-бот в Telegram
Щоб отримувати сповіщення потрібно створити свій окремий бот в який ви будете отримувати всі сповіщення скрипта
До речі, цей бот можна буде використовувати і для інших сповіщень, від інших скриптів, наприклад, я отримую туди сповіщення про низький баланс на акаунтах, або міні-звіти по проектах
- В пошуку по контактах вводимо @BotFather
- Тиснемо /start, потім /newbot
- Вводимо ім’я/назву бота
- Вводимо унікальний та ніким не зайнятий юзернейм бота, який закінчується на bot (наприклад, My_Bot)
- Чат видає нам токен — він виглядає як 12345678910:ABCD… — копіюємо його кудись для наступного етапу
Коли ми створили бота потрібно дізнатись ID чату:
- Запускаємо свого бота прописавши /start
- В рядок браузера вставляємо api.telegram.org/bot 12345678910:ABCD… /getUpdates
- Браузер видасть текст де буде частина “chat”: { “id”: 123456789, …} — ось ці цифри і будуть id чату — зберігаємо їх для наступного кроку
3. Скрипт перевірки
- Переходимо на сторінку Google Apps Script
- Вставляємо у поле готовий текст скрипту замінивші дві змінні на самому початку коду:
const TELEGRAM_TOKEN = ‘12345678910:ABCD…‘;
const CHAT_ID = ‘123456789‘;
Сам код скрипта:
const TELEGRAM_TOKEN = '12345:ABCDF'; const CHAT_ID = '12345678'; // Назви аркушів const SITES_SHEET_NAME = 'Сайти'; const LOG_SHEET_NAME = 'Лог'; // Максимальна кількість сайтів у партії const BATCH_SIZE = 50; function checkWebsites() { Logger.log(`Запуск checkWebsites: ${new Date()}`); const startTime = new Date(); const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); // Отримуємо аркуш із сайтами let sitesSheet; try { sitesSheet = spreadsheet.getSheetByName(SITES_SHEET_NAME); if (!sitesSheet) { sitesSheet = spreadsheet.insertSheet(SITES_SHEET_NAME); sitesSheet.appendRow(['URL', 'Код відповіді', 'Час перевірки', 'Статус', 'Пояснення']); } } catch (error) { Logger.log(`Помилка доступу до аркуша Сайти: ${error}`); return; } // Отримуємо аркуш для логів let logSheet; try { logSheet = spreadsheet.getSheetByName(LOG_SHEET_NAME); if (!logSheet) { logSheet = spreadsheet.insertSheet(LOG_SHEET_NAME); logSheet.appendRow(['Час', 'URL', 'Статус', 'Код відповіді', 'Пояснення']); } } catch (error) { Logger.log(`Помилка доступу до аркуша Лог: ${error}`); return; } let values; try { values = sitesSheet.getRange('A2:A').getValues(); } catch (error) { Logger.log(`Помилка читання URL: ${error}`); return; } const urls = []; const indices = []; // Збираємо непорожні URL for (let i = 0; i < values.length; i++) { let url = values[i][0]; if (!url) continue; url = normalizeUrl(url); urls.push({ url: url, muteHttpExceptions: true }); indices.push(i); } if (urls.length === 0) { Logger.log('Немає URL для перевірки'); return; } if (urls.length > 60) { Logger.log(`Попередження: Кількість сайтів (${urls.length}) перевищує рекомендований ліміт 60`); } // Обробляємо URL партіями for (let batchStart = 0; batchStart < urls.length; batchStart += BATCH_SIZE) { const batchUrls = urls.slice(batchStart, batchStart + BATCH_SIZE); const batchIndices = indices.slice(batchStart, batchStart + BATCH_SIZE); // Виконуємо запити для партії const timestamp = new Date(); const formattedTime = Utilities.formatDate(timestamp, 'Europe/Kiev', 'dd.MM.yyyy HH:mm:ss'); let responses = []; try { Logger.log(`Виконання fetchAll для партії ${batchStart / BATCH_SIZE + 1}`); responses = UrlFetchApp.fetchAll(batchUrls); } catch (error) { Logger.log(`Помилка fetchAll: ${error}`); // Перевіряємо кожен URL окремо for (let j = 0; j < batchUrls.length; j++) { try { responses[j] = UrlFetchApp.fetch(batchUrls[j].url, { muteHttpExceptions: true }); } catch (e) { Logger.log(`Помилка для ${batchUrls[j].url}: ${e}`); responses[j] = null; } } } // Обробляємо відповіді for (let j = 0; j < responses.length; j++) { const i = batchIndices[j]; let url = batchUrls[j].url; let code, explanation; // Спроба з http:// if (!responses[j]) { url = url.replace('https://', 'http://'); try { const fallbackResponse = UrlFetchApp.fetch(url, { muteHttpExceptions: true }); responses[j] = fallbackResponse; } catch (error) { Logger.log(`Помилка для ${url} (http): ${error}`); } } try { if (responses[j]) { code = responses[j].getResponseCode(); explanation = getStatusExplanation(code); } else { code = '❌'; explanation = 'Помилка запиту (адреса недоступна)'; } } catch (error) { code = '❌'; explanation = 'Помилка запиту (адреса недоступна)'; Logger.log(`Помилка обробки відповіді для ${url}: ${error}`); } const previousCode = sitesSheet.getRange(i + 2, 2).getValue() || 200; // Записуємо статус try { sitesSheet.getRange(i + 2, 2).setValue(code); sitesSheet.getRange(i + 2, 3).setValue(formattedTime); sitesSheet.getRange(i + 2, 4).setValue(code === 200 ? '✅ Працює' : '⚠️ Проблема'); sitesSheet.getRange(i + 2, 5).setValue(explanation); } catch (error) { Logger.log(`Помилка запису в аркуш Сайти для ${url}: ${error}`); } // Логування збою if (code !== 200 && previousCode === 200) { const message = `❗ Збій сайту:\n${url}\nЧас: ${formattedTime}\nКод: ${code}\nПояснення: ${explanation} (${code})`; sendTelegramMessage(message); try { logSheet.appendRow([formattedTime, url, 'Збій', code, explanation]); } catch (error) { Logger.log(`Помилка запису в аркуш Лог: ${error}`); } } // Логування відновлення else if (code === 200 && previousCode !== 200) { const message = `✅ Сайт відновлено:\n${url}\nЧас: ${formattedTime}`; sendTelegramMessage(message); try { logSheet.appendRow([formattedTime, url, 'Відновлення', code, 'Сайт знову працює']); } catch (error) { Logger.log(`Помилка запису в аркуш Лог: ${error}`); } } } // Затримка між партіями if (batchStart + BATCH_SIZE < urls.length) { Utilities.sleep(1000); } } Logger.log(`Завершення checkWebsites: тривалість ${(new Date() - startTime) / 1000} секунд`); } // Функція для нормалізації URL function normalizeUrl(url) { url = url.trim(); if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'https://' + url; } return url; } function getStatusExplanation(code) { const explanations = { 200: 'Успішно', 301: 'Постійне перенаправлення', 302: 'Тимчасове перенаправлення', 400: 'Неправильний запит', 401: 'Неавторизовано', 403: 'Заборонено (немає доступу)', 404: 'Сторінку не знайдено', 408: 'Час запиту вичерпано', 410: 'Сторінку видалено', 429: 'Забагато запитів', 500: 'Внутрішня помилка сервера', 502: 'Помилка шлюзу (Bad Gateway)', 503: 'Сервер тимчасово недоступний', 504: 'Час очікування шлюзу вичерпано' }; return explanations[code] || 'Невідома помилка'; } function sendTelegramMessage(message) { const url = `https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage`; const payload = { chat_id: CHAT_ID, text: message, parse_mode: 'HTML' }; try { UrlFetchApp.fetch(url, { method: 'post', contentType: 'application/json', payload: JSON.stringify(payload), muteHttpExceptions: true }); } catch (error) { Logger.log(`Помилка надсилання повідомлення в Telegram: ${error}`); } } // Функція для налаштування тригера function setTrigger() { Logger.log('Налаштування тригера'); const triggers = ScriptApp.getProjectTriggers(); for (const trigger of triggers) { if (trigger.getHandlerFunction() === 'checkWebsites') { ScriptApp.deleteTrigger(trigger); Logger.log('Видалено старий тригер'); } } try { ScriptApp.newTrigger('checkWebsites') .timeBased() .everyMinutes(10) .create(); Logger.log('Тригер створено'); } catch (error) { Logger.log(`Помилка створення тригера: ${error}`); } }
- Зберігаємо скрипт на гугл диску через кнопку іконки з дискетою трохи вище коду
- Активуємо скрипт синьою кнопкою “Ввести в дію” в правому верхньому куті, далі всюди тиснемо “Ок”
4. Налаштовуємо періодичність виконання скрипта
- В бічній панелі праворуч обираємо пункт “Тригери” тобто умови виконання скрипта
- В правому нижньому куті тиснемо синю кнопку “Додати тригер” і обираємо періодичність виконання скрипта, оптимальний варіант не частіше 10 хвилин + обираємо, щоб сервіс негайно сповіщав вас про помилки виконання, ці сповіщення будуть вже не у телеграм, а на пошту
Все, готово) Для перевірки додайте в гугл док посилання на неіснуючий або неробочій сайт, після чого або зачекайте спрацювання тригера, або запустіть скрипт вручну, в Apps Script є кнопка play для запуску скрипта в редакторі
Під самим редактором можна побачити лог з помилками, якщо вони будуть