У цій інструкції покажу, як розгорнути простий скрипт Google Apps Script, який щодня записуватиме готівковий курс ПриватБанку у відділеннях (USD та EUR) у ваш Google Spreadsheet. Рішення працює на офіційному ендпоінті pubinfo і не потребує серверів чи додаткових сервісів — лише Таблиця та вбудовані тригери Apps Script.
Використовується готівковий курс ПриватБанку у відділеннях (тільки USD та EUR) з ендпоінта:
https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=5
Примітка: цей ендпоінт не приймає дату у запиті — він повертає поточні значення «на зараз». Тож для історії з минулими датами скрипт зберігатиме значення щодня під час запуску.
/** * PrivatBank Cash (branches) — USD & EUR only * Endpoint: https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=5 * Sheet: CashRates_Privat * Timezone: Europe/Kyiv */
const PB_CASH = {
URL: 'https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=5',
TIMEZONE: 'Europe/Kyiv',
SHEET: 'CashRates_Privat',
CURRENCIES: ['USD', 'EUR'],
DAILY_HOUR: 9,
DAILY_MINUTE: 5
};
/** Головна ініціалізація: створює лист, заголовки, тягне курси і записує їх */
function setupAndRunOnce() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
if (!ss) {
throw new Error('ActiveSpreadsheet == null. Відкрийте Google Таблицю і запустіть скрипт як container-bound: Таблиця → Розширення → Apps Script.');
}
Logger.log('Активна таблиця: ' + ss.getName());
const sheet = getOrCreateSheet_();
ensureHeader_(sheet);
const dateStr = Utilities.formatDate(new Date(), PB_CASH.TIMEZONE, 'dd.MM.yyyy');
const rates = fetchCash_();
Logger.log('Отримано курси: ' + JSON.stringify(rates));
upsertRow_(dateStr, rates);
SpreadsheetApp.getActiveSpreadsheet().toast('Готівковий курс оновлено: ' + dateStr);
Logger.log('Готово. Дані записані у аркуш: ' + PB_CASH.SHEET);
}
/** Меню у таблиці */
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Готівковий курс')
.addItem('Оновити зараз', 'updateCashToday')
.addItem('Увімкнути щоденне оновлення', 'enableDailyCashTrigger')
.addItem('Вимкнути оновлення', 'disableDailyCashTriggers')
.addSeparator()
.addItem('Заповнити історію (останні 30 днів)', 'backfillCashLast30Days')
.addToUi();
}
/** Отримати курс за сьогодні та записати/оновити рядок */
function updateCashToday() {
const sheet = getOrCreateSheet_();
ensureHeader_(sheet);
const date = new Date();
const dateStr = Utilities.formatDate(date, PB_CASH.TIMEZONE, 'dd.MM.yyyy');
const rates = fetchCash_();
Logger.log('updateCashToday(): ' + dateStr + ' ' + JSON.stringify(rates));
upsertRow_(dateStr, rates);
SpreadsheetApp.getActiveSpreadsheet().toast('Готівковий курс оновлено: ' + dateStr);
}
/** Ввімкнути щоденний тригер */
function enableDailyCashTrigger() {
disableDailyCashTriggers();
ScriptApp.newTrigger('updateCashToday')
.timeBased()
.atHour(PB_CASH.DAILY_HOUR)
.nearMinute(PB_CASH.DAILY_MINUTE)
.everyDays(1)
.inTimezone(PB_CASH.TIMEZONE)
.create();
SpreadsheetApp.getActiveSpreadsheet().toast('Щоденне оновлення ввімкнено (щодня о ' + PB_CASH.DAILY_HOUR + ':' + ('0' + PB_CASH.DAILY_MINUTE).slice(-2) + ').');
}
/** Вимкнути тригери */
function disableDailyCashTriggers() {
ScriptApp.getProjectTriggers().forEach(t => {
if (t.getHandlerFunction() === 'updateCashToday') ScriptApp.deleteTrigger(t);
});
SpreadsheetApp.getActiveSpreadsheet().toast('Щоденні оновлення вимкнено.');
}
/** Історія за останні 30 днів (увага: pubinfo не дає дані «на дату») */
function backfillCashLast30Days() {
const sheet = getOrCreateSheet_();
ensureHeader_(sheet);
const end = new Date();
const start = new Date();
start.setDate(end.getDate() - 29);
const cursor = new Date(start);
while (cursor <= end) {
const dateStr = Utilities.formatDate(cursor, PB_CASH.TIMEZONE, 'dd.MM.yyyy');
// pubinfo повертає поточні значення; історичні треба вести власноруч
const rates = fetchCash_();
Logger.log('backfill ' + dateStr + ': ' + JSON.stringify(rates));
upsertRow_(dateStr, rates);
Utilities.sleep(300);
cursor.setDate(cursor.getDate() + 1);
}
SpreadsheetApp.getActiveSpreadsheet().toast('Бекфіл виконано (останні 30 днів).');
}
/** Витягнення готівкового курсу USD/EUR з pubinfo */
function fetchCash_() {
let resp, code, body;
for (let i = 1; i <= 3; i++) {
resp = UrlFetchApp.fetch(PB_CASH.URL, { method: 'get', muteHttpExceptions: true });
code = resp.getResponseCode();
if (code === 200) break;
Utilities.sleep(400 * i);
}
if (code !== 200) throw new Error('PrivatBank pubinfo HTTP ' + code);
body = resp.getContentText('utf-8');
const arr = JSON.parse(body); // [{ccy, base_ccy, buy, sale}, ...]
const out = {};
PB_CASH.CURRENCIES.forEach(c => (out[c] = { buy: '', sale: '' }));
arr.forEach(item => {
const c = (item.ccy || '').toUpperCase();
if (PB_CASH.CURRENCIES.indexOf(c) === -1) return;
out[c] = {
buy: toNumber_(item.buy),
sale: toNumber_(item.sale)
};
});
return out; // {USD:{buy,sale}, EUR:{buy,sale}}
}
/** Отримати або створити аркуш */
function getOrCreateSheet_() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
if (!ss) throw new Error('ActiveSpreadsheet == null. Запускайте скрипт з контексту Таблиці.');
let sheet = ss.getSheetByName(PB_CASH.SHEET);
if (!sheet) {
Logger.log('Створюю аркуш: ' + PB_CASH.SHEET);
sheet = ss.insertSheet(PB_CASH.SHEET);
}
return sheet;
}
/** Заголовок — закріплюємо перший рядок */
function ensureHeader_(sheet) {
const header = ['Дата', 'USD Покупка', 'USD Продажа', 'EUR Покупка', 'EUR Продажа'];
if (sheet.getLastRow() === 0) {
sheet.getRange(1, 1, 1, header.length).setValues([header]);
sheet.setFrozenRows(1);
return;
}
const width = header.length;
const existing = sheet.getRange(1, 1, 1, width).getValues()[0];
if (existing.join('|') !== header.join('|')) {
sheet.insertRowBefore(1);
sheet.getRange(1, 1, 1, width).setValues([header]);
sheet.setFrozenRows(1);
}
}
/** Додати або оновити рядок для конкретної дати */
function upsertRow_(dateStr, rates) {
const sheet = getOrCreateSheet_();
const rowIdx = findRowByDate_(sheet, dateStr);
const rowValues = [
dateStr,
rates.USD?.buy ?? '',
rates.USD?.sale ?? '',
rates.EUR?.buy ?? '',
rates.EUR?.sale ?? ''
];
if (rowIdx > 0) {
sheet.getRange(rowIdx, 1, 1, rowValues.length).setValues([rowValues]);
} else {
sheet.appendRow(rowValues);
}
}
/** Пошук рядка за датою (повертає індекс рядка або -1) */
function findRowByDate_(sheet, dateStr) {
const last = sheet.getLastRow();
if (last < 2) return -1;
const range = sheet.getRange(2, 1, last - 1, 1).getValues();
for (let i = 0; i < range.length; i++) {
if ((range[i][0] + '') === dateStr) return i + 2;
}
return -1;
}
/** Перетворити текст у число або '' */
function toNumber_(v) {
if (v === null || v === undefined) return '';
const n = Number((v + '').replace(',', '.'));
return isNaN(n) ? '' : n;
}
DAILY_HOUR та DAILY_MINUTE у константі PB_CASH.SHEET на бажану — наприклад, PB_Cash_USD_EUR.'dd.MM.yyyy' на будь-який інший.CURRENCIES, але пам’ятайте, ви просили тільки USD/EUR — інші не потрібні.Чи зберігаються історичні значення?
Так, але лише з моменту запуску тригера. Ендпоінт pubinfo не надає значення «на дату», тож скрипт щодня записує поточний курс за сьогоднішню дату.
Як змінити час щоденного оновлення?
Установіть нові значення DAILY_HOUR та DAILY_MINUTE і знову скористайтеся пунктом меню «Увімкнути щоденне оновлення» (скрипт спочатку вимикає старі тригери).
Що робити, якщо меню «Готівковий курс» не з’явилося?
Оновіть вкладку з Таблицею. Якщо не допомагає — відкрийте Apps Script і виконайте onOpen() вручну (Run), потім ще раз оновіть Таблицю.
Цей скрипт — швидкий спосіб отримувати готівковий курс ПриватБанку (USD/EUR) без сервера і складних інтеграцій. Результат одразу готовий до побудови дашбордів, графіків або використання в інших розрахунках вашої Таблиці.