Рецепти
Готові скрипти під часті задачі баєра. Кожен зібраний з методів, описаних на сусідніх сторінках, - скопіюйте, впишіть свої значення на початку скрипта й запускайте.
У всіх рецептах замініть ВАШ_КЛЮЧ і panel.example.com на свої. Як отримати ключ, див. розділ «Вступ до API».
Дати - за UTC
Статистика й конверсії рахуються за днями UTC. «Вчора» в скриптах нижче - це вчора за UTC, а не за вашим часовим поясом.
Статистика за вчора в Telegram
Щоранку надсилає в Telegram підсумки за вчора: візити, встановлення, реєстрації, депозити, виплати й конверсію.
Що знадобиться:
- Свій бот: надішліть @BotFather команду
/newbotі скопіюйте токен. - ID чату, куди надсилати: напишіть боту будь-яке повідомлення, відкрийте
https://api.telegram.org/bot<ТОКЕН>/getUpdatesі візьмітьchat.idз відповіді. Для групи - додайте бота в групу, напишіть у ній повідомлення й візьмітьchat.idгрупи (він від'ємний).
# pip install requests
import datetime
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
BOT_TOKEN = "ТОКЕН_БОТА"
CHAT_ID = "ID_ЧАТУ"
day = (datetime.datetime.now(datetime.timezone.utc).date() - datetime.timedelta(days=1)).isoformat()
response = requests.get(f"{API}/stats/dashboard", headers=HEADERS, params={
"date_from": day,
"date_to": day,
}, timeout=30)
response.raise_for_status()
metrics = response.json()["data"]["metrics"]
payouts = ", ".join(f'{a["amount"]} {a["currency"]}' for a in metrics["total_deposits"]["amounts"]) or "0"
text = "\n".join([
f"PWA Hub за {day}",
f"Візити: {metrics['total_visits']['value']} ({metrics['total_visits']['trend']}%)",
f"Встановлення: {metrics['total_installs']['value']} ({metrics['total_installs']['trend']}%)",
f"Реєстрації: {metrics['total_registrations']['value']}",
f"Депозити: {metrics['total_deposits']['value']} - {payouts}",
f"CR: {metrics['avg_cr']['value']}%",
])
requests.post(f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage", json={
"chat_id": CHAT_ID,
"text": text,
}, timeout=30).raise_for_status()
print("Надіслано:\n" + text)// Node.js 18+, без залежностей; збережіть як stats-telegram.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json' };
const BOT_TOKEN = 'ТОКЕН_БОТА';
const CHAT_ID = 'ID_ЧАТУ';
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
const day = yesterday.toISOString().slice(0, 10);
const response = await fetch(`${API}/stats/dashboard?${new URLSearchParams({ date_from: day, date_to: day })}`, {
headers,
signal: AbortSignal.timeout(30000),
});
if (!response.ok) throw new Error(`PWA Hub відповів ${response.status}`);
const { metrics } = (await response.json()).data;
const payouts = metrics.total_deposits.amounts.map((a) => `${a.amount} ${a.currency}`).join(', ') || '0';
const text = [
`PWA Hub за ${day}`,
`Візити: ${metrics.total_visits.value} (${metrics.total_visits.trend}%)`,
`Встановлення: ${metrics.total_installs.value} (${metrics.total_installs.trend}%)`,
`Реєстрації: ${metrics.total_registrations.value}`,
`Депозити: ${metrics.total_deposits.value} - ${payouts}`,
`CR: ${metrics.avg_cr.value}%`,
].join('\n');
const sent = await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chat_id: CHAT_ID, text }),
signal: AbortSignal.timeout(30000),
});
if (!sent.ok) throw new Error(`Telegram відповів ${sent.status}`);
console.log(`Надіслано:\n${text}`);<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$botToken = 'ТОКЕН_БОТА';
$chatId = 'ID_ЧАТУ';
$day = (new DateTimeImmutable('yesterday', new DateTimeZone('UTC')))->format('Y-m-d');
$response = $api->get('stats/dashboard', ['query' => ['date_from' => $day, 'date_to' => $day]]);
$metrics = json_decode($response->getBody(), true)['data']['metrics'];
$payouts = implode(', ', array_map(
fn($a) => "{$a['amount']} {$a['currency']}",
$metrics['total_deposits']['amounts']
)) ?: '0';
$text = implode("\n", [
"PWA Hub за {$day}",
"Візити: {$metrics['total_visits']['value']} ({$metrics['total_visits']['trend']}%)",
"Встановлення: {$metrics['total_installs']['value']} ({$metrics['total_installs']['trend']}%)",
"Реєстрації: {$metrics['total_registrations']['value']}",
"Депозити: {$metrics['total_deposits']['value']} - {$payouts}",
"CR: {$metrics['avg_cr']['value']}%",
]);
(new GuzzleHttp\Client(['timeout' => 30]))->post("https://api.telegram.org/bot{$botToken}/sendMessage", [
'json' => ['chat_id' => $chatId, 'text' => $text],
]);
echo "Надіслано:\n{$text}" . PHP_EOL;За розкладом. На сервері з Linux додайте рядок у crontab -e - скрипт спрацьовуватиме щодня о 07:00 за часом сервера:
0 7 * * * /usr/bin/python3 /home/user/stats-telegram.pyДля Node.js - /usr/bin/node /home/user/stats-telegram.mjs, для PHP - /usr/bin/php /home/user/stats-telegram.php. Запускайте не раніше 00:10 UTC: свіжі події потрапляють у статистику із затримкою до 5 хвилин.
trend - зміна до попереднього дня у відсотках. Докладно про показники - у розділі «Статистика».
Клонувати PWA, прив’язати домен і ввімкнути
Робить нове PWA з готового за один запуск: клонує його, прив’язує до копії домен і вмикає її. Зручно, коли під кожну зв’язку потрібен свій застосунок зі своїм доменом.
Що знадобиться: id PWA-зразка (зі списку PWA або адресного рядка панелі) і домен для копії.
Це реальні дії в Cloudflare
Прив’язка домену створює зону й DNS-записи у вашому акаунті Cloudflare. Піддомен (go.example.com) прив’язується, лише якщо його кореневий домен (example.com) уже доданий у панель - інакше прийде 404. Подробиці - у розділі «Домени».
# pip install requests
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
SOURCE_ID = "WZAX" # PWA-зразок
NAME = "Promo EN - зв’язка 12" # назва копії
DOMAIN = "fit-tracker-12.example" # домен для копії
def call(method, path, **kwargs):
response = requests.request(method, f"{API}/{path}", headers=HEADERS, timeout=30, **kwargs)
if not response.ok:
raise SystemExit(f"{method} /{path}: {response.status_code} {response.json().get('message')}")
return response.json()["data"]
# Команда зразка потрібна для PUT (team_lead і admin передають team_id завжди)
team_id = call("GET", f"pwas/{SOURCE_ID}")["pwa"]["team"]["id"]
clone = call("POST", f"pwas/{SOURCE_ID}/clone", json={"names": [NAME]})["clones"][0]
print("Копія:", clone["id"], clone["name"])
domain = call("POST", "domains", json={"domain": DOMAIN, "pwa_id": clone["id"]})["domain"]
print("Домен:", domain["domain"])
call("PUT", f"pwas/{clone['id']}", json={"active": True, "team_id": team_id})
print("PWA увімкнено")// Node.js 18+, без залежностей; збережіть як clone-pwa.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
const SOURCE_ID = 'WZAX'; // PWA-зразок
const NAME = 'Promo EN - зв’язка 12'; // назва копії
const DOMAIN = 'fit-tracker-12.example'; // домен для копії
async function call(method, path, body) {
const response = await fetch(`${API}/${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(30000),
});
const json = await response.json();
if (!response.ok) throw new Error(`${method} /${path}: ${response.status} ${json.message}`);
return json.data;
}
// Команда зразка потрібна для PUT (team_lead і admin передають team_id завжди)
const teamId = (await call('GET', `pwas/${SOURCE_ID}`)).pwa.team.id;
const [clone] = (await call('POST', `pwas/${SOURCE_ID}/clone`, { names: [NAME] })).clones;
console.log('Копія:', clone.id, clone.name);
const { domain } = await call('POST', 'domains', { domain: DOMAIN, pwa_id: clone.id });
console.log('Домен:', domain.domain);
await call('PUT', `pwas/${clone.id}`, { active: true, team_id: teamId });
console.log('PWA увімкнено');<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
'timeout' => 30,
'http_errors' => false,
]);
$sourceId = 'WZAX'; // PWA-зразок
$name = 'Promo EN - зв’язка 12'; // назва копії
$domainName = 'fit-tracker-12.example'; // домен для копії
function call(GuzzleHttp\Client $api, string $method, string $path, ?array $body = null): array
{
$response = $api->request($method, $path, $body === null ? [] : ['json' => $body]);
$json = json_decode($response->getBody(), true);
if ($response->getStatusCode() >= 400) {
exit("{$method} /{$path}: {$response->getStatusCode()} {$json['message']}" . PHP_EOL);
}
return $json['data'];
}
// Команда зразка потрібна для PUT (team_lead і admin передають team_id завжди)
$teamId = call($api, 'GET', "pwas/{$sourceId}")['pwa']['team']['id'];
$clone = call($api, 'POST', "pwas/{$sourceId}/clone", ['names' => [$name]])['clones'][0];
echo "Копія: {$clone['id']} {$clone['name']}" . PHP_EOL;
$domain = call($api, 'POST', 'domains', ['domain' => $domainName, 'pwa_id' => $clone['id']])['domain'];
echo "Домен: {$domain['domain']}" . PHP_EOL;
call($api, 'PUT', "pwas/{$clone['id']}", ['active' => true, 'team_id' => $teamId]);
echo 'PWA увімкнено' . PHP_EOL;Копія отримує налаштування, картинки й акаунти зразка, але не потік фільтрації - якщо він потрібен, задайте filter_stream_id у тому ж PUT (див. розділ «PWA»). Нове PWA починає отримувати трафік протягом кількох хвилин після ввімкнення.
Зупинити PWA без депозитів
Проходить по всіх увімкнених PWA й вимикає ті, у яких за останні 7 днів набралося достатньо встановлень, але немає жодного депозиту.
Що знадобиться: вирішити, скільки встановлень вважати достатнім (MIN_INSTALLS). За замовчуванням скрипт працює в режимі перевірки (DRY_RUN = True) і лише друкує, що вимкнув би. Переконайтеся, що список правильний, і змініть на False.
# pip install requests
import datetime
import time
import requests
API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer ВАШ_КЛЮЧ", "Accept": "application/json"}
MIN_INSTALLS = 50 # менше встановлень - рано судити
DRY_RUN = True # True - лише показати, False - вимикати
today = datetime.datetime.now(datetime.timezone.utc).date()
date_from = (today - datetime.timedelta(days=7)).isoformat()
date_to = (today - datetime.timedelta(days=1)).isoformat()
def get(path, params=None):
response = requests.get(f"{API}/{path}", headers=HEADERS, params=params, timeout=30)
if response.status_code == 401:
raise SystemExit("Ключ не прийнято - перевірте його в профілі")
response.raise_for_status()
return response.json()["data"]
pwas, page = [], 1
while True:
data = get("pwas", {"status": "active", "page": page, "excludes": "stats"})
pwas += data["pwas"]
if page >= data["meta"]["last_page"]:
break
page += 1
for pwa in pwas:
totals = get("stats/funnel", {"date_from": date_from, "date_to": date_to, "pwa_id": pwa["id"]})["funnel"]["totals"]
time.sleep(0.25) # не більше 300 запитів на хвилину
if totals["installs"] < MIN_INSTALLS or totals["deps"] > 0:
continue
print(f"{pwa['id']} {pwa['name']}: {totals['installs']} встановлень, 0 депозитів")
if not DRY_RUN:
requests.put(f"{API}/pwas/{pwa['id']}", headers=HEADERS, json={
"active": False,
"team_id": pwa["team"]["id"],
}, timeout=30).raise_for_status()
print(" вимкнено")// Node.js 18+, без залежностей; збережіть як stop-pwas.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer ВАШ_КЛЮЧ', Accept: 'application/json', 'Content-Type': 'application/json' };
const MIN_INSTALLS = 50; // менше встановлень - рано судити
const DRY_RUN = true; // true - лише показати, false - вимикати
const day = (daysAgo) => new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
const dateFrom = day(7);
const dateTo = day(1);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function get(path, params = {}) {
const response = await fetch(`${API}/${path}?${new URLSearchParams(params)}`, {
headers,
signal: AbortSignal.timeout(30000),
});
if (response.status === 401) throw new Error('Ключ не прийнято - перевірте його в профілі');
if (!response.ok) throw new Error(`${path}: ${response.status}`);
return (await response.json()).data;
}
const pwas = [];
for (let page = 1; ; page++) {
const data = await get('pwas', { status: 'active', page, excludes: 'stats' });
pwas.push(...data.pwas);
if (page >= data.meta.last_page) break;
}
for (const pwa of pwas) {
const { totals } = (await get('stats/funnel', { date_from: dateFrom, date_to: dateTo, pwa_id: pwa.id })).funnel;
await sleep(250); // не більше 300 запитів на хвилину
if (totals.installs < MIN_INSTALLS || totals.deps > 0) continue;
console.log(`${pwa.id} ${pwa.name}: ${totals.installs} встановлень, 0 депозитів`);
if (!DRY_RUN) {
const response = await fetch(`${API}/pwas/${pwa.id}`, {
method: 'PUT',
headers,
body: JSON.stringify({ active: false, team_id: pwa.team.id }),
signal: AbortSignal.timeout(30000),
});
if (!response.ok) throw new Error(`PUT /pwas/${pwa.id}: ${response.status}`);
console.log(' вимкнено');
}
}<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';
$api = new GuzzleHttp\Client([
'base_uri' => 'https://panel.example.com/api/',
'headers' => ['Authorization' => 'Bearer ВАШ_КЛЮЧ', 'Accept' => 'application/json'],
'timeout' => 30,
]);
$minInstalls = 50; // менше встановлень - рано судити
$dryRun = true; // true - лише показати, false - вимикати
$today = new DateTimeImmutable('today', new DateTimeZone('UTC'));
$dateFrom = $today->modify('-7 days')->format('Y-m-d');
$dateTo = $today->modify('-1 day')->format('Y-m-d');
$pwas = [];
for ($page = 1; ; $page++) {
$response = $api->get('pwas', ['query' => ['status' => 'active', 'page' => $page, 'excludes' => 'stats']]);
$data = json_decode($response->getBody(), true)['data'];
$pwas = [...$pwas, ...$data['pwas']];
if ($page >= $data['meta']['last_page']) {
break;
}
}
foreach ($pwas as $pwa) {
$response = $api->get('stats/funnel', ['query' => [
'date_from' => $dateFrom,
'date_to' => $dateTo,
'pwa_id' => $pwa['id'],
]]);
$totals = json_decode($response->getBody(), true)['data']['funnel']['totals'];
usleep(250000); // не більше 300 запитів на хвилину
if ($totals['installs'] < $minInstalls || $totals['deps'] > 0) {
continue;
}
echo "{$pwa['id']} {$pwa['name']}: {$totals['installs']} встановлень, 0 депозитів" . PHP_EOL;
if (!$dryRun) {
$api->put("pwas/{$pwa['id']}", ['json' => ['active' => false, 'team_id' => $pwa['team']['id']]]);
echo ' вимкнено' . PHP_EOL;
}
}Скрипт робить по одному запиту статистики на кожне PWA й тримає паузу 0,25 с, щоб не впертися в ліміт 300 запитів на хвилину. Встановлення й депозити тут - за датою події, як у воронці (див. розділ «Статистика»). Учасники команди PWA отримають сповіщення про кожен вимкнений застосунок.
Статистика в Google Таблицях
Щоранку дописує в Google Таблицю рядок із підсумками за вчора. За місяць виходить готова таблиця для графіків і зведень - без серверів і cron, усе всередині Google.
Налаштування:
- Відкрийте потрібну Google Таблицю й виберіть Extensions → Apps Script.
- Вставте скрипт нижче замість вмісту редактора й замініть
panel.example.comна адресу своєї панелі. - Ліворуч відкрийте Project Settings (шестірня) → Script Properties → Add script property: ім’я
PWA_HUB_KEY, значення - ваш ключ. Так ключ не зберігається в тексті скрипта. - Угорі виберіть функцію
appendYesterdayі натисніть Run. Google попросить дозволи - підтвердьте. У таблиці з’явиться аркушPWA Hubз першим рядком. - Ліворуч відкрийте Triggers (будильник) → Add Trigger: функція
appendYesterday, джерело Time-driven, Day timer, час - наприклад, 7am to 8am. Збережіть.
// Google Таблиця → Extensions → Apps Script
const API = 'https://panel.example.com/api';
const SHEET = 'PWA Hub';
function appendYesterday() {
const key = PropertiesService.getScriptProperties().getProperty('PWA_HUB_KEY');
if (!key) throw new Error('Додайте PWA_HUB_KEY у Script Properties');
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
const day = Utilities.formatDate(yesterday, 'UTC', 'yyyy-MM-dd');
const response = UrlFetchApp.fetch(`${API}/stats/dashboard?date_from=${day}&date_to=${day}`, {
headers: { Authorization: `Bearer ${key}`, Accept: 'application/json' },
muteHttpExceptions: true,
});
if (response.getResponseCode() !== 200) {
throw new Error(`PWA Hub відповів ${response.getResponseCode()}: ${response.getContentText()}`);
}
const metrics = JSON.parse(response.getContentText()).data.metrics;
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spreadsheet.getSheetByName(SHEET);
if (!sheet) {
sheet = spreadsheet.insertSheet(SHEET);
sheet.appendRow(['Дата', 'Візити', 'Унікальні', 'Встановлення', 'Реєстрації', 'Депозити', 'CR, %']);
}
sheet.appendRow([
day,
metrics.total_visits.value,
metrics.total_uniques.value,
metrics.total_installs.value,
metrics.total_registrations.value,
metrics.total_deposits.value,
metrics.avg_cr.value,
]);
}Повторний запуск - повторний рядок
Скрипт просто дописує рядок. Запустили його двічі за день - вчорашній день опиниться в таблиці двічі; зайвий рядок видаліть вручну.
Якщо панель недоступна або ключ відкликано, запуск за тригером завершиться помилкою, а Google надішле лист про збій - рядок за цей день не з’явиться. Пропущений день можна дописати, замінивши yesterday на потрібну дату й запустивши функцію вручну.